2009-02-02 70 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> 
相關問題