wpf 载入大量使用者控件时发生效能问题

szqfcxe2  于 2022-11-18  发布在  其他
关注(0)|答案(2)|浏览(118)

我的应用程序正在将一堆相同的用户控件加载到ScrollPanel中。问题是,这非常慢。
分析工具显示瓶颈是Application.LoadComponent()方法,这个方法是从我的使用者控件的建构函式内部呼叫的。这个方法的文件说明这个方法会载入XAML档案。
问题是,如何使用BAML而不是XAML?如何实现在从用户控件创建新示例时不必反复分析XAML?是否有其他方法可以加快加载用户控件的速度?

l7mqbcuq

l7mqbcuq1#

LoadComponent()已经加载了.baml,不要担心这个问题。微软这样做是有意的,不让开发者依赖baml。未来他们可以在不破坏任何现有应用程序的情况下改进格式。
是的,还有另外一种方法可以让它运行得更快。第一个要做的优化是UI virtualization。WPF已经附带了方便的VirtualizingStackPanel。它与ItemsControls协同工作,并且有一定的限制(例如,如果您创建项目容器并自行添加,则会失去虚拟化,要使用虚拟化,您可能必须重写应用程序以使用ItemsControl + DataBinding样式(ListBox已经启用了虚拟化)
如果您觉得需要更多关于UI虚拟化的信息,请参阅Dan Crevier's blog
最后一点建议。您可以尝试将UserControls重写为Custom Controls。我的简单性能测试显示了以下数字。要创建具有相同可视树的10 K控件,需要:

  • 对于用户控件:* 4932毫秒 *;
  • 对于自定义控件:* 86毫秒;*(快约57倍)

希望这对你有帮助

tjvv9vkg

tjvv9vkg2#

示例化用户控件时,调用链如下所示:

  • UserControl.Constructor
  • UserControl.InitializeComponent()
  • Application.LoadComponent()
  • XamlReader.LoadBAML()

根据我的经验,它是最后的调用,实际上消耗了所有的时间。这是XAML文件(以BAML格式存储)被加载并转换为实际对象以供使用的步骤。
我发现了两种通用的方法来减少或消除在这里花费的时间。
下面这个简单的重构很容易实现,在我所处理的案例中,它可以将加载时间减少大约75%(* 非常粗略!*)。通常,您会从下面这样的代码开始:

典型UserControl起点

MyControl.xaml档案:

<UserControl x:Class="MyNamespace.MyControl" ... >

   <Grid ... >
     ...
   </Grid>

</UserControl>

对应的MyControl.xaml.cs文件:

namespace MyNamespace
{
    partial class MyControl : UserControl
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

重构1:带有xaml/xaml.cs文件的Control

从该起点开始,修改它以从Control而不是UserControl派生,并显式设置ControlTemplate
MyControl.xaml档案:

<Control x:Class="MyNamespace.MyControl" ... >
 <Control.Template>
  <ControlTemplate>
    <Grid ... >
      ...
    </Grid>
  </ControlTemplate>
 </Control.Template>
</Control>

对应的MyControl.xaml.cs文件:

namespace MyNamespace
{
    partial class MyControl : Control
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

不管出于什么原因,在我尝试过的所有例子中,这样做就减少了大约75%(大约)的加载时间。
Visual Studio对这种xaml/xaml.cs的用法非常满意,尽管它有点不标准。
但是请注意,XAML仍然会在控件的每个示例化中加载和处理。这是可以避免的,请参阅下一节。

重构2:Control(带资源字典)

就性能而言,更好的方法是将ControlTemplate重构为资源字典,这完全消除了每个控件示例的XAML负载,而且(根据我的经验)几乎没有任何延迟。
将原始.xaml文件替换为“MyControlResources.xaml“,如下所示:

<ResourceDictionary ... >

    <ControlTemplate x:Key="MyControlTemplateKey">
       <Grid ... >
           ...
       </Grid>
    </ControlTemplate>

    <Style TargetType="{x:Type MyNamespace:MyControl}">
        <Setter
            Property="Template"
            Value="{StaticResource MyControlTemplateKey}"
            />
    </Style>

</ResourceDictionary>

此资源字典需要添加到app.xaml中的合并字典中。
将原始xaml.cs文件替换为包含以下内容的普通MyControl.cs文件:

namespace MyNamespace
{
    class MyControl : Control
    {
        public MyControl()
        {
             // other stuff...
        }
    }
}

请注意,不再有任何对InitializeComponent()的呼叫。样式系统是用来套用其XAML只载入一次的控件样板。
这是一个更实质性的重构,但仍然相对容易执行,因为通常需要很少的其他代码修改。最初由ElementName直接绑定到顶级控件的绑定应该改为使用TemplateBinding
您可以用其他方式组织控件模板和样式资源,这只是显示了一种直接对应于用户控件的原始代码组织的方法。就我个人而言,我喜欢一个资源字典对应一个控件。

相关问题