2009-02-02 61 views
8

Tôi đang cố triển khai một ứng dụng WPF bằng cách sử dụng mẫu MVVM (Model-View-ViewModel) và tôi muốn có phần Xem trong một assembly riêng biệt (một EXE) từ các phần Model và ViewModel (một DLL).Thực hiện MVVM trong WPF mà không cần sử dụng System.Windows.Input.ICommand

Xoay ở đây là giữ cho mô hình Model/ViewModel rõ ràng về bất kỳ sự phụ thuộc WPF nào. Lý do cho điều này là tôi muốn sử dụng lại nó từ các tập tin thực thi với các công nghệ UI khác (không phải WPF), ví dụ như WinForms hoặc GTK # dưới Mono.

Theo mặc định, điều này không thể thực hiện được, vì ViewModel hiển thị một hoặc nhiều ICommands. Nhưng kiểu ICommand được định nghĩa trong không gian tên System.Windows.Input, thuộc về WPF!

Vì vậy, có cách nào để đáp ứng cơ chế ràng buộc WPF mà không cần sử dụng ICommand không?

Cảm ơn!

+2

@aoven: tôi nơi bạn đã khi bạn hỏi này 8 tháng trước, và tự hỏi những gì bạn vết thương lên và làm thế nào nó lo lắng d nó ra cho bạn. Chúc mừng – Berryl

Trả lời

7

Bạn có thể xác định một lệnh định tuyến tùy chỉnh WPF duy nhất trong lớp wpf của bạn và một lớp trình xử lý lệnh đơn. Tất cả các lớp WPF của bạn có thể liên kết với một lệnh này với các tham số thích hợp.

Lớp trình xử lý sau đó có thể dịch lệnh sang giao diện lệnh tùy chỉnh của riêng bạn mà bạn tự xác định trong lớp ViewModel của mình và độc lập với WPF.

Ví dụ đơn giản nhất sẽ là trình bao bọc cho đại biểu trống với phương thức Execute.

Tất cả các lớp GUI khác nhau của bạn chỉ cần dịch từ các loại lệnh gốc của chúng sang các loại lệnh tùy chỉnh của bạn ở một vị trí.

+1

bạn có thể đưa ra một ví dụ không? – Jose

+1

xin vui lòng xem bên dưới ví dụ –

4

WinForms không có cơ sở hạ tầng dữ liệu phong phú và lệnh cần thiết để sử dụng mô hình chế độ xem kiểu MVVM. Cũng giống như bạn không thể tái sử dụng một bộ điều khiển MVC ứng dụng web trong ứng dụng khách (ít nhất là không phải tạo núi bao bọc và bộ điều hợp cuối cùng, làm cho việc viết và gỡ lỗi mã khó hơn mà không cung cấp bất kỳ giá trị nào cho nó). khách hàng) bạn không thể tái sử dụng một MVVM WPF trong một ứng dụng WinForms.

Tôi đã không sử dụng GTK # trên một dự án thực sự vì vậy tôi không có ý tưởng những gì nó có thể hoặc không thể làm nhưng tôi nghi ngờ MVVM không phải là cách tiếp cận tối ưu cho GTK # anyway.

Cố gắng di chuyển nhiều hành vi của ứng dụng vào mô hình, có mô hình xem chỉ hiển thị dữ liệu từ mô hình và gọi vào mô hình dựa trên các lệnh không có logic trong mô hình khung nhìn.

Sau đó, đối với WinForms, chỉ cần loại bỏ mô hình khung nhìn và gọi mô hình từ giao diện người dùng trực tiếp hoặc tạo một lớp trung gian khác dựa trên WinForms hỗ trợ ràng buộc dữ liệu hạn chế hơn.

Lặp lại cho GTK # hoặc viết bộ điều khiển MVC và chế độ xem để cung cấp cho mô hình giao diện người dùng web.

Đừng cố ép buộc một công nghệ vào mẫu sử dụng được tối ưu hóa cho công nghệ khác, không viết cơ sở hạ tầng lệnh của riêng bạn từ đầu (tôi đã thực hiện nó trước đây chứ không phải lựa chọn hiệu quả nhất), sử dụng tốt nhất công cụ cho mỗi công nghệ.

2

Thay vì lệnh hiển thị VM, chỉ cần hiển thị các phương thức. Sau đó sử dụng các hành vi đính kèm để ràng buộc các sự kiện với các phương thức, hoặc nếu bạn cần một lệnh, hãy sử dụng một ICommand có thể ủy quyền cho các phương thức này và tạo lệnh thông qua các hành vi được đính kèm.

2

Tất nhiên điều này là có thể. Bạn có thể tạo ra một mức trừu tượng khác. Thêm bạn sở hữu giao diện IMyCommand tương tự hoặc giống như ICommand và sử dụng giao diện đó.

Hãy xem giải pháp MVVM hiện tại của tôi giải quyết hầu hết các vấn đề bạn đã đề cập nhưng nó hoàn toàn bị trừu tượng hóa từ các nền tảng cụ thể và có thể được sử dụng lại. Ngoài ra tôi sử dụng không có mã-đằng sau chỉ ràng buộc với DelegateCommands thực hiện ICommand. Dialog về cơ bản là một View - một điều khiển riêng biệt có ViewModel của riêng nó và nó được hiển thị từ ViewModel của màn hình chính nhưng được kích hoạt từ UI thông qua DelagateCommand binding.

Xem đầy đủ Silverlight 4 giải pháp ở đây Modal dialogs with MVVM and Silverlight 4

+0

Trả lời chứng minh cách tiếp cận này [ở đây] (http: // stackoverflow.com/a/19795332/954927) – Neutrino

4

tôi cần một ví dụ của việc này vì vậy tôi đã viết một cách sử dụng các kỹ thuật khác nhau.

tôi đã có một vài mục tiêu thiết kế trong tâm trí

1 - giữ nó đơn giản

2 - hoàn toàn không có code-behind trong giao diện (class Window)

3 - chứng minh một sự phụ thuộc của chỉ Tham chiếu hệ thống trong thư viện lớp ViewModel.

4 - giữ logic nghiệp vụ trong ViewModel và định tuyến trực tiếp đến các phương thức thích hợp mà không cần viết một loạt các phương thức "sơ khai".

Dưới đây là các mã ...

App.xaml (không StartupUri là điều duy nhất đáng chú ý)

<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 (tải lên giao diện chính)

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 (ma thuật)

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 (không có mã-đằng sau, nghĩa là xóa!)

<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 (bao gồm mô hình thực tế cũng từ ví dụ này là rất đơn giản, xin vui lòng tha các derailing của mô hình 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

Chỉ cần một cách nhanh chóng và dơ bẩn và tôi hy vọng nó hữu ích cho một ai đó. Tôi thấy một vài cách tiếp cận khác nhau thông qua Google khác nhau nhưng không có gì đơn giản và dễ thực hiện với số lượng mã ít nhất có thể mà tôi muốn. Nếu có một cách để đơn giản hóa hơn nữa xin vui lòng cho tôi biết, cảm ơn.

Chúc mừng Mã hóa :)

EDIT: Để đơn giản hóa mã của riêng tôi, bạn có thể tìm thấy điều này hữu ích cho việc các Thêm vào một lớp lót.

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' thật tuyệt. –

+0

Tôi đi qua cú pháp nhân vật lambda một nơi nào đó ở đây trong SO và rất thích cung cấp tín dụng: nó đã chỉ bị mắc kẹt với tôi. –

+0

Tôi có một cách tiếp cận khác [ở đây] (http://stackoverflow.com/a/19795332/954927) – Neutrino

1

Tôi nghĩ rằng bạn đang tách Dự án của bạn tại điểm sai. Tôi nghĩ bạn chỉ nên chia sẻ mô hình của bạn và các lớp logic kinh doanh.

VM là một mô hình thích ứng cho phù hợp với Chế độ xem WPF. Tôi sẽ giữ cho VM đơn giản và làm điều đó.

Tôi không thể tưởng tượng bắt MVVM khi sử dụng Winforms. OTOH chỉ có mô hình logic & kinh doanh, bạn có thể tiêm trực tiếp vào một Biểu mẫu nếu cần.

0

"bạn không thể tái sử dụng một WPF MVVM trong một ứng dụng WinForms"

Đối với điều này xin vui lòng xem url http://waf.codeplex.com/, tôi đã sử dụng MVVM trong Win Form, bây giờ whenver tôi muốn nâng cấp lên trình bày ứng dụng của từ Win Form để WPF, nó sẽ được thay đổi mà không có thay đổi trong logic ứng dụng,

Nhưng tôi có một vấn đề với việc sử dụng lại ViewModel trong Asp.net MVC, vì vậy tôi có thể làm cho ứng dụng Desktop tương tự trong Web mà không có hoặc ít thay đổi trong Logic ứng dụng ..

Cảm ơn ...

2

Xin lỗi Dave nhưng tôi không thích giải pháp của bạn lắm. Trước tiên, bạn phải mã hóa hệ thống ống nước cho mỗi lệnh theo cách thủ công trong mã, sau đó bạn phải cấu hình CommandRouter để biết về mỗi liên kết view/viewmodel trong ứng dụng.

Tôi đã thực hiện một cách tiếp cận khác.

Tôi có một hội đồng tiện ích Mvvm (không có phụ thuộc WPF) và tôi sử dụng nó trong khung nhìn của mình. Trong assembly đó, tôi khai báo một giao diện ICommand tùy chỉnh và một lớp DelegateCommand thực hiện giao diện đó.

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

Tôi cũng có một thư viện Wpf (tham chiếu thư viện WPF hệ thống), mà tôi tham khảo từ dự án UI WPF của tôi. Trong assembly đó, tôi khai báo một class CommandWrapper có giao diện System.Windows.Input.ICommand chuẩn. CommandWrapper được xây dựng bằng cách sử dụng một thể hiện của ICommand tùy chỉnh của tôi và chỉ cần ủy nhiệm Execute, CanExecute và CanExecuteChanged trực tiếp đến loại ICommand tùy chỉnh của tôi.

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

Trong lắp ráp WPF của tôi, tôi cũng tạo ra một ValueConverter rằng khi thông qua một thể hiện của tùy chỉnh của tôi ICommand spits ra một thể hiện của các CommandWrapper tương thích Windows.Input.ICommand.

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

Bây giờ viewmodels của tôi có thể tiếp xúc với các lệnh như trường hợp của loại lệnh tùy chỉnh của tôi mà không cần phải có bất kỳ sự phụ thuộc vào WPF, và giao diện người dùng của tôi có thể ràng buộc Windows.Input.ICommand lệnh để những viewmodels sử dụng ValueConverter của tôi như vậy. (XAML không gian tên thư rác 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> 

Bây giờ nếu tôi thực sự lười biếng (mà tôi) và không thể bị làm phiền để phải tự áp dụng các CommandConverter mỗi lần sau đó trong lắp ráp WPF của tôi, tôi có thể tạo lớp con Binding của riêng tôi như thế này:

namespace WpfUtil 
{ 
    using System.Windows.Data; 


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

vì vậy, bây giờ tôi có thể liên kết với loại lệnh tùy chỉnh của tôi thậm chí còn đơn giản hơn như vậy:

<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> 
+0

+1, Đừng xin lỗi, tôi hy vọng một ai đó thông minh hơn sẽ xuất hiện trong 2 năm nữa, đó là những gì SO dành cho ;). Chỉ cần lướt qua câu trả lời của bạn nhưng tôi thích sự trừu tượng hóa bổ sung và những gì nó mang lại. "Tôi có thể tạo lớp con Binding của riêng mình như thế này" = khủng khiếp gợi cảm, tôi thích nó;) –

Các vấn đề liên quan