43 votes

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

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, j'aimerais parfois écouter les événements, de sorte que, par exemple, View puisse lancer l'animation ou fermer la fenêtre, lorsque VM le signale.

Il est possible de le faire via une propriété bool avec NotifyPropertyChanged (et de ne lancer l'animation que lorsqu'elle passe de false à true), mais cela ressemble à un hack, je préférerais de loin exposer l'événement, car c'est sémantiquement correct.

Aussi, j'aimerais le faire sans code dans le codebehind, comme faire viewModel.myEvent += handler cela signifierait que je devrais manuellement désenregistrer l'événement afin de permettre à la vue d'être GC'd - les vues WPF sont déjà en mesure d'écouter sur les propriétés "faiblement", et je préfère de loin programmer uniquement de manière déclarative dans la vue.

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

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

1 votes

Propertychanged est un événement. Qu'est-ce qui n'est pas sémantiquement correct dans l'écoute d'une propriété bool dans un trigger ?

11 votes

Je ne comprends vraiment pas pourquoi les gens sont si opposés au codebehind dans la vue. La raison pour laquelle les vues répondent aux propriétés est due au code dans le codebehind, il est juste caché dans le cadre.

2 votes

Adrianm : J'ai essayé de créer une propriété bool dans VM qui est toujours fausse, et de lever OnPropertyChanged sur elle, mais la vue n'a pas réagi - il semble que WPF fera quelque chose seulement quand la valeur réelle change. J'aurais donc besoin d'activer et de désactiver ce bool, MyProp = true ; OnPropertyChanged("MyProp") ; MyProp = false ; OnPropertyChanged("MyProp") ; - au lieu de RaiseMyEvent(). Vous voyez ?

6voto

Phil Points 1467

Merci de m'avoir fait part de votre question.

Utilisez les comportements de mélange et les déclencheurs et actions existants pour répondre à l'exigence de Tomas :

Comment puis-je faire en sorte qu'un déclencheur d'événement WPF sur une vue se déclenche lorsque le Viewmodel sous-jacent l'impose ?

3voto

Kent Boogaart Points 97432

Quelques commentaires :

  • Vous pouvez utiliser le modèle d'événement faible pour s'assurer que la vue peut être GC même si elle est toujours attachée à l'événement du modèle de vue.
  • Si vous basculez déjà plusieurs VM pour une seule vue, ne serait-ce pas l'endroit idéal pour attacher/détacher le handler ?
  • En fonction de votre scénario exact, vous pourriez simplement faire en sorte que la VM expose une propriété d'état que la vue utilise comme déclencheur d'animations, de transitions et d'autres changements visuels. Gestionnaire d'état visuel est génial pour ce genre de choses.

0 votes

Merci pour les liens, Kent. Je connais le modèle d'événement faible, mais j'espérais que WPF avait quelque chose d'intégré (pour lier une action à l'événement VM) + les événements faibles sont vraiment délicats en C# : codeproject.com/KB/cs/WeakEvents.aspx . La commutation peut se faire à partir de plusieurs endroits, et je ne peux pas dire quand il faut désenregistrer le dernier auditeur (cela serait résolu par des événements faibles). L'état est ce dont je n'ai pas besoin - j'ai pratiquement un seul état, mes événements ne se produisent pas lors des changements d'état. Merci, mais je vais voir si quelqu'un fournit une réponse à mon cas.

1 votes

Au fait, je trouve étrange que l'on puisse déclencher par exemple une animation lorsque la propriété liée passe de x à y, mais que le scénario plus naturel (déclencher l'animation lorsque l'événement se produit) semble ne pas être supporté...

0 votes

Le problème est que votre modèle n'a qu'un seul état. Pourquoi voulez-vous lancer une animation si rien ne change dans le modèle ?

2voto

Chris Marcus Points 11

C'est une chose avec laquelle j'ai lutté aussi...

Similaire à ce que les autres disent, mais voici un exemple avec quelques extraits de code... Cet exemple montre comment utiliser pub/sub pour qu'une vue s'abonne à un événement déclenché par la VM - dans ce cas, je fais un GridView. Rebind pour s'assurer que le gv est en synchronisation avec la VM...

Vue (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 de fermeture 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 transmette un Guid, ou un identifiant, qui peut être défini sur Pub et vérifié sur Sub pour s'assurer que Pub/Sub est synchrone.

2 votes

Cela n'utilise-t-il pas le codebehind dans la vue, cependant ? Ce que l'OP a demandé d'éviter.

1voto

voidx Points 11

Imho yYet séparé

  1. état - pour pouvoir déplacer les données dans les deux sens entre la vue <-> vm
  2. actions - pour pouvoir appeler les fonctions/commandes du modèle de vue
  3. notifications - pour pouvoir signaler à la vue que quelque chose s'est produit et que vous voulez qu'elle entreprenne une action, comme faire briller un élément, changer de style, modifier la disposition, mettre en évidence un autre élément, etc.

Bien qu'il soit vrai que vous pouvez le faire avec une liaison de propriété, c'est plus un hack comme Tomas l'a mentionné ; j'ai toujours eu l'impression que c'était le cas.

ma solution pour être capable d'écouter les 'événements' d'un modèle de vue, c'est-à-dire les notifications, est d'écouter simplement les changements de contexte de données et lorsqu'il change, je vérifie que le type est le modèle de vue que je recherche et je connecte les événements. brut mais simple.

ce que j'aimerais vraiment, c'est un moyen simple de définir des déclencheurs d'événements de modèle de vue, puis de fournir une sorte de gestionnaire qui réagirait du côté de la vue, tout cela dans le xaml, et de ne passer au code que pour les choses qui ne sont pas réalisables en xaml.

1voto

JerKimball Points 8994

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

Si la réponse a quelque chose à voir avec des choses réservées à la vue comme les animations, je dirais que le ViewModel n'a pas besoin d'en savoir plus : le code derrière (quand c'est approprié), les Data/Event/PropertyTriggers, et les nouvelles constructions VisualStateManager vous serviront beaucoup mieux, et maintiendront la séparation nette entre View et ViewModel.

Si quelque chose doit "se produire" à la suite de l'événement, il faut alors utiliser un modèle de commande - soit en utilisant le CommandManger, en traitant l'événement en code derrière et en invoquant la commande sur le modèle de vue, soit en utilisant des comportements attachés dans les librairies System.Interactivity.

D'une manière ou d'une autre, vous devez garder votre ViewModel aussi "pur" que possible - si vous y voyez quoi que ce soit de spécifique à la vue, vous vous y prenez probablement mal :)

9 votes

Je ne pense pas que vous ayez bien compris la question - disons que mon modèle est un client twitter qui a l'événement "nouveau message arrivé". ViewModel est à l'écoute du modèle, et signale également un événement 'NewMessageArrived' (et il n'a aucune idée de la façon dont la vue réagit à cet événement). Maintenant, j'aimerais que la vue démarre une animation lorsque l'événement se produit. Il n'y a pas de changement d'état (voir mes commentaires ci-dessus), donc DataTrigger, PropertyTrigger et VisualStateManager sont hors jeu, et EventTrigger écoute les RoutedEvents, qui proviennent uniquement des UIElements (donc le ViewModel ne peut pas les déclencher !).

0 votes

Je suis tout à fait d'accord avec cela, si le code que vous devez ajouter est UNIQUEMENT lié à la vue, c'est-à-dire aux animations ou à la mise à jour de la vue, il ne doit faire partie que de la vue. C'est parce que si vous essayez de créer une autre vue, elle peut avoir des exigences différentes pour les animations ou la mise à jour.

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