MVVM - 为ModelView实现'IsDirty'功能以保存数据

Bre*_*dan 14 c# wpf observablecollection mvvm

作为WPF和MVVM的新手,我在努力学习一些基本功能.

让我先解释一下我的意思,然后附上一些示例代码......

我有一个显示用户列表的屏幕,我在右侧显示所选用户的详细信息,其中包含可编辑的文本框.然后我有一个Save按钮,它是DataBound,但我只想在数据实际发生变化时显示这个按钮.即 - 我需要检查"脏数据".

我有一个完整的MVVM示例,其中我有一个名为User的模型:

namespace Test.Model
{
    class User
    {
        public string UserName { get; set; }
        public string Surname { get; set; }
        public string Firstname { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,ViewModel看起来像这样:

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Test.Model;

namespace Test.ViewModel
{
    class UserViewModel : ViewModelBase
    {
        //Private variables
        private ObservableCollection<User> _users;
        RelayCommand _userSave;

        //Properties
        public ObservableCollection<User> User
        {
            get
            {
                if (_users == null)
                {
                    _users = new ObservableCollection<User>();
                    //I assume I need this Handler, but I am stuggling to implement it successfully
                    //_users.CollectionChanged += HandleChange;

                    //Populate with users
                    _users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"});
                    _users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"});
                }
                return _users;
            }
        }

        //Not sure what to do with this?!?!

        //private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
        //{
        //    if (e.Action == NotifyCollectionChangedAction.Remove)
        //    {
        //        foreach (TestViewModel item in e.NewItems)
        //        {
        //            //Removed items
        //        }
        //    }
        //    else if (e.Action == NotifyCollectionChangedAction.Add)
        //    {
        //        foreach (TestViewModel item in e.NewItems)
        //        {
        //            //Added items
        //        }
        //    } 
        //}

        //Commands
        public ICommand UserSave
        {
            get
            {
                if (_userSave == null)
                {
                    _userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute);
                }
                return _userSave;
            }
        }

        void UserSaveExecute()
        {
            //Here I will call my DataAccess to actually save the data
        }

        bool UserSaveCanExecute
        {
            get
            {
                //This is where I would like to know whether the currently selected item has been edited and is thus "dirty"
                return false;
            }
        }

        //constructor
        public UserViewModel()
        {

        }

    }
}
Run Code Online (Sandbox Code Playgroud)

"RelayCommand"只是一个简单的包装类,"ViewModelBase"也是如此.(为了清楚起见,我会附上后者)

using System;
using System.ComponentModel;

namespace Test.ViewModel
{
    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
    {
        protected ViewModelBase()
        { 
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

        public void Dispose()
        {
            this.OnDispose();
        }

        protected virtual void OnDispose()
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后 - XAML

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:Test.ViewModel"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:UserViewModel/>
    </Window.DataContext>
    <Grid>
        <ListBox Height="238" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top" 
                 Width="197" ItemsSource="{Binding Path=User}" IsSynchronizedWithCurrentItem="True">
            <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                        <TextBlock Text="{Binding Path=Firstname}"/>
                        <TextBlock Text="{Binding Path=Surname}"/>
                </StackPanel>
            </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="232,16,0,0" Name="label1" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="323,21,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/UserName}" />
        <Label Content="Surname" Height="28" HorizontalAlignment="Left" Margin="232,50,0,0" Name="label2" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="323,52,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Surname}" />
        <Label Content="Firstname" Height="28" HorizontalAlignment="Left" Margin="232,84,0,0" Name="label3" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="323,86,0,0" Name="textBox3" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Firstname}" />
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="368,159,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=UserSave}" />
    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

基本上,当我编辑姓氏时,应该启用"保存"按钮; 如果我撤消我的编辑 - 那么它应该再次被禁用,因为没有任何改变.

我在很多例子中看到了这一点,但还没有找到如何做到这一点.

任何帮助将非常感激!布伦丹

Rob*_*ney 8

根据我的经验,如果IsDirty在视图模型中实现,您可能还希望实现视图模型IEditableObject.

假设您的视图模型是通常的排序,实施PropertyChanged和私人或受保护OnPropertyChanged的是提高它的方法,设置IsDirty是相当简单:您只需设置IsDirtyOnPropertyChanged,如果它是不是已经真的.

你的IsDirty二传手应该,如果属性是假的,现在是真的,请打电话BeginEdit.

您的Save命令应该调用EndEdit,它会更新数据模型并设置IsDirty为false.

您的Cancel命令应该调用CancelEdit,从数据模型刷新视图模型并设置IsDirty为false.

CanSaveCanCancel属性(假设你使用RelayCommand这些命令)刚刚返回的当前值IsDirty.

请注意,由于此功能都不依赖于视图模型的特定实现,因此可以将其放在抽象基类中.派生类不必实现任何与命令相关的属性或IsDirty属性; 他们只需要重写BeginEdit,EndEditCancelEdit.


Dan*_*ose 0

由于您的 UserSave 命令位于 ViewModel 中,因此我将在那里跟踪“脏”状态。我将数据绑定到列表框中选定的项目,当它发生更改时,存储选定用户属性的当前值的快照。然后您可以与此进行比较以确定是否应启用/禁用该命令。

但是,由于您直接绑定到模型,因此您需要某种方法来查明某些内容是否发生变化。您还可以在模型中实现 INotifyPropertyChanged,或者将属性包装在 ViewModel 中。

请注意,当命令的 CanExecute 更改时,您可能需要触发 CommandManager.InvalidateRequerySuggested()。