Click here to Skip to main content
15,922,145 members
Articles / Desktop Programming / WPF

A better panel for data binding to a WrapPanel in WPF

Rate me:
Please Sign up or sign in to vote.
4.88/5 (26 votes)
18 Jan 2009MIT3 min read 130.8K   2.8K   37   26
An article on a useful extension to the WrapPanel control.

img4.jpg

Introduction

This article presents an extension to the WPF WrapPanel control that infers aesthetically pleasing ItemWidth and ItemHeight properties. This inference is important in data-binding applications, when you may not know a good value for ItemWidth or ItemHeight to choose at design time.

Background

The System.Windows.Controls.WrapPanel control that comes with WPF positions its child elements sequentially from left to right (or top to bottom), breaking content to the next line at the edge of the containing box. Subsequent ordering happens sequentially from left to right (or top to bottom). The following is the output of using a WrapPanel to display a set of buttons:

img1.jpg

The code used to generate this is straightforward:

XML
<WrapPanel>
    <Button Margin="5,5,5,5">Alpha</Button>
    <Button Margin="5,5,5,5">Bravo</Button>
       
        <!-- ... -->

    <Button Margin="5,5,5,5">Zebra</Button>
</WrapPanel>

The output looks ugly, since each button has a different size. To make the button size uniform, we can set the ItemWidth property:

XML
<WrapPanel ItemWidth="69">
    <Button Margin="5,5,5,5">Alpha</Button>
    <Button Margin="5,5,5,5">Bravo</Button>
       
        <!-- ... -->

    <Button Margin="5,5,5,5">Zebra</Button>
</WrapPanel>

Now, we get a better looking output:

img2.jpg

Picking the right value for ItemWidth requires trial and error. The objective is to make the ItemWidth just big enough to fully display the child control with the largest desired width (the Yesterday Button in this case) without making it any bigger. In this example, if the size is too big, the buttons will look silly. If the size is too small, the text of one or more of the buttons will get cut off.

This problem gets harder when we introduce data binding. When we bind the children of the WrapPanel to a set of data with a corresponding set of data-bound controls, we may not know the maximum child control desired width at design time. To demonstrate this, I first factor the same set of names into the new class TextToDisplay, which I will use to data bind the set of button names:

C#
public class TextToDisplay
{
    public TextToDisplay()
    {
        Text = new ObservableCollection<string>();
        Text.Add("Alpha");
        Text.Add("Bravo");

           //...

        Text.Add("Zebra");
    }

    public ObservableCollection<string> Text
    {
        get;
        private set;
    }
}

This new view code uses data biding to display the Text elements of the TextToDisplay class:

XML
<Window 
    x:Class="UniformWrapPanelExample.WrapPanelWithDataBindingWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:local="clr-namespace:UniformWrapPanelExample"
    Title="WrapPanelWithDataBindingWindow" Height="300" Width="300">
    <Window.Resources>
        <local:TextToDisplay x:Key="TextContainer"/>
    </Window.Resources>
    <ItemsControl
        ItemsSource="{Binding Text,
            Source={StaticResource TextContainer}}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel ItemWidth="69"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type sys:String}">
                <Button
                    Margin="5,5,5,5"
                    Content="{Binding}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

This new implementation allows someone to change the button collection by simply adding or removing strings from the Text property of the TextToDisplay object. The view code handles translating this string into a button automatically.

With data binding, we can no longer reliably use trial and error to guess the right ItemWidth value. Our assumptions about the text width of the buttons may be invalidated at run time. For example, if you guess that 50 is a good value for ItemWidth, you would end up with some words being cut off in the example set:

img3.jpg

To solve the guess work involved in choosing the right ItemWidth (or ItemHeight) value, I created the UniformWrapPanel. The UnfiormWrapPanel is just a WrapPanel, with a special property IsAutoUniform. When this property is true, the ItemWidth (or ItemHeight) will be automatically set to the largest desired width of the children before layout, causing all the items to receive an aesthetically pleasing uniform layout without any guesswork. The IsAutoUniformProperty is defined below:

C#
public class UniformWrapPanel : WrapPanel
{
    public bool IsAutoUniform
    {
        get { return (bool)GetValue(IsAutoUniformProperty); }
        set { SetValue(IsAutoUniformChanged, value); }
    }

    public static readonly DependencyProperty
        IsAutoUniformProperty = DependencyProperty.Register(
        "IsAutoUniform", typeof(bool), typeof(UniformWrapPanel),
        new FrameworkPropertyMetadata(true, 
            new PropertyChangedCallback(IsAutoUniformChanged)));

    //...
    }
}

This property is true by default; setting it to false will make this panel behave like the standard WrapPanel. Because changing this value may affect layout, the PropertyChangedCallback IsAutoUniformChanged will cause the panel to recompute the layout of its elements whenever the IsAutoUniform property gets changed:

C#
private static void IsAutoUniformChanged(DependencyObject sender,
        DependencyPropertyChangedEventArgs e)
{
    if (sender is UniformWrapPanel)
    {
        ((UniformWrapPanel)sender).InvalidateVisual();
    }
}

To produce the correct behavior, the UniformWrapPanel computes an appropriate ItemWidth (or ItemHeight) property, and then defers to the WrapPanel base class to complete the layout:

C#
protected override Size MeasureOverride(Size availableSize)
{
    if (Children.Count > 0 && IsAutoUniform)
    {
        if (Orientation == Orientation.Horizontal)
        {
            double totalWidth = availableSize.Width;
            ItemWidth = 0.0;
            foreach (UIElement el in Children)
            {
                el.Measure(availableSize);
                Size next = el.DesiredSize;
                if (!(Double.IsInfinity(next.Width) ||
                    Double.IsNaN(next.Width)))
                {
                    ItemWidth = Math.Max(next.Width, ItemWidth);
                }
            }
        }
        else
        {
            double totalHeight = availableSize.Height;
            ItemHeight = 0.0;
            foreach (UIElement el in Children)
            {
                el.Measure(availableSize);
                Size next = el.DesiredSize;
                if (!(Double.IsInfinity(next.Height) ||
                    Double.IsNaN(next.Height)))
                {
                    ItemHeight = Math.Max(next.Height, ItemHeight);
                }
            }
        }
    }
    return base.MeasureOverride(availableSize);
}

Using the Code

To use this new panel, replace the ItemsPanel in the above data binding example with:

XML
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <local:UniformWrapPanel/>
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

which produces the following output:

img4.jpg

Points of Interest

When I originally made this panel, I made it as an extension to Panel, rather than WrapPanel. In the original version, there was no ItemWidth or ItemHeight property, since these were inferred automatically. This seemed limiting, since it couldn't always be used in place of WrapPanel, so I switched to the above model. Sure enough, the code actually got simpler when I did this :)

History

Any future changes or improvements I make will later be posted here.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionStartupUri Pin
Member 152779856-Jul-21 11:16
Member 152779856-Jul-21 11:16 
QuestionHidden Items in list occupies the space Pin
Member 993742828-Jul-15 7:30
Member 993742828-Jul-15 7:30 
GeneralMy vote of 5 Pin
vinod.sankuthodi5-Mar-13 23:54
vinod.sankuthodi5-Mar-13 23:54 
GeneralUseful Pin
Philip Lee10-Jan-12 20:20
Philip Lee10-Jan-12 20:20 
Suggestionreally goog sample Pin
BlaiseBraye21-Dec-11 22:14
BlaiseBraye21-Dec-11 22:14 
GeneralMy vote of 5 Pin
BlaiseBraye21-Dec-11 22:09
BlaiseBraye21-Dec-11 22:09 
GeneralUseful Pin
AWdrius12-Aug-09 23:54
AWdrius12-Aug-09 23:54 
GeneralA Nice Article Pin
draiko22-Jan-09 22:28
draiko22-Jan-09 22:28 
GeneralRe: A Nice Article Pin
Joe Gershgorin25-Apr-09 10:16
Joe Gershgorin25-Apr-09 10:16 
GeneralRe: A Nice Article Pin
adam.cataldo26-Apr-09 6:40
adam.cataldo26-Apr-09 6:40 
GeneralRe: A Nice Article Pin
Joe Gershgorin26-Apr-09 16:20
Joe Gershgorin26-Apr-09 16:20 
GeneralGreat idea Pin
Josh Smith19-Jan-09 3:22
Josh Smith19-Jan-09 3:22 
GeneralRe: Great idea Pin
adam.cataldo19-Jan-09 4:55
adam.cataldo19-Jan-09 4:55 
GeneralRe: Great idea Pin
Josh Smith19-Jan-09 4:56
Josh Smith19-Jan-09 4:56 
GeneralI cannot download anything Pin
Egidio18-Jan-09 2:45
Egidio18-Jan-09 2:45 
GeneralRe: I cannot download anything Pin
adam.cataldo18-Jan-09 4:56
adam.cataldo18-Jan-09 4:56 
GeneralRe: I cannot download anything Pin
springy7619-Jan-09 0:35
springy7619-Jan-09 0:35 
GeneralRe: I cannot download anything Pin
adam.cataldo19-Jan-09 4:51
adam.cataldo19-Jan-09 4:51 
GeneralRe: I cannot download anything Pin
springy7619-Jan-09 5:19
springy7619-Jan-09 5:19 
GeneralRe: I cannot download anything Pin
adam.cataldo19-Jan-09 5:31
adam.cataldo19-Jan-09 5:31 
GeneralRe: I cannot download anything Pin
adam.cataldo19-Jan-09 5:36
adam.cataldo19-Jan-09 5:36 
AnswerRe: I cannot download anything Pin
springy7619-Jan-09 22:20
springy7619-Jan-09 22:20 
GeneralRe: I cannot download anything Pin
adam.cataldo20-Jan-09 4:49
adam.cataldo20-Jan-09 4:49 
GeneralMy take on this Pin
Sacha Barber18-Jan-09 0:24
Sacha Barber18-Jan-09 0:24 
GeneralRe: My take on this Pin
adam.cataldo18-Jan-09 4:47
adam.cataldo18-Jan-09 4:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.