65.9K
CodeProject 正在变化。 阅读更多。
Home

启用数据绑定的分层 TreeView 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (15投票s)

2005年3月27日

CPOL

3分钟阅读

viewsIcon

283476

downloadIcon

6968

该控件扩展了标准TreeView控件,使其完全支持数据绑定。

Sample Image - bindablehierarchicaltree.gif

引言

前段时间,我的任务是编写类似虚拟文件系统的东西。当然,我决定使用类型化的DataSet,因为我已经编写了一个可以轻松工作和更新它们的框架。 使用这项技术,很容易显示文件夹的内容。 关系型DataTable是非常棒的工具。 一切都很好,但是当我看到结果时 - 我崩溃了! 这不是我告诉客户的外观和感觉! 所以我打开Google并开始搜索启用数据绑定的TreeView。 当然,我找到了一些东西:这个(C#中的数据绑定TreeView)很漂亮,但那不是我脑海中的层次结构;这个(如何使用基类和数据提供程序将分层数据填充到TreeView中)也很漂亮,但我不明白为什么作者不喜欢标准绑定。 两者都不适合我! 作为一个真正的乌克兰人,我决定自己写一个!

绑定实现

首先,我们需要一种方法来填充树视图中的所有数据。为此,我将数据项的引用存储在ArrayList中。这给了我一个不在树中的项目的指示器。我将迭代这些项目,直到此数组的长度变为0。如果某些项目无法在try中找到它们的位置,则此实现将抛出异常。如果您需要此方法的另一种实现(例如,不执行任何操作或将这些项目存储在根节点的底部),请告诉我。我会尝试更新这篇文章。

ArrayList unsortedNodes = new ArrayList(); 
//This is list of items that still have no place in tree

for (int i = 0; i < this.listManager.Count; i++)
{
    //Fill this list with all items.
    unsortedNodes.Add(this.CreateNode(this.listManager, i));
}

int startCount;

//Iterate until list will not empty.
while (unsortedNodes.Count > 0)
{    
    startCount = unsortedNodes.Count;

    for (int i = unsortedNodes.Count-1; i >= 0 ; i--)
    {                    
        if (this.TryAddNode((DataTreeViewNode)unsortedNodes[i]))
        {
            //Item found its place.
            unsortedNodes.RemoveAt(i);
        }
    }

    if (startCount == unsortedNodes.Count)
    {
        //Throw if nothing was done, in another way this 
        //will continuous loop.
        throw new ApplicationException("Tree view confused 
                      when try to make your data hierarchical.");
    }
}

private bool TryAddNode(DataTreeViewNode node)
{
    if (this.IsIDNull(node.ParentID))
    {
        //If parent is null this mean that this is root node.
        this.AddNode(this.Nodes, node);                
        return true;
    }
    else
    {
        if (this.items_Identifiers.ContainsKey(node.ParentID))
        {
            //Parent already exists in tree so we can add item to it.
            TreeNode parentNode = 
                    this.items_Identifiers[node.ParentID] as TreeNode;
            if (parentNode != null)
            {
                this.AddNode(parentNode.Nodes, node);                
                return true;
            }
        }
    }
    //Parent was not found at this point.
    return false;
}

响应外部数据更改

好的……现在我们的树视图已填充了所有项目。我们需要做的第二件事是响应外部数据更改。为此,我们需要处理当前上下文的ListChanged事件。

((IBindingList)this.listManager.List).ListChanged += 
                     new ListChangedEventHandler(DataTreeView_ListChanged);

句柄的实现非常简单。

private void DataTreeView_ListChanged(object sender, ListChangedEventArgs e)
{            
    switch(e.ListChangedType)
    {
        case ListChangedType.ItemAdded:
            //Add item here.
            break;

        case ListChangedType.ItemChanged:
            //Change node associated with this item
            break;

        case ListChangedType.ItemMoved:
            //Parent changed.
            break;

        case ListChangedType.ItemDeleted:
            //Item removed
            break;                    

        case ListChangedType.Reset:
            //This reset all data and control need to refill all data.
            break;
        
    }
}

现在我们的控件特别支持数据绑定。您能够看到数据,它将与外部数据同步更改。

选定的节点

您可以问需要什么其他功能? - 哦!这仅仅是个开始。

因此,下一个要点:如果您更改数据源位置,我们的控件将不会更改它。货币管理器具有PositionChanged事件。我们将使用它。

this.listManager.PositionChanged += 
                  new EventHandler(listManager_PositionChanged);

在起点,我们根据位置向它们添加了位置和节点的索引。这使我们可以通过其位置轻松找到节点。因此,这段简短的代码将使我们能够按其位置找到选定的节点。

DataTreeViewNode node = 
          this.items_Positions[this.listManager.Position] as DataTreeViewNode;
if (node != null)
{
    this.SelectedNode = node;
}

当前上下文位置

现在您不能将此控件用作表的父项。基本上,我们需要做的是根据节点的选择来更改上下文的位置。这不是问题,因为我们将项目的位置存储在每个节点中。发出AfterSelect事件

private void DataTreeView_AfterSelect(object sender,
                              System.Windows.Forms.TreeViewEventArgs e)
{    
    DataTreeViewNode node = e.Node as DataTreeViewNode;
    if (node != null)
    {
        //Set position.
        this.listManager.Position = node.Position;
    }
}

标签编辑

没问题!只需使用AfterLabelEdit

private void DataTreeView_AfterLabelEdit(object sender, 
                           System.Windows.Forms.NodeLabelEditEventArgs e)
{
    DataTreeViewNode node = e.Node as DataTreeViewNode;
    if (node != null)
    {
        //This will found appropriate converter for 
        //type and see if it can convert from string. 
        if (this.PrepareValueConvertor()
          && this.valueConverter.IsValid(e.Label)//Lets converter to check value.
           )
        {
            //Set property.
            this.nameProperty.SetValue(
                this.listManager.List[node.Position],
                this.valueConverter.ConvertFromString(e.Label)
                );
            this.listManager.EndCurrentEdit();
            return;
        }
    }
    //Node text are not editable.
    e.CancelEdit = true;
}

Using the Code

作为DataSource,您可以使用任何数据,例如DataGrid可以使用的数据。您可以使用任何类型的列进行绑定。基本上,只有名称列仅限于可以从string转换的类型。这仅在EditLabeltrue时适用。

在大多数情况下,数据必须包含三列:标识符、名称和父行的标识符。如果您需要像'FirstName + " " + LastName'这样的Name字段 - 您可以在DataSet中创建自动计算列。

我没有在绑定中包含图像索引,因为我不需要它。如果您需要此功能,请告诉我。我将更新这篇文章。

奖励

第一个好处是完整的设计时支持。与“Code Project”上的所有其他数据绑定树不同,此树视图具有所有标准设计器,例如DataGrid具有的所有标准设计器(当然,有一些更改,请参阅Design命名空间)。第二个是roundup框架错误,TreeView中的底部滚动条(滚动条始终可见,即使根本不需要它)。

就这样!尽情享受吧。访问我的博客以获取最新消息!

© . All rights reserved.