8
votes

Équivalent de postmessage en C # pour synchroniser avec le fil principal avec MVVM?

Je dois être attardé avec la recherche, car voici un autre problème apparemment commun que je n'ai pas pu résoudre.

Voici mon problème - j'utilise WPF et MVVM, et j'ai une statemachine qui s'exécute dans le modèle. Si une erreur se produit, je dois transmettre des informations vers la fenêtre pour afficher l'erreur. Cette partie semble fonctionner correctement. Lorsque l'utilisateur clique sur le comportement souhaité, le code du modèle continue et examine l'objet que l'utilisateur interagit pour déterminer quoi faire ensuite.

Le problème est que le modèle doit recharger un fichier, qui met à jour l'interface graphique avec le contenu dudit fichier. Parce que le modèle est exécutant dans un fil, vous pouvez imaginer ce que je vais demander ensuite - comment diable synchronisez-vous avec l'interface graphique? En MFC, j'aurais utilisé SendMessage ou PostMessage pour accomplir la mise à jour de l'interface graphique.

J'ai Lire les articles pour Winforms qui suggère d'utiliser invoquée pour appeler automatiquement Débutant si nécessaire. En fait, je ne savais pas que Begininvoke accomplirait ce que je voulais, alors j'ai été encouragé à apprendre cela.

Comment puis-je réellement appeler begininvoke de mon modèle? Cette méthode s'applique-t-elle même au WPF? Je suis allé de l'avant et j'ai mis en œuvre un délégué, puis appelé invoke, mais je reçois la même erreur qui me dit que la collection ne peut pas être modifiée à partir de ce fil. J'ai aussi essayé Begininvoke pour l'enfer, mais je suppose que cela ne fonctionnerait pas aussi parce qu'il ne se lancerait pas d'un fil différent de toute façon.

confus. Si j'ai manqué quelque chose de vraiment évident qui a été affiché sur tout Internet, allez-y et donnez-moi des coups verbaux, je le mérite probablement.

edit - Je devrais probablement ajouter que je cherche quelque chose d'autre qu'une solution à la fois qu'une minuterie ou une solution basée sur le travail d'arrière-plan, à moins que ce soit le seul moyen de résoudre ce problème dans WPF / Mvvm. En outre, je me demande si l'une des outils à outils MVVM aurait déjà des installations pour ce type de chose ...


3 commentaires

Pour clarifier: je suppose que vous appeliez simplement invoquez () sur le délégué directement plutôt que de faire répartiteur.Invoke ()? La première exécutera simplement le délégué sur le même thread, la seconde mettra marshal l'appel au filetage du répartiteur (qui peut exister ou non en fonction du type d'application consommera votre viewModel).


Wayne, tu as raison. J'étais juste grossièrement l'article, et je me demande si le fait que c'était pour Winforms, c'est pourquoi leur exemple fonctionne? Quoi qu'il en soit, je pense que Franci avait raison et j'ai besoin de passer un répartiteur de tout moyen nécessaire. :) Merci pour votre temps. J'espère que je peux bientôt que cela résolue.


Il est possible que vous puissiez tirer parti de la classe MVVMMights Messenger pour envoyer un message à l'interface graphique sans traverser. Vous devriez toujours passer par le répartiteur si le code derrière touche l'une des interfaces interditives réelles. Je suis un peu confus, car la DataLinding passe déjà le répartiteur pourquoi il s'agit d'un problème. Si votre fil met à jour une propriété liée à la vue, elle doit automatiquement être mise au point sur l'interface utilisateur sans problème.


3 Réponses :


4
votes

Je pense que votre viewModel ne devrait vraiment pas savoir quoi que ce soit sur la vue, y compris s'il s'agit ou non d'une interface utilisateur WPF, ou si l'interface utilisateur a même eu le concept de fil de répartiteur, de sorte que le drapeau rouge devrait voler dès que Lorsque vous commencez à écrire du code dans votre viewModel qui tente de checkAccess () ou invoquée afin de prélever du code au fil de l'interface utilisateur. Au lieu de cela, j'aurai que le modèle soulevade un événement que la vue peut écouter et se mettre à jour en conséquence, ou la vue de la vue expose une propriété (par exemple, la fileisation de BoolLoading) que la vue se lie simplement pour détecter et afficher ce que le modèle est Faire de manière asynchrone et c'est la responsabilité de la viewModel de veiller à ce que la valeur de cette propriété soit exacte.

Par exemple: P>

public partial class MainWindow : Window {
    private ViewModel _model = new ViewModel();

    public MainWindow() {
        InitializeComponent();
        DataContext = _model;
    }

    private void Button_Click(object sender, RoutedEventArgs e) {
        _model.Run();
    }
}


<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Click="Button_Click"
                Content="Run"
                IsEnabled="{Binding IsIdle}" />
    </Grid>
</Window>



public class ViewModel : INotifyPropertyChanged {

    private bool _isIdle = true;

    public bool IsIdle {
        get { return _isIdle; }
        set {
            _isIdle = value;
            OnPropertyChanged("IsIdle");
        }
    }

    public void Run() {
        ThreadPool.QueueUserWorkItem((state) => {
            IsIdle = false;
            Thread.Sleep(10000);
            IsIdle = true;
        });
    }

    #region INotifyPropertyChanged Implementation

    protected void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if (propertyChanged != null) {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}


9 commentaires

C'est absolument vrai, les trucs de niveau inférieur ne devraient rien savoir sur les trucs de niveau supérieur. J'essaie de garder cela à l'esprit quand je travaille sur mon application. Dans ce cas particulier, je n'ai même pas remarqué que je modifiant les choses dans la viewModel, et cela pourrait être dû au fait que ce composant particulier que je travaille sur le modèle et la viewModel dans une classe (pour gagner du temps avec l'alpha). Je pense que l'événement est la voie à suivre, car votre suggestion sur la VM exposer une propriété est exactement ce que je fais, mais je ne peux finalement pas mettre à jour cette propriété à partir du fil sans événement.


Eh bien, techniquement, même exposer / changer une propriété consiste à soulever un événement - vous devez vous assurer que votre point de vue implémente Inoti.


Databinding est bien, j'ai déjà travaillé pendant un moment. Donc, peut-être que je devrais simplement confirmer cela - votre suggestion d'une solution basée sur des événements fonctionnera lorsque mon thread de travailleur lève l'événement, et mon ViewModel a enregistré le gestionnaire d'événements?


Le fil de travailleur est initié à partir du point de vue? Dans ce cas, la vue n'est pas impliquée, non? Votre problème est "Comment puis-je dire à l'opinion que les travaux d'arrière-plan ont terminé?" Pour cela, je ne vois pas pourquoi une propriété simple (liée aux données) dans la vue de la vue ne suffirait pas. Est-ce que je manque quelque chose?


Le thread du travailleur est effectivement initié à partir du point de vue. Voici comment ça se passe -> Bouton Clics de l'utilisateur, ICommand est exécuté et appelle dans la machine virtuelle, qui appelle ensuite le modèle. Le modèle reproduit un fil de travail et fait sa chose. Le modèle échoue finalement et doit récupérer. L'acte de récupération nécessite alors que l'interface graphique soit rafraîchie. Je veux que le modèle soit en mesure d'être en mesure de signaler asynchroneusement l'interface graphique à mettre à jour, ce qui est actuellement tenté en ayant la mise à jour de la vue Mettre à jour une observation d'observaBound à la vue. BTW, l'événement ne synchronise pas le fil de l'interface graphique principale.


>> L'acte de récupération nécessite alors que l'interface graphique soit rafraîchie. Dans WPF, vous ne devez pas le contrôler. La plomberie de la base de données s'occupera de celui-ci tant que les éléments liés (viewModel, objets) sont inotifyclangés / inotifyCollectionCollectionDangued). Je posterai un échantillon ci-dessus.


Notez que tout événement sera toujours soulevé sur le thread de fond, vous devez donc toujours comprendre comment passer au fil de l'interface utilisateur pour effectuer le travail là-bas. Que ce commutateur soit dans le modèle ou que le point de vue est une bonne discussion de design, mais c'est orthogonal pour votre question.


@Wayne: Votre approche semble fonctionner pour des propriétés, mais cela ne fonctionne pas pour les observations observables. J'ai modifié votre exemple (merci de l'avoir publié !!!) Pour ajouter une observationCollection, et modifier la méthode de votre exécution () entraîne en conséquence le même crash que j'essaie de contourner. C'est assez intéressant (pour moi au moins) :)


Droite - J'avais oublié que bien que WPF prend en charge l'envoi inotiferpropertychanged, il ne supporte pas encore l'envoi inotifyCollectionChanged. J'ai regardé dans un code ancien pour voir comment je faisais cela dans mes applications, et j'utilise quelque chose qui s'apparente à ceci: Julmar.com/blog/mark/2009/04/01/...



8
votes

Si vous souhaitez planifier certains travaux d'un fil d'arrière-plan sur le thread d'interface utilisateur dans WPF, utilisez le DispatcherObject . Voici un bel article sur la façon de Construire des applications plus réactives avec le répartiteur .

Mise à jour: Notez que si vous utilisez un événement pour envoyer des notifications du modèle à la vue, vous devez toujours passer au fil de l'interface utilisateur quelque part. Si ce commutateur doit être dans le modèle ou que le point de vue est une bonne discussion de design, mais c'est orthogonal pour votre question.

L'événement sera soulevé sur le fil de répartiteur correspondant. Puisque vous devez accéder au fil de l'interface utilisateur, vous devez utiliser un répartiteur créé sur le fil de l'interface utilisateur. Le moyen le plus simple d'obtenir un est d'utiliser la propriété DisternerObject.Dispatcher sur l'un des éléments de l'interface utilisateur. L'alternative est de créer un dans votre modèle ou votre menuelle. Si vous êtes puriste de design, je vous suggère de créer le répartiteur de votre modèle et de répartir le rappel au fil de l'interface utilisateur avant de soulever l'événement à laquelle la viewModel écoute. De cette façon, tout le commutation de threads et la gestion sont contenus dans votre modèle et la viewModel fonctionne comme un seul fileté sur le fil de l'interface utilisateur uniquement.


4 commentaires

Comme c'est drôle, je lisais simplement cet article et pensais à DispatcherObject. Cependant, Wayne monte un bon point et je devrais examiner à l'aide d'un événement pour y faire face.


Je pense que je vais devoir donner le tourbillon DispatcherObject. Votre point est valide que peu importe l'architecture de l'interface graphique, j'ai toujours besoin d'un moyen de se synchroniser avec le fil principal. Pouvez-vous expliquer pourquoi Wayne's Exemple ci-dessus fonctionne pour les propriétés? Je suis passé juste pour me prouver que cela fait vraiment tout, d'un fil différent, et cela fonctionne toujours comme on le voudrait.


J'aurais dû me rafraîchir mon navigateur avant de commenter. De toute façon, merci pour le conseil. C'est une idée intéressante, si je vous comprends correctement. Au lieu de prendre la propriété du répartiteur de la vue et de la passer en panne (qui semble violer MVVM de toute façon), vous recommandez de créer une référence à un répartiteur dans mon modèle, puis essentiellement, via une interface de rappel (qui est transmise au constructeur du modèle. ), le modèle peut demander le répartiteur de la vue? Je suppose que la vue devrait également transmettre une référence à la vue de la vue lors de sa création?


Problème résolu. J'ai juste que le contrôleur reçoit une référence au répartiteur de la vue (qui est transmis à partir de la vue -> VM -> Modèle), puis tout fonctionne. Merci, Franci, et merci Wayne pour avoir essayé de m'aider avec ça.



1
votes

J'ai une autre approche qui semble fonctionner et je voulais juste le jeter là-bas pour obtenir des commentaires (si quelqu'un lit même cette question!).

J'ai commencé à utiliser la classe MVVM Light Toolkit, et il semble très bien fonctionner pour moi. Par exemple, prenons la barre de progression comme exemple. J'ai enregistré deux messages avec ma viewModel pour définir la valeur de progression et la progression maximale. Ensuite, dans mon modèle, car il établit les tâches et le processus global, il envoie ces messages. Lorsque la machine virtuelle reçoit les messages, elle met simplement à jour les valeurs de données de données et mon GUI met à jour automatiquement! C'est super duper facile, mais je me demandais ce que vous pensiez tous à cette approche. Est-ce que quelqu'un d'autre fait cela sans incident?


0 commentaires