I spent more time attempting this than I probably should have, and couldn't get it to work. I understand what's going on here, but in pure XAML, I'm having trouble figuring out how to solve the issue. I think I see how to solve the problem, but it involves a converter.
Warning: Things are going to get complicated as I explain my conclusions.
The underlying problem comes from the fact that the Width of the controls stretch to the Width of their container. When virtualization is enabled, the Width will not change. In the underlying ScrollViewer
inside of ListBox
, the ViewportWidth
property corresponds to the Width you see. When another control stretches out further (you select it), the ViewportWidth
is still the same, but the ExtentWidth
shows the full width. Binding the width of all controls to that of the ExtentWidth
should work...
But it doesn't. I set the FontSize to 100 for quicker testing in my case. When an item is selected, ExtentWidth="4109.13
. Going down the tree to your ControlTemplate's Border
, I see ActualWidth="4107.13"
. Why the 2 pixel difference? ListBoxItem contains a Border with 2 Pixel padding, causing the ContentPresenter to render slightly smaller.
I added the following Style
with help from here to allow me to directly access the ExtentWidth:
<Style x:Key="{x:Type ListBox}" TargetType="ListBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
Name="Border"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<ScrollViewer
Name="scrollViewer"
Margin="0"
Focusable="false">
<StackPanel IsItemsHost="True" />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Border" Property="Background"
Value="White" />
<Setter TargetName="Border" Property="BorderBrush"
Value="Black" />
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note I added a name to ScrollViewer
for this purpose.
Then, I attempted to bind the Width of your Border to the ExtentWidth:
Width="{Binding ElementName=scrollViewer, Path=ExtentWidth}"
However, because of that 2 pixel padding, the controls will resize in an infinite loop, with the padding adding 2 pixels to the ExtentWidth
, which resizes the Border width, which adds 2 more pixels to the ExtentWidth
, etc. until you delete the code and refresh.
If you added a Converter that subtracted 2 from the ExtentWidth, I think this might work. However, when the scroll bar does not exist (you have not selected anything), ExtentWidth="0"
. Thus, binding to MinWidth
instead of Width
may work better so the items appear correctly when no scroll bar is visible:
MinWidth="{Binding ElementName=scrollViewer, Path=ExtentWidth, Converter={StaticResource PaddingSubtractor}}"
A better solution would be if you could directly databind the MinWidth
of the ListBoxItem
itself. You could bind directly to ExtentWidth, and no converter would be necessary. However I have no idea how to get access to that item.
Edit: For organization sake, here's the clip required to do that. Makes everything else unnecessary:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" />
</Style>
Best Answer
WrapPanel
WPF Tutorial