OpenExpressApp:精通 WPF UI Virtualization-阿里云开发者社区

开发者社区> 杰克.陈> 正文

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/ ]

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9489 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
11199 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13172 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
9050 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
21901 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4009 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6887 0
+关注
杰克.陈
一个安静的程序猿~
10427
文章
2
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载