【发布时间】:2020-04-17 10:57:45
【问题描述】:
情况: 我有一个适用于幻想类的小应用程序。在下面的示例中,我将其归结为裸露的骨头。在位于主窗口的组合框中,用户从从数据库加载的列表中选择一个幻想类(战士、盗贼、法师等)。此信息被传递到位于主窗口中的 UserControl,它使用 MVVM 和数据绑定公开有关该类的详细信息。到目前为止,所有这些都有效。
DB 有一个保存为 int 的值(在本例中为 Gear),此时在屏幕上显示为 int。将其解析为字符串是应用程序的责任。
所以问题是:我如何在 UserControl 的 ViewModel 中连接一个方法,以便在其关联的 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>
主窗口视图模型:
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 定义。继承自封装了 INotifyPropertyChanged 的 Observable
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 转换为依赖项属性失败。 我试图在 DetailsViewModel 中覆盖 Observable 的 SetProperty。
如果我设法正确实施它们,我不知道这些尝试中的哪一个(如果有的话)是可行的(我现在才编码几个月:))
我可以使用 DetailsView 代码隐藏让它工作,但这不是 MVVM'y。
【问题讨论】:
-
角色有
intGear,您想将其可视化为string。这可以通过使用简单的转换器来实现。另一种可能性是使用 viewmodel:你已经有SelectedClass属性,在它的设置器中你可以运行逻辑,这将改变另一个属性。