2009-02-02 10 views
8

Je suis en train de mettre en œuvre une application WPF utilisant modèle MVVM (Model-View-ViewModel) et je voudrais avoir la partie Voir dans un ensemble séparé (un EXE) des parties Model et ViewModel (une DLL).MVVM mise en œuvre dans WPF sans utiliser System.Windows.Input.ICommand

La torsion ici consiste à garder l'ensemble Model/ViewModel à l'écart de toute dépendance de WPF. La raison en est que je voudrais le réutiliser à partir d'exécutables avec différentes technologies d'interface utilisateur (non-WPF), par exemple WinForms ou GTK # sous Mono. Par défaut, cela ne peut pas être fait, car ViewModel expose un ou plusieurs ICommands. Mais le type ICommand est défini dans l'espace de noms System.Windows.Input, qui appartient au WPF!

Alors, existe-t-il un moyen de satisfaire le mécanisme de liaison WPF sans utiliser ICommand?

Merci!

+2

@aoven: Je suis là où vous étiez quand vous avez posé il y a 8 mois, et je me demandais ce que vous blessez en faisant et comment ça marche D pour toi. Salut – Berryl

Répondre

7

Vous devriez être en mesure de définir une seule commande personnalisée mis en déroute WPF dans votre couche WPF et une seule classe de gestionnaire de commandes. Toutes vos classes WPF peuvent se lier à cette commande avec les paramètres appropriés.

La classe de gestionnaire peut alors traduire la commande à votre propre interface de commande personnalisée que vous définissez vous-même dans votre couche ViewModel et est indépendante de WPF.

L'exemple le plus simple serait un wrapper à un délégué vide avec une méthode Execute.

Tout ce que vous les différentes couches de l'interface graphique ont simplement besoin de traduire de leurs types de commandes natives à vos types de commandes personnalisées dans un seul endroit.

+1

pouvez-vous donner un exemple? – Jose

+1

s'il vous plaît voir ci-dessous par exemple –

4

WinForms ne dispose pas de l'infrastructure de liaison de données et de commandes requise pour utiliser un modèle de vue de style MVVM. Tout comme vous ne pouvez pas réutiliser les contrôleurs MVC d'une application Web dans une application cliente (du moins pas sans créer des montagnes de wrappers et d'adaptateurs qui compliquent l'écriture et le débogage du code sans apporter de valeur aux données). client), vous ne pouvez pas réutiliser un MVVM WPF dans une application WinForms. Je n'ai pas utilisé GTK # sur un projet réel, donc je n'ai aucune idée de ce qu'il peut ou ne peut pas faire mais je suspecte que MVVM n'est pas l'approche optimale pour GTK # de toute façon. Essayez de déplacer le plus grand nombre possible de comportements de l'application dans le modèle, ayez un modèle de vue qui n'expose que les données du modèle et appelle le modèle en fonction des commandes sans logique dans le modèle de vue.

Ensuite, pour WinForms supprimer simplement le modèle de vue et appeler le modèle de l'interface utilisateur directement, ou créer une autre couche intermédiaire qui est basée sur WinForms de données plus limitées de soutien obligatoire.

Répétez l'opération pour GTK # ou écrire des contrôleurs MVC et des vues pour donner le modèle d'un frontal Web. N'essayez pas de forcer une technologie dans un modèle d'utilisation optimisé pour une autre, n'écrivez pas votre propre infrastructure de commandes à partir de zéro (je l'ai déjà fait, pas mon choix le plus productif), utilisez le meilleur outils pour chaque technologie.

2

Au lieu des commandes d'exposition VM, il suffit d'exposer les méthodes. Utilisez ensuite des comportements attachés pour lier des événements aux méthodes ou, si vous avez besoin d'une commande, utilisez un ICommand qui peut déléguer à ces méthodes et créer la commande via des comportements attachés.

2

Bien sûr que c'est possible. Vous pouvez créer juste un autre niveau d'abstraction. Ajoutez votre propre interface IMyCommand similaire ou identique à ICommand et utilisez-la.

Jetez un oeil à ma solution MVVM actuelle qui résout la plupart des problèmes que vous avez mentionnés et qui est complètement abstraite des choses spécifiques à la plate-forme et qui peut être réutilisée. De plus, je n'ai utilisé aucune liaison par code uniquement avec DelegateCommands qui implémentent ICommand. La boîte de dialogue est fondamentalement une vue - un contrôle séparé qui a son propre ViewModel et il est affiché à partir du ViewModel de l'écran principal mais déclenché à partir de l'interface utilisateur via la liaison DelagateCommand.

Voir solution complète Silverlight 4 ici Modal dialogs with MVVM and Silverlight 4

+0

Réponse démontrant cette approche [ici] (http: // stackoverflow.com/a/19795332/954927) – Neutrino

4

je besoin d'un exemple j'ai donc écrit une en utilisant diverses techniques.

J'ai eu quelques objectifs de conception à l'esprit

1 - keep it simple

2 - absolument pas de code-behind dans la vue (classe Window)

3 - démontrer une dépendance de seulement la référence Système dans la bibliothèque de classes ViewModel. - conserver la logique métier dans le ViewModel et acheminer directement vers les méthodes appropriées sans écrire un tas de méthodes "stub".

est ici le code ...

App.xaml (pas StartupUri est la seule chose à noter)

<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 (charger la vue principale)

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 (la magie)

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 (il n'y a pas de code-behind, littéralement supprimé!)

<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 (y compris le modèle réel ainsi depuis cet exemple est si simplifiée, s'il vous plaît excuser le faire dérailler du motif 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

Juste un rapide et sale et j'espère que ce sera utile à quelqu'un. J'ai vu quelques approches différentes à travers Google différents, mais rien n'était tout à fait aussi simple et facile à mettre en œuvre avec le moins de code possible que je voulais. S'il y a un moyen de simplifier encore plus, s'il vous plaît faites le moi savoir, merci.

Bonne programmation :)

EDIT: Pour simplifier mon propre code, vous pourriez trouver ce utile pour faire l'ADDS en une seule ligne.

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' est plutôt cool. –

+0

Je suis tombé sur la syntaxe de caractères lambda quelque part ici dans SO et aimerais donner du crédit: il vient de rester avec moi. –

+0

J'ai une approche différente [ici] (http://stackoverflow.com/a/19795332/954927) – Neutrino

1

Je pense que vous séparez votre projet au mauvais endroit. Je pense que vous devez partager uniquement vos classes de modèle et de logique métier.VM est une adaptation du modèle aux vues WPF. Je garderais VM simple et fais juste cela.

Je ne peux pas imaginer forcer MVVM sur Winforms. OTOH ayant juste le modèle & logique d'entreprise, vous pouvez les injecter directement dans un formulaire si nécessaire.

0

« vous ne pouvez pas réutiliser une MVVM WPF dans une application WinForms »

Pour cette s'il vous plaît voir url http://waf.codeplex.com/, je l'ai utilisé MVVM dans Win formulaire, maintenant whenver je voudrais améliorer la présentation de l'application de Win formulaire à WPF, il sera changé sans changement dans la logique applicative,

Mais j'ai un problème avec la réutilisation de ViewModel dans Asp.net MVC, donc je peux faire que même Desktop gagne l'application dans le Web sans ou moins de changement dans la logique de l'application.

Merci ...

2

Désolé Dave mais je n'ai pas beaucoup aimé votre solution. Premièrement, vous devez coder le plomberie pour chaque commande manuellement dans le code, puis vous devez configurer le CommandRouter pour connaître chaque association view/viewmodel dans l'application.

J'ai pris une approche différente.

J'ai un assembly utilitaire Mvvm (qui n'a pas de dépendances WPF) et que j'utilise dans mon viewmodel. Dans cet assembly, je déclare une interface ICommand personnalisée et une classe DelegateCommand qui implémente cette interface.

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

J'ai aussi un ensemble de bibliothèque WPF (qui fait référence aux bibliothèques système WPF), que je référence de mon projet UI WPF. Dans cet assembly, je déclare une classe CommandWrapper qui a l'interface standard System.Windows.Input.ICommand. CommandWrapper est construit en utilisant une instance de mon ICommand personnalisé et délègue simplement Execute, CanExecute et CanExecuteChanged directement à mon type ICommand personnalisé.

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

Dans mon assemblage WPF je crée aussi un ValueConverter que lorsqu'il est passé une instance de mon habitude ICommand recrache une instance de la CommandWrapper compatible 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(); 
     } 
    } 
} 

Maintenant, mes viewmodels peut exposer les commandes comme des instances de mon type de commande personnalisée sans avoir à avoir une dépendance à l'égard WPF, et mon interface utilisateur peut lier Windows.Input.ICommand commandes à ces viewmodels utilisant mon ValueConverter comme si. (Spam XAML namespace spam).

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

Maintenant, si je suis vraiment paresseux (que je suis) et ne peut pas être pris la peine d'avoir à appliquer manuellement le CommandConverter chaque fois puis dans mon assemblée WPF je peux créer ma propre sous-classe Binding comme ceci:

namespace WpfUtil 
{ 
    using System.Windows.Data; 


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

alors maintenant, je peux lier à mon type de commande personnalisée encore plus simplement comme ceci:

<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, Ne soyez pas désolé, j'espère que quelqu'un est arrivé plus intelligemment dans 2 ans, c'est ce que le SO est pour ;). Juste écrémé votre réponse mais j'aime l'abstraction ajoutée et ce qu'elle apporte. "Je peux créer ma propre sous-classe Binding comme ça" = terriblement sexy, je l'aime;) –