WPF、MVVM 从字典中填充级联组合框
WPF, MVVM Populating Cascading ComboBoxes from a dictionary
所以,我有一个包含 2 个组合框的 UserControl,我想从字典中填充它们。因此 ComboBoxA 被字典键填满,ComboBoxB 被字典 [ComboBoxA selected item] 填满。我如何使用 MVVM 实现这一目标? Category 基本上是 int,Parameter 是一个字符串。
到目前为止我的代码:
型号
public class CategoryUserControlModel
{
public Dictionary<Category, List<Parameter>> parametersOfCategories { get; set;}
public Category chosenCategory { get; set; }
public Parameter chosenParameter { get; set; }
}
ViewModel
public class CategoryUserControlViewModel
{
public CategoryUserControlViewModel(CategoryUserControlModel controlModel)
{
Model = controlModel;
}
public CategoryUserControlModel Model { get; set; }
public Category ChosenCategory
{
get => Model.chosenCategory;
set
{
Model.chosenCategory = value;
}
}
public Parameter ChosenParameter
{
get => Model.chosenParameter;
set => Model.chosenParameter = value;
}
}
XAML
<Grid>
<ComboBox x:Name="Categories" HorizontalAlignment="Left" Margin="0,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.parametersOfCategories.Keys}"/>
<TextBlock x:Name="Text" HorizontalAlignment="Left" Height="15" Margin="0,-2,0,0" TextWrapping="Wrap" Text="Категория" VerticalAlignment="Top" Width="60"/>
<ComboBox x:Name="Parameter" HorizontalAlignment="Left" Margin="125,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.parametersOfCategories.Values}/>
<TextBlock x:Name="ParameterText" HorizontalAlignment="Left" Height="15" Margin="125,-2,0,0" TextWrapping="Wrap" Text="Параметр" VerticalAlignment="Top" Width="60"/>
</Grid>
</UserControl>
您为您的模型和视图模型使用了不合适的名称,它们永远不应与视图相关,并且您的模型中的成员的名称不应给人以模型需要用户交互的印象。考虑一个类似于这个的改进版本:
public class CategoryWithParameterModel
{
public Dictionary<Category, List<Parameter>> ParametersOfCategories { get; set;}
public Category Category { get; set; }
public Parameter Parameter { get; set; }
}
您的视图模型必须实现 INotifyPropertyChanged
接口以通知 UI 它需要刷新绑定,这对于模型来说不是必需的,因为您将它包装在视图模型中。也就是说,您的视图模型定义将变成:
public class CategoryWithParameterViewModel : INotifyPropertyChanged
{ ... }
接下来,由于您想绑定到字典中的列表,因此您的视图模型必须公开一个指向该列表的 属性,我们称它为 AvailableParameters
,因此应该定义它像这样:
public List<Parameter> AvailableParameters
{
get
{
if (Model.ParametersOfCategories.ContainsKey(ChosenCategory))
return Model.ParametersOfCategories[ChosenCategory];
else
return null;
}
}
这是需要绑定到名为“参数”的第二个组合框 ItemsSource
的 属性:)
但是,属性 ChosenCategory
根本没有绑定,因此您需要将它绑定到第一个组合框的选定项目,以便能够检测用户选择,从而允许 viemodel 找到列表参数,同样适用于 ChosenParameter
,所以这里是更新后的 xaml 代码:
<Grid>
<ComboBox x:Name="Categories" HorizontalAlignment="Left" Margin="0,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.ParametersOfCategories.Keys}" SelectedItem="{Binding ChosenCategory}"/>
<TextBlock x:Name="Text" HorizontalAlignment="Left" Height="15" Margin="0,-2,0,0" TextWrapping="Wrap" Text="Категория" VerticalAlignment="Top" Width="60"/>
<ComboBox x:Name="Parameter" HorizontalAlignment="Left" Margin="125,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding AvailableParameters}" SelectedItem="{Binding ChosenParameter}"/>
<TextBlock x:Name="ParameterText" HorizontalAlignment="Left" Height="15" Margin="125,-2,0,0" TextWrapping="Wrap" Text="Параметр" VerticalAlignment="Top" Width="60"/>
</Grid>
最后,您必须在 ChosenCategory
更改时通知 UI,因此为此您需要为 AvailableParameters
引发 PropertyChanged
事件。实现将使视图模型变成这样:
public class CategoryWithParameterViewModel : INotifyPropertyChanged
{
public CategoryWithParameterViewModel(CategoryWithParameterModel model)
{
Model = model;
}
// This should be read-only
public CategoryWithParameterModel Model { get; /*set;*/ }
public Category ChosenCategory
{
get => Model.Category;
set
{
Model.Category = value;
OnPropertyChanged(nameof(AvailableParameters));
}
}
public Parameter ChosenParameter
{
get => Model.Parameter;
set => Model.Parameter = value;
}
public List<Parameter> AvailableParameters
{
get
{
if (Model.ParametersOfCategories.ContainsKey(ChosenCategory))
return Model.ParametersOfCategories[ChosenCategory];
else
return null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
所以,我有一个包含 2 个组合框的 UserControl,我想从字典中填充它们。因此 ComboBoxA 被字典键填满,ComboBoxB 被字典 [ComboBoxA selected item] 填满。我如何使用 MVVM 实现这一目标? Category 基本上是 int,Parameter 是一个字符串。
到目前为止我的代码:
型号
public class CategoryUserControlModel
{
public Dictionary<Category, List<Parameter>> parametersOfCategories { get; set;}
public Category chosenCategory { get; set; }
public Parameter chosenParameter { get; set; }
}
ViewModel
public class CategoryUserControlViewModel
{
public CategoryUserControlViewModel(CategoryUserControlModel controlModel)
{
Model = controlModel;
}
public CategoryUserControlModel Model { get; set; }
public Category ChosenCategory
{
get => Model.chosenCategory;
set
{
Model.chosenCategory = value;
}
}
public Parameter ChosenParameter
{
get => Model.chosenParameter;
set => Model.chosenParameter = value;
}
}
XAML
<Grid>
<ComboBox x:Name="Categories" HorizontalAlignment="Left" Margin="0,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.parametersOfCategories.Keys}"/>
<TextBlock x:Name="Text" HorizontalAlignment="Left" Height="15" Margin="0,-2,0,0" TextWrapping="Wrap" Text="Категория" VerticalAlignment="Top" Width="60"/>
<ComboBox x:Name="Parameter" HorizontalAlignment="Left" Margin="125,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.parametersOfCategories.Values}/>
<TextBlock x:Name="ParameterText" HorizontalAlignment="Left" Height="15" Margin="125,-2,0,0" TextWrapping="Wrap" Text="Параметр" VerticalAlignment="Top" Width="60"/>
</Grid>
</UserControl>
您为您的模型和视图模型使用了不合适的名称,它们永远不应与视图相关,并且您的模型中的成员的名称不应给人以模型需要用户交互的印象。考虑一个类似于这个的改进版本:
public class CategoryWithParameterModel
{
public Dictionary<Category, List<Parameter>> ParametersOfCategories { get; set;}
public Category Category { get; set; }
public Parameter Parameter { get; set; }
}
您的视图模型必须实现 INotifyPropertyChanged
接口以通知 UI 它需要刷新绑定,这对于模型来说不是必需的,因为您将它包装在视图模型中。也就是说,您的视图模型定义将变成:
public class CategoryWithParameterViewModel : INotifyPropertyChanged
{ ... }
接下来,由于您想绑定到字典中的列表,因此您的视图模型必须公开一个指向该列表的 属性,我们称它为 AvailableParameters
,因此应该定义它像这样:
public List<Parameter> AvailableParameters
{
get
{
if (Model.ParametersOfCategories.ContainsKey(ChosenCategory))
return Model.ParametersOfCategories[ChosenCategory];
else
return null;
}
}
这是需要绑定到名为“参数”的第二个组合框 ItemsSource
的 属性:)
但是,属性 ChosenCategory
根本没有绑定,因此您需要将它绑定到第一个组合框的选定项目,以便能够检测用户选择,从而允许 viemodel 找到列表参数,同样适用于 ChosenParameter
,所以这里是更新后的 xaml 代码:
<Grid>
<ComboBox x:Name="Categories" HorizontalAlignment="Left" Margin="0,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.ParametersOfCategories.Keys}" SelectedItem="{Binding ChosenCategory}"/>
<TextBlock x:Name="Text" HorizontalAlignment="Left" Height="15" Margin="0,-2,0,0" TextWrapping="Wrap" Text="Категория" VerticalAlignment="Top" Width="60"/>
<ComboBox x:Name="Parameter" HorizontalAlignment="Left" Margin="125,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding AvailableParameters}" SelectedItem="{Binding ChosenParameter}"/>
<TextBlock x:Name="ParameterText" HorizontalAlignment="Left" Height="15" Margin="125,-2,0,0" TextWrapping="Wrap" Text="Параметр" VerticalAlignment="Top" Width="60"/>
</Grid>
最后,您必须在 ChosenCategory
更改时通知 UI,因此为此您需要为 AvailableParameters
引发 PropertyChanged
事件。实现将使视图模型变成这样:
public class CategoryWithParameterViewModel : INotifyPropertyChanged
{
public CategoryWithParameterViewModel(CategoryWithParameterModel model)
{
Model = model;
}
// This should be read-only
public CategoryWithParameterModel Model { get; /*set;*/ }
public Category ChosenCategory
{
get => Model.Category;
set
{
Model.Category = value;
OnPropertyChanged(nameof(AvailableParameters));
}
}
public Parameter ChosenParameter
{
get => Model.Parameter;
set => Model.Parameter = value;
}
public List<Parameter> AvailableParameters
{
get
{
if (Model.ParametersOfCategories.ContainsKey(ChosenCategory))
return Model.ParametersOfCategories[ChosenCategory];
else
return null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}