【问题标题】:Way to prevent dependencies for ViewModels防止 ViewModel 依赖的方法
【发布时间】:2015-11-26 03:34:28
【问题描述】:

例如,我有一个 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 的实际示例,我正在尝试找出一种将它们解耦的方法。

【问题讨论】:

  • 您的问题不清楚,您最终要做什么?可以使用事件删除依赖项。例如,如果我想通过按下按钮对 TextBox 进行一些更改,我会通过处理 Click 事件并在那里更改 TextBox 状态来实现。
  • 对不起,使用事件如何消除依赖关系?无论如何,它都会破坏我的 MVVM 模式。为了清楚起见,我正在更新我的问题...
  • 使用接口,当打开一个Popup时,将Popup实例存储在接口类型变量中。然后您可以通过调用接口方法轻松关闭此弹出窗口。 IPopup 弹出 = Popup1; popup.Close();
  • 我想知道你为什么想要IsPopupVisible 属性。对我来说完全是无稽之谈。您可以在 Popup 类上使用 Activity 模式,或者您可以使用 EventAggregator/MessageBus 模式,具体取决于用例。

标签: c# wpf mvvm .net-4.0


【解决方案1】:

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

单例视图模型(至少通过静态访问器,由依赖注入容器管理它的单个实例很好)违反了松散耦合的 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 等多个平台的支持)最近)。一开始它有一个陡峭的学习曲线,但是一旦你掌握了它就会非常强大。

上面的代码会发送一条可以从其他ViewModels访问的通知消息,即

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,因为有关此主题的大多数教程都仅限于此。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 感谢您的回答 Aron,但我使用的是 C# 4(无异步),而且我也在使用 MVVM 模式。对代码进行 Popup 控件会破坏它...
      • @Tyress 实际上我第一次看到这种模式是在 .net 4.0 MVPVM 项目中。诀窍是在使用 WPF 时使用 ServiceLocator 来实例化 IViewModel 的视图。至于 .net 4.0,你只需要它来进行异步等待,这不是强制性的。
      • @Tyress 但是,此时您不应该为 .net 4.0 进行开发。微软将在几周内(1 月 16 日)放弃 .net 4.0 support.microsoft.com/en-us/gp/framework_faq
      • 请注意,C# 4.0(以及 5.0、6.0)与 .NET 2.0 完全向后兼容。这意味着您可以使用 C# 5.0 或 6.0 语法(包括 async/await)来编写代码,并且仍然可以针对 .NET 4.0 或 3.5 Framework 进行编译,因为自 .NET 3.5(作为第 3 方程序集)开始提供 TPL(任务并行库)并且从 4.0 开始作为 .NET 4.0 框架的一部分)。您只需要一个 C# 5.0 或 6.0 编译器,它们通常随较新的 Visual Studio(分别为 2012 和 2015)版本提供
      • 除非您不需要 Visual Studio Enterprise 功能和您公司的社区版限定符,否则使用 Visual Studio 社区版是免费的。 visualstudio.com/support/legal/mt171547
      猜你喜欢
      • 2013-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-14
      • 1970-01-01
      • 2019-05-24
      • 2015-07-14
      • 1970-01-01
      相关资源
      最近更新 更多