wpfcomboboxscrollbarcollectionview

WPF grouped combobox scrolls groups not items


We are attempting to implement simple grouping of items within a ComboBox using the ICollectionView. The grouping and sorting being used in the CollectionView works correctly. But the popup items list created by the ComboBox does not function as intended because the scroll bar scrolls the groups not the items.

eg: If there were 2 groups of 25 items, then the scroll bar would have two positions/points to scroll through rather than the desired 50.

Can someone explain why the scroll bar scrolls the groups not the items within the groups, and how we might change this behavior?

Viewmodel setting up the ICollectionView:

public ViewModel()
{

CurrenciesView.Filter = CurrencyFilter;
CurrenciesView.SortDescriptions.Add(new SortDescription("MajorCurrency", ListSortDirection.Descending));
CurrenciesView.SortDescriptions.Add(new SortDescription("Code", ListSortDirection.Ascending));
CurrenciesView.GroupDescriptions.Add(new PropertyGroupDescription("MajorCurrency", new CurrencyGroupConverter()));


public ICollectionView CurrenciesView { get { return CollectionViewSource.GetDefaultView(currencies); } }
    private ObservableCollection<Currency> currencies = new ObservableCollection<Currency>();
public ObservableCollection<Currency> Currencies
{
    get { return this.currencies; }
    set
    {
        if (this.currencies != value)
        {
            this.currencies = value;
            this.PropertyChanged(this, new PropertyChangedEventArgs("Currencies"));
        }
    }
}

XAML UserControl hosting the ComboBox

<UserControl x:Class="FilterView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:vm="clr-namespace:ViewModels">
<UserControl.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="CurrencyItem">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Code}" FontWeight="ExtraBold"/>
                <TextBlock Text="{Binding Description}" Margin="10,0,0,0"/>
            </StackPanel>
        </DataTemplate>
        <Style TargetType="ComboBox">
            <Setter Property="MinWidth" Value="100"/>
            <Setter Property="Margin" Value="0,5,0,5"/>
        </Style>
        <DataTemplate x:Key="GroupHeader">
            <TextBlock Text="{Binding Name}" Padding="3"/>
        </DataTemplate>
    </ResourceDictionary>
</UserControl.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <Label Grid.Column="0" Grid.Row="0">Currency</Label>
    <ComboBox Grid.Column="1" Grid.Row="0" Name="Currency"
                        ItemsSource="{Binding Path=CurrenciesView, Mode=OneWay}"
                        ItemTemplate="{StaticResource CurrencyItem}"
                        SelectedValuePath="Code"
                        IsSynchronizedWithCurrentItem="True">
        <ComboBox.GroupStyle>
            <GroupStyle HeaderTemplate="{StaticResource GroupHeader}"/>
        </ComboBox.GroupStyle>
    </ComboBox>
</Grid>
</UserControl>

Scroll Position One

Scroll Position Two


Solution

  • Fixed by setting CanContentScroll = false on PART_Popup template for the child ScrollViewer.

    <UserControl x:Class="FilterView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:ViewModels">
      <UserControl.Resources>
        <ResourceDictionary>
          <DataTemplate x:Key="CurrencyItem">
            <StackPanel Orientation="Horizontal">
              <TextBlock Text="{Binding Code}" FontWeight="ExtraBold"/>
              <TextBlock Text="{Binding Description}" Margin="10,0,0,0"/>
            </StackPanel>
          </DataTemplate>
          <Style TargetType="ComboBox">
            <Setter Property="MinWidth" Value="100"/>
            <Setter Property="Margin" Value="0,5,0,5"/>
          </Style>
          <DataTemplate x:Key="GroupHeader">
            <TextBlock Text="{Binding Name}" Padding="3"/>
          </DataTemplate>
        </ResourceDictionary>
      </UserControl.Resources>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"/>
          <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" Grid.Row="0">Currency</Label>
        <ComboBox Grid.Column="1" Grid.Row="0" Name="Currency"
                  ItemsSource="{Binding Path=CurrenciesView, Mode=OneWay}"
                  ItemTemplate="{StaticResource CurrencyItem}"
                  SelectedValuePath="Code"
                  IsSynchronizedWithCurrentItem="True"
                  **ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  ScrollViewer.VerticalScrollBarVisibility="Auto"
                  ScrollViewer.CanContentScroll="True"**>
          <ComboBox.GroupStyle>
            <GroupStyle HeaderTemplate="{StaticResource GroupHeader}"/>
          </ComboBox.GroupStyle>
        </ComboBox>
      </Grid>
    </UserControl>
    

    Or in ComboBox derived control:

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            Popup popup = GetTemplateChild("PART_Popup") as Popup;
            if (popup != null)
            {
                ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(popup.Child);
                if (scrollViewer != null)
                {
                    scrollViewer.CanContentScroll = false;
                }
            }
        }