如何使用父项和子项实现组合框的层次结构?

时间:2014-07-07 05:50:37

标签: c# wpf combobox

这篇文章的标题可以使用一些工作,但我在没有详细说明的情况下难以表达自己。

所以这里。

我有简单的WPF应用程序,可以从第三方API检索StateCityComplexBuilding的列表。

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

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

如果这些是平面列表,这将是整洁的,但请注意ParentIdCityComplex模型中的Building字段。

因此,这四个列表必须作为层次结构处理。实际上,名称ParentId非常具有误导性,因为它们并未引用同一类型的另一个实体。在查看具有上述所有外键的Foo类时,这一点变得很明显。

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

为了进一步补充这一点,我更希望通过创建WPF用户控件来解决此问题,因为这四个列表将在未来的许多视图中再次出现,并且需要相同的功能。但我对所有解决方案持开放态度。我还希望在没有额外访问API的情况下这样做,因为列表非常大。

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

如果您有其他问题,请发表评论。

1 个答案:

答案 0 :(得分:1)

经过一些讨论和一些反复试验后,我们找到了几种方法来解决这个问题。

在大多数情况下,第一个解决方案就足够了,大多数人都推荐这个解决方案,因为它很容易在视图模型中用最少的逻辑实现,从而迫使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设置为第一个组合框Children的{​​{1}}属性。添加尽可能多的组合框,因为您的层次结构中有子级别。

解决方案2

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

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

解决方案3

上述解决方案均未支持我们的特定用例,因此这是我们最终实施的解决方案。

我们有四个表格,其中每一行都有一个可选的ParentId和一个ParentId。换句话说,我们将为某些客户提供实体层次结构,而其他客户则根本没有层次结构。这意味着上述解决方案不能很好地工作,因为它不能解释孤儿。

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

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

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

您最好通过实现依赖项属性绑定到父SelectedItem的{​​{1}},并将另一个依赖项属性绑定到{{1}类型的实体的完整列表来实现此目的。 1}}应该用于查找实际ElementName的项目。

我们成功的关键是意识到我们需要一个自定义HierarchicalComboBox来替换父组合框的选择更改事件,因为这使我们有机会从当前组合中有条件地触发该操作框的选择改变了事件。

我不会详细了解实施情况,但如果您感到好奇,请随时提出问题。