9
votes

Mise à jour de l'interface utilisateur de la classe ViewModel (motif MVVM) dans WPF

J'utilise le modèle MVVM dans ma première application WPF et j'ai un problème avec quelque chose de basique que je suppose.

Lorsque l'utilisateur frappe le bouton "Enregistrer" à mon avis, une commande est exécutée qui appelle la sauvegarde du vide privé () dans mon mode de vue.

Le problème est que le code dans "Enregistrer ()" prend un certain temps pour exécuter, alors je voudrais masquer le bouton "Enregistrer" dans la vue UI avant d'exécuter le gros morceau du code.

Le problème est que la vue ne met pas à jour jusqu'à ce que tous les codes soient exécutés dans la vue ViewModel. Comment puis-je forcer la vue à redessiner et à traiter les événements de l'état de propriété avant d'exécuter le code Save ()?

En outre, je voudrais une manière réutilisable, de sorte que je puisse facilement faire la même chose dans d'autres pages aussi. Quelqu'un d'autre a fait quelque chose comme ça déjà? Un message "Chargement ..."?


0 commentaires

6 Réponses :


11
votes

Si cela prend beaucoup de temps, envisagez d'utiliser un fil séparé, par exemple en utilisant un travailleur d'arrière-plan , de sorte que le thread de l'interface utilisateur peut rester réactif (c'est-à-dire mettre à jour l'interface utilisateur) pendant que l'opération est effectuée.

Dans votre Enregistrer la méthode , vous seriez

  • Modifiez l'interface utilisateur (c'est-à-dire de modifier certains inotifypropertychanged ou de dépendanceProperty isbusysocignant Boolean qui est lié à votre UI, cache le bouton Enregistrer et affiche peut-être une barre de progression avec isindétermine = true ) et
  • Démarrer un WORD .

    dans le gestionnaire d'événements Dowork de votre travailleur d'arrière-plan, vous effectuez une longue opération d'économie.

    dans le gestionnaire exécuté Ce qui est exécuté dans le fil d'interface utilisateur, vous définissez isbusysocignant sur false et peut-être modifier d'autres éléments dans l'interface utilisateur pour montrer que vous avez terminé.

    exemple de code (non testé): xxx


7 commentaires

Désolé je suis un Dumbo total quand il s'agit de enfiler. Dans le code de sauvegarde I (parfois) essayez de naviguer vers une autre page. Mais parce que je suis dans un autre fil, cela donne une erreur d'exécution. Je suppose que je dois faire un rappel au fil d'origine et naviguer de là à l'autre page. Mais je vais essayer cela moi-même, je suis sûr qu'il n'est pas difficile de communiquer avec le fil d'origine.


"Le fil d'appel ne peut pas accéder à cet objet car un fil différent est propriétaire." est le message que je reçois. Si vous savez par cœur ce dont j'ai besoin, faites le moi savoir :-)


RunworkerCompleted, c'est ce dont j'avais besoin. Merci beaucoup. Aussi génial de voir que je peux déclarer l'événementHandler comme ça, ne savait pas ça!


Oui, RunworkerComplet est exactement le bon endroit pour cela puisqu'il fonctionne dans le fil de l'interface utilisateur. Si vous devez modifier UI Stuff pendant l'opération de sauvegarde, vous pouvez utiliser appliquer.Current.dispatcher.invoke ou le fichier ReportProgress méthode / < Code> ProgressChanged Combinaison d'événements du travailleur d'arrière-plan .


Vous devez toujours désactiver votre bouton de sauvegarde lorsque vous êtes true == True, ou vous devez fournir une serrure dans la Dowork afin de ne pas vous mettre dans une condition de course.


Est-il suffisant de définir la canexecute sur "isbusyspunaving == False" de mon Savecommand?


Je suppose. Si vous souhaitez faire quelque chose de fantaisie, vous pouvez également ajouter du texte de "économie en cours ..." sur votre interface utilisateur dont la visibilité est liée à isbusyspunaving via un BooleantVisiviConverter .



0
votes

Vous pouvez toujours faire quelque chose comme ceci:

public class SaveDemo : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  private bool _canSave;

  public bool CanSave
  {
    get { return _canSave; }
    set
    {
      if (_canSave != value)
      {
        _canSave = value;
        OnChange("CanSave");
      }
    }
  }

  public void Save()
  {
    _canSave = false;

    // Do the lengthy operation
    _canSave = true;
  }

  private void OnChange(string p)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
      handler(this, new PropertyChangedEventArgs(p));
    }
  }
}


3 commentaires

(1) Si vous définissez _Cansave au lieu de CANSAVE, ONCHANGE ne sera pas soulevé. (2) Je ne pense pas que cela fonctionnerait, car économisez des exécutions dans le fil de l'interface utilisateur, l'interface utilisateur WPF ne sera donc mise à jour avant la fin de l'enregistrement.


Oui, j'apprécie la réponse mais je ne pense pas que cela résoue mon problème. La suggestion de Heinz lui a fixé.


@Heinzi - Bon point sur le CANVERSAVE, et oui, il fonctionnera car le changement de notification est soulevé au début de l'opération Save - par conséquent, les mises à jour de l'UI à ce moment-là.



3
votes

Vous utilisez le motif MVVM, la commande de votre bouton de sauvegarde est donc définie sur une instance de l'objet routéCommand qui est ajouté à la collection CommandBindings de la fenêtre de manière déclarée ou impérativement.

supposant que vous le faites déclarative. Quelque chose comme p> xxx pré>

pour le gestionnaire d'événements routés exécutés, votre méthode de sauvegarde (), à l'entrée, vous définissez une variable sur FALSE, à votre retour, vous le réglez sur TRUE. Quelque chose comme. P> xxx pré>

pour le gestionnaire de l'événement routé Canexecute, la méthode Save_Canexecute (), vous utilisez la variable comme l'une des conditions. P>

void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = _canExecute && _others;
}


0 commentaires


0
votes

Vous pouvez y accomplir par le code suivant ..

workerThread.join();
SaveButtton.isEnable = true;


0 commentaires

0
votes

Réponse tardive, mais je pensais que cela serait bien de saisir un peu aussi.

Au lieu de créer votre propre nouveau thread, il serait probablement préférable de laisser tomber jusqu'à la filetage pour exécuter la sauvegarde. Il ne force pas à courir instantanément, comme créer votre propre thread, mais il vous permet de sauvegarder des ressources de filetage. P>

Le moyen de faire est le suivant: P>

ThreadPool.QueueUserWorkItem(Save);


2 commentaires

Merci pour votre réponse. J'ai utilisé la réponse acceptée dans ma demande et cela fonctionne bien. Je ne sais pas comment l'utilisation des ressources est par rapport à votre solution, mais le travail d'arrière-plan est très pratique de travailler avec.


Keith: En .NET 3.5 et plus, envisagez des expressions Lambda pour couper l'objet État si vous n'en avez pas besoin. Threadpool.QueueUeUeUserSerWorkItem (état => sauvegarder ()); Peut-être un peu plus de frais généraux ici, mais le code est souvent simplifié avec moins de méthodes d'apaisement des délégués flottant. Vous pouvez également utiliser un Lambda pour lancer l'objet d'état sur tout ce dont vous avez besoin et retirer des propriétés spécifiques. Threadpool.QueueUeUeUserMorworkItem (State => Enregistrer (Etat comme Savaregs) .Length, état comme Savelgs) .Timestamp);