防止ViewModel依赖的方法

时间:2015-11-26 03:34:28

标签: c# wpf mvvm .net-4.0

例如,我在弹出控件中有一个View的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的一个实际例子,我试图想出一种解耦它们的方法。

3 个答案:

答案 0 :(得分:3)

嗯,第一步是删除单例模式。单身被认为是一种反模式,有几个原因超出了答案的范围。

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

为了能够解耦它们,你需要控制反转(IoC)模式,最好是一个管理这种依赖关系的IoC容器(你也可以在没有IoC容器的情况下实现它,但是这样做会更难)。您将通过Constructor注入您的依赖关系链,即

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会对其做出反应。

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

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

MVVM可能非常复杂,只要您使用单个View超越单个ViewModel,因为大多数关于此主题的教程都限于此。

答案 1 :(得分:0)

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

答案 2 :(得分:-1)

您可以使用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
}