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

DoubleTree – 一个双面 TreeView 控件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (9投票s)

2005 年 10 月 31 日

6分钟阅读

viewsIcon

55757

downloadIcon

758

该控件允许您在一个双向树形显示中,直观地关联多个数据组之间的多对多关系。

引言

我需要一个控件,能够将一些数据关联起来,用于我工作的科学建模应用程序。数据基本上是一张表中的一组行与另一张表中的一组行相关联,并且每张表都有子表。为了演示这种行为,我选择创建一个简单的应用程序,展示这个控件如何用于显示关于一个家庭的信息。一个家庭可以有多个成员,一个家庭也可以有多个地址(比如家庭地址和度假地址)。每个成员可以有多个电子邮件地址,每个地址可以有多个电话号码。看来是时候上图了……

上面的图片展示了我的控件在我小小的测试应用程序中的数据呈现方式。请注意左边的家庭成员,每个人都有电子邮件地址(希望是虚构的),右边是多个地址,以及电话线等等。孩子电话上的分机号可能没意义,但它就在那里,只是为了展示控件如何能继续拥有子节点。

此外,允许多个组,而标准 TreeView 控件基本上只能有一个分组。每个组之间都提供了一些间距。

背景

对于那些只想使用该控件的人,我希望这个控件能够尽可能地像 TreeView 控件一样工作。如果您熟悉那个控件,我的进一步解释应该会很清楚。如果不熟悉,请查看 TreeView 的相关解释。希望一切都会足够清晰。

目前,我只打算介绍如何使用该控件,而不涉及太多代码,除了最有趣的部分。大部分代码都是用于计算一切位置的数学。

Using the Code

TreeView 控件公开了一个 TreeNodeCollection,允许您向其中添加节点。每个节点都可以包含自己的节点组,依此类推。DoubleTree 控件公开了一个 DoubleTreeGroupCollection,您可以向其中添加 DoubleTreeGroup。一个 DoubleTreeGroup 包含 LeftNodes 和 RightNodes,它们是 DoubleTreeNodeCollection 类型的 DoubleTreeNode 集合,用于在左侧和右侧显示内容。从那里开始,节点的行为就和 TreeView 上的节点一样了。

DoubleTreeNode 具有 TextTag 对象,与 TreeNode 类似。设置 Text 会创建显示文本,而 Tag 则允许您将一个通用对象关联到特定的节点。

当您选择一个左侧或右侧节点时,DoubleTree 会引发 LeftItemChosenRightItemChosen 事件,并将选定的节点和选定的组返回。我创建的一个属性叫做 AllowSelectionFromDifferentGroups。当它被设置为 false(默认值)时,如果您从不同组中选择任一侧的节点,控件将自动选择您所选组中另一侧的第一个节点。

另一个属性叫做 RootLineSize。左右两侧之间可以出现额外的间距。这会设置长度,并且每组顶部的线条会根据根线大小进行扩展。ItemHeight 允许您设置每个项目的大小。GroupMargin 允许您指定每个组之间的垂直空间,而 SomeRoomAtTop 允许您在顶部设置一些额外的空间。

关注点

我曾试图弄清楚并相信我已经理解了 MeasureCharacterRanges 函数。这个函数返回一个包围您文本的区域。我见过的大多数示例都令人困惑,所以这段代码可能会有所帮助。我使用这个区域来绘制选定节点周围白色文本的黑色背景。MeasureString 返回的区域比实际使用的要大。MeasureCharacterRanges 返回一个更精确的区域。

SizeF sizString = p_objGraphics.MeasureString(p_objNode.Text, this.Font);
...
StringFormat objStrFormat = new StringFormat();
// this will Right justify the string
objStrFormat.Alignment = StringAlignment.Far;

RectangleF objLayoutRect = new RectangleF(intLeftSideOfString, 
                                intTopOfString, sizString.Width, 
                                sizString.Height);

// save off the bounds of the text. We'll use it when 
// someone clicks our control to find out if they clicked me
p_objNode.Bounds = objLayoutRect;

// if we were selected, let's highlight the selection
if (p_objNode.m_blnIsSelected && p_objNode.Text.Length > 0)
{
    // this code makes the box around the text closer in. 
    // Measure string includes a little extra space
    // around the text. Using MeasureCharacterRanges provides a more 
    // accurate region and makes the highlight box prettier
    CharacterRange[] ranges = {new CharacterRange(0, p_objNode.Text.Length)};
    objStrFormat.SetMeasurableCharacterRanges(ranges);
    Region[] objRegion = p_objGraphics.MeasureCharacterRanges(p_objNode.Text, 
                                      this.Font, objLayoutRect, objStrFormat);
    p_objGraphics.FillRegion(drawBrush, objRegion[0]);
    // the text will be white
    drawBrush.Color = Color.White;
}
p_objGraphics.DrawString(p_objNode.Text, this.Font, drawBrush, 
                                    objLayoutRect, objStrFormat);

这段代码来自 DrawLeftNode 函数。首先,我使用 MeasureString 函数来测量字符串。这可以得到一个接近的尺寸。我见过的大多数示例都是猜测一个起始尺寸,但我认为这种方法更优雅。

最终(...),我创建了一个字符串格式对象和一个布局矩形。我指定了“Far”(靠右)的字符串对齐方式,因为我希望左侧节点文本显示在布局矩形左侧的最远处,同时仍在矩形内。这使得我的文本在左侧显示时右对齐。布局矩形使用我测量的字符串大小。其余的就比较直接了。我创建我的字符范围,并将可测量字符范围设置到我的字符串格式对象上。然后,我使用 MeasureCharacterRanges 函数重新测量我的字符串,该函数返回一个更精确的区域,然后我用黑色填充它。然后我更改颜色,使我的字符串显示为白色。

请注意,在编写未选定节点的文本时,我不关心 MeasureCharacterRanges,而是使用最初计算的稍大的区域作为包含显示文本的矩形的边界,以便在有人点击我的控件时进行检查。我想,如果我在控件上显示更多内容,我可能需要收紧这一点,但保留额外的松弛度可以让用户稍微偏离文本边缘也能触发选择。这一点在较长的文本上会更明显。您可以称之为懒惰。我称之为一项特性。

待办事项

可以添加一些东西。首先,我没有包含 TreeView 的展开和折叠功能,即通过显示的 + 或 - 来选择是否查看子节点。好吧,这个也算懒惰。

一个组可能需要一些文本来标记它。这可能会显示在树的顶部,并有一条小线连接到第一组节点。我的应用程序不需要这个,但您可以随意添加。

我可能需要为右键菜单(复制/粘贴)或拖放添加一些功能。这将允许我将数据从一个组复制到另一个组,这可能会很方便。

代码编写为每次都重新计算每个节点文本所需的虚拟空间。这也许可以优化。我认为 .NET 2.0 允许您在不使用 Graphics 对象的情况下计算 string 的大小。这样,当您更改节点的文本时,就可以在 Paint 调用之外运行计算,这似乎是组织事物的更好方式。

另一个想法可能是,节点不应只有 TextTag 属性,而应允许您将其关联到一个实现特定接口的对象。该接口将包括一个您的对象需要实现的 abstract 函数,节点将调用该函数来获取您的文本 string。该接口还将包括一个 abstract 事件,您可以在对象内部触发该事件,以告知节点文本已更改。在需要同时告知节点和您的对象更改情况时,这似乎很傻。相反,您可以更新您的对象,而您的对象将通过事件通知视图。

历史

  • 2005年10月31日:初始版本

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.