0

I have ~ 800 items in my ItemsSource combobox, when I first open the ComboBox it takes A LONG time (1-3 seconds) to show the popup, as if its generating it to be shown, however, if I disable my style for ComboBox it shows almost instantly without any slowdown. I've tried everything I read online (VirtualizingStack panels everywhere, disable stylus support, touch support etc..) but I have no idea what is causing this slowdown. Below is my XAML code:

<Style x:Key="FocusVisual">
  <Setter Property="Control.Template">
    <Setter.Value>
      <ControlTemplate>
        <Rectangle StrokeDashArray="1 2" StrokeThickness="1" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" Margin="2"/>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
<Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
  <Setter Property="OverridesDefaultStyle" Value="true"/>
  <Setter Property="IsTabStop" Value="false"/>
  <Setter Property="Focusable" Value="false"/>
  <Setter Property="ClickMode" Value="Press"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type ToggleButton}">
        <Border x:Name="templateRoot" SnapsToDevicePixels="true" Background="White" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="#e0e0e0">
          <Border x:Name="splitBorder" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" SnapsToDevicePixels="true" Margin="0" HorizontalAlignment="Right" BorderThickness="1" BorderBrush="Transparent">
            <Path x:Name="arrow" VerticalAlignment="Center" Margin="0" HorizontalAlignment="Center" Fill="Black" Data="F1 M 0,0 L 2.667,2.66665 L 5.3334,0 L 5.3334,-1.78168 L 2.6667,0.88501 L0,-1.78168 L0,0 Z"/>
          </Border></Border>
          <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
              <Setter Property="Fill" TargetName="arrow" Value="#3498db">
            </Setter>
          </Trigger>
            <Trigger Property="IsPressed" Value="True">
              <Setter Property="BorderBrush" TargetName="templateRoot" Value="#3498db">
            </Setter>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
    <ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
      <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition
                        MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0" />
        </Grid.ColumnDefinitions>
        <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling">
          <Popup x:Name="PART_Popup" AllowsTransparency="false" Grid.ColumnSpan="2"
                       IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                       Margin="1" MaxHeight="160" MinHeight="0" Placement="Bottom" MinWidth="{TemplateBinding ActualWidth}" >
            <Border x:Name="dropDownBorder" BorderBrush="#e0e0e0" BorderThickness="1" Background="White">
              <ScrollViewer x:Name="DropDownScrollViewer" >
                <Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
                  <Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top"
                                        Width="0">
                    <Rectangle x:Name="opaqueRect"
                                               Fill="{Binding Background, ElementName=dropDownBorder}"
                                               Height="{Binding ActualHeight, ElementName=dropDownBorder}"
                                               Width="{Binding ActualWidth, ElementName=dropDownBorder}" />
                  </Canvas>
                  <VirtualizingStackPanel  IsItemsHost="True" Orientation="Vertical" VirtualizationMode="Recycling" IsVirtualizing="True" x:Name="ItemsPresenter"

                                                KeyboardNavigation.DirectionalNavigation="Contained"
                                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                </VirtualizingStackPanel>
              </Grid>
            </ScrollViewer>
          </Border>
        </Popup>
      </VirtualizingStackPanel>
        <ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}"
                              Background="{TemplateBinding Background}" Grid.ColumnSpan="2"
                              IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                              Style="{StaticResource ComboBoxToggleButton}" />
        <ContentPresenter x:Name="contentPresenter"
                                  ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
                                  ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                                  Content="{TemplateBinding SelectionBoxItem}"
                                  ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  IsHitTestVisible="false" Margin="{TemplateBinding Padding}"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
      </Grid>
      <ControlTemplate.Triggers>
        <Trigger Property="HasItems" Value="false">
          <Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
        </Trigger>
      </ControlTemplate.Triggers>
    </ControlTemplate>
  </Style>
  <Style TargetType="{x:Type ComboBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
    <Setter Property="Background" Value="White"/>
    <Setter Property="BorderBrush" Value="#e0e0e0"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="ScrollViewer.PanningMode" Value="None"/>
    <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
    <Setter Property="OverridesDefaultStyle" Value="True">
  </Setter>
    <Setter Property="Template" Value="{StaticResource ComboBoxTemplate}"/>
  </Style>
  <Style TargetType="{x:Type ComboBoxItem}">
    <Setter Property="OverridesDefaultStyle" Value="True">
  </Setter>
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="Width" Value="Auto">
  </Setter>
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="ComboBoxItem">
          <Border
                            Name="Border"
                            Padding="2"
                            SnapsToDevicePixels="true">
            <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling">
              <ContentPresenter />
            </VirtualizingStackPanel>
          </Border>
          <ControlTemplate.Triggers>
            <Trigger Property="IsHighlighted" Value="true">
              <Setter TargetName="Border" Property="Background"
                                        Value="#f0f0f0" />
            </Trigger>
            <Trigger Property="IsEnabled" Value="false">
              <Setter Property="Foreground" Value="Gray" />
            </Trigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

And here is how I add it to my form..

<ComboBox x:Name="GameCombobox"  Margin="20,0,18,0" Height="25" ItemsSource="{Binding Games, Mode=OneWay, Source={x:Static ui:Ui.Instance}}" SelectedValue="Name" IsEditable="False">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <VirtualizingStackPanel Orientation="Vertical" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" Height="45" Width="Auto">
                <Label FontFamily="Resources/Fonts/#Lato" FontSize="14px" Foreground="Black"   Content="{Binding Name}"/>
                <VirtualizingStackPanel Orientation="Horizontal" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizing="True" Margin="0 -5 0 0" >
                    <Path Margin="5 5 0 0" Fill="Crimson"  Data="M 25 12 C 11.667228 12 1.25 24.34375 1.25 24.34375 A 1.0001 1.0001 0 0 0 1.25 25.65625 C 1.25 25.65625 11.667228 38 25 38 C 38.332772 38 48.75 25.65625 48.75 25.65625 A 1.0001 1.0001 0 0 0 48.75 24.34375 C 48.75 24.34375 38.332772 12 25 12 z M 25 14 C 27.627272 14 30.141915 14.544587 32.46875 15.375 C 34.032931 17.140338 35 19.450427 35 22 C 35 27.535732 30.535732 32 25 32 C 19.464268 32 15 27.535732 15 22 C 15 19.45074 15.935707 17.139242 17.5 15.375 C 19.834652 14.538846 22.362198 14 25 14 z M 14.1875 16.84375 C 13.439134 18.407614 13 20.155051 13 22 C 13 28.616268 18.383732 34 25 34 C 31.616268 34 37 28.616268 37 22 C 37 20.163179 36.580282 18.404914 35.84375 16.84375 C 41.492764 19.714987 45.555865 23.87765 46.59375 25 C 44.969234 26.756721 35.970973 36 25 36 C 14.029027 36 5.0307657 26.756721 3.40625 25 C 4.4456392 23.876024 8.5256535 19.715345 14.1875 16.84375 z M 25 17 C 22.238576 17 20 19.238576 20 22 C 20 24.761424 22.238576 27 25 27 C 27.761424 27 30 24.761424 30 22 C 30 19.238576 27.761424 17 25 17 z">
                        <Path.RenderTransform>
                            <ScaleTransform ScaleY="0.3" ScaleX="0.3"/>
                        </Path.RenderTransform>
                    </Path>
                    <Label Margin="-30 0 0 0" FontFamily="Resources/Fonts/#Lato" FontSize="14px" Foreground="#959699"  Content="{Binding Players}"/>
                </VirtualizingStackPanel>
            </VirtualizingStackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling"></VirtualizingStackPanel>
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
</ComboBox>
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Rageline
  • 79
  • 7
  • 1
    So there's multiple things in there I'd question. Like why all the nested virtualization panels that appear really, really redundant? Or the nested Rectangle inside of a Canvas with bound height/width with it's parent panel canvas has explicity height/width=0, or what makes the rendertransforms necessary? Or the negative margins and stuff. Basically it looks like you're super overcomplicating parts that require measure/arrange passes multiple times over etc. Not to be negative but I'd refactor the hell out of that thing if I were you. – Chris W. Dec 02 '15 at 19:02
  • Rectangle-Canvas was inserted by VS, not by me.. – Rageline Dec 02 '15 at 19:08
  • 1
    If you have 800+ items in a combobox, you need to rethink your design. Think about it from a user perspective... would YOU want to scroll through 800 items? Maybe a treeview lazy-loaded would be a better approach. You could make the combo look/act like a tree and get a better design functionally and visually. – CoderForHire Dec 02 '15 at 19:52
  • 1
    [You need a ScrollViewer for the VirutalizingStackPanel to work correctly](http://stackoverflow.com/a/2784220/302677). As it is now, all 800 items are being loaded. – Rachel Dec 02 '15 at 21:01

1 Answers1

0

The slowdown is a result of your very "heavy" DataTemplate that is the ItemsTemplate of the ComboBox. Even if you get virtualization working, this heavy of a DataTemplate you may find to cause scrolling to be slow, because all these UI objects are going to get instantiated and rendered and then disposed each time items are realized and virtualized respectively. There are a couple of things you can do to make it much "lighter" (fewer and simpler visual objects) but still have the same appearance.

  1. The VirtualizingStackPanels inside the DataTemplate are completely unnecessary. If you want virtualization, you don't put these "everywhere", you put them somewhere very specific: ItemsPanelTemplate for the ItemsControl you are using (in this case a ComboBox)
  2. Use a simple TextBlock instead of Label (this may also eliminate the need for you negative margins)
  3. The very complicated Path drawing you have defined is getting instantiated and rendered anew for each item in your ComboBox. Instead of putting it inside the DataTemplate, create a single VisualBrush of that complicated Path as a static resource and use that as the Fill of something simple like a Rectangle. Since Brushes are "freezable" they can be re-used, and the Path will only be instantiated and rendered one time. My guess is that this Path is likely the culprit of the vast majority of the poor performance you are seeing (profiling in VisualStudio would tell you exactly what things are taking so long to layout/render). Rendering it only one time instead of 800 times, should help a lot.

Your VisualBrush (defined somewhere as a StaticResource):

<VisualBrush x:Key="ComboBoxItemIconBrush">
    <VisualBrush.Visual>
        <Path Fill="Crimson" Data="M 25 12 C 11.667228 12 1.25 24.34375 1.25 24.34375 A 1.0001 1.0001 0 0 0 1.25 25.65625 C 1.25 25.65625 11.667228 38 25 38 C 38.332772 38 48.75 25.65625 48.75 25.65625 A 1.0001 1.0001 0 0 0 48.75 24.34375 C 48.75 24.34375 38.332772 12 25 12 z M 25 14 C 27.627272 14 30.141915 14.544587 32.46875 15.375 C 34.032931 17.140338 35 19.450427 35 22 C 35 27.535732 30.535732 32 25 32 C 19.464268 32 15 27.535732 15 22 C 15 19.45074 15.935707 17.139242 17.5 15.375 C 19.834652 14.538846 22.362198 14 25 14 z M 14.1875 16.84375 C 13.439134 18.407614 13 20.155051 13 22 C 13 28.616268 18.383732 34 25 34 C 31.616268 34 37 28.616268 37 22 C 37 20.163179 36.580282 18.404914 35.84375 16.84375 C 41.492764 19.714987 45.555865 23.87765 46.59375 25 C 44.969234 26.756721 35.970973 36 25 36 C 14.029027 36 5.0307657 26.756721 3.40625 25 C 4.4456392 23.876024 8.5256535 19.715345 14.1875 16.84375 z M 25 17 C 22.238576 17 20 19.238576 20 22 C 20 24.761424 22.238576 27 25 27 C 27.761424 27 30 24.761424 30 22 C 30 19.238576 27.761424 17 25 17 z"/>
    </VisualBrush.Visual>
<VisualBrush>

And then your ComboBox:

<ComboBox x:Name="GameCombobox" Margin="20,0,18,0" Height="25" ItemsSource="{Binding Games, Mode=OneWay, Source={x:Static ui:Ui.Instance}}" SelectedValue="Name" IsEditable="False">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock FontFamily="Resources/Fonts/#Lato" FontSize="14px" Foreground="Black" Text="{Binding Name}"/>
                <StackPanel Orientation="Horizontal">
                    <Rectangle Fill="{StaticResource ComboBoxItemIconBrush}" Width="45" Height="45"/>
                    <TextBlock FontFamily="Resources/Fonts/#Lato" FontSize="14px" Foreground="#959699" Text="{Binding Players}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling"></VirtualizingStackPanel>
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
</ComboBox>

You may need to play with the some of the margins, and also the size of VisualBrush's ViewBox and ViewPort and the Width/Height of the Rectangle, to make this look exactly the same as your previous template. I don't have an XAML renderer handy to see exactly what you were going for.

Dave M
  • 2,863
  • 1
  • 22
  • 17