web-dev-qa-db-fra.com

WPF: comment signaler un événement de ViewModel à View sans code dans le code derrière?

J'ai un problème assez simple (j'espère :)):

Dans MVVM, View écoute généralement les modifications des propriétés de ViewModel. Cependant, je voudrais parfois écouter sur événement, afin que, par exemple, View puisse démarrer l'animation ou fermer la fenêtre lorsque VM signale.

Le faire via la propriété bool avec NotifyPropertyChanged (et démarrer l'animation uniquement lorsqu'elle passe de false à true) est possible, mais cela ressemble à un hack, je préférerais de beaucoup exposer l'événement, car il est sémantiquement correct.

Aussi, je voudrais le faire sans code dans codebehind, comme faire viewModel.myEvent += handler cela signifierait que j'aurais désenregistré manuellement l'événement afin de permettre à View d'être GC'd - Les vues WPF sont déjà capables d'écouter les propriétés 'faiblement', et je préférerais de loin ne programmer que de manière déclarative dans View .

L'abonnement standard aux événements forts est également mauvais, car je dois changer plusieurs ViewModels pour une seule vue (car la création de vues à chaque fois prend trop de temps CPU).

Merci pour les idées (s'il existe une solution standard, un lien vers msdn suffira)!

40
Tomáš Kafka

Certains commentaires:

  • Vous pouvez utiliser le modèle d'événement faible pour vous assurer que la vue peut être GC'd même si elle est toujours attachée à l'événement du modèle de vue
  • Si vous changez déjà plusieurs VM pour la même vue, ne serait-ce pas l'endroit idéal pour attacher/détacher le gestionnaire?
  • En fonction de votre scénario exact, vous pouvez simplement demander à VM d'exposer une propriété d'état que la vue utilise comme déclencheur pour des animations, des transitions et d'autres modifications visuelles. Visual state manager est idéal pour ce genre de chose.
3
Kent Boogaart

Ce fil est assez ancien, mais je fournirai mes 0,02 $ car c'est quelque chose avec lequel j'ai lutté aussi récemment ...

Similaire à ce que disent les autres, mais voici un exemple avec quelques extraits de code ... Cet exemple montre comment utiliser pub/sub pour qu'un abonnement à une vue soit déclenché par le VM - dans ce cas, je fais une GridView. Reliez pour vous assurer que le gv est synchronisé avec la VM ...

Voir (Sub):

 using Microsoft.Practices.Composite.Events;
 using Microsoft.Practices.Composite.Presentation.Events;

 private SubscriptionToken getRequiresRebindToken = null;

    private void SubscribeToRequiresRebindEvents()
    {
        this.getRequiresRebindToken =
            EventBus.Current.GetEvent<RequiresRebindEvent>()
            .Subscribe(this.OnRequiresRebindEventReceived, 
                ThreadOption.PublisherThread, false,
                MemoryLeakHelper.DummyPredicate);
    }

    public void OnRequiresRebindEventReceived(RequiresRebindEventPayload payload)
    {
        if (payload != null)
        {
            if (payload.RequiresRebind)
            {
                using (this.gridView.DeferRefresh())
                {
                    this.gridView.Rebind();
                }
            }
        }
    }

    private void UnsubscribeFromRequiresRebindEvents()
    {
        if (this.getRequiresRebindToken != null)
        {
            EventBus.Current.GetEvent<RequiresRebindEvent>()
                .Unsubscribe(this.getRequiresRebindToken);
            this.getRequiresRebindToken = null;
        }
    }

Appelez unsub à partir de la méthode close pour éviter les fuites de mémoire.

ViewModel (Pub):

 private void PublishRequiresRebindEvent()
 {
      var payload = new RequiresRebindEventPayload();
      payload.SetRequiresRebind();
      EventBus.Current.GetEvent<RequiresRebindEvent>().Publish(payload);
 }

classe de charge utile

using System;
using Microsoft.Practices.Composite.Presentation.Events;

public class RequiresRebindEvent 
    : CompositePresentationEvent<RequiresRebindEventPayload>
{

}

public class RequiresRebindEventPayload
{
    public RequiresRebindEventPayload()
    {
        this.RequiresRebind = false;
    }

    public bool RequiresRebind { get; private set; }

    public void SetRequiresRebind()
    {
        this.RequiresRebind = true;
    }
}

Notez que vous pouvez également configurer le constructeur pour qu'il passe un Guid, ou certains identifiés dans, qui peuvent être définis sur Pub et vérifiés sur sub pour être sûr que pub/sub est synchronisé.

2
Chris Marcus

à mon humble avis et séparé

  1. état - pour pouvoir déplacer les données d'avant en arrière entre les vues <-> vm
  2. actions - pour pouvoir appeler sur les fonctions/commandes du modèle de vue
  3. notifications - pour être en mesure de signaler à la vue que quelque chose s'est produit et que vous souhaitez qu'il effectue une action de visualisation comme faire briller un élément, changer de style, changer la disposition, concentrer un autre élément, etc.

bien qu'il soit vrai que vous pouvez le faire avec une propriété, c'est plus un hack comme mentionné précédemment; a toujours ressenti ça pour moi.

ma solution pour pouvoir écouter les `` événements '' à partir d'un modèle de vue aka notifications est d'écouter simplement les changements de contexte de données et quand il change, je vérifie que le type est le vm que je recherche et connecte les événements. brut mais simple.

ce que j'aimerais vraiment, c'est un moyen simple de définir des déclencheurs "afficher le modèle d'événement", puis de lui fournir une sorte de gestionnaire qui réagirait du côté de la vue tout dans le xaml et ne tomberait que pour coder derrière pour des choses qui ne sont pas faisable dans xaml

1
voidx

Une question plus générale à poser est: "Pourquoi est-ce que j'essaie de gérer cet événement dans mon ViewModel?"

Si la réponse a quelque chose à voir avec des choses en lecture seule comme les animations, je dirais que le ViewModel n'a pas besoin de le savoir: code derrière (le cas échéant), Data/Event/PropertyTriggers, et les nouvelles constructions VisualStateManager vous serviront beaucoup mieux et maintenez une séparation nette entre View et ViewModel.

Si quelque chose doit "se produire" à la suite de l'événement, alors ce que vous voulez vraiment utiliser est un modèle de commande - soit en utilisant le CommandManger, en gérant l'événement en code derrière et en invoquant la commande sur le modèle de vue, soit en utilisant comportements attachés dans les bibliothèques System.Interactivité.

Quoi qu'il en soit, vous voulez garder votre ViewModel aussi "pur" que possible - si vous voyez quelque chose de spécifique à View, vous vous trompez probablement. :)

0
JerKimball

Comme l'a dit adrianm, lorsque vous déclenchez votre animation sur une propriété bool, vous répondez en fait à un événement. Plus précisément l'événement PropertyChanged dont le sous-système WPF. Qui est conçu pour se fixer/se détacher correctement de/pour ne pas fuir la mémoire (vous pouvez oublier de le faire lorsque vous câblez un événement vous-même et provoquer une fuite de mémoire en ayant une référence active sur un objet qui autrement devrait être GCed) .

Cela vous permet d'exposer votre ViewModel en tant que DataContext pour le contrôle et de répondre correctement à la modification des propriétés du contexte de données via la liaison de données.

MVVM est un modèle qui fonctionne particulièrement bien avec WPF à cause de toutes ces choses que WPF vous donne, et déclencher un changement de propriété est en fait une manière élégante d'utiliser tout le sous-système WPF pour atteindre vos objectifs :)

0
Jim Wallace