2009-02-02 87 views
8

我试图实现使用MVVM(模型 - 视图 - 视图模型)模式的WPF应用程序,我想有一个单独的程序的视图部分(一个EXE)来自Model和ViewModel部件(一个DLL)。实现MVVM在WPF不使用System.Windows.Input.ICommand

这里的麻烦是保持Model/ViewModel程序集清除任何WPF依赖。原因是我想重复使用不同(非WPF)UI技术的可执行文件,例如Mono下的WinForms或GTK#。

默认情况下,这是无法完成的,因为ViewModel公开一个或多个ICommands。但是ICommand类型是在属于WPF的System.Windows.Input命名空间中定义的!

那么,有没有办法在不使用ICommand的情况下满足WPF绑定机制?

谢谢!

+2

@aoven:我你在哪里,当你问这个8个月前,和你想知道什么伤口在做什么以及它有多好为你而做。欢呼 – Berryl 2009-10-06 16:08:06

回答

7

您应该能够在您的wpf层和一个命令处理程序类中定义一个WPF自定义路由命令。您所有的WPF类都可以使用适当的参数绑定到这一个命令。

处理程序类就可以命令翻译成你自己的自定义命令界面,你在你的视图模型层定义自己,是独立的WPF的。

最简单的例子是使用Execute方法的void委托的包装。

所有你不同的GUI层只需要从他们的本地命令类型转换到一个位置的自定义命令类型。

+1

你能举个例子吗? – Jose 2009-05-29 15:21:26

+1

请看下面的例子 – 2011-03-03 01:18:58

4

WinForms没有使用MVVM样式视图模型所需的丰富数据绑定和命令基础结构。

就像你不能在一个客户端应用程序中重用一个web应用程序MVC控制器一样(至少在不创建大量的包装和适配器的情况下,最终会使得编写和调试代码变得更加困难,而不提供任何价值客户),您无法在WinForms应用程序中重复使用WPF MVVM。

我没有用一个真正的项目GTK#,所以我不知道什么可以或不可以做,但我怀疑MVVM不是GTK#的最佳方法呢。

尝试尽可能多的应用程序到所述模型的行为的移动,具有只从模型公开数据并调用基于命令在视图模型没有逻辑模型的图模型。

然后,对于的WinForms只是删除视图模型和直接调用从UI模型,或者建立一个基于的WinForms更有限的数据绑定支持另一个中间层。

重复GTK#或写MVC控制器和视图给模型一个Web前端。

不要试图一个技术强行进入被另一个优化的使用模式,不要从头开始写自己的命令的基础设施(我以前做过,不是我最有生产力的选择),用最好的每种技术的工具。

2

而不是VM暴露命令,只是暴露方法。然后使用附加行为将事件绑定到方法,或者如果您需要命令,请使用可委派给这些方法并通过附加行为创建命令的ICommand。

2

当然这是可能的。您可以创建另一个抽象层次。 添加自己的与IMommand类似或相同的IMyCommand界面并使用它。

看看我目前的MVVM解决方案,它解决了您提到的大多数问题,但它完全从平台特定的事物中抽象出来,并且可以重复使用。我也没有使用代码隐藏,只绑定实现ICommand的DelegateCommands。对话框基本上是一个视图 - 一个单独的控件,它有自己的ViewModel,它从主屏幕的ViewModel显示,但是通过DelagateCommand绑定从UI触发。

看到这里Modal dialogs with MVVM and Silverlight 4

+0

答案演示这种方法[here](http:// stackoverflow。com/a/19795332/954927) – Neutrino 2013-11-05 17:48:28

4

全Silverlight 4的解决方案,我需要这样的一个例子,所以我写了一个使用各种技术。

我脑子里想的

1的几个设计目标 - 保持简单

2 - 视图(Window类)绝对没有代码隐藏

3 - 展示仅依赖ViewModel类库中的System引用。

4 - 将业务逻辑保留在ViewModel中,并直接路由到合适的方法,而无需编写大量“存根”方法。

下面的代码...

的App.xaml(没有的StartupUri值得一提的唯一的事情)

<Application 
    x:Class="WpfApplicationCleanSeparation.App" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
</Application> 

App.xaml.cs(加载主视图)

using System.Windows; 
using WpfApplicationCleanSeparation.ViewModels; 

namespace WpfApplicationCleanSeparation 
{ 
    public partial class App 
    { 
     protected override void OnStartup(StartupEventArgs e) 
     { 
      var view = new MainView(); 
      var viewModel = new MainViewModel(); 

      view.InitializeComponent(); 
      view.DataContext = viewModel; 
      CommandRouter.WireMainView(view, viewModel); 
      view.Show(); 
     } 
    } 
} 

CommandRouter.cs(魔术)

using System.Windows.Input; 
using WpfApplicationCleanSeparation.ViewModels; 

namespace WpfApplicationCleanSeparation 
{ 
    public static class CommandRouter 
    { 
     static CommandRouter() 
     { 
      IncrementCounter = new RoutedCommand(); 
      DecrementCounter = new RoutedCommand(); 
     } 

     public static RoutedCommand IncrementCounter { get; private set; } 
     public static RoutedCommand DecrementCounter { get; private set; } 

     public static void WireMainView(MainView view, MainViewModel viewModel) 
     { 
      if (view == null || viewModel == null) return; 

      view.CommandBindings.Add(
       new CommandBinding(
        IncrementCounter, 
        (λ1, λ2) => viewModel.IncrementCounter(), 
        (λ1, λ2) => 
         { 
          λ2.CanExecute = true; 
          λ2.Handled = true; 
         })); 
      view.CommandBindings.Add(
       new CommandBinding(
        DecrementCounter, 
        (λ1, λ2) => viewModel.DecrementCounter(), 
        (λ1, λ2) => 
         { 
          λ2.CanExecute = true; 
          λ2.Handled = true; 
         })); 
     } 
    } 
} 

MainView.xaml(没有代码隐藏,字面删除!)

<Window 
    x:Class="WpfApplicationCleanSeparation.MainView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WpfApplicationCleanSeparation="clr-namespace:WpfApplicationCleanSeparation" 
    Title="MainWindow" 
    Height="100" 
    Width="100"> 
    <StackPanel> 
     <TextBlock Text="{Binding Counter}"></TextBlock> 
     <Button Content="Decrement" Command="WpfApplicationCleanSeparation:CommandRouter.DecrementCounter"></Button> 
     <Button Content="Increment" Command="WpfApplicationCleanSeparation:CommandRouter.IncrementCounter"></Button> 
    </StackPanel> 
</Window> 

MainViewModel.cs(包括实际模型,以及因为本实施例中是如此简化,请原谅MVVM模式的脱轨。

using System.ComponentModel; 

namespace WpfApplicationCleanSeparation.ViewModels 
{ 
    public class CounterModel 
    { 
     public int Data { get; private set; } 

     public void IncrementCounter() 
     { 
      Data++; 
     } 

     public void DecrementCounter() 
     { 
      Data--; 
     } 
    } 

    public class MainViewModel : INotifyPropertyChanged 
    { 
     private CounterModel Model { get; set; } 
     public event PropertyChangedEventHandler PropertyChanged = delegate { }; 

     public MainViewModel() 
     { 
      Model = new CounterModel(); 
     } 

     public int Counter 
     { 
      get { return Model.Data; } 
     } 

     public void IncrementCounter() 
     { 
      Model.IncrementCounter(); 

      PropertyChanged(this, new PropertyChangedEventArgs("Counter")); 
     } 

     public void DecrementCounter() 
     { 
      Model.DecrementCounter(); 

      PropertyChanged(this, new PropertyChangedEventArgs("Counter")); 
     } 
    } 
} 

Proof

只是快速和肮脏的,我希望它是有用的人。我通过各种Google看到了几种不同的方法,但没有什么比使用我想要的最少量的代码实现简单和容易。如果有办法进一步简化请让我知道,谢谢。

编码快乐:)

编辑:为了简化自己的代码,你可能会发现制作又添成单行这个有用。

private static void Wire(this UIElement element, RoutedCommand command, Action action) 
    { 
     element.CommandBindings.Add(new CommandBinding(command, (sender, e) => action(), (sender, e) => { e.CanExecute = true; })); 
    } 
+0

`λ1,λ2`有点酷。 – 2013-01-13 05:20:28

1

我认为你是在错误的地方分离你的项目。我认为你应该只分享你的模型和业务逻辑类。

虚拟机是适应WPF视图模型的改编。我会保持VM简单,并做到这一点。

我无法想象在Winforms上强制MVVM。 OTOH只有&业务逻辑,如果需要,可以直接将它们注入到表单中。

0

对于这种“你不能在一个WinForms应用程序重复使用WPF MVVM”请参阅网址http://waf.codeplex.com/,我已经在Win形式使用MVVM,现在whenver我想从赢窗体应用程序的演示升级到WPF,它将被改变,没有任何变化的应用程序逻辑,

但我有一个问题,在ASP.NET MVC中重复使用ViewModel,所以我可以在Web中做同样的桌面win应用程序没有或应用程序逻辑更少的变化..

谢谢...

2

对不起戴夫,但我不喜欢你的解决方案。首先,你必须在代码中手动为每个命令编写管道代码,然后你必须配置CommandRouter以了解应用程序中的每个视图/视图模型关联。

我采取了不同的方法。

我有一个Mvvm工具程序集(它没有WPF依赖关系),我在我的viewmodel中使用。在该程序集中,我声明了一个自定义的ICommand接口和一个实现该接口的DelegateCommand类。

namespace CommonUtil.Mvvm 
{ 
    using System; 


    public interface ICommand 
    { 
     void Execute(object parameter); 
     bool CanExecute(object parameter); 

     event EventHandler CanExecuteChanged; 
    } 

    public class DelegateCommand : ICommand 
    { 
     public DelegateCommand(Action<object> execute) : this(execute, null) 
     { 

     } 

     public DelegateCommand(Action<object> execute, Func<object, bool> canExecute) 
     { 
      _execute = execute; 
      _canExecute = canExecute; 
     } 

     public void Execute(object parameter) 
     { 
      _execute(parameter); 
     } 

     public bool CanExecute(object parameter) 
     { 
      return _canExecute == null || _canExecute(parameter); 
     } 


     public event EventHandler CanExecuteChanged; 

     private readonly Action<object> _execute; 
     private readonly Func<object, bool> _canExecute; 
    } 
} 

我也有一个WPF库程序集(它引用的系统WPF库),这是我从我的WPF UI项目中引用。在那个程序集中,我声明了一个具有标准System.Windows.Input.ICommand接口的CommandWrapper类。 CommandWrapper是使用我的自定义ICommand实例构造的,并将Execute,CanExecute和CanExecuteChanged直接委托给我的自定义ICommand类型。

namespace WpfUtil 
{ 
    using System; 
    using System.Windows.Input; 


    public class CommandWrapper : ICommand 
    { 
     // Public. 

     public CommandWrapper(CommonUtil.Mvvm.ICommand source) 
     { 
      _source = source; 
      _source.CanExecuteChanged += OnSource_CanExecuteChanged; 
      CommandManager.RequerySuggested += OnCommandManager_RequerySuggested; 
     } 

     public void Execute(object parameter) 
     { 
      _source.Execute(parameter); 
     } 

     public bool CanExecute(object parameter) 
     { 
      return _source.CanExecute(parameter); 
     } 

     public event System.EventHandler CanExecuteChanged = delegate { }; 


     // Implementation. 

     private void OnSource_CanExecuteChanged(object sender, EventArgs args) 
     { 
      CanExecuteChanged(sender, args); 
     } 

     private void OnCommandManager_RequerySuggested(object sender, EventArgs args) 
     { 
      CanExecuteChanged(sender, args); 
     } 

     private readonly CommonUtil.Mvvm.ICommand _source; 
    } 
} 

在我的WPF组装我也创建了ValueConverter,当通过我的自定义的实例ICommand的吐出Windows.Input.ICommand兼容CommandWrapper的一个实例。

namespace WpfUtil 
{ 
    using System; 
    using System.Globalization; 
    using System.Windows.Data; 


    public class CommandConverter : IValueConverter 
    { 

     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      return new CommandWrapper((CommonUtil.Mvvm.ICommand)value); 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      throw new System.NotImplementedException(); 
     } 
    } 
} 

现在我的ViewModels可以公开为我的自定义命令类型的实例的命令,而无需对任何WPF依赖性,并且使用我ValueConverter像这样我的用户界面可以结合Windows.Input.ICommand命令来那些的ViewModels。 (XAML命名空间垃圾邮件ommited)。

<Window x:Class="Project1.MainWindow"> 

    <Window.Resources> 
     <wpf:CommandConverter x:Key="_commandConv"/> 
    </Window.Resources> 

    <Grid> 
     <Button Content="Button1" Command="{Binding CustomCommandOnViewModel, 
             Converter={StaticResource _commandConv}}"/> 
    </Grid> 

</Window> 

现在,如果我真的懒惰(我是),并不能不屑于每次都手动应用CommandConverter然后在我的WPF组装我可以创建自己的绑定子类是这样的:

namespace WpfUtil 
{ 
    using System.Windows.Data; 


    public class CommandBindingExtension : Binding 
    { 
     public CommandBindingExtension(string path) : base(path) 
     { 
      Converter = new CommandConverter(); 
     } 
    } 
} 

所以,现在我可以绑定到我的自定义命令类型甚至喜欢更简单地这样:

<Window x:Class="Project1.MainWindow" 
       xmlns:wpf="clr-namespace:WpfUtil;assembly=WpfUtil"> 

    <Window.Resources> 
     <wpf:CommandConverter x:Key="_commandConv"/> 
    </Window.Resources> 

    <Grid> 
     <Button Content="Button1" Command="{wpf:CommandBinding CustomCommandOnViewModel}"/> 
    </Grid> 

</Window>