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));
    }
}