防止 ViewModel 依赖的方法

Way to prevent dependencies for ViewModels

例如,我有一个用于弹出控件内视图的 ViewModel。

ManagerView.xaml

<Popup Name="Popup1" AllowsTransparency="True" Placement="Center" PlacementTarget="{Binding ElementName=AGrid}" StaysOpen="True">
    <Popup.Style>
        <Style TargetType="{x:Type Popup}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsPopupVisible}" Value="True">
                    <Setter Property="IsOpen" Value="True"></Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding IsPopupVisible}" Value="False">
                    <Setter Property="IsOpen" Value="False"></Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Popup.Style>
        <View:AssignView></View:AssignView>
    </Popup>

AssignViewModel.cs:

    public AssignViewModel()
    {
        Default = this;
    }
    public static AssignViewModel Default;
    private ICommand iCloseCommand;
    .
    .
    .
    private void Close()
    {
        ManagerViewModel.Default.IsPopupVisible = false;
    }

ManagerViewModel 也访问 AssignViewModel。我通过为每个 ViewModel 分配一个自身的静态实例来传递参数。这很好用,但是当然有一个依赖问题,但这也是因为它们实际上依赖于彼此的值。

任何人都可以建议我没有任何依赖关系的设计吗?这只是一个 ViewModel 依赖于我经常遇到的另一个 ViewModel 的实际示例,我正在尝试找出一种方法来解耦它们。

保持单向数据流。 IsPopupVisible 可以映射到 Model 中的某个状态,ManagerViewModel 可以监听 Model 的状态变化并改变自己的状态 属性 而 AssignViewModel 可以改变共享 Model 的状态。

您可以使用 Activity 模式。

public class PopupResult
    : IViewModel
{
    //Some code
}

public class ActivityRunner
{
    public Task<PopupResult> Do<PopupResult>()
    {
        var tcs = new TaskCompletionSource<PopupResult>();
        var view = new Popup();
        var model = new PopupResult();
        view.DataContext = model;
        view.Close += (e, o) => tcs.SetResult(model);
        return tcs.Task;
    }
}


async void ShowPopupThenDoSomething()
{
    PopupResult result = await IActivityRunner.Do<Popup>();
    //Do something when we close
}

嗯,第一步是去掉单例模式。由于超出答案范围的几个原因,Singleton 被认为是一种反模式。

Singleton ViewModels(至少通过静态访问器,由依赖注入容器管理它的单个实例很好)违反了松散耦合的 MVVM 概念之一。

为了能够解耦它们,您需要控制反转 (IoC) 模式,最好是一个管理此依赖项的 IoC 容器(您也可以在没有 IoC 容器的情况下进行,但它会更难这样做)。您将通过构造函数注入依赖链,即

public class MainViewModel 
{
    private readonly IMessageService messageService;

    public MainViewModel(IMessageService messageService) 
    {
        if(messageService == null) 
        {
            throw new ArgumentNullException("messageService");
        }

        this.messageService = messageService;
    }


    private void AssignUser(int userId)
    {
        this.messageService.Send<AssignUserMessage>(
            new AssignUserMessage() 
            {
                UserId = userId
            }
        );
    }
}

说到这里,我们也进入下一个话题,消息服务。消息服务将是一个发布和订阅事件的服务,类似于C#/.NET中的EventHandler,但真正解耦。 IMessageService 只是这里的一个例子,但那里有许多可用的消息服务。

我个人将 Prism 用于私人和企业级应用程序,因为它具有 MVVM 所需的基础知识(区域支持导航、消息传递、绑定和通知以及对 WPF、Silverlight 等多个平台的支持,UWP/WinPhone 和最近的 Xamarin)。一开始它的学习曲线很陡,但是一旦掌握它就会非常强大。

以上代码将发送一条通知消息,可以从其他 ViewModel 访问该消息,即

public class AssignUserViewModel 
{
    private readonly IMessageService messageService;
    private readonly IUserRepository users;

    public AssignUserViewModel(IMessageService messageService, IUserRepository userRepository) 
    {
        if(messageService == null) 
        {
            throw new ArgumentNullException("messageService");
        }

        if(userRepository == null) 
        {
            throw new ArgumentNullException("userRepository");
        }

        this.messageService = messageService;
        this.users = userRepository;

        // register to the AssingUserMessage here
        this.messageService.Register<AssignUserMessage>(OnAssignUserMessage);
    }

    private void OnAssignUser(AssignUserMessage message)
    {
        var user = await users.GetByUserIdAsync(message.UserId);
        // display your user and whatever you want to assign it and once done, 
        // save the changes, then send a notification that the user has been updated
        this.messageService.Send<UserAssignedMessage>(
            new UserAssignedMessage() 
            {
                UserId = user.Id
            }
        );
    }
}

这样两个 ViewModel 就解耦了。 MainViewModel 不知道 AssignUserViewModel 的存在,反之亦然。 MainViewModel 只会发送一个通知,指出需要分配用户,AssingUserViewModel 会对此做出反应。

当事情变得更复杂时,您可能还需要一个导航服务,它将导航(切换视图或打开一个新的 window,等等)到一个视图并将所需的参数传递给它,但是那是另一个话题。您通常会像消息服务一样注入导航服务。

关于如何使用导航服务的示例,请查看我的其他回复here and here

MVVM 可能非常复杂,一旦您超越了具有单个 View 的单个 ViewModel,因为有关该主题的大多数教程都限于此。