C# 如何在WPF中惰性地创建UI元素?

C# 如何在WPF中惰性地创建UI元素?,c#,wpf,performance,C#,Wpf,Performance,我们正在创建一个WPF应用程序,它大量使用高度修饰的输入元素。装饰元素的一个简单示例是一个文本框,它在没有焦点时看起来像一个只读文本块,在接收到焦点后变成一个文本框。此外,在将更改的值保存到数据库时,还会提供额外的视觉反馈。问题是,显示包含大量这些元素(比如100个)的视图非常慢,并且使应用程序非常无响应 我们已经将此装饰器实现为UserControl,它包含所有必需的元素(例如,用于显示未聚焦文本的TextBlock和用于忙碌指示器的旋转图像)。然后,我们将输入元素添加为这个decorator

我们正在创建一个WPF应用程序,它大量使用高度修饰的输入元素。装饰元素的一个简单示例是一个文本框,它在没有焦点时看起来像一个只读文本块,在接收到焦点后变成一个文本框。此外,在将更改的值保存到数据库时,还会提供额外的视觉反馈。问题是,显示包含大量这些元素(比如100个)的视图非常慢,并且使应用程序非常无响应

我们已经将此装饰器实现为UserControl,它包含所有必需的元素(例如,用于显示未聚焦文本的TextBlock和用于忙碌指示器的旋转图像)。然后,我们将输入元素添加为这个decorator控件的子元素,这意味着除了所有额外的元素外,decorator还将输入元素包含在其可视化树中。在XAML中,这看起来像:

<custom:Decorator Context="{Binding ValueHelper}" >
    <TextBox Text="{Binding ValueHelper.Text}"/>
</custom:Decorator>

这使得我们可以轻松地装饰我们想要的任何输入元素,无论是文本框、日期选择器、组合框还是任何自定义元素

现在回到问题:假设我们有一个包含100个装饰文本框的视图,我们导航到该视图。会发生什么?至少我的四核笔记本电脑冻结了很长时间,因为它必须创建数百个文本块、矩形、图像等,以便为每个装饰元素提供视觉反馈,尽管还没有任何装饰可见。真正需要的只是100个文本块,因为这是屏幕上可见的。只有在元素接收到鼠标悬停事件或焦点后,才需要其他元素。此外,一次只编辑一个元素,因此只有一个输入元素(在本例中为textbox)就足以用于整个应用程序

那么,在不为视图中的每个元素创建所有装饰元素(或实际输入元素)的情况下,实现相同装饰的最佳方法是什么


说明用例的装饰文本框示例:

当文本框没有焦点或鼠标光标当前不在其上方时,该文本框看起来像只读文本块(状态1)。此外,显示三个点(“…”),因为元素当前没有任何值

当鼠标光标移动到元素顶部时,TextBlock周围会出现一个绿色的点矩形,表示可以修改元素(状态2)。如果文本框恰好是只读的,则颜色将为红色

接收到焦点元素后,焦点元素变为实际文本框,可用于修改实际值(状态3)

文本框失去焦点后,该值被存储到数据库中,为了显示当前正在保存该值,元素左侧会出现一个忙指示器(状态4)

最后,值被保存,元素返回到空闲状态,显示新值(状态5)。(事实上,元素有更多与验证和其他特定需求相关的状态,但您肯定知道元素实际上是高度装饰的。)


只绘制所需的UI元素,而不是预先绘制所有UI元素

WPF允许您使用
数据触发器
修改对象的
模板
,因此您可以根据触发器调整装饰器的模板

装饰器的示例样式可能如下所示:

<Style TargetType="{x:Type ContentControl}">
    <!-- Default Template -->
    <Setter Property="ContentTemplate" 
            Value="{StaticResource NoDecoratorTemplate}" />

    <Style.Triggers>
        <DataTrigger Property="Text" Value="">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource BlankTemplate}" />
        </DataTrigger>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource MouseOverTemplate}" />
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource FocusedTemplate}" />
        </Trigger>
        <DataTrigger Property="{Binding IsLoading}" Value="True">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource LoadingTemplate}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

此外,WPF不应加载不可见的项目。您可以尝试将decorators
Visibility=“Collapsed”
设置为一个测试,以查看这是否修复了加载时间

如果确实如此,并且您不想使用
模板
路径,则可以确保首先将所有对象的
可见性
设置为
折叠
,并且仅在应显示对象时才将其设置为
可见


如果它不能解决您的加载时间,那么您的问题可能在其他地方。例如,您的一些装饰器项目似乎是根据其内部的内容控件来调整大小的,因此,可能真正让您慢下来的不是显示对象,而是确定对象的大小。

谢谢!控件模板绝对是装饰元素的方式。然而,这并不能解决实际输入元素的问题。TextBox是我们最轻的元素,我们有更重的自定义输入元素(例如日期选择器),我们无法创建。我们已经将“可见性”设置为“已折叠”,但仍然会创建可视化树并加载所有相关资源,这会导致严重的性能问题。@user544511当您说“所有相关资源均已加载”时,您的意思是每个装饰器都已加载吗?因为,如果您对装饰器使用单个UI控件并交换模板,则不应获得该行为,因为可视化树中一次只存在一个元素。或者,我正在休假,无法提前回复。我的意思是,实际的输入元素(它是decorator的子元素,例如日期选择器或文本框)被创建,它的所有资源(包括子元素)被加载并添加到可视化树中(即使它被折叠)。这会造成巨大的性能损失,因为日期选择器比文本框等元素重得多。