77 votes

Comment passer le distributeur d'interface utilisateur au modèle de visualisation (ViewModel) ?

Je suis censé être en mesure d'accéder à l'application Dispatcher qui appartient à la vue, je dois le transmettre au modèle de vue. Mais la vue ne doit rien savoir du modèle de vue, alors comment la transmettre ? Introduire une interface ou, au lieu de la passer aux instances, créer un distributeur global singleton qui sera écrit par la vue ? Comment résolvez-vous ce problème dans vos applications et frameworks MVVM ?

EDIT : Notez que puisque mes ViewModels peuvent être créés dans des threads en arrière-plan, je ne peux pas simplement faire Dispatcher.Current dans le constructeur du ViewModel.

47voto

winSharp93 Points 7124

J'ai abstrait le Dispatcher à l'aide d'une interface IContext :

public interface IContext
{
   bool IsSynchronized { get; }
   void Invoke(Action action);
   void BeginInvoke(Action action);
}

L'avantage est que vous pouvez tester vos ViewModels de manière unitaire plus facilement.
J'injecte l'interface dans mes ViewModels en utilisant le MEF (Managed Extensibility Framework). Une autre possibilité serait un argument de constructeur. Cependant, je préfère l'injection à l'aide du MEF.

Mise à jour (exemple tiré du lien pastebin dans les commentaires) :

public sealed class WpfContext : IContext
{
    private readonly Dispatcher _dispatcher;

    public bool IsSynchronized
    {
        get
        {
            return this._dispatcher.Thread == Thread.CurrentThread;
        }
    }

    public WpfContext() : this(Dispatcher.CurrentDispatcher)
    {
    }

    public WpfContext(Dispatcher dispatcher)
    {
        Debug.Assert(dispatcher != null);

        this._dispatcher = dispatcher;
    }

    public void Invoke(Action action)
    {
        Debug.Assert(action != null);

        this._dispatcher.Invoke(action);
    }

    public void BeginInvoke(Action action)
    {
        Debug.Assert(action != null);

        this._dispatcher.BeginInvoke(action);
    }
}

2 votes

Pourriez-vous fournir un exemple de mise en œuvre dans un UserControl wpf ?

3 votes

Oui, des exemples peuvent-ils être fournis sur la mise en œuvre ? Merci.

47voto

Pourquoi ne pas utiliser

 System.Windows.Application.Current.Dispatcher.Invoke(
         (Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));

au lieu de garder une référence au répartiteur de l'interface graphique.

1 votes

Bizarrement Dispatcher.CurrentDispatcher n'a pas fonctionné pour moi. Application.Current.Dispatcher a fonctionné.

19voto

Jakob Christensen Points 9381

Il se peut que vous n'ayez pas besoin du dispatcheur. Si vous liez des propriétés de votre modèle de vue à des éléments de l'interface graphique dans votre vue, le mécanisme de liaison de WPF marshallise automatiquement les mises à jour de l'interface graphique vers le thread de l'interface graphique à l'aide du répartiteur.


EDITAR:

Cette modification est une réponse au commentaire d'Isak Savo.

Dans le code de Microsoft pour la gestion de la liaison aux propriétés, vous trouverez le code suivant :

if (Dispatcher.Thread == Thread.CurrentThread)
{ 
    PW.OnPropertyChangedAtLevel(level);
} 
else 
{
    // otherwise invoke an operation to do the work on the right context 
    SetTransferIsPending(true);
    Dispatcher.BeginInvoke(
        DispatcherPriority.DataBind,
        new DispatcherOperationCallback(ScheduleTransferOperation), 
        new object[]{o, propName});
} 

Ce code marshalise toutes les mises à jour de l'interface utilisateur vers le thread UI. Ainsi, même si vous mettez à jour les propriétés faisant partie du binding depuis un autre thread, WPF sérialisera automatiquement l'appel vers le thread UI.

10 votes

Imaginez une ObservableCollection liée à l'interface utilisateur et vous essayez d'appeler _collection.Add() à partir d'un thread de travail.

0 votes

Je sais. Bien entendu, les considérations habituelles en matière d'interprétation s'appliquent toujours.

3 votes

Actuellement, nous avons besoin du dispatcher dans le seul but d'ajouter des éléments à la collection observable.

16voto

Andrew Shepherd Points 16670

J'obtiens du ViewModel qu'il stocke le répartiteur actuel en tant que membre.

Si le ViewModel est créé par la vue, vous savez que le répartiteur actuel au moment de la création sera le répartiteur de la vue.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}

0 votes

Dans mon scénario, les ViewModels sont créés dans des threads. C'est la raison pour laquelle je posais la question en premier lieu.

5 votes

Mais cela pose des problèmes pour les tests unitaires. Comment écrire ce type de code et tester vos VMs ?

0 votes

@Roy : Voir cette question stackoverflow.com/questions/1106881/

5voto

Will Points 76760

Un autre modèle courant (qui est aujourd'hui très utilisé dans le cadre) est le modèle SynchronizationContext .

Il vous permet d'effectuer des envois de manière synchrone et asynchrone. Vous pouvez également définir le SynchronizationContext actuel sur le thread actuel, ce qui signifie qu'il peut être facilement simulé. Le DispatcherSynchronizationContext est utilisé par les applications WPF. D'autres implémentations du SynchronizationContext sont utilisées par WCF et WF4.

2 votes

C'est la meilleure méthode et la plus simple, je pense. Vous pouvez définir par défaut le SynchronizationContext du ViewModel comme étant le contexte actuel du thread dans lequel le ViewModel a été créé, et si un UserControl a besoin de le modifier, il peut le faire à sa guise.

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