Подтвердить что ты не робот

WPF MVVM - как определить, является ли вид "грязным",

В настоящее время у меня есть требование уведомить пользователя моего приложения, если какие-либо поля были изменены/обновлены в представлении.

Например, если пользователь изменяет поле даты в представлении, а затем пытается закрыть представление, приложение отобразит сообщение с просьбой о продолжении и потерях изменений или Отмена, чтобы они могли нажать кнопку "Сохранить".

Проблема заключается в следующем: как определить, что какое-либо из полей данных изменилось в представлении?

Надеюсь, что это имеет смысл, чем вы заранее, приветствует,

4b9b3361

Ответ 1

Один подход, который вы можете предпринять, - использовать IChangeTracking и INotifyPropertyChanged.

Если вы создаете абстрактный базовый класс, который ваши модели представлений наследуют от (ViewModelBase), который реализует интерфейсы IChangeTracking и INotifyPropertyChanged, вы можете подключить базовую модель вашего представления к уведомлению об изменениях свойств (фактически сигнализируя, что модель представления была изменен) и который установит свойство IsChanged в значение true, чтобы указать, что модель представления "грязная".

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

В описанном вами случае вы можете обращаться с Unloaded или Closing событие вашего просмотра для проверки DataContext; и если DataContext реализует IChangeTracking, вы можете использовать свойство IsChanged, чтобы определить, были ли сделаны какие-либо неприемлемые изменения.

Простой пример:

/// <summary>
/// Provides a base class for objects that support property change notification 
/// and querying for changes and resetting of the changed status.
/// </summary>
public abstract class ViewModelBase : IChangeTracking, INotifyPropertyChanged
{
    //========================================================
    //  Constructors
    //========================================================
    #region ViewModelBase()
    /// <summary>
    /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
    /// </summary>
    protected ViewModelBase()
    {
        this.PropertyChanged += new PropertyChangedEventHandler(OnNotifiedOfPropertyChanged);
    }
    #endregion

    //========================================================
    //  Private Methods
    //========================================================
    #region OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
    /// <summary>
    /// Handles the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for this object.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
    private void OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e != null && !String.Equals(e.PropertyName, "IsChanged", StringComparison.Ordinal))
        {
            this.IsChanged = true;
        }
    }
    #endregion

    //========================================================
    //  IChangeTracking Implementation
    //========================================================
    #region IsChanged
    /// <summary>
    /// Gets the object changed status.
    /// </summary>
    /// <value>
    /// <see langword="true"/> if the object’s content has changed since the last call to <see cref="AcceptChanges()"/>; otherwise, <see langword="false"/>. 
    /// The initial value is <see langword="false"/>.
    /// </value>
    public bool IsChanged
    {
        get
        {
            lock (_notifyingObjectIsChangedSyncRoot)
            {
                return _notifyingObjectIsChanged;
            }
        }

        protected set
        {
            lock (_notifyingObjectIsChangedSyncRoot)
            {
                if (!Boolean.Equals(_notifyingObjectIsChanged, value))
                {
                    _notifyingObjectIsChanged = value;

                    this.OnPropertyChanged("IsChanged");
                }
            }
        }
    }
    private bool _notifyingObjectIsChanged;
    private readonly object _notifyingObjectIsChangedSyncRoot = new Object();
    #endregion

    #region AcceptChanges()
    /// <summary>
    /// Resets the object’s state to unchanged by accepting the modifications.
    /// </summary>
    public void AcceptChanges()
    {
        this.IsChanged = false;
    }
    #endregion

    //========================================================
    //  INotifyPropertyChanged Implementation
    //========================================================
    #region PropertyChanged
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    #region OnPropertyChanged(PropertyChangedEventArgs e)
    /// <summary>
    /// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
    /// </summary>
    /// <param name="e">A <see cref="PropertyChangedEventArgs"/> that provides data for the event.</param>
    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }
    #endregion

    #region OnPropertyChanged(string propertyName)
    /// <summary>
    /// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyName"/>.
    /// </summary>
    /// <param name="propertyName">The <see cref="MemberInfo.Name"/> of the property whose value has changed.</param>
    protected void OnPropertyChanged(string propertyName)
    {
        this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region OnPropertyChanged(params string[] propertyNames)
    /// <summary>
    /// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyNames"/>.
    /// </summary>
    /// <param name="propertyNames">An <see cref="Array"/> of <see cref="String"/> objects that contains the names of the properties whose values have changed.</param>
    /// <exception cref="ArgumentNullException">The <paramref name="propertyNames"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
    protected void OnPropertyChanged(params string[] propertyNames)
    {
        if (propertyNames == null)
        {
            throw new ArgumentNullException("propertyNames");
        }

        foreach (var propertyName in propertyNames)
        {
            this.OnPropertyChanged(propertyName);
        }
    }
    #endregion
}

Ответ 2

В MVVM вид привязан к модели представления, которая, в свою очередь, привязана к модели.

Вид не может быть грязным, так как он изменяется сразу же на View-Model.

Если вы хотите, чтобы изменения применялись к модели только по "ОК" или "Принять",
привязать View к View-Model, которая не применима к Model,
пока не будет применен ApplyCommand или AcceptCommand (который вы определяете и реализуете).

(Команды, которые привязаны к представлению, реализованы с помощью View-Model.)

Пример - VM:

public class MyVM : INotifyPropertyChanged
{
    public string MyText
    {
        get
        {
            return _MyText;
        }
        set
        {
            if (value == _MyText)
                return;

            _MyText = value;
            NotifyPropertyChanged("MyText");
        }
    }
    private string _MyText;

    public string MyTextTemp
    {
        get
        {
            return _MyTextTemp;
        }
        set
        {
            if (value == _MyTextTemp)
                return;

            _MyTextTemp = value;
            NotifyPropertyChanged("MyTextTemp");
            NotifyPropertyChanged("IsTextDirty");
        }
    }
    private string _MyTextTemp;

    public bool IsTextDirty
    {
        get
        {
            return MyText != MyTextTemp;
        }
    }

    public bool IsMyTextBeingEdited
    {
        get
        {
            return _IsMyTextBeingEdited;
        }
        set
        {
            if (value == _IsMyTextBeingEdited)
                return;

            _IsMyTextBeingEdited = value;

            if (!value)
            {
                MyText = MyTextTemp;
            }

            NotifyPropertyChanged("IsMyTextBeingEdited");
        }
    }
    private bool _IsMyTextBeingEdited;


    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Пример - Вид:

    <Label Content="{Binding MyText}" />

    <!-- You can translate the events to commands by using a suitable framework -->
    <!-- or use code behind to update a new dependency property as in this example -->
    <TextBox
        LostFocus="TextBox_LostFocus"
        GotFocus="TextBox_GotFocus"
        Text="{Binding Path=MyTextTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        />

Пример - вид - код позади:

    public MainWindow()
    {
        InitializeComponent();

        SetBinding(IsTextBoxFocusedProperty,
            new Binding
            {
                Path = new PropertyPath("IsMyTextBeingEdited"),
                Mode = BindingMode.OneWayToSource,
            });
    }

    private void TextBox_LostFocus(object sender, RoutedEventArgs e)
    {
        IsTextBoxFocused = false;
    }

    private void TextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        IsTextBoxFocused = true;
    }

    #region IsTextBoxFocused

    /// <summary>
    /// Gets or Sets IsTextBoxFocused
    /// </summary>
    public bool IsTextBoxFocused
    {
        get
        {
            return (bool)this.GetValue(IsTextBoxFocusedProperty);
        }
        set
        {
            this.SetValue(IsTextBoxFocusedProperty, value);
        }
    }

    /// <summary>
    /// The backing DependencyProperty behind IsTextBoxFocused
    /// </summary>
    public static readonly DependencyProperty IsTextBoxFocusedProperty = DependencyProperty.Register(
      "IsTextBoxFocused", typeof(bool), typeof(MainWindow), new PropertyMetadata(default(bool)));

    #endregion