WPF MasterDetail ViewModel задерживает обновления в основном списке. (запретить автоматическое обновление сетки) - PullRequest
0 голосов
/ 30 ноября 2009

Первая попытка внедрения шаблона MVVM в бизнес-проекте. Я сталкиваюсь с вопросами, на которые, как я полагаю, есть ответы eaiser:

Окно Protype - это базовое представление основных элементов списка элементов. (список объектов Person). Представление содержит Infragistics xamDataGrid для основного списка. Когда элемент выбран в сетке, вы можете редактировать детали на панели подробностей ниже и делать это, когда вы вкладываете поля на панели подробностей, обновления отображаются в реальном времени в данных сетки. Единственное, что я не хочу «presto», я хочу «подождать, пока я нажму кнопку« Применить изменения »».

Я надеялся избежать создания отдельного экземпляра списка, чтобы отделить основной список от рабочего набора элементов, которые я добавляю / удаляю / изменяю на панели сведений.

Путь, по которому я пошел:

Я переопределил стиль CellValuePresenter в поле сетки, чтобы установить привязку «OneWay». Это предотвращает обновление в реальном времени.

<ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
  <ControlTemplate.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="Background" Value="{Binding Path=DataItem.NameUIProperty.IsDirty, Converter={StaticResource BooleanBrushConverter}}" />
      <Setter Property="IsEnabled" Value="{Binding Path=DataItem.NameUIProperty.IsEditable}" />
    </Style>
  </ControlTemplate.Resources>
  <ContentControl>
    <TextBlock Text="{Binding Path=DataItem.Name, Mode=OneTime}" />
  </ContentControl>                                                     
</ControlTemplate>      

Затем я добавляю команду «ApplyUpdates» (RelayCommand) в мой PersonListViewModel. Это вызывает сообщение «PERSON _ITEM_ UPDATED». Я использую порты VB классов MVVM Foundation Messenger и RelayCommand.

#Region "ApplyUpdates Command"

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

Private Sub ApplyUpdates()
    'the changes are already in the object in the list so we don't have to do anything here except fire off the Applied message
    Messages.AppMessenger.NotifyColleagues(Messages.PERSON_ITEM_UPDATED)
End Sub

#End Region

PersonView регистрирует сообщение PERSON _ITEM_ UPDATED и перепривязывает сетку при получении сообщения.

'In Loaded Event

'register for window messages we care about
Messages.AppMessenger.Register(Messages.PERSON_ITEM_UPDATED, AddressOf OnPersonItemUpdated)

'EventHandler
Private Sub OnPersonItemUpdated()
  PersonGrid.DataSource = Nothing
  PersonGrid.DataSource = mViewModel.List
End Sub

Итак, это работает, но пахнет неправильно . Кажется, что в представлении слишком много логики, и ViewModel не определяет состояние пользовательского интерфейса, это представление.

Что мне не хватает? Какой метод вы бы использовали, чтобы заставить ViewModel отложить публикацию изменений в представлении?

Обновление: сейчас я иду по пути создания пользовательской модели представления для сетки (только для чтения, без уведомлений об изменении свойств) и редактируемой модели представления для области сведений. Обе виртуальные машины будут переносить одни и те же бизнес-объекты, но версия ReadOnly не будет публиковать изменения. Это позволит ВМ контролировать, когда обновляется представление.

Ответы [ 2 ]

0 голосов
/ 05 декабря 2009

У меня была похожая проблема при реализации диалога настроек в MVVM. Вы хотите, чтобы пользователь мог редактировать свойства вашей ViewModel, но вносить изменения только после их применения. Я нашел разумное решение. Вот код для простейших пэдов опций только с одним логическим свойством "Звук":

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<PinBallOptionsPad>(o => o.SoundEdit);

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

}

Для одного свойства это выглядит много, но приятно то, что все для этого свойства содержится в области звука. Поэтому, если вы скопируете и вставите это, а также выполните поиск и замену, вы сможете создавать новые свойства относительно быстро. Чтобы понять, как работают CommitActions и CancelActions, вам также понадобится класс AbstractOptionsPad :

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

    /// <summary>
    /// If overriding this method, make sure to call base.Commit first.
    /// </summary>
    public virtual void Commit()
    {
        foreach (var commitAction in CommitActions)
        {
            commitAction();
        }
        CommitActions.Clear();
        CancelActions.Clear();
    }

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

    #endregion

    #region " Cancel "

    /// <summary>
    /// If overriding this method, make sure to call base.Cancel first.
    /// </summary>
    public virtual void Cancel()
    {
        foreach (var cancelAction in CancelActions)
        {
            cancelAction();
        }
        CancelActions.Clear();
        CommitActions.Clear();
    }

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

    #endregion

    public event EventHandler OptionChanged;

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

Вот как выглядит View для этого пэда:

<DataTemplate DataType="{x:Type local:PinBallOptionsPad}">
    <CheckBox IsChecked="{Binding SoundEdit}">
        <TextBlock Text="Sound"/>
    </CheckBox> 
</DataTemplate>

Таким образом, он привязывается к SoundEdit в настройках, но остальная часть приложения может быть привязана к свойству Sound и обновляться на основе события NotifyPropertyChanged.

0 голосов
/ 30 ноября 2009

При объявлении макета вашего поля для сетки данных инфраструктуры вы можете использовать UnboundField вместо Field. Этот класс предоставляет свойства BindingPath и BindingMode для базовой привязки. Используя эту технику, вы можете избавиться от обновления в режиме реального времени, и вам не понадобится пользовательский шаблон элемента управления.

Мои мысли о переносе логики в ВМ:

Создание односторонней привязки источника данных сетки и nViewModel.List. Затем ApplyChanges может вызвать: BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();, чтобы принудительно обновить целевое свойство DataSource. К сожалению, это связывает вашу виртуальную машину с привязкой, но, по вашему мнению, не содержит никакого кода.

Большая проблема здесь заключается в том, что если у вас есть этот сценарий отложенного связывания, ApplyChanges действительно нуждается в некотором IoC в представлении, потому что только представление будет знать, как действительно выполнить обновление (будь то с помощью привязок или чего-либо еще). В конце концов, что-то в цепочке будет управлять двумя экземплярами списка: экземпляром в представлении и фактическим экземпляром в виртуальной машине. В этом конкретном сценарии отложенное обновление выглядит как поведение представления. Однако команда UpdateChanges на виртуальной машине фактически связывает это поведение с виртуальной машиной, и в этом случае, я бы сказал, имеет смысл сохранить два экземпляра списка в вашей виртуальной машине.

Надеюсь, это поможет.

...