mortalapeman
mortalapeman

Reputation: 1425

WPF: Synchronize width of all items in ItemsControl

Is it possible to adjust the width of all TextBlocks in the WrapPanel to the size of the largest TextBlock within the WrapPanel? The end result should be that the width of the control containing "Some data" has the same width as the control containing "Even more data than before." I have attached to my initial code as a starting point. I have used strings as an example, but the collection data and template could be anything, so I can't rely on the length of the string.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Collections:ArrayList x:Key="data">
            <System:String>Some data</System:String>
            <System:String>Some more data</System:String>
            <System:String>Even more data than before</System:String>
        </Collections:ArrayList>
    </Window.Resources>
    <ItemsControl ItemsSource="{StaticResource data}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel></WrapPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border Margin="5" BorderThickness="1" BorderBrush="Black">
                    <TextBlock Text="{Binding}"></TextBlock>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

And an image of the desired output:

Desired output

Upvotes: 9

Views: 9259

Answers (4)

Toby Crawford
Toby Crawford

Reputation: 827

Use a shared size grid:

<ItemsControl ItemsSource="{StaticResource data}" Grid.IsSharedSizeScope="True">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel></WrapPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="ColumnSize" />
                </Grid.ColumnDefinitions>
                <Border Margin="5" BorderThickness="1" BorderBrush="Black">
                    <TextBlock Text="{Binding}"></TextBlock>
                </Border>
            </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

All columns are guaranteed to be the same width as they share a size group. As they are sized auto, they will also size to the largest instance of any of the grid's content.

Upvotes: 23

McGarnagle
McGarnagle

Reputation: 102793

This is an alternative to Philip Stuyck's custom panel suggestion that you may find more straightforward for your particular scenario. (It is a bit of a hack admittedly.)

You can calculate the length of a string using the FormattedText class. So you could iterate over your strings and calculate the maximum length (this also assumes that you know the font family and size). Then just bind the width of your text blocks to the maximum width. I would store the width value in a single property at the parent level, then using a RelativeSource binding:

<TextBlock Text="{Binding}" 
           Width="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl},
                           Path=MaximumWidth}}" 
       />

(One drawback of this approach is that if the items collection changes, you'll have to recalculate MaximumWidth.)

Upvotes: 1

Ayyappan Subramanian
Ayyappan Subramanian

Reputation: 5366

You can add HorizontalContentAlignment=Stretch and Add UniformGrid in itemsPanel.

 <Window.Resources>
    <Collections:ArrayList x:Key="data">
        <System:String>Some data</System:String>
        <System:String>Some more data</System:String>
        <System:String>Even more data than before</System:String>
    </Collections:ArrayList>
</Window.Resources>
<ListBox ItemsSource="{StaticResource data}" HorizontalContentAlignment="Stretch">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid IsItemsHost="True"></UniformGrid>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border Margin="5" BorderThickness="1" BorderBrush="Black" >
                <TextBlock Text="{Binding}" Width="{Binding 
            RelativeSource={RelativeSource TemplatedParent},
            Path=Width}"></TextBlock>
            </Border>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Upvotes: 2

Philip Stuyck
Philip Stuyck

Reputation: 7467

You have to create your own custom version of a wrappanel that is derived from wrappanel to achieve what you want. Normally for a custom panel you have to override following 2 methods :

  • MeasureOverride
  • ArrangeOverride

In your case you need measueoverride where you will iterate over the elements and find out which one is the biggest and then use that same size for all elements in arrangeoverride.

Some more info on how to create a custom panel : http://www.wpftutorial.net/CustomLayoutPanel.html

Upvotes: 4

Related Questions