OpenExpressApp:精通 WPF UI Virtualization

简介: 原文:OpenExpressApp:精通 WPF UI Virtualization     本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提升 OEA 框架中 TreeGrid 控件的性能,同时,给出了一些学习 UIV 的资源。
原文: OpenExpressApp:精通 WPF UI Virtualization

    本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提升 OEA 框架中 TreeGrid 控件的性能,同时,给出了一些学习 UIV 的资源。

 

 

问题


    最近对 OEA 的 TreeGrid 控件进行了比较大的改造,并使用新的控件来替换了系统中所有的 DataGrid 控件。新的 TreeGrid 控件实现了很多新的功能,(之后会写一篇文章说明),但是最后遗留了一个问题:由于使用它替换了原来的 DataGrid,而 DataGrid 默认是支持 UI Virtualization 的,当有些界面的数据量比较大时,没有支持 UIV 的TreeGrid 控件就显得有些力不从心了。为了解决这个问题,这两天看了许多文章并学习了 WPF 中 UIV 的知识,在最后终于解决了,待写下此文予以记录。

    先来看看实现 UIV 前:

image

518 条数据,生成了 18130 个 Visuals。

 

其实,在解决完后看来,问题主要出在 TreeGrid 的 Template 上,直接贴上来给大家看看:

<ScrollViewer Style="{StaticResource GridTreeViewScroll}" Background="{TemplateBinding Background}"
    Focusable="false"
    CanContentScroll="false"
    HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
    VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
    Padding="{TemplateBinding Padding}"
    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
    <Grid>
        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
        <TextBlock Opacity="0.5" TextWrapping="Wrap" FontSize="36" Text="没有数据" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" FontFamily="STCaiyun" RenderTransformOrigin="0.5,0.5" Foreground="#80000000">
            <TextBlock.Visibility>
                <MultiBinding>
                    <MultiBinding.Converter>
                        <oeaModuleWPF:ItemsControlNoDataConverter/>
                    </MultiBinding.Converter>
                    <Binding Path="Data" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type oea:GridTreeView}}"/>
                    <Binding Path="Items.Count" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type oea:GridTreeView}}"/>
                </MultiBinding>
            </TextBlock.Visibility>
                <TextBlock.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1.5"/>
                        <SkewTransform AngleX="-30"/>
                        <RotateTransform Angle="-30"/>
                    </TransformGroup>
                </TextBlock.RenderTransform>
        </TextBlock>
    </Grid>
</ScrollViewer>

 

    其中,为了实现在列表没有数据时,显示 “没有数据” 四个字,使用了一个 Grid 包含了一个 ItemsPresenter 以及一个 TextBlock。这段代码看上去没有什么问题,所以搞了很久都没有把 UIV 调试出来,最终只有在网上耐心学习了很我 UIV 的相关知识。

 

 

解决方案

 


     其实, 相关的 UIV 知识点有那么几个:

 

  1. WPF 中的 VirtualizingStackPanel 只支持一层数据的 UIV。(这一点好像在 WPF3.5 SP1 后有所改善?)
  2. WPF3.5 SP1 以前的 TreeView 是不支持 UIV的。而之后的 TreeView 在默认情况下 UIV 处于关闭状态,需要手动打开。
  3. 实现 UIV 需要一个对应的 ScollViewer。
  4. ScollViewer 中的 CanContentScroll 属性为 True 时,子对象才能实现 UIV。
    该属性为 True 时,ScollViewer 在 Measure 时会把当前的 ViewPort 大小传给 Content 元素。否则,它会把 Infinite 传给 Content。
    同 时,由子元素(也就是 VirtualizingStackPanel)需要实现 IScollInfo 并返回 Scroll 相关信息,而 ScollViewer 则只是一个简单的视窗;这样,子元素就可以在内部实现 UIV,并告知其对应的 ScrollOwner(ScrollViewer) 相关的拖动信息。

    所以,上面的 xaml 主要有两个错误:

  1. ScrollViewer.CanContentScroll 应该设置为 True。
  2. 应该把 VirtualizingStackPanel 作为 ScrollViewer 的内容元素(Content)。

修改为以下 xaml 即可:

<Grid>
    <ScrollViewer Style="{StaticResource GridTreeViewScroll}" Background="{TemplateBinding Background}"
        Focusable="false"
        CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
        HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
        VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
        Padding="{TemplateBinding Padding}"
        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
        <VirtualizingStackPanel IsItemsHost="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
    </ScrollViewer>
    <TextBlock Opacity="0.5" TextWrapping="Wrap" FontSize="36" Text="没有数据">……</TextBlock>
</Grid>
 

同时,注意打开 TreeView 的 UIV 支持:

public class GridTreeView : TreeView
{
    static GridTreeView()
    {
        VirtualizingStackPanel.IsVirtualizingProperty.OverrideMetadata(typeof(GridTreeView), new FrameworkPropertyMetadata(true));
    }
 

来看看优化后的结果:

image

Visuals 的数量由 1W8 降到了 3000,当行数更多时,也就保持初始生成 3000 个左右。拖动起来也明显地感觉到流畅了许多。

大功告成!

 

相关资源


一篇通俗易懂的 UIV 概念文章:《UI Virtualization》,其中讲到了 WPF 及 SilverLight 中的 UIV。(它还有后续的文章:《Data virtualization》,也很不错)。

 

之前系统中用到的 DataGrid 控件,一旦数据被分组之后,性能异常低下。原因其实也和 UIV 有关:

目前 WPF 中的控件在 Group 分组后是不支持 UI Virtualization 的,原因是当 ScrollViewer.CanContentScroll 设置为 true 时,模式由 Scroll By Pixel 变为 Scroll by Item。而分组后的控件中每一个组 GroupItem 其实就是一个 Item,这时,如果继续使用 Scroll by Item 模式,将会得到非常差的用户体验,所以 MS 决定不支持分组后的 UIV,ListBox 控件的默认模板中有一个 Trigger 当 IsGrouping 为 True 时,设置 ConContentScroll 为 False。相关的内容参见:《UI Virtualization》。其它与分组相关的 UIV 文章如下:

WPF DataGrid Virtualization with Grouping》、《MSDN Sample Code:Grouping and Virtualization》、《Problem: ListView Virtualization

 

Virtualizing TreeViewItem》: 其中的最佳答案说到几个知识点:VirtualizingStackPanel 需要和 ScrollViewer 进行交互,同时,它只支持一层的 Virtualization。可以考虑变通地使用 ListBox/ListView 来实现假的 TreeView,这样就可以实现整个列表的虚拟化。

 

WPF - Virtualizing an ItemsControl》:文中指出,ItemsControl 默认不支持 UI Virtualization,原因是它的模板中没有一个 ScrollViewer。

 

《Are there any tricks that will help me improve TreeView’s performance》:这个系列的文章一共3篇:《Part I》、《Part II》、《Part III》, 最后一篇说明了在如何使用 ListBox 模拟一个 TreeView,这样,由于 ListBox 本身支持 UIVirtualization,所以最后的 “TreeView” 也就支持了 UI Virtualization。类似的控件已经有人传到了 CodeProject 上:《Virtualizing Tree View (VTreeView)》,其中还正好谈到了上面的这系列文章,非常凑巧的是,它还谈到了 CodeProject上被我们系统选择来实现 TreeGrid 控件的资源:《A Versatile TreeView for WPF》。

 

更高级的自定义 UI Virtualization,可以先参考以下几篇文章,很不错:《Virtualizing WrapPanel》、《Implementing a virtualized panel in WPF (Avalon)》、《IScrollInfo in Avalon part I》、《IScrollInfo in Avalon part II》、《IScrollInfo in Avalon part III》。

 

MS 自己的相关资源:

MSDN Control Performance》、《How to: Find a TreeViewItem in a TreeView》(如何在 UIV 的情况下找到控件)、《Changing selection in a virtualized TreeView

 

欢迎转载,转载请注明:

转载自 胡庆访http://zgynhqf.cnblogs.com/ ]

目录
相关文章
|
6月前
|
C# 开发者 Windows
基于Material Design风格开源、易用、强大的WPF UI控件库
基于Material Design风格开源、易用、强大的WPF UI控件库
375 0
|
2月前
|
搜索推荐 前端开发 C#
推荐7款美观且功能强大的WPF UI库
推荐7款美观且功能强大的WPF UI库
|
3月前
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库
|
3月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
63 0
|
3月前
|
C# 开发者 设计模式
WPF开发者必读:命令模式应用秘籍,轻松简化UI与业务逻辑交互,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,命令模式是简化UI与业务逻辑交互的关键技术,通过将请求封装为对象,实现UI操作与业务逻辑分离,便于代码维护与扩展。本文介绍命令模式的概念及实现方法,包括使用`ICommand`接口、`RelayCommand`类及自定义命令等方式,并提供示例代码展示如何在项目中应用命令模式。
47 0
|
3月前
|
开发者 C# 存储
WPF开发者必读:样式与模板的艺术,轻松定制UI外观,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,样式与模板是实现美观界面与一致性的关键工具。样式定义了控件如字体、颜色等属性,而模板则允许自定义控件布局与子控件,两者均可存储于`.xaml`文件中。本文介绍了样式与模板的基础知识,通过示例展示了如何创建并应用它们来改变按钮的外观,从而提升用户体验。
65 0
|
4月前
|
C# Windows
一款开源、免费、现代化风格的WPF UI控件库
一款开源、免费、现代化风格的WPF UI控件库
139 0
|
6月前
|
前端开发 C# 索引
浅谈WPF之UI布局
一个成功的软件,离不开人性化的UI设计,如何抓住用户第一视觉,让用户产生依赖感,合适优雅的布局必不可少。本文以一些简单的小例子,简述WPF中布局 面板 控件的使用,仅供学习分享使用,如有不足之处,还请指正。
98 1
|
6月前
|
搜索推荐 C# 开发者
3个值得推荐的WPF UI组件库
3个值得推荐的WPF UI组件库
368 0
|
6月前
|
前端开发 JavaScript C#
一个WPF版的Layui前端UI库
一个WPF版的Layui前端UI库
167 0