如何实现具有父项和子项的组合框的层次结构

本文关键字:组合 层次结构 何实现 实现 | 更新日期: 2023-09-27 18:11:46

这篇文章的标题可以使用一些工作,但我有困难表达自己没有进入一些细节。

我有一个简单的WPF应用程序,它从第三方API检索State, City, ComplexBuilding的列表。

public class State
{
    public int Id { get; set; }
    public string Name { get; set; }
}
public class City
{
    public int Id { get; set; }
    public int ParentId { get; set; } // FK to Id in State
    public string Name { get; set; }
}
public class Complex
{
    public int Id { get; set; }
    public int ParentId { get; set; } // FK to Id in City
    public string Name { get; set; }
}
public class Building
{
    public int Id { get; set; }
    public int ParentId { get; set; } // FK to Id in Complex
    public string Name { get; set; }
}

当填充一些数据时,这些列表看起来像这样;

StateOptions = new ObservableCollection<State>
{
    new State() { Id = 1, Name = "California"},
    new State() { Id = 2, Name = "New York"},
};
CityOptions = new ObservableCollection<City>
{
    new City() { Id = 1, ParentId = 1, Name = "Los Angeles"},
    new City() { Id = 2, ParentId = 2, Name = "New York City"}
};
ComplexOptions = new ObservableCollection<Complex>
{
    new Complex() { Id = 1, ParentId = 1, Name = "Los Angeles International Airport"},
    new Complex() { Id = 2, ParentId = 2, Name = "John F. Kennedy International Airport"}
};
BuildingOptions = new ObservableCollection<Building>
{
    new Building() { Id = 1, ParentId = 1, Name = "Terminal 1"},
    new Building() { Id = 2, ParentId = 1, Name = "Terminal 2"},
    new Building() { Id = 3, ParentId = 1, Name = "Terminal 3"},
    new Building() { Id = 4, ParentId = 2, Name = "Terminal 1"},
    new Building() { Id = 5, ParentId = 2, Name = "Terminal 2"},
    new Building() { Id = 6, ParentId = 2, Name = "Terminal 3"},
};

使用这些列表,我需要创建一个新对象Foo,然后将其发送回API。

public class Foo
{
    public int Id { get; set; }
    public int StateId { get; set;
    public int CityId { get; set;
    public int ComplexId { get; set;
    public int BuildingId { get; set;
}

这本身非常简单,因为您可以将列表放在四个可观察集合中,并将它们用作四个ComboBox元素的ItemSource绑定。然后,您可以为每个组合框使用SelectedItem绑定属性来创建Foo对象。

如果这些是平面列表,这将是整洁的,但要注意City, ComplexBuilding模型中的ParentId字段。

因此,这四个列表必须被视为一个层次结构。事实上,ParentId这个名字很容易引起误解,因为它们并没有引用相同类型的另一个实体。在查看Foo类时,这一点变得很明显,它具有上述所有内容的外键。

因此,我需要过滤City组合框中的项目,以仅显示Id等于State组合框的SelectedItem的ParentId的项目。ComplexBuilding组合框也是如此。

为了进一步补充,我更倾向于通过创建WPF用户控件来解决这个问题,因为这四个列表将在未来的许多视图中再次出现,并且需要相同的功能。然而,我对所有的解决方案都持开放态度。我也更喜欢这样做,而不需要额外访问API,因为列表非常大。

为方便起见,我在https://db.tt/IAyOiq35创建了一个演示项目。这个项目通过将四个列表视为平面来说明这个问题,因此您可以选择洛杉矶国际机场作为综合体,尽管选择纽约作为州。这显然是错误的,而这正是我想要防止用户犯的错误。

如何实现具有父项和子项的组合框的层次结构

如果你有其他问题,请留下评论。

经过一些讨论和尝试,我们找到了一些解决这个问题的方法。

在大多数情况下,第一个解决方案就足够了,并且可能被大多数人推荐,因为它很容易在视图模型中使用最少的逻辑实现,从而强制使用XAML中的层次结构。

然而,我又添加了两个解决方案,因为许多人可能也考虑过这些。事实上,我们最终采用了第三种解决方案,因为第一种解决方案不能考虑孤儿,除非你将每个孤儿作为每个项目的孩子。

方案1(推荐)

根据Sheridan的建议,您可以构建一个分层数据模型,将您的Node变成HierarchicalNode。这可能是最简单的解决方案,如果可以的话,我可能会推荐使用这个。

public class Node
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public string Name { get; set; }
}
public class HierarchicalNode
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public List<HierarchicalNode> Children { get; set; }
}
从这里你可以绑定你的List<TreeNode>作为第一个组合框的ItemSource。然后将第二个组合框的ItemSource设置为第一个组合框的SelectedItemChildren属性。在层次结构中添加多少个子级别就添加多少个组合框。

解决方案2

另一个解决方案是在视图模型中使用ILookup<int, HierarchyEntityBase>,其中TKeyParentId。在SelectedItem的绑定属性中,您将使用该项目为子组合框构建一个新的ItemSource,方法是使用该项目的ParentId作为查找中的键。

这是可行的,但是以这种方式切换出组合框的项目源有几个问题。例如,您可能需要实现一种方法,在切换出组合框的项目源之前记住组合框的选定值,然后尝试在新的项目列表中再次查找该项目。

解决方案

3

以上解决方案都不支持我们的特定用例,所以这是我们最终实现的一个。

我们有四个表,每一行都有一个可选的ParentId和一个ParentTableType。换句话说,对于某些客户,我们将具有实体层次结构,而其他客户则根本没有层次结构。这意味着上面的解决方案不会很好地工作,因为它没有考虑孤儿。

解决方案是创建一个自定义用户控件,HierarchicalComboBox继承自ComboBox。这些将支持类型为HierarchicalEntityBase的实体,其中包括IdParentIdParentTableType的属性。

这个新的用户控件满足了我的所有要求,但是实现起来相当痛苦。

基本思想是修改您的ItemSource作为父或子组合框的选择的变化。如果父组合框的选择发生更改,则子组合框必须更新其子组合框并相应地添加其孤儿。在某些情况下,用户可能从层次结构中的最后一个组合框开始,在这种情况下,您必须正确地将父级设置为每个父级组合框的SelectedItem,一直到顶部。

你最好实现一个依赖属性绑定到父HierarchicalComboBoxElementName,另一个依赖属性绑定到HierarchicalEntityBase类型实体的完整列表,该列表应该用于查找实际ItemSource的项目。

我们成功的关键是意识到我们需要一个自定义的Action来取代父组合框的选择改变事件,因为这让我们有机会有条件地触发当前组合框的选择改变事件的动作。

我不会详细介绍实现,但如果您好奇,请随时提出问题。