Есть класс 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<Person> Persons {get;set;}
public UndoRedoService UndoRedoService {get;set;} = new UndoRedoService();
}
Хотелось бы также откатывать изменения, которые происходят в PersonVm
.
Можно было бы подписаться на событие PropertyChanged
у всех персон и получать название свойства в котором произошло изменение, текущее и новое значения.
Но в таком случае откатывать изменения пришлось бы через рефлексию — искать по названию нужное свойство и менять его. А это не слишком быстрый способ.
Возможно сделать как-то иначе?
Так как реализация "отката" изменений самостоятельно дело довольное утомительное. Проще использовать готовое решение. Одним из WPF-Фреймворков, который предоставляет данную функциональность из коробки является Catel.
Ниже показан простейший пример такого приложения:
1) Создаем новый проект: File - New - Project... - WpfApplication
.
2) Устанавливаем Catel.
PM> Install-Package Catel.MVVM
PM> Install-Package Catel.Core -Version 4.5.4
3) В проекте создаем стандартную структуру из папок: Models, ViewModels, Views.
4) В папке Models cоздаем класс User, представляющий нашу модель, он будет содержать всего пару свойств Name и LastName и наследуем его от ModelBase.
public class UserModel : ModelBase
{
public string Name
{
get { return GetValue<string>(AuthorProperty); }
set { SetValue(AuthorProperty, value); }
}
public static readonly PropertyData AuthorProperty =
RegisterProperty(nameof(Name), typeof(string), string.Empty);
public string LastName
{
get { return GetValue<string>(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
public static readonly PropertyData LastNameProperty =
RegisterProperty(nameof(LastName), typeof(string), string.Empty);
}
5) Далее в папке Views создаем нашу View, назовем ее MainView, ее разметка представлена ниже. Обратите внимание, что тип окна catel:Window
.
<catel:Window x:Class="WpfApplication1.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WpfApplication1.ViewModels"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel, IsDesignTimeCreatable=False}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
ItemsSource="{Binding Users}"
SelectedItem="{Binding SelectedUser}">
<DataGrid.Columns>
<DataGridTextColumn Width="*"
Binding="{Binding Name}"
Header="Имя" />
<DataGridTextColumn Width="100"
Binding="{Binding LastName}"
Header="Фамилия" />
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1"
Command="{Binding EditUserCommand}"
Content="Редактировать" />
</Grid>
</catel:Window>
6) В папке ViewModels создаем ViewModel для нашей MainView. Назовем ее MainViewModel.
public class MainViewModel : ViewModelBase
{
// Сервис открытия окон. Поставляется из коробки.
private readonly IUIVisualizerService _uiVisualizerService;
public MainViewModel(IUIVisualizerService uiVisualizerService)
{
_uiVisualizerService = uiVisualizerService;
EditUserCommand = new Command(EditUserAsync, () => SelectedUser != null);
}
private async void EditUserAsync()
{
// Создаем нашу ViewModel.
var editUserViewModel = new EditUserViewModel(SelectedUser);
// Передаем объект ViewModel сервису окон, он самостоятельно найдет соответствующую ей View.
await _uiVisualizerService.ShowDialogAsync(editUserViewModel);
}
// Инициализируем коллекцию тестовыми данными.
protected override Task InitializeAsync()
{
Users.Add(new UserModel()
{
Name = "Вася",
LastName = "Иванов"
});
Users.Add(new UserModel()
{
Name = "Петя",
LastName = "Петров"
});
return base.InitializeAsync();
}
public Command EditUserCommand { get; }
// Catel использует DP для уведомления View об изменении.
// Выбранный в DataGrid пользователь.
public UserModel SelectedUser
{
get { return GetValue<UserModel>(SelectedUserProperty); }
set { SetValue(SelectedUserProperty, value); }
}
public static readonly PropertyData SelectedUserProperty =
RegisterProperty(nameof(SelectedUser),
typeof(UserModel));
// Список всех пользователей.
public ObservableCollection<UserModel> Users
{
get { return GetValue<ObservableCollection<UserModel>>(UsersProperty); }
set { SetValue(UsersProperty, value); }
}
public static readonly PropertyData UsersProperty =
RegisterProperty(nameof(Users),
typeof(ObservableCollection<UserModel>),
new ObservableCollection<UserModel>());
}
7) Создаем представление, для редактирования нашего пользователя, оно будет следующим. Обратите внимание, что тип окна catel:DataWindow
.
<catel:DataWindow x:Class="WpfApplication1.Views.EditUserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WpfApplication1.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:EditUserViewModel, IsDesignTimeCreatable=False}"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Имя" />
<TextBox Grid.Row="0"
Grid.Column="1"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="Фамилия" />
<TextBox Grid.Row="1"
Grid.Column="1"
Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
8) И ViewModel для него:
public class EditUserViewModel : ViewModelBase
{
public EditUserViewModel(UserModel user)
{
User = user;
}
// Свойства, которые связаны со View и отражаются на объект редактируемой модели.
[ViewModelToModel(nameof(User), nameof(UserModel.Name))]
public string Name
{
get { return GetValue<string>(AuthorProperty); }
set { SetValue(AuthorProperty, value); }
}
public static readonly PropertyData AuthorProperty =
RegisterProperty(nameof(Name), typeof(string), string.Empty);
// Свойства, которые связаны со View и отражаются на объект редактируемой модели.
[ViewModelToModel(nameof(User), nameof(UserModel.LastName))]
public string LastName
{
get { return GetValue<string>(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
public static readonly PropertyData LastNameProperty =
RegisterProperty(nameof(LastName), typeof(string), string.Empty);
// Переданный объект модели, который мы редактируем.
[Model]
public UserModel User
{
get { return GetValue<UserModel>(UserProperty); }
set { SetValue(UserProperty, value); }
}
public static readonly PropertyData UserProperty =
RegisterProperty(nameof(User), typeof(UserModel));
}
9) В принципе на этом все, в Solution Explorer наш проект теперь выглядит так:
10) Теперь если запустить приложение, выбрать в DataGrid какого-нибудь пользователя и начать редактировать его, а после нажать на кнопку Отмена все изменения будут отменены.
P.S. Что делать, если не охота использовать этот громоздкий синтаксис с использованием DP из Catel?
Вариант 1
Установить Catel.Fody
PM> Install-Package Catel.Fody -Version 2.17.0
В этом случае нужный код для DP будет сгенерирован автоматически, путем перезаписи IL, после чего во ViewModel достаточно написать так:
public class EditUserViewModel : ViewModelBase
{
public EditUserViewModel(UserModel user)
{
User = user;
}
[ViewModelToModel(nameof(User), nameof(UserModel.Name))]
public string Name { get; set; }
[ViewModelToModel(nameof(User), nameof(UserModel.LastName))]
public string LastName { get; set; }
[Model]
public UserModel User { get; set; }
}
Вариант 2
Использовать Code Snippets
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Подскажите пожалуйста, как сделать чтобы при нажатии на Button один раз TextBox включился и второй раз нажать на этот же Button TextBox должен выключитсяСпасибо
Ограничение максимального числа ввода добавления в корзину minishop2 из tv availability Чтобы расширить поле availability читаем: //githubcom/bezumkin/miniShop2/blob/master/assets/components/minishop2/js/web/default