如何在WPF中懒加载创建UI元素?

7
我们正在创建一个WPF应用程序,其中大量使用高度装饰的输入元素。一个简单的例子是TextBox,当它没有焦点时看起来像只读的TextBlock,而在获得焦点后会变成TextBox。此外,在将更改的值保存到数据库时还提供了额外的视觉反馈。问题在于,显示包含大量这些元素(假设有100个)的视图非常缓慢,并使应用程序非常不响应。
我们已经将此装饰器实现为UserControl,其中包含所有所需的元素(例如用于显示未聚焦文本的TextBlock和用于繁忙指示符的旋转图像)。然后,我们将输入元素作为该装饰器控件的子元素添加,这意味着除了所有额外的元素之外,装饰器还在其可视树中包含输入元素。在XAML中,这将如下所示:
<custom:Decorator Context="{Binding ValueHelper}" >
    <TextBox Text="{Binding ValueHelper.Text}"/>
</custom:Decorator>

这使我们能够轻松地装饰任何输入元素,无论是文本框、日期选择器、组合框还是任何自定义元素。但让我们回到问题上来:假设我们有一个包含100个装饰文本框的视图,并且我们导航到该视图。会发生什么呢?至少我的四核笔记本电脑会冻结很长时间,因为它必须创建数百个文本块、矩形、图像等,以提供每个装饰元素的视觉反馈,虽然尚未显示任何装饰。实际上所需的只是100个TextBlocks,因为这就是屏幕上可见的内容。只有在元素接收到鼠标悬停事件或焦点时才需要其他元素。此外,一次只编辑一个元素,因此整个应用程序只需要一个输入元素(在本例中是文本框)即可。 那么,如何在不为视图中的每个元素创建所有装饰元素(或实际输入元素)的情况下实现相同的装饰效果最好的方法是什么? 下面是一个装饰TextBox的示例,以说明用例: 当TextBox没有焦点或鼠标光标当前不在其上方(状态1)时,文本框看起来像只读TextBlock。此外,会显示三个点(“…”),因为元素当前没有任何值。 当鼠标光标移动到元素上方时,一个虚线绿色矩形出现在TextBlock周围,以指示可以修改该元素(状态2)。如果TextBox是只读的,则颜色将变为红色。 在接收到焦点后,元素变成了实际的TextBox,可以用来修改实际值(状态3)。 在文本框失去焦点后,将值存储到数据库中,并显示忙碌指示器以显示当前正在保存该值(状态4)。 最后,该值已保存,元素返回到其空闲状态并显示新值(状态5)。 (实际上,元素甚至具有与验证和其他特定要求相关的更多状态,但您肯定明白元素确实高度装饰的意思。) 图片链接:https://istack.dev59.com/EZ8KW.webp
1个回答

4

不要一开始就绘制所有UI元素,只绘制所需的元素。

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

Decorator的示例样式可能如下所示:

<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 不应加载非可见项。您可以尝试将修饰符 Visibility="Collapsed" 设置为测试,看看是否可以解决加载时间的问题。
如果可以并且您不想使用 Template,则可以确保所有对象的Visibility一开始都设置为Collapsed,只有在需要显示时才将其设置为Visible
如果它不能解决您的加载时间问题,则可能问题出在其他地方。例如,似乎某些修饰符项目根据放置在其中的内容控件的大小进行调整,因此实际上减缓速度的可能不是显示对象,而是确定对象的大小。

谢谢!控件模板绝对是装饰元素的最佳选择。然而,这并不能解决实际输入元素的问题。TextBox 是我们最轻的元素,而我们有更重的自定义输入元素(例如日期选择器),我们无法承担创建它们的成本。我们已经将可见性设置为折叠,但仍会创建可视树并加载所有相关资源,这会导致严重的性能问题。 - user544511
@user544511 当您说“所有相关资源已加载”时,是指每个装饰器都已加载吗?因为如果您针对装饰器使用单个 UI 控件并交换模板,则不应出现此行为,因为在可视树中只存在一个元素。 - Rachel
抱歉我度假期间无法及时回复。我的意思是实际的输入元素(它是装饰器的子元素,例如日期选择器或文本框)被创建,其所有资源(包括子元素)都被加载并添加到可视树中(即使它处于折叠状态)。这会导致巨大的性能损失,因为日期选择器比文本框等元素更重。 - user544511

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接