具有ViewModel依赖关系的ICommand

时间:2015-07-24 07:58:59

标签: c# wpf mvvm icommand

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

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 方法。我想过两个解决方案,但我不知道它们是否优雅。

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

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;
       }
    }

在我看来,第一个是这个问题的最佳解决方案,您认为什么是最佳解决方案。你会申请另一种解决方案吗?

2 个答案:

答案 0 :(得分:0)

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

您的MyViewModelCommandManager目前直接与MyViewModel相关联,因为它需要后者的Message,那么将它们分开有什么好处?为什么不简单地在MyViewModel内实现命令?

如果这需要向MyViewModel注入太多依赖项,那么请考虑一下您实际需要执行的命令,并抽象掉其他所有不需要的命令。

  • 该命令显示一条消息。
  • 消息由MyViewModel
  • 保存
  • 您想在MyViewModel之外显示消息(也许其他视图模型也需要显示消息,您想重用代码吗?)
  • 所以你真正需要的是来自MyViewModel的某种通知,它要显示一条消息,或者发生了一些会导致消息显示的消息。

可能的解决方案:

  1. IMessageDisplayService注入MyViewModelMyViewModel通过邮件调用它。
  2. MyViewModel注入与上述类似的回调。
  3. MyViewModel举起一个事件,并将该消息作为EventArg。
  4. 上述解决方案的推断责任略有不同。

    1. 表示MyViewModel负责人。它想要显示一条消息。
    2. 不太明确。 MyViewModel知道它需要调用回调,但并不真正了解或关心它的作用。
    3. 就像2,但更加分离。多件事可以订阅或取消订阅活动,但MyViewModel仍然无知。
    4. 所有这三个意味着显示消息的内容无需了解MyViewModel。你已经将它们分离了。它是MyViewModelFactory,需要进行任何布线。

答案 1 :(得分:0)

感谢您的意见。

当你说我正在创建一个复杂的模式时,我理解你,但是在一个拥有大型开发团队的大项目中,如果没有明确的模式具有拆分响应性,则代码维护可能无法执行。

阅读你和你的第三个解决方案我想到了一个可能的解决方案。这似乎很复杂,但在我看来,它可以提高代码质量。我将创建一个commandContext,它只具有代码所需的viewmodel属性,避免在命令管理器中拥有所有viewmodel。此外,我将创建一个类,其责任是在viewmodel更改时保留更新的上下文。这是可能的代码:

internal class MyCommandContext
{
    public string Message { get; set; }
}

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

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

    public string OtherProperty { get; set; }

    public ObservableCollection<MyChildViewModel> Childs { get; set; }

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

       ....
}

internal interface IMyViewModelCommandManager
{
    void ExectueMyCommand();
}

internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
   private readonly MyCommandContext context;

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

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

internal interface IMyViewModelCommandSynchronizer
{
    void Initialize();
}

internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
{
     private readOnly MyViewModel viewModel;
     private readOnly MyCommandContext context;

     MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
     {
         this.viewModel = viewModel;
         this.context = context;
     }

     public void Initialize()
     {
         this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
     }

    private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Message")
        {
             this.context.Message = this.viewModel.Message;
        }
    }

    // Dispose code to deattach the events.
}

internal class MyViewModelFactory: IMyViewModelFactory
{
   private readonly IContainerWrapper container;

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

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

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

       ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);

       MyViewModel viewModel = new MyViewModel(myCommand);

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

       synchronizer.Initialize();

       return viewModel;
   }
}