Есть класс PersonVm, который представляет информацию о человеке:
public class PersonVm : BaseViewModel
{
private string _name;
public string Name
{
get {return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}
}
Класс PersonManager представляет собой коллекцию персон и позволяет добавлять/удалять
персоны, а также откатывать эти изменения через UndoRedoService:
public class PersonManager : BaseViewModel
{
public ObservableCollection Persons {get;set;}
public UndoRedoService UndoRedoService {get;set;} = new UndoRedoService();
}
Хотелось бы также откатывать изменения, которые происходят в PersonVm.
Можно было бы подписаться на событие PropertyChangedу всех персон и получать название
свойства в котором произошло изменение, текущее и новое значения.
Но в таком случае откатывать изменения пришлось бы через рефлексию — искать по названию
нужное свойство и менять его. А это не слишком быстрый способ.
Возможно сделать как-то иначе?
Ответы
Ответ 1
Я расширил базовый класс для ViewModel, чтобы он вёл историю изменений.
У меня пример тестовый, поэтому так, вам скорее всего потребуются оба класса - с
ведением истории и без нее, чтобы можно было наследоваться либо от того либо от другого.
А еще лучше завести список с перечнем классов и их свойств по которым должна вестись
история изменений, а может даже и сочинить вместо этого кастомный атрибут.
У меня был такой класс:
abstract class Vm : INotifyPropertyChanged
{
protected bool Set(ref T field, T value, [CallerMemberName]string propertyName
= null)
{
if (EqualityComparer.Default.Equals(field, value))
return false;
field = value;
NotifyPropertyChanged(propertyName);
return true;
}
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
Я его дополнил следующим функционалом:
// Пришлось добавить флаги для того чтобы отличать обычную
// установку свойства от Undo/Redo
static bool isUndoProcess = false;
static bool isRedoProcess = false;
// Пара стеков для хранения истории
static Stack<(object Obj, string Prop, object OldValue)> undoHistory
= new Stack<(object Obj, string Prop, object OldValue)>();
static Stack<(object Obj, string Prop, object OldValue)> redoHistory
= new Stack<(object Obj, string Prop, object OldValue)>();
static void Undo()
{
if (undoHistory.Count == 0) return;
var undo = undoHistory.Pop();
UndoCommand.RaiseCanExecuteChanged();
// Обернуто для того чтобы в случае исключения флаг всё равно снимался
try
{
isUndoProcess = true;
undo.Obj.GetType().GetProperty(undo.Prop).SetValue(undo.Obj, undo.OldValue);
}
finally
{
isUndoProcess = false;
}
}
static void Redo()
{
if (redoHistory.Count == 0) return;
var redo = redoHistory.Pop();
RedoCommand.RaiseCanExecuteChanged();
try
{
isRedoProcess = true;
redo.Obj.GetType().GetProperty(redo.Prop).SetValue(redo.Obj, redo.OldValue);
}
finally
{
isRedoProcess = false;
}
}
static void SaveHistory(object obj, string propertyName, object value)
{
if (isUndoProcess)
{
redoHistory.Push((obj, propertyName, value));
RedoCommand.RaiseCanExecuteChanged();
}
else if (isRedoProcess)
{
undoHistory.Push((obj, propertyName, value));
UndoCommand.RaiseCanExecuteChanged();
}
else
{
undoHistory.Push((obj, propertyName, value));
UndoCommand.RaiseCanExecuteChanged();
redoHistory.Clear();
RedoCommand.RaiseCanExecuteChanged();
}
}
static void ClearHistory()
{
undoHistory.Clear();
UndoCommand.RaiseCanExecuteChanged();
redoHistory.Clear();
RedoCommand.RaiseCanExecuteChanged();
}
// Команды, которые можно выставлять в GUI
public static DelegateCommand UndoCommand { get; }
= new DelegateCommand(_ => Undo(), _ => undoHistory.Count > 0);
public static DelegateCommand RedoCommand { get; }
= new DelegateCommand(_ => Redo(), _ => redoHistory.Count > 0);
public static DelegateCommand ClearHistoryCommand { get; }
= new DelegateCommand(_ => ClearHistory());
Теперь добавим сохранение в метод Set:
protected bool Set(ref T field, T value, [CallerMemberName]string propertyName
= null)
{
if (EqualityComparer.Default.Equals(field, value))
return false;
// Сюда
SaveHistory(this, propertyName, field);
field = value;
NotifyPropertyChanged(propertyName);
return true;
}
Обратите внимание, решение не безопасно к потокам, но это и не требуется, т.к. вы
должны обновлять свойства VM только в потоке GUI
Здесь использована следующая реализация команды:
class DelegateCommand : ICommand
{
protected readonly Predicate
Комментариев нет:
Отправить комментарий