0 votes

WPF ViewModel MasterDetail Retarder les mises à jour de la liste principale (empêcher la grille de se mettre à jour automatiquement)

Première tentative d'implémentation du modèle MVVM sur un projet métier en ligne. Je rencontre des questions pour lesquelles je suppose qu'il existe des réponses plus simples comme celle-ci :

La fenêtre de prototype est une vue basique maître-détail d'une liste d'éléments. (une liste d'objets Personne). La vue contient un Infragistics xamDataGrid pour la liste maître. Lorsque l'élément est sélectionné dans la grille, vous pouvez modifier les détails dans le panneau inférieur et voilà, lorsque vous passez d'un champ à l'autre dans le panneau de détails, les mises à jour sont affichées en temps réel dans les données de la grille. La seule chose est que je ne veux pas de mises à jour en temps réel, je veux attendre d'appuyer sur le bouton "Appliquer les modifications".

J'espérais éviter de créer une instance séparée de la liste pour séparer la liste maître de l'ensemble de travail d'éléments que j'ajoute/supprime/modifie dans le panneau de détails.

Le chemin que j'ai emprunté :

J'ai remplacé le style de CellValuePresenter dans le champ de la grille pour pouvoir définir la liaison sur "OneWay". Cela empêche la mise à jour en temps réel.

      <Setter Property="Background" Value="{Binding Path=DataItem.NameUIProperty.IsDirty, Converter={StaticResource BooleanBrushConverter}}" />
      <Setter Property="IsEnabled" Value="{Binding Path=DataItem.NameUIProperty.IsEditable}" />

Ensuite, j'ajoute une commande "ApplyUpdates" (RelayCommand) à mon PersonListViewModel. Cela déclenche le message "PERSON_ITEM_UPDATED". J'utilise des ports VB des classes MVVM Foundation Messenger et RelayCommand.

#Region "Commande ApplyUpdates"

Private mApplyUpdatesCommand As New RelayCommand(AddressOf ApplyUpdates)
Public ReadOnly Property ApplyUpdatesCommand() As ICommand
    Get
        Return mApplyUpdatesCommand
    End Get
End Property

Private Sub ApplyUpdates()
    'les modifications sont déjà dans l'objet dans la liste donc nous n'avons rien à faire ici sauf envoyer le message Applied
    Messages.AppMessenger.NotifyColleagues(Messages.PERSON_ITEM_UPDATED)
End Sub

#End Region

Le PersonView s'enregistre pour le message PERSON_ITEM_UPDATED et reboucle la grille lorsque le message est reçu.

'Dans l'événement Loaded

'Enregistrer pour les messages de fenêtre qui nous intéressent
Messages.AppMessenger.Register(Messages.PERSON_ITEM_UPDATED, AddressOf OnPersonItemUpdated)

'Gestionnaire d'événements
Private Sub OnPersonItemUpdated()
  PersonGrid.DataSource = Nothing
  PersonGrid.DataSource = mViewModel.List
End Sub

Donc, ça fonctionne, mais ça sent mauvais. La vue semble contenir trop de logique et le ViewModel ne dicte pas l'état de l'interface utilisateur, c'est la vue qui le fait.

Qu'est-ce que je rate ? Quelle méthode utiliseriez-vous pour que le ViewModel retarde la publication des modifications à la vue ?

Mise à jour : Je suis maintenant en train de créer un ViewModel personnalisé pour la grille (lecture seule, pas de notifications PropertyChanged) et un ViewModel modifiable pour la zone de détails. Les deux MV envelopperaient les mêmes objets métier, mais la version en lecture seule ne publierait pas les modifications. Cela laisserait au VM le contrôle de la mise à jour de la vue.

0voto

Szymon Rozga Points 11277

Lors de la déclaration de la mise en page de votre champ pour la grille de données infragistics, vous pouvez utiliser UnboundField au lieu de Field. Cette classe expose les propriétés BindingPath et BindingMode pour la liaison sous-jacente. En utilisant cette technique, vous pouvez vous débarrasser de la mise à jour en temps réel et vous ne devriez pas avoir besoin d'un modèle de contrôle personnalisé.

Mes réflexions sur le transfert de la logique vers le VM :

Créez une liaison à sens unique entre la Source de données de la grille et la Liste nViewModel. ApplyChanges peut ensuite appeler : BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget(); pour forcer la propriété cible Source de données à se rafraîchir. Malheureusement, cela lie votre VM à la liaison mais ne génère aucun code dans votre vue.

Un gros problème ici est que si vous avez ce scénario de liaison différée, ApplyChanges est quelque chose qui aura vraiment besoin d'un IoC dans la Vue car seulement la Vue saurait vraiment comment effectuer la mise à jour (que ce soit en utilisant des Liaisons ou autre chose). Au final, quelque part le long de la chaîne, deux instances d'une liste seront gérées : l'instance dans la vue et l'instance réelle dans le VM. Dans ce scénario en particulier, la mise à jour différée semble être un comportement de la Vue. Cependant, la commande UpdateChanges sur le VM attache en fait ce comportement au VM, auquel cas, je dirais qu'il est logique de stocker les deux instances de liste dans votre VM.

J'espère que cela vous aidera.

0voto

Scott Whitlock Points 8172

J'ai rencontré un problème similaire en implémentant une boîte de dialogue Options en MVVM. Vous voulez permettre à l'utilisateur de modifier les propriétés de votre ViewModel, mais ne valider les modifications que lorsqu'il appuie sur Appliquer. J'ai trouvé une solution raisonnable. Voici le code du plus simple des pads d'options avec une seule propriété booléenne "Son" :

class PinBallOptionsPad : AbstractOptionsPad
{
    public PinBallOptionsPad()
    {
        Name = "PinBallOptionsPad";
    }

    public override void Commit()
    {
        base.Commit();
        Properties.Settings.Default.Save();
    }

    #region "Sound"

    public bool SoundEdit
    {
        get
        {
            return m_SoundEdit;
        }
        set
        {
            if (m_SoundEdit != value)
            {
                m_SoundEdit = value;
                CommitActions.Add(
                    () => Properties.Settings.Default.Sound = m_SoundEdit);
                CancelActions.Add(
                    () =>
                    {
                        m_SoundEdit = Properties.Settings.Default.Sound;
                        NotifyPropertyChanged(m_SoundEditArgs);
                        NotifyPropertyChanged(m_SoundArgs);
                    });
                NotifyOptionChanged();
                NotifyPropertyChanged(m_SoundEditArgs);
            }
        }
    }
    private bool m_SoundEdit = Properties.Settings.Default.Sound;
    static readonly PropertyChangedEventArgs m_SoundEditArgs =
        NotifyPropertyChangedHelper.CreateArgs(o => o.SoundEdit);

    public bool Sound
    {
        get
        {
            return Properties.Settings.Default.Sound;
        }
    }
    static readonly PropertyChangedEventArgs m_SoundArgs =
        NotifyPropertyChangedHelper.CreateArgs(o => o.Sound);
    #endregion

}

Ça semble beaucoup pour une seule propriété, mais la chose intéressante est que tout pour cette propriété entière est contenu dans la région Sound. Ainsi, si vous copiez et collez cela, et faites une recherche et remplacez, vous pouvez créer de nouvelles propriétés relativement rapidement. Afin de comprendre comment fonctionnent les CommitActions et CancelActions, vous aurez besoin de la classe AbstractOptionsPad également :

public abstract class AbstractOptionsPad : AbstractPad, IOptionsPad
{
    #region " Commit "

    /// 
    /// Si vous remplacez cette méthode, assurez-vous d'appeler d'abord base.Commit.
    /// 
    public virtual void Commit()
    {
        foreach (var commitAction in CommitActions)
        {
            commitAction();
        }
        CommitActions.Clear();
        CancelActions.Clear();
    }

    protected IList CommitActions
    {
        get
        {
            return m_commitActions;
        }
    }
    private readonly IList m_commitActions = new List();

    #endregion

    #region " Cancel "

    /// 
    /// Si vous remplacez cette méthode, assurez-vous d'appeler d'abord base.Cancel.
    /// 
    public virtual void Cancel()
    {
        foreach (var cancelAction in CancelActions)
        {
            cancelAction();
        }
        CancelActions.Clear();
        CommitActions.Clear();
    }

    protected IList CancelActions
    {
        get
        {
            return m_cancelActions;
        }
    }
    private readonly IList m_cancelActions = new List();

    #endregion

    public event EventHandler OptionChanged;

    protected void NotifyOptionChanged()
    {
        var evt = OptionChanged;
        if (evt != null)
        {
            evt(this, new EventArgs());
        }
    }
}

Voici à quoi ressemble la Vue de ce pad :

Ainsi, il se lie à SoundEdit dans les options, mais le reste de l'application peut être lié à la propriété Sound, et être mis à jour en fonction de l'événement NotifyPropertyChanged.

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