具有大量元素的 WPF 绘图性能
我正在尝试在 WPF 中创建一个自定义控件来显示 围棋游戏的游戏树(请参阅此处了解其外观)。我或多或少已经完成了节点的布局,但我发现的一个问题是,当节点数量大于大约时,渲染/导航(在滚动查看器中)变得明显缓慢30. 由于围棋游戏平均包含约 200 步棋(更不用说玩家可能会分叉的其他分支),因此这在任何现实游戏中都将是一个相当大的问题。
目前,我正在对游戏节点使用单独的用户控件(每个节点都是一个带有阴影位图效果和一些文本注释的椭圆),并为树中的弧线使用简单的线条,所有这些都绝对定位在画布中。
布局算法不是这样的问题,因为这只需要在创建新分支时执行(否则该节点可以直接添加到其父节点下方,因此不需要重新定位其他节点)。主要问题在于,对该画布及其元素的任何类型的操作都非常慢,大概只是由于它拥有的子元素数量所致。随着树的宽度和复杂性的增加,它显然会变得更慢,因为有更多的弧和节点。
我的问题:绘制这样的大型/复杂结构的正确方法是什么,以使其随着增长而不会渲染得太慢?
编辑:这与我的其他问题相关。
编辑:这是我用于节点的用户控件的标记:
<UserControl x:Class="Go.UI.GameNodeMarker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Go.UI"
xmlns:wpftools="clr-namespace:WpfTools.Extensions;assembly=WpfTools"
x:Name="_This">
<UserControl.Resources>
<!-- Some brushes, resources, etc. are omitted -->
<Style x:Key="StoneStyle" TargetType="{x:Type Ellipse}">
<Setter Property="StrokeThickness" Value="0"/>
<Setter Property="BitmapEffect" Value="{StaticResource StoneEffect}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=_This, Path=StoneColour}" Value="Black">
<Setter Property="Fill" Value="{StaticResource BlackStoneBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=_This, Path=StoneColour}" Value="White">
<Setter Property="Fill" Value="{StaticResource WhiteStoneBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=_This, Path=IsEmpty}" Value="True">
<Setter Property="Fill" Value="{StaticResource EmptyBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Ellipse Style="{StaticResource StoneStyle}"/>
<TextBlock Text="{Binding ElementName=_This, Path=Text}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Medium"/>
</Grid>
</UserControl>
这些标记被动态添加到画布中以绘制树。
I'm trying to create a custom control in WPF to display the game tree for a game of go (see here for what it looks like). I've more-or-less got it working laying out the nodes, but one problem I've found is that it begins gets noticeably slow to render/navigate (it's in a scroll viewer) when the number of nodes gets larger than about 30. Since an average game of go consists of around 200 moves (not to mention other branches that the player might fork onto), this is going to be a fairly big problem in any realistic game.
Currently, I'm using individual user controls for the game nodes (each being an ellipse with a shadow bitmap effect on it and some text annotation) and simple lines for the arcs in the tree, all of which are positioned absolutely in a canvas.
The layout algorithm isn't such a problem since this only has to be executed when a new branch is created (otherwise the node can be added directly beneath its parent, so no other nodes need to be relocated). The main problem is simply that any kind of manipulation of this canvas and its elements is very slow, presumably just due to the number of children it has. It obviously gets slower as the width and complexity of the tree increases, since there are more arcs as well as nodes.
My question: What's the proper way to about drawing a large/complex structure like this in such a way that it doesn't render too slowly as it grows?
Edit: This is related to my other question.
Edit: Here's the markup for the user controls I'm using for the nodes:
<UserControl x:Class="Go.UI.GameNodeMarker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Go.UI"
xmlns:wpftools="clr-namespace:WpfTools.Extensions;assembly=WpfTools"
x:Name="_This">
<UserControl.Resources>
<!-- Some brushes, resources, etc. are omitted -->
<Style x:Key="StoneStyle" TargetType="{x:Type Ellipse}">
<Setter Property="StrokeThickness" Value="0"/>
<Setter Property="BitmapEffect" Value="{StaticResource StoneEffect}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=_This, Path=StoneColour}" Value="Black">
<Setter Property="Fill" Value="{StaticResource BlackStoneBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=_This, Path=StoneColour}" Value="White">
<Setter Property="Fill" Value="{StaticResource WhiteStoneBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=_This, Path=IsEmpty}" Value="True">
<Setter Property="Fill" Value="{StaticResource EmptyBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Ellipse Style="{StaticResource StoneStyle}"/>
<TextBlock Text="{Binding ElementName=_This, Path=Text}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Medium"/>
</Grid>
</UserControl>
These are being added dynamically to a canvas in order to draw the tree.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
渲染控件实际上相当昂贵。如果您在客户区中进行自定义绘图(自己绘制项目而不是放置控件),您将看到性能显着提高。
这是我在从事此类工作时一次又一次的体会。如果您需要十多个控件,请选择“所有者绘制”。
顺便说一句,不要让自定义绘图吓倒您。它并不比动态放置控件复杂多少。
链接到自定义 WPF 绘图示例
Rendering controls is actually quite expensive. If you do custom drawing (draw the items yourself instead of placing controls) in the client area you will see your performance increase dramatically.
This has been my experience time and again when doing this type of work. If you need more than a dozen controls, go for owner drawn.
By the way, don't let custom drawing intimidate you. It's not too much more complex than placing controls dynamically.
Link to custom WPF drawing sample
我自己就是一名 4-D 围棋玩家:) 您可能想要创建棋盘的位图,然后重新绘制已更改的部分。
I'm a 4-D go player myself :) You might want to create a bit-map of the board, and just redraw the part that has changed.
我不知道您是否会喜欢我的建议,因为它看起来很难看,但您可以尝试通过以下方式关闭抗锯齿:
将当前窗口的 RenderOptions.EdgeMode 设置为“未指定”(无抗锯齿)
您应该获得更好的性能,但某种程度的粗画。
祝你好运 !
埃里克
I don't know if you will like my suggestion because it will look ugly but you could try to turn off antialiasing by:
Set RenderOptions.EdgeMode for the current Window to "Unspecified" (no antialiasing)
You should get better performance but some kind of coarse drawing.
Good luck !
Eric
我知道这可能并不能真正回答问题,但我认为值得指出。
WPF4 将在不久的将来推出,它将支持元素图形的缓存,从而在许多情况下无需重绘。在您想要利用此功能的元素上添加几个属性,然后就可以开始了。如果您最终使用了此功能,请尽量避免不必要的动画。如果屏幕上的每个元素都需要在每一帧上重绘,那么缓存内容就没有意义。翻转动画就可以了。
I know this may not really answer the question, but I think it is worth pointing out.
WPF4 will be coming in the near future, and it will support caching of the element's graphics, making redraws unnecessary in many cases. Add a couple attributes on the elements you want to take advantage of this, and you're good to go. If you do end up using this, try to avoid unnecessary animations. There's no point in caching things if every element on the screen requires redraws on every frame. Rollover animations would be fine.