WPF 驱动的 3D 图形库






4.63/5 (19投票s)
使用 WPF 创建 3D 条形图和饼图。
引言
早在 1995 年,一种名为 VRML 的新型标记语言引起了轰动。正如你们许多人记得的那样,VRML 旨在将 3D 带到网络上。起初,我们都看着这个新功能说:“太酷了!现在我们可以在互联网上放 3D 的东西了。”然后我们又想了想。最终,我们说:“嗯……那我们用它来做什么呢?”
快进到 2007 年,微软即将向公众发布 Vista。Vista 的一部分是新的 Windows Presentation Foundation(以前称为 Avalon)。WPF 为 Windows 带来了各种酷炫的变换、效果和 3D 功能。有了它,我们现在可以轻松地为我们的应用程序添加 3D 渲染。但我们中的一些人不禁会想:“嗯……那我们用它来做什么呢?”
我在这里想表达的观点是,除了游戏之外,很难在常规的商业应用程序中找到 3D 的有效用途。如果你试图告诉你的雇主,你想花几周时间给你的界面添加一些无用的 3D 元素,那么,发挥你的想象力吧。尤其是当你考虑到你必须通过 painstaking 的手工绘制来创建网格中的每一个三角形时。微软甚至都没有费心给我们提供任何实用的基本图形。
但是,还是有一些好的用途的,而且这些用途不需要一整队 CAD 设计师(微软似乎不明白并非每家公司都有这样的团队)。其中一个用途是创建 3D 图形和图表。并不是说以前做不到,只是从来没有这么容易过。通过动画、光照和纹理等功能,有很多方法可以为图表增添趣味。
这个库试图向您展示在 WPF 中创建 3D 图形的可能性。我不是图形专家,也没有创建一个包罗万象的软件包。我创建了这个库,并认为把它展示给社区会很有趣。如果您有任何建议,请在本文底部发表,因为我很乐意扩展这个库的功能。我也希望那些想尝试 WPF 的程序员能看到这篇文章,并了解使用 WPF 是多么容易。
基本原则
我是 KISS 原则(保持简单、傻瓜)的忠实拥护者,并试图在这个库中牢记这一点。该库将尽力为您屏蔽创建图形的所有困难。我的想法是,允许您传入一些数据集,然后代码将决定如何缩放和显示图形以包含所有信息。
图形接受三种类型的输入:列表(Lists)、字典(Dictionaries)和数据行(DataRows)。最终,所有东西都被归结为 Dictionary
。这样做的原因是为了让图形的每一部分都有一个键和一个值。这样,当用户与图形交互时,就有一种方式可以沟通他们正在操作图形的哪个部分。
我最近进行了一些重大的重构,以使图形更易于使用和扩展。我也在努力为轻松创建更多图形扫清道路。现在有一个 BaseGraph
类,其中包含一些常用功能,例如命中测试。此外,更加关注用户与图形的交互,因为这是使用 WPF 的一大优势。
条形图
要创建一个条形图,您只需创建一个新的 BarGraph
对象。
BarGraph bg = new BarGraph(dict, Colors.CadetBlue, Colors.BurlyWood, new TimeSpan(0, 0, 0, 0, 500));
参数如下:
- 一个
IList
、IDictionary
或DataRow
对象。这包含了要在图形中显示的数据。图形会尝试将每个值转换为double
类型。如果无法转换,它将跳过该数据并继续。 - 应用于每个条形的颜色。
- 高亮颜色。当用户将鼠标悬停在条形上时,它会变成这种颜色。
- 高亮持续时间。这表示在条形颜色和高亮颜色之间切换所需的时间。
之后,我们获取我们的 Viewport3D
对象,它可以添加到窗口的任何位置。
Viewport3D v = bg.GetViewport();
在示例中,视口被添加到一个网格(grid)中。这就像向界面添加标签或按钮一样简单。
饼图
饼图和条形图一样简单。传入的数据经过分析,以确定如何分割饼图。创建图形的方式也与条形图非常相似。
PieGraph pg = new PieGraph(dr, Colors.BurlyWood, new TimeSpan(0, 0, 0, 0, 500));
参数如下:
- 一个
IList
、IDictionary
或DataRow
对象。这包含了要在图形中显示的数据。图形会尝试将每个值转换为double
类型。如果无法转换,它将跳过该数据并继续。 - 高亮颜色。当用户将鼠标悬停在饼图的某个部分上时,它会变成这种颜色。
- 高亮持续时间。这表示在饼图块的颜色和高亮颜色之间切换所需的时间。
每个饼图块的颜色由 PieGraph
类中声明的一个数组决定。该数组遵循 ROYGBIV(红橙黄绿蓝靛紫)模式,并根据需要显示的块数循环使用。您可以随意更改显示的颜色;我不会介意的,至少不会太介意。
用户交互
用户可以通过多种方式与图形进行交互,您也可以对此作出响应。现在两种图形都有 MouseOver
(鼠标悬停)、LeftClicked
(左键单击)和 RightClicked
(右键单击)事件。您可以附加到这些事件,并获取与用户交互的图形部分相关联的键和值。例如:
BarGraph bg = new BarGraph(...);
bg.MouseOver += new GraphActionDelegate(BarGraphMouseOver);
...
void BarGraphMouseOver(BaseGraph sender, object key, object value)
{
// Respond to the mouse over event.
}
另一个选项是从图形中可用的一组预编程响应中进行选择。这些响应按图形进行枚举。以下是一个示例:
PieGraph pg = new PieGraph(...); pg.LeftClickAction = PieGraphUserActions.Focus; pg.MouseOverAction = PieGraphUserActions.Highlight;
这指示饼图在用户鼠标悬停时高亮显示饼图的一块。当左键单击时,图形将聚焦于该块,这将使该块从饼图的其余部分中升起。这些默认操作可以与标准事件处理一起使用。
高亮显示
WPF 的一个很酷的功能是能够为几乎任何属性制作动画,包括颜色。因此,当我们收到鼠标悬停事件时,我们可以通过动画改变条形或饼图块的颜色,以表示它已被高亮。我知道人们想要的一个功能是识别用户正在高亮显示图形的哪一部分。为此,两个图形都有一个名为 GraphHighlightChanged
的事件。它可以这样使用:
PieGraph pg = new PieGraph(...); pg.GraphHighlightChanged += new GraphHighlightChangedDelegate(myHandler); ... void myHandler(IGraph sender, object key, object value) { ... }
当前高亮显示的块可以通过事件处理方法中传递的参数读取,也可以通过图形上的 HighlightKey
和 HighlightValue
属性读取。
聚焦于饼图的一块
我觉得展示图形可以响应点击事件并为颜色以外的其他东西制作动画会很有趣。一个应用是让用户点击饼图的一块,然后将该饼图块提升到其他块之上。您可以通过附加到 GraphFocusChanged
事件来监听聚焦事件。它与 GraphHighlightChanged
事件非常相似。此外,您还可以轮询 FocusKey
和 FocusValue
来获取当前聚焦块的信息。
轮廓线
3DTools 库有一个名为 ScreenSpaceLines3D
的类,它使我们能够创建等宽的线条。这很重要,因为没有它,我们绘制的线条会根据其与摄像机的距离而改变大小。这是微软本应提供给我们但却没有提供的又一个小东西。

上图显示了有无轮廓线的区别。您可以通过一个简单的属性来打开或关闭轮廓线。但请确保在调用 GetViewport()
之前调整此属性。
PieGraph pg = new PieGraph(dr); pg.Outlining = false; Viewport3D v = pg.GetViewport();
工具提示
我最近添加的一个新功能是图形的工具提示。实现这个功能遇到了很多问题。一个问题是在 Viewport3D
上方显示清晰的文本会导致我失去鼠标悬停通知。当用户交互在这个图形库中显然很重要时,这根本不是一个可行的选择。
遇到的下一个问题是在 3D 空间中显示文本是多么困难。文本实际上必须作为纹理绘制在网格上,并插入到场景中。这就引出了下一个问题,如何让文本始终面向摄像机并始终显示相同的大小?这促使我创建了 ToolTip3D
类。
ToolTip3D
的工作方式是利用查看场景的 PerspectiveCamera
的信息。摄像机的上向量、观察向量、位置和近平面距离被用来计算工具提示的位置。基本上,工具提示被计算为移动到摄像机正前方,略微超出其近平面的位置。这是它在条形图上工作的截图:

它还不够完美。正如您所见,存在一些扭曲。我猜测这与使用 double
值并且没有正确处理与透视摄像机相关的任何失真有关。这是我会随着时间的推移继续努力解决的问题,但我想我还是把它发布在这里,看看是否有人能改进这段代码。
已完成的增强功能
- 高光反射 - 使用一些不同的材质和灯光,图形看起来好一些了
- 轮廓线 - 感谢 Daniel Lehenbauer[^] 的 3DTools[^],饼图块现在有了轮廓线
- 代码注释 - 我不再偷懒,加入了 XML 注释
- 重构代码以实现重用和更简便的用户交互
- 工具提示 - 可以工作,但需要完善
未来的增强
以下是我近期想实现的一些想法列表:
- 使用 3DTools 的线条来显示条形图中的刻度
- 允许用户更改图形背后的值,并为这些更改制作动画
- 添加摄像机动画
- 更好的数据绑定,以便可以在设计器窗口中看到图形
摘要
我希望您喜欢这个库。创建它很有趣,并且应该能展示出使用 WPF 的 3D 功能是多么容易。我也希望我为如何在商业应用中使用 3D 提供了一个案例。随着时间的推移,我想改进这个库以不断添加新功能,我感谢您的建议和评论。