XAML 连接组合框的绑定问题

XAML binding issues with connected comboboxes

我有两个组合框:类别和类型。当我的表单最初显示时,我列出了数据库中存在的所有类别和类型。对于这两个组合框,我手动插入第 0 行的值为“全部”,这样他们就不必选择任何一个,如果他们不想的话。

我将两个组合框都绑定到 ReactiveObjects,这样如果用户选择一个类别,Types 组合框会自动重新填充一个查询,以仅显示与所选类别相关的类型以及添加的第 0 行。

当用户选择类别时,它 运行 正确查询,returns 相关类型,正确添加第 0 行并正确填充组合框;但是,在 XAML 尺寸上,它没有选择第 0 行,而是在组合框周围添加了红色轮廓,表示做出了无效选择。

如果没有选择类型组合框并提交了表单,则会传递正确的值 0。因此,虽然一切正常,但 Types 组合框周围的红色框正在向用户传达他们做错了什么,我无法确定为什么 XAML 没有选择选定的值。我有 运行 没有添加第 0 行的代码,它仍然具有相同的行为,即组合框已正确填充,但没有选择任何行并出现红色轮廓。

XAML 用于组合框

<ComboBox 
    Grid.Row="3" 
    Grid.Column="1" 
    Width="200" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top"
    Style="{StaticResource SimpleComboBox}"
    ItemsSource="{Binding Categories}"
    SelectedValue="{Binding SearchCriteria.CategoryID}"
    SelectedValuePath="ComboValueID"
    DisplayMemberPath="ComboDataValue"
    />

<TextBlock 
    Grid.Row="3" 
    Grid.Column="2" 
    Style="{StaticResource NormalTextNarrow}" 
    Text="Type" VerticalAlignment="Top" 
    />

<ComboBox 
    Grid.Row="3" 
    Grid.Column="3" 
    Width="200" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top"
    Style="{StaticResource SimpleComboBox}"
    ItemsSource="{Binding Types}"
    SelectedValue="{Binding SearchCriteria.TypeId}"
    SelectedValuePath="ComboValueID"
    DisplayMemberPath="ComboDataValue"
    />

相关虚拟机代码

// Definition of SearchCriteria.  ResourceItem is a ReactiveObject and 
// all of the relevant properties watch for changes in values.
private ResourceItem searchCriteria;
public ResourceItem SearchCriteria
{
    get { return searchCriteria; }
    set { this.RaiseAndSetIfChanged(ref searchCriteria, value); }
}

// This all happens in my constructor

// Defining Row 0
var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All" };

// Populating the comboboxes from the database
Categories = omr.GetKTValues("RES_CATEGORIES");
Types = omr.GetKTValuesRU("RES_TYPES");

// Adding the row 0    
Categories.Insert(0, b);
Types.Insert(0, b);

// The form is displayed correctly at this point with the row 0 selected

问题代码

// When the user picks a category, this is the method that is invoked:
private void categoryChanged()
{
    if (SearchCriteria.CategoryID != 0)
    {
        Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
        SearchCriteria.TypeId = 0;
    }
}

// This runs correctly and returns the relevant Types
public List<GenericCombobox> GetCategoryTypes(int categoryId)
{
    string sql = "res.usp_GetCategoryTypes";
    var types = new List<GenericCombobox>();

    SqlConnection sqlCn = DatabaseCommunication.OpenConnection();

    using (SqlCommand cmd = new SqlCommand(sql, sqlCn))
    {
        // Omitting db stuff for brevity...
        try
        {
            SqlDataReader dr = cmd.ExecuteReader();
            while (dr.Read())
            {
                types.Add(new GenericCombobox
                {
                    ComboValueID = (int)dr["TypeId"],
                    ComboDataValue = (string)dr["Type"],
                    IsSelected = false,
                    Description = (string)dr["Type"],
                    ComboDataCode = (string)dr["Type"]
                });
            }
            // More db-stuff omitted
        }

        // Adding the row 0
        var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All", IsSelected = false, Description = "All", ComboDataCode = "All" };
        types.Insert(0, b);

        return types;
    }

使用附加代码更新

// Object containing the TypeId property
public class ResourceItem : ReactiveObject, ISelectable
{
    public int Id { get; set; }
    public int? OriginalItemId { get; set; }

    // ...many other properties...

    private int typeId;
    public int TypeId
    {
        get { return typeId; }
        set { this.RaiseAndSetIfChanged(ref typeId, value); }
    }

    // ...and many more follow...

我已经能够重现该问题,并且发现我可以做一些愚蠢的事情来阻止它发生。

  1. 如果我 select 类型中的项目不是 "All",那么当我更改类别中的 selection 时,它 selects "All" 在类型中。

  2. 如果在 categoryChanged() 中替换此行...

    SearchCriteria.TypeId = 0;
    

    这个:

    SearchCriteria = new ResourceItem() { 
        TypeId = 0, 
        CategoryID = SearchCriteria.CategoryID 
    };
    
  3. 如果我 ResourceItem.TypeId.set raise PropertyChanged 无论值是否改变,它都会再次正常工作。

我的假设是类型组合框(您甚至没有使用它!)中的 SelectedItem 在集合更改时不会更改,因为您没有告诉它更新 SelectedValue.

SearchCriteria.TypeId 已经等于零时,设置 SearchCriteria.TypeId = 0 是一个空操作,因为 RaiseAndSetIfChanged() 的作用正如其名:它检查值是否真的改变了,如果没有,则不会引发 PropertyChanged

SelectedValue 碰巧与新的 "All" 项目具有相同的值,但组合框不在乎。它只知道没有人告诉它去寻找新的 SelectedItem,而它拥有的旧的不再有用,因为它不在 ItemsSource 中。

所以这也有效:

private void categoryChanged()
{
    if (SearchCriteria.CategoryID != 0)
    {
        Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);

        SearchCriteria.SelectedType = Types.FirstOrDefault();

        //SearchCriteria.TypeId = 0;
        //SearchCriteria = new ResourceItem() { TypeId = 0, CategoryID = SearchCriteria.CategoryID };
    }
}

XAML:

<ComboBox 
    Grid.Row="3" 
    Grid.Column="3" 
    Width="200" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top"
    ItemsSource="{Binding Types}"
    SelectedValue="{Binding SearchCriteria.TypeId}"

    SelectedItem="{Binding SearchCriteria.SelectedType}"

    SelectedValuePath="ComboValueID"
    DisplayMemberPath="ComboDataValue"
    />

class 资源项

private GenericCombobox selectedType;
public GenericCombobox SelectedType
{
    get { return selectedType; }
    set { this.RaiseAndSetIfChanged(ref selectedType, value); }
}

我认为你最好的选择是我上面的选项 #2:

private void categoryChanged()
{
    if (SearchCriteria.CategoryID != 0)
    {
        Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);

        SearchCriteria = new ResourceItem() {
            TypeId = 0,
            CategoryID = SearchCriteria.CategoryID
        };

        //  Do you need to do this?
        //  SearchCriteria.PropertyChanged += SearchCriteria_PropertyChanged;
    }
}

这里的潜在问题是,在我的测试代码中,我从 SearchCriteria 上的 PropertyChanged 处理程序调用了 categoryChanged()。如果我创建一个新的 SearchCriteria,我需要确保在新的事件上处理该事件。

鉴于此,也许在类型组合框上绑定 SelectedItem 毕竟是最好的解决方案:这是我能想到的唯一一个不需要视图模型做奇怪的事情来弥补不当行为的方法认为它真的不应该知道。