【问题标题】:WPF - UserControl constructing performance (very poor)WPF - UserControl 构造性能(很差)
【发布时间】:2022-03-07 01:24:09
【问题描述】:

我手头有一个更复杂的代码,但为了问这个问题,我带来了一个更简单的代码示例。

我的应用程序将以特定字体遍历所有字形(预计 500 到 5000 个字形)。每个字形都应该有一个特定的自定义视觉效果,以及一些功能。为此,我认为实现这一目标的最佳方法是为每个字形创建一个 UserControl

在我所做的检查中,随着我的UserControl 变得越来越复杂,构建它需要更多时间。即使是简单地添加Style 也会对性能产生有意义的影响。

我在此示例中尝试的是在 ListBox 2000 字形中显示。为了注意性能差异,我放了 2 个 ListBoxes - First 绑定到一个简单的 ObservableCollection 字符串。 Second 绑定到我的 UserControl 的 ObservableCollection

这是我的MainWindowxaml

    <Grid Background="WhiteSmoke">
      <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
      </Grid.RowDefinitions>
      <ListBox Margin="10" ItemsSource="{Binding MyCollection}"></ListBox>
      <ListBox Margin="10" Grid.Row="1" ItemsSource="{Binding UCCollection}"
             VirtualizingPanel.IsVirtualizing="True"
             VirtualizingPanel.VirtualizationMode="Recycling"></ListBox>
    </Grid>

后面的代码上,我有 2 个 ObservableCollection,如上所述:

public static ObservableCollection<string> MyCollection { get; set; } = new ObservableCollection<string>();
public static ObservableCollection<MyUserControl> UCCollection { get; set; } = new ObservableCollection<MyUserControl>();

对于string第一个列表,我添加如下:

    for (int i = 0; i < 2000; i++)
    {
       string glyph = ((char)(i + 33)).ToString();
       string hex = "U+" + i.ToString("X4");
       MyCollection.Add($"Index {i}, Hex {hex}:  {glyph}");
    }

对于MyUserControl第二个列表,我添加如下:

    for (int i = 0; i < 2000; i++)
    {
       UCCollection.Add(new MyUserControl(i + 33));
    }

MyUserControl xaml 看起来像这样:

<Border Background="Black" BorderBrush="Orange" BorderThickness="2" MinWidth="80" MinHeight="80">
    <Grid Margin="5">
      <Grid.RowDefinitions>
        <RowDefinition Height="2*"></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
      </Grid.RowDefinitions>
      <TextBlock HorizontalAlignment="Center" Foreground="White" FontSize="40" Text="{Binding Glyph}"/>
      <TextBlock HorizontalAlignment="Center" Foreground="OrangeRed" Text="{Binding Index}" Grid.Row="1"/>
      <TextBlock HorizontalAlignment="Center" Foreground="White" Text="{Binding Hex}" Grid.Row="2"/>
    </Grid>
  </Border>

以及MyUserControl代码背后

    public partial class MyUserControl : UserControl
    {
        private int OrgIndex { get; set; } = 0;
        public string Hex => "U+" + OrgIndex.ToString("X4");
        public string Index => OrgIndex.ToString();
        public string Glyph => ((char)OrgIndex).ToString();

        public MyUserControl(int index)
        {
            InitializeComponent();
            OrgIndex = index;
        }
    }

为了跟踪性能问题,我使用了Stopwatch。将 2000 个字符串项添加到第一个列表需要 1ms。将 2000 个用户控件添加到第二个列表需要 ~1100 毫秒。而且它只是一个简单的 UserControl,当我向它添加一些东西时,它会花费更多时间并且性能会变得更差。例如,如果我只是将这个 Style 添加到 Border 时间会上升到 ~1900ms

     <Style TargetType="{x:Type Border}" x:Key="BorderMouseOver">
      <Setter Property="Background" Value="Black" />
      <Setter Property="BorderBrush" Value="Orange"/>
      <Setter Property="MinWidth" Value="80"/>
      <Setter Property="MinHeight" Value="80"/>
      <Setter Property="BorderThickness" Value="2" />
      <Style.Triggers>
        <DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" Value="True">
          <Setter Property="Background" Value="#FF2A3137" />
          <Setter Property="BorderBrush" Value="#FF739922"></Setter>
        </DataTrigger>
      </Style.Triggers>
    </Style>

我并不完全熟悉 WPF 解决方法,所以非常感谢您的帮助。这是完全错误的方法吗?我已经阅读了一些关于它的帖子,但无法通过以下方式阅读:herehereherehere 等等。

本示例完整工程可以下载Here

【问题讨论】:

  • 如果用户必须滚动才能看到其中任何一个,那么您应该使用 mvvm 方法和数据模板来实例化这些用户控件。然后你可以虚拟化并且只构建那些可见的。
  • @Andy - 感谢您的回复。在真实的应用程序中,这些用户控件假设在WrapPanel 中,但用户确实需要滚动。正如我所提到的:我并不完全熟悉 WPF 解决方法,如果您能进一步指导这个示例如何采用 "mvvm 方法和数据模板,这将非常有帮助"。找不到有用答案的帖子。
  • 您可以只使用 itemspaneltemplate 来为每个项目生成用户控件。绑定一个可观察的集合或项目视图模型列表。项目视图模型通过绑定提供了用户控件所需的所有信息。

标签: c# wpf performance user-controls


【解决方案1】:

对于您的情况,您可以像这样在用户控件中创建 DependencyProperty(仅作为示例)。

    #region DP

    public int OrgIndex
    {
        get => (int)GetValue(OrgIndexProperty);
        set => SetValue(OrgIndexProperty, value);
    }

    public static readonly DependencyProperty OrgIndexProperty = DependencyProperty.Register(
        nameof(OrgIndex), typeof(int), typeof(MyUserControl));

    #endregion

并且其他属性可以设置为 DP 或在 init 或加载事件中处理... 然后在列表框中使用您的用户控件作为项目模板...

    <ListBox
        Grid.Row="1"
        Margin="10"
        ItemsSource="{Binding IntCollection}"
        VirtualizingPanel.IsVirtualizing="True"
        VirtualizingPanel.VirtualizationMode="Recycling">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <local:MyUserControl OrgIndex="{Binding Path=.}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

然后在你的虚拟机中,创建简单的类型列表

    public static ObservableCollection<int> IntCollection { get; set; } = new ObservableCollection<int>();

        for (int i = 0; i < rounds; i++)
        {
            IntCollection.Add(i + 33);
        }

这比创建用户控件列表要快得多,并且您可以将用户控件及其样式设置为列表视图项

【讨论】:

  • 非常感谢您的帮助!它看起来很棒,也很简单。我已经设法植入它,并且ListBox 显示在MainWindow 中,并带有UserControl 的项目。但是用户控件的每个项目都不会针对IntCollection 中的每个int 值进行更新。看起来有些东西是OrgIndex 的绑定无法正常工作:&lt;local:MyUserControl OrgIndex="{Binding Path=.}"/&gt;
  • @YeshurunKubi 是的,如果列表视图项具有一些复杂的样式或结构等,我就是这样设置它的样式……是的,数据绑定部分可能有些错误。我还没测试...
  • 为了使它成为一个可预期的答案,我希望你能测试它,以获得一个正确的绑定方法。否则我将不得不适应 Andy 的 MVVM 方式 - 这可行,但比您的解决方案复杂得多。
【解决方案2】:

目前解决此问题的方法是遵循@Andy 建议使用MVVM 方法。这对我来说有点复杂,必须做一些学习。

我做了什么:

  1. 取消了 UserControl
  2. 创建了一个类GlyphModel。这代表了每个字形及其信息。
  3. 创建了一个类GlyphViewModel。这将构建一个 ObservableCollection 列表。
  4. 将 GlyphModel 的设计设置为 ListBox.ItemTemplate

所以现在 GlyphModel 类,植入INotifyPropertyChanged,看起来像这样:

public GlyphModel(int index)
        {
            _OriginalIndex = index;
        }

        #region Private Members

        private int _OriginalIndex;

        #endregion Private Members

        public int OriginalIndex
        {
            get { return _OriginalIndex; }
            set
            {
                _OriginalIndex = value;
                OnPropertyChanged("OriginalIndex");
            }
        }

        public string Hex => "U+" + OriginalIndex.ToString("X4");

        public string Index => OriginalIndex.ToString();

        public string Glyph => ((char)OriginalIndex).ToString();

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion INotifyPropertyChanged Members

GlyphViewModel 类看起来像这样:

public static ObservableCollection<GlyphModel> GlyphModelCollection { get; set; } = new ObservableCollection<GlyphModel>();

public static ObservableCollection<string> StringCollection { get; set; } = new ObservableCollection<string>();

        public GlyphViewModel(int rounds)
        {
            for (int i = 33; i < rounds; i++)
            {
                GlyphModel glyphModel = new GlyphModel(i);
                GlyphModelCollection.Add(glyphModel);
                StringCollection.Add($"Index {glyphModel.Index}, Hex {glyphModel.Hex}:  {glyphModel.Glyph}");
            }
        }

MainWindow XML 中,我使用DataTemplate 定义了列表:

<ListBox.ItemTemplate>
          <DataTemplate>
            <Border Style="{StaticResource BorderMouseOver}">
              <Grid>
                <Grid.RowDefinitions>
                  <RowDefinition Height="2*"></RowDefinition>
                  <RowDefinition></RowDefinition>
                  <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <TextBlock HorizontalAlignment="Center" Foreground="White" FontSize="40" Text="{Binding Glyph}" />
                <TextBlock HorizontalAlignment="Center" Foreground="OrangeRed" Text="{Binding Index}" Grid.Row="1" />
                <TextBlock HorizontalAlignment="Center" Foreground="White" Text="{Binding Hex}" Grid.Row="2" />
              </Grid>
            </Border>
          </DataTemplate>
        </ListBox.ItemTemplate>

最后为 MainWindow 设置 DataContext

 DataContext = new GlyphViewModel(2000);

它确实有效,即使对于 4000 个字形也能非常快地工作。希望这是这样做的正确方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-25
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    相关资源
    最近更新 更多