« Stretching ListBoxItem Content to fit a Listbox | Main | Range converter for WPF »

Give me the maximum and no more ListBox!

WPF ListBoxItems are sized to fit their content regardless of what that does to the ListBox visually. If a ListBox has really wide items, while virtualized, the ListBoxItems, as they are created and destroyed may cause the horizontal scrollbar to appear and disappear randomly. I find that annoying from a user experience perspective. (It's one thing that makes a Virtualizing ListBox frustrating).

One simple solution is to hide the horizontal scrollbar completely:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
  <Page.Resources>
    <Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListBoxItem}">
      <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    </Style>
  </Page.Resources>
  <Grid>
    <ListBox Width="80" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
      ItemContainerStyle="{StaticResource StretchedContainerStyle}">
      <ListBox.Items>
        <ListBoxItem>
          <Grid>
            <TextBlock>My listbox item 1</TextBlock>
          </Grid>
        </ListBoxItem>
        <ListBoxItem>
          <Grid Background="Silver">
            <TextBlock>My listbox item 1</TextBlock>
          </Grid>
        </ListBoxItem>
      </ListBox.Items>
    </ListBox>
  </Grid>
</Page>

The attached property ScrollViewer.HorizontalScrollBarVisibility does the job nicely. Except, now the content that is beyond the margin (the right margin) is clipped and not visible at all! A tooltip set for each item could resolve the general clipping problem. But, it still seems like half a solution -- partly because the content may not look clipped, thereby misleading the user.

The other "straightforward" technique is to cheat a bit. What I wanted to happen was for the text content to show ellipsis when clipped -- not just get clipped. Here's what's happening in the sample code below (paste into XamlPad to experiment)...

The MaxWidth property of the ListBoxItem is being set to the ListBox's ScrollViewer's ViewPort width. Through a reasonably interesting Binding:

Find an parent (FindAncestor) that is a ScrollViewer (AncestorType={x:Type ScrollViewer}) and bind to the ViewportWidth property.

I added a general TextBlock style that includes the TextTrimming that I wanted as well so it would apply to all TextBlocks.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
  <Page.Resources>
    <Style TargetType="{x:Type TextBlock}">
      <Setter Property="TextBlock.TextTrimming" Value="CharacterEllipsis" />
      <Setter Property="TextBlock.TextWrapping" Value="NoWrap" />
    </Style>
    <Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListBoxItem}">
      <Setter Property="HorizontalContentAlignment" Value="Stretch" />
      <Setter Property="MaxWidth"
        Value="{Binding RelativeSource={RelativeSource FindAncestor, 
                AncestorType={x:Type ScrollViewer}}, Path=ViewportWidth}" />
    </Style>
  </Page.Resources>
  <Grid>
    <ListBox Width="80" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
      ItemContainerStyle="{StaticResource StretchedContainerStyle}">
      <ListBox.Items>
        <ListBoxItem>
          <Grid>
            <TextBlock>My listbox item 1</TextBlock>
          </Grid>
        </ListBoxItem>
        <ListBoxItem>
          <Grid Background="Silver">
            <TextBlock>My listbox item 123 </TextBlock>
          </Grid>
        </ListBoxItem>
      </ListBox.Items>
    </ListBox>
  </Grid>
</Page>

 

Technorati tags: , ,
Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).