WPF实现一个控件编辑状态的撤销和前进功能,适用于任何具有编辑功能的场景

一·需要用到的特性如下:

        1,数据绑定;2,列表容器的深拷贝;3,路由命令;4,MVVM思想

二·下面我用一个可编辑数据的DataGrid作为示范:

     1. 首先,我们在xaml中创建一个DataGrid控件,控件的的数据源绑定在ViewModel中


       2.当然数据源绑定的操作也可以在后台代码中进行,因为后台中也会进行路由命令的绑定操作:

namespace View
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();
        }

//将ViewModel类作为Window的依赖属性
        public static readonly DependencyProperty CurrentViewModelProperty = DependencyProperty.Register("CurrentViewModel", typeof(ViewModel), typeof(MainWindow),
                new PropertyMetadata(new ViewModel()));

        public ViewModel CurrentViewModel
        {
            get { return (ViewModel)GetValue(CurrentViewModelProperty); }
            set { SetValue(CurrentViewModelProperty, value); }
        }
//创建路由命令,增加快捷键手势
        public static readonly RoutedCommand SaveCommand = new RoutedCommand("Save", typeof(MainWindow));
        public static readonly RoutedCommand UndoCommand = new RoutedCommand("Undo", typeof(MainWindow));
        public static readonly RoutedCommand RedoCommand = new RoutedCommand("Redo", typeof(MainWindow));
        static MainWindow()
        {
            MainWindow.SaveCommand.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control));
            MainWindow.UndoCommand.InputGestures.Add(new KeyGesture(Key.Z, ModifierKeys.Control));
            MainWindow.RedoCommand.InputGestures.Add(new KeyGesture(Key.Y, ModifierKeys.Control));
//将命令绑定到控件上
Grid.CommandBindings.Add(new CommandBinding(SaveCommand, CurrentViewModel.save, CurrentViewModel.canSave));
Grid.CommandBindings.Add(new CommandBinding(UndoCommand, CurrentViewModel.undo, CurrentViewModel.canUndo));
Grid.CommandBindings.Add(new CommandBinding(RedoCommand, CurrentViewModel.redo, CurrentViewModel.canRedo));
}
        /// 
        /// 开始编辑时触发备份操作,因为DataGrid实现了IEditObject借口所以我们直接使用这个方法,如果是其他类型的请自行定义备份时机
        /// 
        /// 
        /// 
        void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
        {
            CurrentViewModel.savePreviousCase();
        }

       3.最关键的就是我们的ViewModle类,其中包含了我们数据源的获取方式和撤销/前进的实现:

namespace ViewModels
{
    public class ViewModel : INotifyPropertyChanged//用于更新数据源时自动更新界面
    {
        

       
///按照这种思路,我们只需要将控件所编辑的数据源进行备份和还原就能实现这个功能///

//用撤销栈保存所有编辑前的数据列表
Stack> _previousData;
//当前数据源列表
        ObservableCollection< someData > _currentData;
//将撤销前的数据源保存到前进栈中
        Stack> _nextData;

        public ViewModel()
        {
            _currentData = //从数据库获取数据的方法或者其它;
            _previousData = new Stack >();
            _nextData = new Stack >();           
        }

        
        public  Stack > PreviousData 
        {
            get { return _previousData; }
            set 
            { 
                _previousData= value;
                onPropertiesChanged("PreviousData");         
            }
        }

        public Stack > NextTestData
        {
            get { return _nextData; }
            set
            {
                _nextData = value;
                onPropertiesChanged("NextData");
                
            }
        }

        public  ObservableCollection< someData > CurrentList
        {
            get
            {
                return _currentData;
            }
            set
            {
                _currentData = value;
                onPropertiesChanged("CurrentList");
            }
        }
        /// 
        /// 判断能否执行保存
        /// 
        /// 
        /// 
        public void canSave(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (PreviousData.Count != 0) && isModified();
        }
        
        /// 
        /// 判断能佛执行撤销
        /// 
        /// 
        /// 
        public void canUndo(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (PreviousData.Count != 0) && isModified();               
        }

        /// 
        /// 深拷贝一个列表
        /// 
        /// 
        /// 
        ObservableCollection clone(ObservableCollection sourceList)
        {
            var clonePre = new List();
            sourceList.ToList().ForEach(a => clonePre.Add(copyByReflaction(a)));
           return new ObservableCollection(clonePre);
        }
//利用反射和泛型来拷贝一个数据对象
        T copyByReflaction(T source)
        {
            T result = Activator.CreateInstance();
            for(int i = 0; i < source.GetType().GetProperties().Count(); i++)
            {
                var sValue = source.GetType().GetProperties()[i].GetValue(source,null);
                result.GetType().GetProperties()[i].SetValue(result, sValue,null);
            }
            return result;
        }

        /// 
        /// 判断是否被修改,如果未修改则将重复的数据源弹出栈避免重复
        /// 
        /// 
        public bool isModified()
        {
            int a = CurrentList[0].GetType().GetProperties().Count();
            if(PreviousData.Peek().Count != CurrentList.Count)
                return true;
            else
            {
                for (int i = 0; i < CurrentList.Count; i++)                
                    for (int j = 0; j < a; j++)
                    {
                        //利用反射遍历每个属性判断两个值是否相同
                        var p = PreviousData.Peek()[i].GetType().GetProperties()[j].GetValue(PreviousData.Peek()[i],null);
                        var c = CurrentList[i].GetType().GetProperties()[j].GetValue(CurrentList[i],null);
                        if (!p.Equals(c))
                            return true;
                    }                      
                PreviousData.Pop();//这其实是一个递归操作,弹出后会自动更新各个命令的执行条件,直到能够执行或者撤销栈中的值为空,避免没有修过操作时保存的数据源冗余;
                return false;                        
            }
        }
         /// 
         /// 撤回路由版
         /// 
        public void undo(object sender, ExecutedRoutedEventArgs e)
        {
                NextData.Push(clone(CurrentList));
                CurrentList = clone(PreviousData.Pop());
        }


        /// 
        /// 压栈存储当前数据源
        /// 
        public void savePrevious()
        {
//每次开始编辑时都应该清除前进栈中的数据
            NextData.Clear();
            PreviousData.Push(clone(CurrentList));
        }

        /// 
        /// 前进判断路由版
        /// 
        /// 
        /// 
        public void canRedo(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (NextData.Count != 0);
        }

        /// 
        /// 前进路由版
        /// 
        public void redo(object sender, ExecutedRoutedEventArgs e)
        {
            PreviousData.Push(clone(CurrentTestCase));
            CurrentList = clone(NextData.Pop());   
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

上面没有实现保存数据的操作,这个因人而异了,自己实现一个同步到数据库或者任何存储数据的方法就可以了

PS :转载请注明出处哦,有bug大家一起交流吧!

你可能感兴趣的:(c#,开发技巧,wpf,.net)