Reputation: 135
I am using WPF MVVM. I have the following code:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView ItemsSource="{Binding ItemCollection}" Height="160" Width="810">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="500" Height ="150" ItemWidth="100" ItemHeight="30"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked}">
<TextBlock Text="{Binding Label}"/>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
The code shows that each CheckBox
has a width of 100. Each line in the WrapPanel
can contain at most 5 CheckBox
es due to its size and the ItemWidth
(500 / 100).
I have multiple CheckBox
with different widths.
I do not want to set the ItemWidth
explicitly to 280, because most of the items are smaller. Instead, I want each CheckBox
to take up the space of a multiple of 100 that can display all of its content. If the current line in the WrapPanel
does not have enough space, then move it to next line.
For the sample widths above I expect this.
How can I achieve that?
Upvotes: 0
Views: 804
Reputation: 22089
The ItemWidth
explicitly defines the width for all items.
A Double that represents the uniform width of all items that are contained within the WrapPanel. The default value is NaN.
Do not set an ItemWidth
. Then each item occupies its individual size.
A child element of a WrapPanel may have its width property set explicitly. ItemWidth specifies the size of the layout partition that is reserved by the WrapPanel for the child element. As a result, ItemWidth takes precedence over an element's own width.
Now, if you do not explicitly define the widths of your items, they are sized to fit their content, but do not align with a multiple of 100
. Scaling items up to a multiple of a defined size automatically is not supported in WrapPanel
.
If you want to enable this kind of dynamic sizing, you will have to create a custom wrap panel or you can write a custom behavior. I show you an example of the latter, as it is reusable and more flexible. I use the Microsoft.Xaml.Behaviors.Wpf
NuGet package that contains base classes for that.
public class AlignWidthBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty AlignmentProperty = DependencyProperty.Register(
nameof(Alignment), typeof(double), typeof(AlignWidthBehavior), new PropertyMetadata(double.NaN));
public double Alignment
{
get => (double)GetValue(AlignmentProperty);
set => SetValue(AlignmentProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.LayoutUpdated += OnLayoutUpdated;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.LayoutUpdated -= OnLayoutUpdated;
}
private void OnLayoutUpdated(object sender, EventArgs e)
{
var size = AssociatedObject.ActualWidth;
var alignment = Alignment;
var isAligned = size % alignment < 10E-12;
if (!double.IsNaN(alignment) && !isAligned)
AssociatedObject.Width = Math.Ceiling(size / alignment) * alignment;
}
}
This behavior is triggered, when the layout of an item changes. It then checks, if the width of the associated item is aligned with the value given by the Alignment
property and adapts it if not.
<DataTemplate>
<CheckBox IsChecked="{Binding Checked}">
<b:Interaction.Behaviors>
<local:AlignWidthBehavior Alignment="100"/>
</b:Interaction.Behaviors>
<TextBlock Text="{Binding Label}"/>
</CheckBox>
</DataTemplate>
You have to attach the behavior in XAML as shown above and set the Alignment
to your desired value and do not forget to remove the ItemWidth
property from WrapPanel
.
Upvotes: 2