Ситуация: у меня есть небольшое приложение, которое работает с классами фэнтези. В приведенном ниже примере я сварил его до костей. В ComboBox, расположенном в главном окне, пользователь выбирает класс фэнтези (warrior, rogue, mage et c.) Из списка, загруженного из БД. Эта информация передается в UserControl, находящийся в главном окне, который предоставляет подробную информацию о классе с использованием MVVM и привязки данных. Пока все это работает.
DB имеет значение (в данном случае Gear), сохраненное как int, которое в данный момент отображается как int на экране. Приложение должно проанализировать это в строку.
Итак, вопрос в следующем: Как мне подключить метод в ViewModel UserControl для запуска всякий раз, когда его связанный View имеет DataContext ( выбранный CharacterClass) изменить?
Главное окно:
<Window x:Class="ExampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ExampleApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Height="22" MinWidth="70"
ItemsSource="{Binding Classes}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedClass}"/>
<local:DetailsView Grid.Column="1" DataContext="{Binding SelectedClass}"/>
</Grid>
</Window>
Главное окно ViewModel:
namespace ExampleApp
{
class MainWindowViewModel : Observable
{
private ObservableCollection<CharacterClass> _Classes;
private CharacterClass _SelectedClass;
public ObservableCollection<CharacterClass> Classes
{
get { return _Classes; }
set { SetProperty(ref _Classes, value); }
}
public CharacterClass SelectedClass
{
get { return _SelectedClass; }
set { SetProperty(ref _SelectedClass, value); }
}
public MainWindowViewModel()
{
LoadCharacterClasses();
}
private void LoadCharacterClasses()
{
//simulated data retrieval from a DB.
//hardcoded for demo purposes
Classes = new ObservableCollection<CharacterClass>
{
//behold: Gear is saved as an int.
new CharacterClass { Name = "Mage", Gear = 0, Stats = "3,2,1" },
new CharacterClass { Name = "Rogue", Gear = 1, Stats = "2,2,2" },
new CharacterClass { Name = "Warrior", Gear = 2, Stats = "1,2,3" }
};
}
}
}
Определение моего CharacterClass. Наследование от Observable, которое инкапсулирует INotifyPropertyChanged
namespace ExampleApp
{
public class CharacterClass : Observable
{
private string _Name;
private int _Gear;
private string _Stats;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public int Gear
{
get { return _Gear; }
set { SetProperty(ref _Gear, value); }
}
public string Stats
{
get { return _Stats; }
set { SetProperty(ref _Stats, value); }
}
}
}
Подробная информация о базовом классе Observable:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExampleApp
{
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Пользовательский контроль DetailsView:
<UserControl x:Class="ExampleApp.DetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ExampleApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:DetailsViewModel}">
<local:DetailsView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Label Content="Name:"/>
<Label Content="Base Stats"/>
<Label Content="Starting Gear"/>
</StackPanel>
<StackPanel Grid.Column="1">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Stats}"/>
<Label Content="{Binding gearToString}"/>
</StackPanel>
</Grid>
</UserControl>
и, наконец: DetailsViewModel:
public class DetailsViewModel : Observable
{
public string GearToString;
//The method I would like to have called whenever the selected
//CharacterClass (DetailsView.DataContext, so to speak) changes.
private void OnCharacterClassChanged(int gearNumber)
{
switch (gearNumber)
{
case 0:
GearToString = "Cloth";
break;
case 1:
GearToString = "Leather";
break;
case 2:
GearToString = "Plate";
break;
default:
GearToString = "*Error*";
break;
}
}
}
Я возился с попыткой запустить команду при обновлении метки DetailsView. Сделана неудачная попытка преобразовать DetailsViewModel.GearToString в свойство зависимости. Я попытался переопределить SetProperty Observable внутри DetailsViewModel.
Я не знаю, какие из этих попыток были бы жизнеспособны, если бы мне удалось реализовать их правильно (я только кодировал для через несколько месяцев:))
Я мог бы заставить его работать, используя кодовый фрагмент DetailsView, но это не MVVM'ы.