62 votes

MVVM de la Folie: les Commandes

J'aime MVVM. Je n'ai pas l'amour, mais comme lui. La plupart de cela a du sens. Mais, je continue à lire les articles que vous encourager à écrire beaucoup de code, de sorte que vous pouvez écrire XAML et ne pas avoir à écrire du code dans le code-behind.

Laissez-moi vous donner un exemple.

Récemment, j'ai voulu le raccordement d'une commande à mon ViewModel pour une ListView MouseDoubleClickEvent. Je n'étais pas tout à fait sûr de savoir comment le faire. Heureusement, Google a les réponses à tout. J'ai trouvé les articles suivants:

Alors que les solutions ont été utile dans ma compréhension de commandes, il y avait des problèmes. Certaines de ces dispositions rendu le concepteur WPF inutilisable en raison d'un commun hack ajoutant "Interne" après une propriété de dépendance; le concepteur WPF ne pouvez pas le trouver, mais le CLR peut. Certaines solutions ne permettent pas de plusieurs commandes à la même commande. Certaines solutions ne permettent pas de paramètres.

Après avoir expérimenté pendant quelques heures, j'ai simplement décidé de faire ceci:

private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
    ListView lv = sender as ListView;
    MyViewModel vm = this.DataContext as MyViewModel;

    vm.DoSomethingCommand.Execute(lv.SelectedItem);
}

Donc, MVVM les puristes, s'il vous plaît dites-moi ce qui ne va pas? Je peux encore l'Unité de test de ma commande. Cela semble très pratique, mais il semble enfreindre la ligne directrice de "ZOMG... vous avez le code dans votre code-behind!!!!" Merci de partager vos pensées.

Merci à l'avance.

37voto

Greg D Points 24218

Je pense que la faute réside dans la pureté de l'exigence. Modèles de conception MVVM inclus, sont un outil dans la boîte à outils, pas une fin en eux-mêmes. Si il est plus logique de rupture avec la pureté du modèle pour un cas (et c'est clairement on dirait que vous avez examiné ce cas), puis de rompre avec le modèle.

Si cela fonctionne pour vous, et vous ne croyez pas que c'est une indûment le fardeau de l'entretien, alors je dirais que ce n'est pas ce que vous avez fait. Je pense que vous avez de toute évidence rencontré le fardeau de la preuve pour montrer que c'est une solution raisonnable à votre problème, en dépit de ce qu'un pur MVVM la mise en œuvre pourrait être.

(Je considère que cet argument similaire à la arguments pour multiparadigm langues. Alors qu'une Pure OO approche peut être appliquée, parfois, à faire les choses plus fonctionnelle est plus approprié. Alors qu'une Pure approche Fonctionnelle peut être appliqué, parfois, le compromis de montrer que OO techniques sont plus que la valeur du temps.)

13voto

Heinzi Points 66519

Je suis d'accord avec vous que beaucoup de MVVM-Commande des solutions sont trop compliqués. Personnellement, j'utilise une approche mixte et de définir mes Commandes dans la Vue plutôt que dans le ViewModel, à l'aide de méthodes et de propriétés dans le ViewModel.

XAML:

<Window.Resources>
    <RoutedCommand x:Key="LookupAddressCommand" />
</Window.Resources>
<Window.CommandBindings>
    <CommandBinding Command="{StaticResource LookupAddressCommand}" x:Name="cmdLookupAddress" />
</Window.CommandBindings>

Code (Affichage):

Private Sub cmdLookupAddress_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs) Handles cmdLookupAddress.CanExecute
    e.CanExecute = myViewModel.SomeProperty OrElse (myViewModel.SomeOtherProperty = 2)
End Sub

Private Sub cmdLookupAddress_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs) Handles cmdLookupAddress.Executed
    myViewModel.LookupAddress()
End Sub

Il n'est pas pur MVVM, mais c'est simple, il fonctionne, il n'a pas besoin spécial MVVM-commande-classes et il rend votre code plus facile à lire pour les non-MVVM-experts (= mes collègues).

10voto

Thomas Levesque Points 141081

Bien que je préfère ne pas écrire de code-behind lors de l'utilisation du pattern MVVM, je pense que c'est OK de le faire tant que ce code est purement liées à l'INTERFACE utilisateur.

Mais ce n'est pas le cas ici : vous appelez une vue-modèle de commande à partir du code-behind, il n'est donc pas purement liées à l'INTERFACE utilisateur, et la relation entre la vue et la vue-modèle de commande n'est pas directement apparent dans le code XAML.

Je pense que vous pouvez le faire facilement dans le code XAML, à l'aide de joint comportement de la commande. De cette façon, vous pouvez "lier" l' MouseDoubleClick événement à une commande de votre point de vue-modèle :

<ListView ItemSource="{Binding Items}">
   <local:CommandBehaviorCollection.Behaviors>
      <local:BehaviorBinding Event="MouseDoubleClick" Action="{Binding DoSomething}" />
   </local:CommandBehaviorCollection.Behaviors>

    ...
</ListView>

Vous pouvez également facilement accéder à l'élément sélectionné de la ListView sans la nommer directement, à l'aide de l' ICollectionView interface :

private ICommand _doSomething;

public ICommand DoSomething
{
    get
    {
        if (_doSomething == null)
        {
            _doSomething = new DelegateCommand(
                () =>
                {
                    ICollectionView view = CollectionViewSource.GetDefaultView(Items);
                    object selected = view.CurrentItem;
                    DoSomethingWithItem(selected);
                });
        }
        return _doSomething;
    }
}

5voto

Reed Copsey Points 315315

Je crois que le but est d'avoir "Pas de code dans le code-behind", c'est exactement cela, un objectif à atteindre pour l' - pas quelque chose que vous devriez le prendre comme un dogme absolu. Il y a des endroits appropriés pour le code dans la Vue - et ce n'est pas forcément un mauvais exemple de la façon dont le code peut être plus simple que d'une approche alternative.

L'avantage de l'autre s'approche de vous de la liste, y compris les propriétés ou événements attachés., c'est qu'ils sont réutilisables. Lorsque vous raccordez un événement directement, puis faire ce que vous avez fait, il est très facile pour la fin de la duplication de code dans votre application. Par la création d'une seule propriété jointe ou d'un événement à gérer que le câblage, vous ajoutez un peu de code supplémentaire dans le domaine de la plomberie, mais c'est le code qui est réutilisable pour n'importe quel ListView où vous souhaitez vous double-cliquez sur la manipulation.

Cela étant dit, j'ai tendance à préférer l'aide de la plus "puriste" de la démarche. Garder tous les de la la gestion des événements de la Vue peut-être pas d'effet sur le scénario de test (qui traitent spécifiquement), mais il n'a d'effet l'ensemble du programme designability et de maintenabilité. Par l'introduction de code dans votre code derrière, vous êtes en limitant votre Vue pour toujours à l'aide d'une ListView avec le gestionnaire d'événement filaire qui ne colle pas votre point de Vue dans le code, et de restreindre la marge de manœuvre pour remaniement par un designer.

2voto

dthrasher Points 10641

Ce que @JP décrit dans la question d'origine et @Heinzi mentionne dans l'est de répondre à une approche pragmatique de la manipulation difficile des commandes. À l'aide d'un petit morceau de code de gestion des événements dans le code-behind est particulièrement pratique lorsque vous avez besoin de faire un peu de travail avant de l'INTERFACE utilisateur de l'invocation de la commande.

Considérons le cas classique de la OpenFileDialog. Il est beaucoup plus facile d'utiliser un événement de clic sur le bouton, l'affichage de la boîte de dialogue, puis envoyer les résultats à une commande sur votre ViewModel que de l'adopter tout de la complexité de la messagerie routines utilisées par le MVVM boîtes à outils.

Dans votre XAML:

<Button DockPanel.Dock="Left" Click="AttachFilesClicked">Attach files</Button>

Dans votre code derrière:

    private void AttachFilesClicked(object sender, System.Windows.RoutedEventArgs e)
    {
        // Configure open file dialog box
        Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
        dlg.FileName = "Document"; // Default file name
        dlg.DefaultExt = ".txt"; // Default file extension
        dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension

        // Show open file dialog box
        bool? result = dlg.ShowDialog();

        // Process open file dialog box results
        if (result == true)
        {
            string filename = dlg.FileName;

            // Invoke the command.
            MyViewModel myViewModel = (MyViewModel)DataContext;
            if (myViewModel .AttachFilesCommand.CanExecute(filename))
            {
                noteViewModel.AttachFilesCommand.Execute(filename);  
            }
        }
    }

La programmation informatique est inflexible. Nous les programmeurs doivent être souples afin de les gérer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X