首页 > 解决方案 > 使用太多自定义控件时 WPF 会降低性能

问题描述

我是 WPF 编程的新手,我的应用程序性能很慢。

我有一个带有网格和 2 列的视图。第一列是 ListBox,其中是 RunTypes,第二列是 RunType 的详细视图。在此详细视图中,有一些用于标签等属性的基本控件和一个 NavBar 控件(来自 devexpress),其中是 RunConfigs。NavBar 的工作方式类似于手风琴控件,可显示所有 RunConfig,并且可以展开它们的详细信息。

我需要一个选项来控制所有字段的可见性和启用状态,所以我创建了这个自定义控件:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:controls="clr-namespace:ManualDistill.Controls"
                    xmlns:converters="clr-namespace:ManualDistill.Converters">

    <Style TargetType="{x:Type controls:EditorControl}" BasedOn="{StaticResource {x:Type ContentControl}}">
        <Style.Triggers>
            <Trigger Property="IsInEditMode" Value="True">
                <Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=EditContent}" />
            </Trigger>
            <Trigger Property="IsInEditMode" Value="False">
                <Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=ViewContent}" />
            </Trigger>
        </Style.Triggers>
    </Style>

</ResourceDictionary>

和cs文件:

public class EditorControl: ContentControl
{

    public EditorControl()
    {
        DefaultStyleKey = typeof(EditorControl);
        //DefaultStyleKeyProperty.OverrideMetadata(typeof(EditorControl), new FrameworkPropertyMetadata(typeof(EditorControl)));

        Messenger.Default.Register<LayoutChangeMessage>(this,
        (message) =>
        {
            if (this.Feature == message.Feature && this.DisplayField == message.DisplayField)
            {
                if (message.LayoutData != null)
                {                        
                    DispatcherHelper.RunAsync(() =>
                    {
                        ResetLayout();
                        SetLayout(message.LayoutData);
                    });
                }
            }                
        });
    }

    #region DependencyProperties
    /// <summary>
    /// If the control is currently in edit mode
    /// </summary>
    public bool IsInEditMode
    {
        get { return (bool)GetValue(IsInEditModeProperty); }
        set { SetValue(IsInEditModeProperty, value); }
    }

    public static readonly DependencyProperty IsInEditModeProperty =
        DependencyProperty.Register("IsInEditMode",
        typeof(bool),
        typeof(EditorControl),
        new PropertyMetadata(false));

    public string DisplayField
    {
        get { return (string)GetValue(DisplayFieldProperty); }
        set { SetValue(DisplayFieldProperty, value); }
    }

    public static readonly DependencyProperty DisplayFieldProperty =
       DependencyProperty.Register("DisplayField",
       typeof(string),
       typeof(EditorControl),
       new PropertyMetadata(""));

    public string Feature
    {
        get { return (string)GetValue(FeatureProperty); }
        set { SetValue(FeatureProperty, value); }
    }

    public static readonly DependencyProperty FeatureProperty =
       DependencyProperty.Register("Feature",
       typeof(string),
       typeof(EditorControl),
       new PropertyMetadata(""));

    public object EditContent
    {
        get { return GetValue(EditContentProperty); }
        set
        {                
            SetValue(EditContentProperty, value);
        }
    }

    public static readonly DependencyProperty EditContentProperty =
            DependencyProperty.Register("EditContent",
                    typeof(object),
                    typeof(EditorControl),
                    new FrameworkPropertyMetadata((object)null));

    public object ViewContent
    {
        get { return GetValue(ViewContentProperty); }
        set { SetValue(ViewContentProperty, value); }
    }

    public static readonly DependencyProperty ViewContentProperty =
            DependencyProperty.Register("ViewContent",
                    typeof(object),
                    typeof(EditorControl),
                    new FrameworkPropertyMetadata((object)null));
    #endregion

    public Visibility DefaultVisibility { get; set; }
    public bool DefaultIsEnabled { get; set; }

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        this.DefaultVisibility = this.Visibility;
        this.DefaultIsEnabled = this.IsEnabled;

        if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
        {
            var layouts = StillLayoutManager.Instance.GetActiveLayouts(this.Feature, this.DisplayField);
            if (layouts != null)
            {
                SetLayout(layouts);
            }
        }
    }

    private void ResetLayout()
    {
        this.Visibility = this.DefaultVisibility;
        this.IsEnabled = this.DefaultIsEnabled;
    }

    private void SetLayout(IEnumerable<ILayoutData> layouts)
    {
        foreach (var data in layouts)
        {
            this.Visibility = data.Visibility ? Visibility.Visible : Visibility.Collapsed;
            this.IsEnabled = data.Editable;                
        }            
    }
}

如您所见,我有 2 个用于编辑/查看的内容属性,并根据编辑模式将它们设置在 Content 属性上。然后,此自定义控件就像我需要的每个字段或每个对象的容器一样工作。然后可以由操纵这些控件的系统管理字段或对象的可见性(这与问题无关,它只是解释为什么我在那里有这些控件)

问题是,当我单击带有 RunType 的列表框时,我必须等待几秒钟才能看到详细信息。所有控件都被创建然后被释放,所以什么都不会被记住。RunType 集合将被更改,因此可以添加更多或从中删除。所以控件总是动态创建的。

RunManagement、RunTypeControl 和 RunConfigControl 的 xaml 文件在这里:

<UserControl ...> 
      <Grid cmn:GridUtils.ColumnDefinitions="Auto, *">
                <lc:LayoutGroup Orientation="Vertical" View="GroupBox" Header="{lex:LocTextUpper RunType}" MinWidth="250">
                    <dxe:ListBoxEdit x:Name="RunTypesList" 
                                     ItemsSource="{Binding RunTypes}"
                                     SelectedItem="{Binding SelectedRunType}">
                        <dxe:ListBoxEdit.ContextMenu>
                            <ContextMenu>
                                <MenuItem Header="{lex:LocText Copy}" Command="{Binding CmdCopy}"/>
                            </ContextMenu>
                        </dxe:ListBoxEdit.ContextMenu>
                        <dxe:ListBoxEdit.ItemTemplate>
                            <DataTemplate>
                                <TextBlock HorizontalAlignment="Left" Text="{Binding Label}" TextWrapping="Wrap"/>
                            </DataTemplate>
                        </dxe:ListBoxEdit.ItemTemplate>
                    </dxe:ListBoxEdit>
                </lc:LayoutGroup>

                <Grid Grid.Column="1">                        
                    <local:RunTypeControl DataContext="{Binding SelectedRunType}" />
                </Grid>
        </Grid>
</UserControl>

...

<UserControl ...>        
    <Grid  Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <lc:LayoutControl Orientation="Horizontal">
            <lc:LayoutGroup Orientation="Vertical">  
                <controls:EditorControl IsInEditMode="{Binding IsInEditMode}">
                    <controls:EditorControl.ViewContent>
                        <lc:LayoutItem Label="{lex:LocText Label}">
                            <TextBlock Text="{Binding Label}" />
                        </lc:LayoutItem>
                    </controls:EditorControl.ViewContent>
                    <controls:EditorControl.EditContent>
                        <lc:LayoutItem Label="{lex:LocText Label}">
                            <dxe:TextEdit Text="{Binding Label}"/>
                        </lc:LayoutItem>
                    </controls:EditorControl.EditContent>
                </controls:EditorControl>                        
            </lc:LayoutGroup>
            <lc:LayoutGroup Orientation="Vertical">
                <lc:LayoutItem>
                </lc:LayoutItem>
            </lc:LayoutGroup>
        </lc:LayoutControl>

        <lc:LayoutGroup Grid.Row="1" Margin="0,5" View="Tabs">
            <lc:LayoutGroup Orientation="Horizontal" Header="{Binding TabHeaderRunStates}">
                <lc:LayoutItem>
                        <dxn:NavBarControl Name="navBar" Grid.Column="1" ItemsSource="{Binding RunConfigs}" Margin="10 0 0 0">
                            <dxn:NavBarControl.ItemTemplate>
                                <DataTemplate>
                                    <controls:EditorControl IsInEditMode="{Binding IsInEditMode}">
                                        <controls:EditorControl.ViewContent>
                                            <dxn:NavBarGroup DisplaySource="Content" Header="{Binding DisplayName}" IsExpanded="False">
                                                <dxn:NavBarGroup.Content>
                                                    <local:RunConfigControl DataContext="{Binding}"/>                                                       
                                                </dxn:NavBarGroup.Content>
                                            </dxn:NavBarGroup>
                                        </controls:EditorControl.ViewContent>
                                        <controls:EditorControl.EditContent>
                                            <dxn:NavBarGroup DisplaySource="Content" Header="{Binding DisplayName}" IsExpanded="False">
                                                <dxn:NavBarGroup.Content>
                                                    <local:RunConfigControl DataContext="{Binding}"/>
                                                </dxn:NavBarGroup.Content>
                                            </dxn:NavBarGroup>
                                        </controls:EditorControl.EditContent>
                                    </controls:EditorControl>
                                </DataTemplate>
                            </dxn:NavBarControl.ItemTemplate>
                            <dxn:NavBarControl.View>
                                <dxn:ExplorerBarView Click="ExplorerBarView_Click"
                                                     GroupAdding="ExplorerBarView_GroupAdding" />
                            </dxn:NavBarControl.View>
                        </dxn:NavBarControl> 
                </lc:LayoutItem>
            </lc:LayoutGroup>
        </lc:LayoutGroup>
    </Grid>
</UserControl>

...

<UserControl ...>
    <AdornerDecorator>
        <lc:LayoutControl Orientation="Horizontal">
            <lc:LayoutGroup Orientation="Vertical"> 

                <controls:EditorControl IsInEditMode="{Binding IsInEditMode}">
                    <controls:EditorControl.ViewContent>
                        <lc:LayoutItem Label="{lex:LocText CloseCut}">
                            <TextBlock Text="{Binding CloseCut}" />
                        </lc:LayoutItem>
                    </controls:EditorControl.ViewContent>
                    <controls:EditorControl.EditContent>
                        <lc:LayoutItem Label="{lex:LocText CloseCut}" HorizontalContentAlignment="Right">
                            <dxe:TextEdit Text="{Binding CloseCut}" Width="200"/>
                        </lc:LayoutItem>
                    </controls:EditorControl.EditContent>
                </controls:EditorControl>

                <controls:EditorControl IsInEditMode="{Binding IsInEditMode}">
                    <controls:EditorControl.ViewContent>
                        <lc:LayoutItem Label="{lex:LocText PressureSetpoint}">
                            <TextBlock Text="{Binding PressureSetpoint}"/>
                        </lc:LayoutItem>
                    </controls:EditorControl.ViewContent>
                    <controls:EditorControl.EditContent>
                        <lc:LayoutItem Label="{lex:LocText PressureSetpoint}" HorizontalContentAlignment="Right">
                            <dxe:TextEdit Text="{Binding PressureSetpoint}" Width="200"/>
                        </lc:LayoutItem>
                    </controls:EditorControl.EditContent>
                </controls:EditorControl>

                <controls:EditorControl IsInEditMode="{Binding IsInEditMode}">
                    <controls:EditorControl.ViewContent>
                        <lc:LayoutItem Label="{lex:LocText SpinningBand}">
                            <TextBlock Text="{Binding SpinningBand, Converter={StaticResource BooleanOnOffConverter}}" Width="200" />
                        </lc:LayoutItem>
                    </controls:EditorControl.ViewContent>
                    <controls:EditorControl.EditContent>
                        <lc:LayoutItem Label="{lex:LocText SpinningBand}" HorizontalContentAlignment="Right">
                            <dxe:ToggleSwitchEdit IsChecked="{Binding SpinningBand}" IsEnabled="True" ContentPlacement="Both"
                                                  CheckedStateContent="{lex:LocText ON}" 
                                                  UncheckedStateContent="{lex:LocText OFF}"/>
                        </lc:LayoutItem>
                    </controls:EditorControl.EditContent>
                </controls:EditorControl>

                ...

            </lc:LayoutGroup>
        </lc:LayoutControl>
    </AdornerDecorator>
</UserControl>

我知道有太多的 xaml 代码我试图缩短它并只显示重要部分。

如果我不使用自定义控件并且只有 2 个列表框用于选择 runtype 和 runco​​nfigs 并查看详细信息,那么它工作得很快。

我计算了在此过程中创建的控件数量。如果一个运行类型有 4 个属性和 5 个运行配置并且每个运行配置有 8 个属性,那么我创建了 88 个自定义控件:

8 个自定义控件 => 4 个属性(4 个用于编辑模式,4 个用于查看模式) 80 个自定义控件 => 5 x 8 个 runco​​nfig 属性(40 个控件用于编辑模式,40 个用于查看模式)

这是降低性能的问题,但我认为 WPF 可以毫无问题地做到这一点,甚至更多的控件。我做错了什么,我需要在控件上设置一些设置吗?

谢谢你的帮助。

标签: c#wpf

解决方案


推荐阅读