c# - 具有 ViewModel 依赖项的 ICommand

标签 c# wpf mvvm icommand

当我使用 ICommand 时,我正在寻找一种模式以在我的应用程序中保持 SOLID 原则。基本上我的问题是命令执行与 View 模型有依赖关系,但同时 View 模型与命令有依赖关系(我通过构造函数注入(inject)它们)。我只想保留带有属性的 View 模型,所以这是我当前实现的一个示例:

public class MyViewModel : INotifyPropertyChanged
{
   public ICommand MyCommand { get; private set; }

   public string Message { get; set; } // PropertyChanged ommited

   public MyViewModel()
   {            
   }

   public void SetCommand(ICommand myCommand)
   {
       this.MyCommand = myCommand;
   }

   ....
}

internal interface IMyViewModelCommandManager
{
    void ExectueMyCommand();
}

internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
   private readOnly MyViewModel myViewModel;

   public MyViewModelCommandManager(MyViewModel myViewModel)
   {
       this.myViewModel = myViewModel;
   }

   public ExectueMyCommand()
   {
        MessageBox.Show(this.myViewModel.Message);
   }
}

internal class MyViewModelFactory: IMyViewModelFactory
{
   private readonly IContainerWrapper container;

   public MyViewModelFactory(IContainerWrapper container)
   {
      this.container = container;
   }

   public MyViewModel Create()
   {
       MyViewModel viewModel = new MyViewModel();

       IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });

       ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);

       viewModel.SetCommand(myCommand);

       return viewModel;
   }
}

因此,要避免使用SetCommand 方法。我想过两种解决方案,但我不知道它们是否优雅。

第一个是将 View 模型依赖从构造函数移动到以这种方式更新代码的方法:

public class MyViewModel : INotifyPropertyChanged
{
   public ICommand MyCommand { get; private set; }

   public string Message { get; set; } // PropertyChanged ommited

   public MyViewModel(ICommand myCommand)
   {
       this.MyCommand = myCommand;            
   }

   ....
}

internal interface IMyViewModelCommandManager
{
    void ExectueMyCommand(MyViewModel viewModel);
}

internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
   public MyViewModelCommandManager()
   {
       ....
   }

   public ExectueMyCommand(MyViewModel viewModel)
   {
        MessageBox.Show(myViewModel.Message);
   }
}

internal class MyViewModelFactory: IMyViewModelFactory
{
   private readonly IContainerWrapper container;

   public MyViewModelFactory(IContainerWrapper container)
   {
      this.container = container;
   }

   public MyViewModel Create()
   {
       IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);

       ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);

       MyViewModel viewModel = new MyViewModel(myCommand);
       return viewModel;
   }
}

当然,xaml代码会用到CommandParameter:

<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />

我想到的其他解决方案是使用一个技巧来创建 viewModel 的 Wrapper,并且 commandManager 依赖于 Wrapper 而不是 viewModel:

internal class MyViewModelCommandContext
   {
      public MyViewModel ViewModel { get; set; }
   }

   public class MyViewModel : INotifyPropertyChanged
    {
       public ICommand MyCommand { get; private set; }

       public string Message { get; set; } // PropertyChanged ommited

       public MyViewModel(ICommand myCommand)
       {
           this.MyCommand = myCommand;            
       }

       ....
    }

    internal interface IMyViewModelCommandManager
    {
        void ExectueMyCommand();
    }

    internal class MyViewModelCommandManager : IMyViewModelCommandManager
    {
       private readonly MyViewModelCommandContext context;

       public MyViewModelCommandManager(MyViewModelCommandContext context)
       {
           this.context = context;
           ....
       }

       public ExectueMyCommand()
       {
            MessageBox.Show(this.context.myViewModel.Message);
       }
    }

    internal class MyViewModelFactory: IMyViewModelFactory
    {
       private readonly IContainerWrapper container;

       public MyViewModelFactory(IContainerWrapper container)
       {
          this.container = container;
       }

       public MyViewModel Create()
       {
           MyViewModelCommandContext context = new MyViewModelCommandContext();

           IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });

           ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);

           MyViewModel viewModel = new MyViewModel(myCommand);
           context.ViewModel = viewModel;
           return viewModel;
       }
    }

在我看来第一个是这个问题最好的解决方案,你认为什么是最好的解决方案。您会应用其他解决方案吗?

最佳答案

恕我直言,这两种解决方案都过于复杂。 SOLID 很棒,KISS 更好。

您的 MyViewModelCommandManager 目前直接耦合到 MyViewModel,因为它需要后者的 Message,那么将它们分开有什么好处呢?为什么不简单地在 MyViewModel 中实现命令?

如果这需要向 MyViewModel 中注入(inject)太多依赖项,那么请考虑您实际需要该命令执行的操作,并抽象出不需要的所有其他内容。

  • 该命令显示一条消息。
  • 消息由MyViewModel保存
  • 您想在 MyViewModel 之外显示消息(也许其他 View 模型也需要显示消息,您想要重用代码?)
  • 因此,您真正需要的只是来自 MyViewModel 的某种通知,告知它要显示消息,或者发生了导致显示消息的事件。

可能的解决方案:

  1. IMessageDisplayService 注入(inject)到 MyViewModelMyViewModel 使用消息调用它。
  2. MyViewModel 注入(inject)回调,与上面类似。
  3. MyViewModel 引发事件,并将消息作为 EventArg。

上述解决方案的推断责任略有不同。

  1. 表示 MyViewModel 负责。它想要显示一条消息。
  2. 不太明确。 MyViewModel 知道它需要调用回调,但并不真正知道或关心它做了什么。
  3. 就像 2,但更加解耦。多个事物可以订阅或取消订阅该事件,但 MyViewModel 仍然幸福无知。

所有这三个都意味着显示消息的东西不需要知道 MyViewModel。你已经把它们解耦了。它是 MyViewModelFactory 进行所需的任何连接。

关于c# - 具有 ViewModel 依赖项的 ICommand,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31605152/

相关文章:

wpf - M-V-VM WPF : Way to maintain Databinding while passing parent object of Databound object into IValueConverter?

Kendo UI MVVM 中选择选项的 Javascript 设置

c# - 每个层次结构继承的 Entity Framework 表

c# - 是什么导致 EF 插入比普通 ADO.NET 慢得多?

c# - 检索满足选定条件的数据的最佳实践

c# - 将密码存储在内存中的风险有多大?

c# - 我应该在我的 MVVM 应用程序中使用 Microsoft.Practicies.Unity.IUnityContainer 吗?

c# - 使用 ViewBag 时出现 RuntimeBinderException

c# - WPF BitmapFrame 和多线程

c# - 使用 ItemSource 绑定(bind)访问 ListView 内的另一个数据上下文