Page view counter

Silverlight Toolkit: WrapPanel

Hi Folks,

I’d like for us to talk about one of the new controls in the Silverlight Toolkit – WrapPanel.

In the following article we’ll deep dive into WrapPanel and see how it behaves in different situations.

 

 

Setup

1. Create a new project.

image2

 

2. Add a reference to the Silverlight Controls assembly (Microsoft.Windows.Controls.dll) which can be downloaded at http://codeplex.com/Silverlight.

image10   image_thumb

 

3. Look under "Custom Controls" In the Blend Asset Library.

image13

 

4. Add a WrapPanel to the Page.

image

 

And here's the XAML we got:

<UserControl

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    mc:Ignorable="d"

    x:Class="SilverlightControlsNovember2008.WrapPanelPage"

    d:DesignWidth="640" d:DesignHeight="480"

xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls">

    <Grid x:Name="LayoutRoot" Background="#FFFFFFFF">

        <controls:WrapPanel  />

    </Grid>

</UserControl>

 

 

WrapPanel with Horizontal Orientation

Let’s have a look at a basic StackPanel usage.

<StackPanel Height="300" Width="300" Background="#FFE1E1E1">

    <Image Height="100" Width="100" Source="Cookie1.png"/>

    <Image Height="100" Width="100" Source="Cookie2.png"/>

    <Image Height="100" Width="100" Source="Cookie3.png"/>

    <Image Height="100" Width="100" Source="Cookie4.png"/>

    <Image Height="100" Width="100" Source="Cookie5.png"/>

</StackPanel>

We’ll Stack 5 cookies on top of each other and we’ll get this display:

image

So each cookie has a height & Width of 100 pixels over a 100 pixels.
StackPanel arranges them top-down. So cookie #1 is first, followed by cookie #2 and all the way to cookie #5.

However, our StackPanel has a width of 300, and you can see all that wasted space in the grey background of our StackPanel.

 

We’d like a way to stack the cookies from Left-To-right until there isn’t anymore place to add another cookie.

In comes – WrapPanel.

We’ll drag & drop our 7 cookies from the solution explorer to the WrapPanel.

image

By default, Blend sets the Image.StrechMode to fill, which we’ll want to change back to None.

image

We’ll select the cookie and set it’s StretchMode.Nonde.

image

And we’ll get a normal sized Cookie.

image

We’ll repeat this for cookie #2.

image

We can see here that Cookie #2 has been aligned to the right of cookie #1 that preceded it.
Let’s add Cookie #3.

image

Again, Cookie #3 got added to the right of Cookie #2 which is on the right of cookie #1.

Let’s add cookie #4.

image

We can see that Cookie #4 didn’t have any more “place” on the first row, so WrapPanel added another row for it and placed it there.

Let’s fill the WrapPanel with 7 Cookies.

image

If we run this, we’ll get the same picture, just running the browser.

image

 

Here’s the XAML Blend generated for us:

<controls:WrapPanel Height="300" Width="300">

    <Image Source="Cookie1.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie5.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

We can see that our WrapPanel was able to place as many items as it could at every row and when it needed more space, it added another row.

That behavior is specified by Orientation.Horizontal which is the default for WrapPanel.
So setting WrapPanel.Orientation=Orientation.Horizontal would be equivalent to the default value. Let’s do that anyway though.

image

 

image --> image

We get the following XAML:

<controls:WrapPanel Height="300" Width="300" Orientation="Horizontal">

    <Image Source="Cookie1.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie5.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

Which looks exactly the same as did the default sample:

image

 

So now we have 7 Cookies properly Wrapped. 7 Cookies.

image

http://www.youtube.com/watch?v=u7hTkzEwFZ0

 

 

WrapPanel with Vertical Orientation

the keen sighted might have noticed that we also have Orientation.Vertical as an option. Let’s set WrapPanel.Orientation to that and see how it arranges our controls.

image --> image

Here’s the XAML Blend generated for us:

<controls:WrapPanel Height="300" Width="300" Orientation="Vertical">

    <Image Source="Cookie1.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie5.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

And here’s how the WrapPanel looks like:

image

Our 7 Cookies are now aligned top-to-bottom.

First, WrapPanel places Cookie #1 in the top left corner.
Than it places Cookie #2 and Cookie #3 below it.

Once Cookie #4 needs to get placed, there’s no room left on the first column. So WrapPanel opens another column and starts placing Cookies #4, #5 & #6 in that column.

And once there’s no more room left in the second column, WrapPanel places Cookie #7 in the third column.

 

 

Horizontal WrapPanel Items with Vertical alignment

A problem might arise though, some of our controls might not be in the same size as others.

Let’s assume that cookie #5 isn’t 100x100 pixels in Height & Width. Rather, let’s assume that cookie #5 is 150 pixels in Height.

<controls:WrapPanel Height="400" Width="350" Orientation="Horizontal">

    <Image Source="Cookie1.png" Stretch="None"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie5.png" Stretch="Fill" Height="200" Width="100"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

image

We can see that the WrapPanel still makes sense. However, Cookie #4 & Cookie #6 are aligned to the center of Row #2 (which contains Cookies 4, 5 & 6).

In real-world scenarios we might like to have different alignments for Cookies #4 & #6.

In Blend, we’ll select Cookie #4 and set it’s VerticalAlignment to Top and we’ll select Cookie #6 and set it’s VerticalAlignment to Bottom.

image image

And now, Once we run our sample, we can see this image:

image

So Cookie #4 was aligned to the Top of the row, and Cookie #6 was aligned to the bottom of the row.

And we got the following XAML:

<controls:WrapPanel Height="400" Width="350" Orientation="Horizontal">

    <Image Source="Cookie1.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100" VerticalAlignment="Top"/>

    <Image Source="Cookie5.png" Stretch="Fill" Height="200" Width="100"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100" VerticalAlignment="Bottom"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

 

 

Vertical WrapPanel Items with Horizontal alignment

A similar problem can arise in a WrapPanel that has a Vertical Orientation.

Let’s take our previous Vertical WrapPanel, and set the width of Cookie #5 to 200.

<controls:WrapPanel Height="350" Width="400" Orientation="Vertical">

    <Image Source="Cookie1.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100" />

    <Image Source="Cookie5.png" Stretch="Fill" Height="100" Width="200"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

 

Which would render like so:

image

We can see that in this sample, Cookie #4 and Cookie #6 were both aligned Center in terms of HorizontalAlignment.

Let’s set Cookie #4 HorizontalAlignment to Left, and Cookie #6 HorizontalAlignment to Right.

image image

 

Let’s run the sample.

image

 

And we can indeed see that Cookie #4 is aligned to the Left of the 2nd column and Cookie #6 is aligned to the Right of the 2nd column.

 

Here’s the XAML Blend generated for us:

<controls:WrapPanel Height="350" Width="400" Orientation="Vertical">

    <Image Source="Cookie1.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie2.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie3.png" Stretch="None" Height="100" Width="100"/>

    <Image Source="Cookie4.png" Stretch="None" Height="100" Width="100" HorizontalAlignment="Left" />

    <Image Source="Cookie5.png" Stretch="Fill" Height="100" Width="200"/>

    <Image Source="Cookie6.png" Stretch="None" Height="100" Width="100" HorizontalAlignment="Right"/>

    <Image Source="Cookie7.png" Stretch="None" Height="100" Width="100"/>

</controls:WrapPanel>

 

 

Changing ListBox.ItemsPanel to WrapPanel

Let’s do something a bit more interesting with our new WrapPanel than just put cookies in it. Let’s put our cookies in a ListBox!

First, we’ll add a new Listbox to our Page.

image

Next, we’d like to say “each item in this ListBox is an Image”.

Right click on Listbox –>  Edit Other Templates –> Edit Generated Items (ItemTemplate) –> Create Empty.

image

What we’re doing here is saying “well, the default template for each of our items is a bit different that you’d expect”.

We’ll name the new DataTemplate “CookieItemTemplate”.

image

 

And this is what we see now:

image

There’s an Empty <Grid> in our new DataTemplate. Since we want to display an Image we’ll add an Image control.

image

Next, we’d like our Image to be 100x100 pixels.

image

And finally, we’ll introduce some DataBinding. We’d like to DataBind our Image.Source property to whatever is bound to the DataTemplate.

click “Advanced property options” next to source.

image

Select “Custom Expression”

image

And put in “{Binding}”.

image

and here’s the XAML blend generated for us:

<ListBox x:Name="cookies" Margin="0,0,0,0" Height="300" Width="300" ItemTemplate="{StaticResource CookieItemTemplate}"/>

And here’s the CookieItemTemplate resouce:

<UserControl.Resources>

    <DataTemplate x:Key="CookieItemTemplate">

        <Grid>

            <Image Width="100" Height="100" Source="{Binding}"/>

        </Grid>

    </DataTemplate>

</UserControl.Resources>

 

Next step before we run this sample is to specify a ListBox.ItemsSource (the URIs for all our images).

void WrapPanelPage_Loaded(object sender, RoutedEventArgs e)

{

    cookies.ItemsSource = new BitmapImage[]

                              {

                  new BitmapImage(new Uri("Cookie1.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie2.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie3.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie4.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie5.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie6.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie7.png", UriKind.Relative))

                              };

}

This is a pretty scary syntax. honestly, it’s just the syntax for a “Image.Source={Binding}”. There’s nothing really special here and you should just take it for what it is – the syntax the framework requires in this case.

 

Finally, we can run our Listbox:

image 

Most cookies aren’t visible at all. We can scroll to see them, but that’s not quite the effect we’re looking for here.

image image

image

 

This would be an appropriate place to use a WrapPanel. To do that we’ll edit the ListBox.ItemsPanel

Let’s go back to Blend, select our ListBox, Right click it and select “Edit Other Templates –> Edit Layout Items (ItemsPanel) –> Create empty…”.

image

We’ll name it “cookiesItemsPanel”.

image

 

And here’s what we got:

image

What we want to do here is tell our ListBox “well, don’t use a grid, use a WrapPanel”

So, we’ll delete our Grid, and put in a WrapPanel instead of it.

image image

 

Now, let’s re-run our application.

image

Hmm… this ScrollBar doesn’t work for us. So let’s specify we don’t need it.

image

And re-run our application.

image 

And here we go – a ListBox that uses a WrapPanel instead of the default layout.

 

And here’s the ListBox blend genrated for us:

<ListBox x:Name="cookies" Height="330" Width="330" ItemTemplate="{StaticResource CookieItemTemplate}" ItemsPanel="{StaticResource CookiesItemsPanel}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>

Our ItemTemplate & ItemsPanelTemplate:

<UserControl.Resources>

    <DataTemplate x:Key="CookieItemTemplate">

        <Grid >

            <Image Source="{Binding}" Width="100" Height="100"/>

        </Grid>

    </DataTemplate>

    <ItemsPanelTemplate x:Key="CookiesItemsPanel">

        <controls:WrapPanel/>

    </ItemsPanelTemplate>

</UserControl.Resources>

And code-behind:

void WrapPanelPage_Loaded(object sender, RoutedEventArgs e)

{

    cookies.ItemsSource = new BitmapImage[]

                              {

                  new BitmapImage(new Uri("Cookie1.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie2.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie3.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie4.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie5.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie6.png", UriKind.Relative)),

                  new BitmapImage(new Uri("Cookie7.png", UriKind.Relative))

                              };

}

 

-- Justin-Josef Angel

Microsoft Silverlight Program Manager

Published Wednesday, November 05, 2008 4:58 PM by JustinAngel
Filed under:

Comments

# Silverlight Cream for November 06, 2008 -- #421

In this issue: Boyan Nikolov, Scott Hanselman, Page Brooks, Justin Angel, and John Papa. If you haven

Thursday, November 06, 2008 12:22 PM by Community Blogs

# Silverlight News for November 07, 2008

Pingback from  Silverlight News for November 07, 2008

Friday, November 07, 2008 3:19 AM by Silverlight News for November 07, 2008

# A Ridiculous List of Silverlight Toolkit Resources

The Silverlight Toolkit is off to a great start and lots of people have been spending time writing content

Friday, November 07, 2008 11:35 PM by Shawn Burke's Blog

# 2008 November 09 - Links for today &laquo; My (almost) Daily Links

Pingback from  2008 November 09 - Links for today &laquo; My (almost) Daily Links

# Toolkit posts: &quot;Silverlight in Style,&quot; dependency properties in WPF and Silverlight, and TreeView expansion - Jeff Wilcox

Pingback from  Toolkit posts: &quot;Silverlight in Style,&quot; dependency properties in WPF and Silverlight, and TreeView expansion - Jeff Wilcox

# re: Silverlight Toolkit: WrapPanel

That's a really useful article, and covers exactly the thought process someone is likely to go through when adding WrapPanel-eseque functionality to their application.  Thank you Justin!

Thursday, November 27, 2008 7:12 PM by roberino

# re: Silverlight Toolkit: WrapPanel

Quick Question.

Lets say for a moment, rather than add Images as the DataTemplate we wanted to use our own control. Let's call it MyControl.  My control has two events. StartedDoingSomething and StoppedDoingSomething.  It also has one Method StopDoingSomething.

In my wrap panel I want to allow for only on of MyControls to be 'doing' something at a time. So when mycontrol b StartedDoingSomething and mycontrol A is already doing something, I want to tell mycontrol b to StopDoingSomething.  

Programmatically if I use a wrappanel I can get at the children for the wrappanel and I could either user LINQ or a forEach loop to get to any  other controls and tell them to stop playing.  However if I apply the wrap panel in XAML, the only way I've been able to get at the wrap panel programatically is via the TreeViewHelper and the wrappanel is six layers deep.  After further investigation, it just points me back to the Object bound to the control, not the control bound to the control.

Any suggestions.

Tuesday, December 02, 2008 6:17 PM by BigDubb

# re: Silverlight Toolkit: WrapPanel

Thank you Justin. Here is my solution of the gallery. kalimevole.blogspot.com/.../galerie-pomoc-wrappanelu.html .

The working app you can see here: student.vsmie.cz/.../Default.html

Thank you

Monday, January 12, 2009 4:17 AM by kalimevole

# re: Silverlight Toolkit: WrapPanel

Justin,

I can't find the UniformGrid in SL3.

Where is this gem located?

Thanks!

Karl

Monday, July 27, 2009 3:34 AM by molenator