简体   繁体   中英

WPF - UserControls very slow

I am designing something similar a PropertyGrid where I want to show properties of objects. For special reasons I am not going to use the PropertyGrid but create my own.

For each property I have created a custom usercontrol. Now to my horror the performance is very bad. If I have something like 100 properties it takes 500 milliseconds to show them in a StackPanel/Listbox.

I did an experiment where I add 200 default UserControls to a StackPanel. It took about 50 milliseconds. Still a very high number I think.

Should I not use usercontrols for such a purpose? It seems very object-oriented to do it this way and I can not really see another solution.

However I can see that PropertyGrid and TreeView performs good, so what have they done and what should I do?

Edit:

        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        using (var suspend = Dispatcher.DisableProcessing())
        {
            // Add all children here
            for (int i = 0; i < 200; i++)
            {
                this.propertiesStackPanel.Children.Add(new System.Windows.Controls.Button(){Content = "Testing"});
            }
        }
        stopwatch.Stop();

This still takes about 50 milliseconds. If I change to my own custom usercontrol it is much higher. I might add that scrolling is not a problem.

Edit2:

OK. It has nothing to do with stackpanel. I have found out that it is because creating UserControls is a very expensive operation. If you have any other idea of what to do I would gladly hear them :)

Edit3: Nothing is going on in the constructor of my usercontrol other than InitializeComponent method. Here is an example of a usercontrol I am adding.

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="PropertyBox.GroupUC"
x:Name="UserControl"
d:DesignWidth="640" d:DesignHeight="480" Background="#FF32B595" BorderThickness="0">

<Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="20px"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Border x:Name="border" BorderThickness="0,1" Grid.Column="1">
        <TextBox Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Right" BorderThickness="0" Padding="0" Visibility="Hidden"/>
    </Border>
    <Label x:Name="groupNameLabel" HorizontalAlignment="Left" Margin="5,0,0,0" VerticalAlignment="Center" Content="Label" Padding="0" Grid.Column="1"/>
    <Button x:Name="expandButton" HorizontalAlignment="Left" VerticalAlignment="Center" Width="12" Height="12" Content="" Click="ExpandButtonClick" Margin="4,0,0,0" Padding="0" Grid.ColumnSpan="2" d:IsHidden="True"/>
    <Image x:Name="expandButton2" Visibility="Hidden"  Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None"/>
</Grid>

My suspicion is that you're triggering many layout updates while adding your hundreds of children.

If that is the bottleneck, you may want to consider doing:

using(var suspend = Dispatcher.DisableProcessing())
{
       // Add all children here
}

This will cause the dispatcher to stop processing messages while you add your controls, and do the entire layout and render in one pass at the end.

If you find that creating many UserControl s is too expensive an operation, what about a solution where you don't need to create UserControl s at all?

I'm by no means a WPF expert, but what about using data binding a ListBox or ListView against a list of objects each of which represents one property of the object under inspection? Instead of a StackPanel , you'd have a Listbox or ListView ; instead of UserControl s, you would define one or more DataTemplate s.


Basic code example:

Define a type that represents an entry in your custom property grid; for example:

public namespace YourApplication
{
    public class Prop
    {
        public string Name { get; set; }
        public Type Type { get; set; }
    }

    public class Props : List<Prop> { }
}

Then, in XAML (I can't currently check this for 100% correctness, but hopefully you get the idea):

<Window ... xmlns:local="clr-namespace:YourApplication">
  <Window.Resources>
    <!-- this Prop list serves only as a demonstration in the XAML designer -->
    <local:Props x:Key="somePropsForDemonstration">
      <local:Prop Name="Name" Type="System.String" />
      <local:Prop Name="Age" Type="System.TimeSpan" />
    </local:Props>
  </Window.Resources>
  <!-- here's your StackPanel replacement; bind it to a real items source -->
  <ListView ItemsSource={StaticResource somePropsForDemonstration}>
    <ListView.ItemsTemplate>
      <!-- this is a UI template that defines how a Prop object gets displayed;
           together with the Prop type, it replaces your UserControl -->
      <DataTemplate DataType="local:Prop">
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="{Binding Name}" FontWeight="Bold" />
          <TextBlock Text=": " />
          <TextBlock Text="{Binding Type}" FontStyle="Italic" />
        </StackPanel>
      </DataTemplate>
    </ListView.ItemsTemplate>
  </ListView>
</Window>

I seem to remember that there are ways to have "polymorphic" data templates; ie. depending on a property's type, you could use a different sub-class of Prop and also use a different data template per item type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM