Page view counter

November 2008 - Posts

Silverlight Toolkit: TreeView, TreeViewItem & HierarchalDataTemplate

Hi folks,

 

I’d like to go over the TreeView control which is part of the new Silverlight Toolkit (http://www.codeplex.com/Silverlight).
We’ll talk about the TreeView control itself, how to style the TreeViewItem and what is HierarchalDataTemplate all about.  

Recommended reading before reading this article

1. Silverlight Toolkit: HeaderedContentControl & HeaderedItemsControl

 

 

Setup

1. Create a new project.

image2_thumb2

 

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

image10_thumb2   image_thumb3313

 

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

image13_thumb2

 

4. Add a TreeView to the Page.

image

 

And here's the XAML Blend generated for us:

<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.TreeViewPage"

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

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

    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">

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

        <controls:TreeView Margin="79,98,0,0" VerticalAlignment="Top" Height="200" Width="120" HorizontalAlignment="Left"/>

    </Grid>

</UserControl>

 

 

Manually Adding New Textual TreeViewItems to a TreeView

Let’s add some TreeViewItems to our TreeView that will reflect a family’s genealogy tree.  like so:

image

 

We’ll right click on TreeView –> Add TreeViewItem.

image

And we can see that we indeed got a new TreeViewItem nested under or TreeView:

image

 

Next, we’ll go to the TreeViewItem’s properties and set Header to “Sally” and IsExpanded to true.

image 

 

Here’s the XAML blend generated for us up until now:

<controls:TreeView  Height="200" Width="120">

    <controls:TreeViewItem IsExpanded="True" Header="Sally"/>

</controls:TreeView>

 

Now we’d like to add “John” as a child TreeViewItem of “Sally”.

We’ll Right click on “Sally” TreeViewItem –> Add TreeViewItem.

image

And we got another nested note inside the previous TreeViewItem:

image

 

We’ll edit it’s Header to “John” and we’ll keep IsExpanded to false, because it has no nested nodes.

image

 

We’ll keep it up until we get the following TreeView:

image

 

Let’s run our sample:

image

And we can start collapsing, expanding & Selecting TreeViewItems:

image

(In the above sample, Greg is collapsed, and John is selected)

 

And here’s the XAML Blend generated for us:

<controls:TreeView Height="200" Width="120">

    <controls:TreeViewItem IsExpanded="True" Header="Sally">

        <controls:TreeViewItem Header="John"/>

        <controls:TreeViewItem Header="Greg" IsExpanded="True">

            <controls:TreeViewItem Header="Linda"/>

            <controls:TreeViewItem Header="Darren"/>

        </controls:TreeViewItem>

        <controls:TreeViewItem Header="Allice" IsExpanded="True">

            <controls:TreeViewItem Header="Neil"/>

        </controls:TreeViewItem>

    </controls:TreeViewItem>

</controls:TreeView>

 

 

 

Editing the TreeViewItem’s Header property in Visual Studio XAML Editor

Now it’s time to uncover the horrible truth – Sally and her children are all aliens. The green “blip blip” kind.

This is how our TreeView should look like when we’re done:

image 

 

Blend currently does not support editing the Header property in a visual way. So we’ll open up Visual Studio’s XAML editor for that.

Right click on “TreeViewPage.xaml” (which is our page) in the Blend project tab –> Edit In Visual Studio.

image

 

And in a few second we’ll see this screen:

image

 

We’ll start off by changing the first TreeViewItem:

<controls:TreeViewItem IsExpanded="True" Header="Sally">

First, we’ll expand the Header property to allow XAML content:

<controls:TreeViewItem IsExpanded="True">

    <controls:TreeViewItem.Header></controls:TreeViewItem.Header>

We’ll fill in the “Sally” content:

<controls:TreeViewItem IsExpanded="True">

    <controls:TreeViewItem.Header>

        <TextBlock Text="Sally" />

    </controls:TreeViewItem.Header>

And our TreeView still looks exactly the same:

image

 

Now we’d like to add the image of our Alien, but the Header can only contain a single element.
So we’ll group the <Image> and the <TextBlock> in a horizontal <StackPanel>.

<controls:TreeViewItem.Header>

    <StackPanel Orientation="Horizontal">

        <TextBlock Text="Sally" />

    </StackPanel>

</controls:TreeViewItem.Header>

Now that we have a container in the Header, we’ll add the <Image> tag.

<controls:TreeViewItem.Header>

    <StackPanel Orientation="Horizontal">

        <Image Source="Alien1.png" />

        <TextBlock Text="Sally" />

    </StackPanel>

</controls:TreeViewItem.Header>

 

In our Visual Studio preview windows we can see the following image:

image

Well, The text is too small for our picture. So let’s change the FontSize for the TreeView to 22.

<controls:TreeView FontSize="22" Height="200" Width="120">

 

image

We can see the Horizontal and Vertical Scrollbars appear because now our TreeViewItems are bigger than the TreeView. We’ll change the TreeView Width & Height accordingly. 

<controls:TreeView FontSize="22" Height="350" Width="250">

 

Now our TreeView looks much better:

image

 

here’s our XAML up until now:

<controls:TreeView FontSize="22" Height="350" Width="250" Margin="84,101,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">

    <controls:TreeViewItem IsExpanded="True">

       <controls:TreeViewItem.Header>

            <StackPanel Orientation="Horizontal">

                <Image Source="Alien1.png" />

                <TextBlock Text="Sally" />

            </StackPanel>

        </controls:TreeViewItem.Header>

        <controls:TreeViewItem Header="John"/>

        <controls:TreeViewItem Header="Greg" IsExpanded="True">

            <controls:TreeViewItem Header="Linda"/>

            <controls:TreeViewItem Header="Darren"/>

        </controls:TreeViewItem>

        <controls:TreeViewItem Header="Allice" IsExpanded="True">

            <controls:TreeViewItem Header="Neil"/>

        </controls:TreeViewItem>

    </controls:TreeViewItem>

</controls:TreeView>

 

We’ll repeat the process of editing an Image for the other 6 TreeViewItems. Than we’ll run our application.

image

 

here’s the XAML we wrote:

<controls:TreeView FontSize="22" Height="350" Width="250" Margin="84,101,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">

    <controls:TreeViewItem IsExpanded="True">

        <controls:TreeViewItem.Header>

            <StackPanel Orientation="Horizontal">

                <Image Source="Alien1.png" />

                <TextBlock Text="Sally" />

            </StackPanel>

        </controls:TreeViewItem.Header>

                <controls:TreeViewItem>

                    <controls:TreeViewItem.Header>

                        <StackPanel Orientation="Horizontal">

                            <Image Source="Alien2.png" />

                            <TextBlock Text="John" />

                        </StackPanel>

                    </controls:TreeViewItem.Header>

                </controls:TreeViewItem>

                <controls:TreeViewItem IsExpanded="True">

                    <controls:TreeViewItem.Header>

                        <StackPanel Orientation="Horizontal">

                            <Image Source="Alien3.png" />

                            <TextBlock Text="Greg" />

                        </StackPanel>

                    </controls:TreeViewItem.Header>

                    <controls:TreeViewItem>

                        <controls:TreeViewItem.Header>

                            <StackPanel Orientation="Horizontal">

                                <Image Source="Alien4.png" />

                                <TextBlock Text="Linda" />

                            </StackPanel>

                        </controls:TreeViewItem.Header>

                    </controls:TreeViewItem>

                    <controls:TreeViewItem>

                        <controls:TreeViewItem.Header>

                            <StackPanel Orientation="Horizontal">

                                <Image Source="Alien5.png" />

                                <TextBlock Text="Darren" />

                            </StackPanel>

                        </controls:TreeViewItem.Header>

                    </controls:TreeViewItem>

                    </controls:TreeViewItem>

        <controls:TreeViewItem IsExpanded="True">

                    <controls:TreeViewItem.Header>

                        <StackPanel Orientation="Horizontal">

                            <Image Source="Alien6.png" />

                            <TextBlock Text="Allice" />

                        </StackPanel>

                    </controls:TreeViewItem.Header>

                    <controls:TreeViewItem>

                        <controls:TreeViewItem.Header>

                            <StackPanel Orientation="Horizontal">

                                <Image Source="Alien7.png" />

                                <TextBlock Text="Neil" />

                            </StackPanel>

                        </controls:TreeViewItem.Header>

                    </controls:TreeViewItem>

        </controls:TreeViewItem>

    </controls:TreeViewItem>

</controls:TreeView>

We can definitely see that we’ve did some copy & paste here. Next we’ll see how we can use DataBinding to remove this repeatable code. 

 

 

Specifying a DataTemplate as the TreeView’s ItemTemplate

Well, now we’d like to remove that duplicated code by using some DataBinding.

We’ve got this Alien class:

    public class Alien

    {

        public Alien(string name, string pictureUrl)

        {

            Name = name;

            Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));

        }

 

        public string Name { get; set;  }

        public BitmapImage Picture { get; set; }

    }

Pretty simple, we’ve got a Name property, and a Picture property the we’re settings with way Silverlight uses for Image.Source databinding.

We’ll jump back to Blend.

image

And clear all the Items from the TreeViewItem by selecting the first TreeViewItem and deleting it.

image

image

 

Now that we have an empty TreeView, we’d like to set a x:Name so we can setup the TreeView‘s DataContext from our Code-behind.

image

 

We’ll set the TreeView ItemSource to run on a collection of aliens.

void TreeViewPage_Loaded(object sender, RoutedEventArgs e)

{

    trvAliens.ItemsSource = new List<Alien>()

                                {

                        new Alien("Sally", "Alien1.png"),

                        new Alien("John", "Alien2.png"),

                        new Alien("Greg", "Alien3.png"),

                        new Alien("Linda", "Alien4.png"),

                        new Alien("Darren", "Alien5.png"),

                        new Alien("Alice", "Alien6.png"),

                        new Alien("Neil", "Alien7.png"),

                                };

}

 

Now, we can finally get down to business – changing the TreeView’s ItemTemplate.

Right Click on TreeView –> Edit Other Templates –> Edit Generated Items (ItemTemplate) –> Create Empty.

image

We’ll call our new Template AlienTemplate.

image

We can see that we have an empty DataTemplate:

image

 

First, we’ll Change the Grid to a Horizontal StackPanel.

Right Click Grid –> Change Layout Type –> StackPanel.

image image

 

Next we’ll add an Image control.

image

And we’ll want to DataBind it’s Source Property to Alien.Picture property.

Click Advanced Property options next to Source.

image

Select “Custom Expression”.

image

And put in “{Binding Picture}”.

image

 

Next we’ll add a TextBlock and Bind it’s Text property to “{Binding Name}”.

image --> image –>

image --> image

 

Let’s run this sample:

image

 

Here’s the XAML Blend Generated for our TreeView:

<controls:TreeView FontSize="22" Height="350" Width="250" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}"/>

Here’s the XAML Blend generated for our ItemTemplate:

<UserControl.Resources>

    <DataTemplate x:Key="AlienTemplate">

        <StackPanel Orientation="Horizontal">

            <Image Source="{Binding Path=Picture}"/>

            <TextBlock Text="{Binding Path=Name}"/>

        </StackPanel>

    </DataTemplate>

</UserControl.Resources>

Here’s our Code-behind:

void TreeViewPage_Loaded(object sender, RoutedEventArgs e)

{

    trvAliens.ItemsSource = new List<Alien>()

                                {

                        new Alien("Sally", "Alien1.png"),

                        new Alien("John", "Alien2.png"),

                        new Alien("Greg", "Alien3.png"),

                        new Alien("Linda", "Alien4.png"),

                        new Alien("Darren", "Alien5.png"),

                        new Alien("Alice", "Alien6.png"),

                        new Alien("Neil", "Alien7.png"),

                                };

}

And our current Alien Class:

public class Alien

{

    public Alien(string name, string pictureUrl)

    {

        Name = name;

        Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));

    }

 

    public string Name { get; set;  }

    public BitmapImage Picture { get; set; }

}

 

 

 

Specifying a HierarchalDataTemplate as a TreeView’s ItemTemplate in Visual Studio XAML Editor

In case you haven’t noticed, our current TreeView is pretty flat. It only has 1 level. And we’d like to get a similar TreeView to the one we previously had, with Nested TreeViewItems.

We’ll start by changing our CLR Alien Type:

    public class Alien

    {

        public Alien(string name, string pictureUrl, params Alien[] children)

        {

            Name = name;

            Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));

           Children = children;

        }

 

        public string Name { get; set; }

        public BitmapImage Picture { get; set; }

       public Alien[] Children { get; set;  }

    }

All we did is add a property that is collection of Aliens that are the Children of that Alien.

 

Now that we’ve got a Hierarchical Alien class, we’ll change our code behind to use it:

void TreeViewPage_Loaded(object sender, RoutedEventArgs e)

{

    trvAliens.ItemsSource = new List<Alien>()

                        {

                new Alien("Sally", "Alien1.png",

                    new Alien("John", "Alien2.png"),

                   new Alien("Greg", "Alien3.png",

                        new Alien("Linda", "Alien4.png"),

                        new Alien("Darren", "Alien5.png")

                            ),

                    new Alien("Alice", "Alien6.png",

                        new Alien("Neil", "Alien7.png")

                            )

                          )

                        };

}

 

The next part of changing our ItemTemplate is not supported by Blend. so we’re back at editing XAML in Visual Studio XAML Editor.

This is our current DataTemplate:

<DataTemplate x:Key="AlienTemplate">

    <StackPanel Orientation="Horizontal">

        <Image Source="{Binding Path=Picture}"/>

        <TextBlock Text="{Binding Path=Name}"/>

    </StackPanel>

</DataTemplate>

Let’s change it to a HierarchicalDataTemplateL:

<controls:HierarchicalDataTemplate x:Key="AlienTemplate">

    <StackPanel Orientation="Horizontal">

        <Image Source="{Binding Path=Picture}"/>

        <TextBlock Text="{Binding Path=Name}"/>

    </StackPanel>

</controls:HierarchicalDataTemplate>

 

And we’ll need to point to our new Hierarchical CLR property – Children.

We’ll do that by Setting HierarchicalDataTemplate.ItemsSource to “{Binding Children}”.

<controls:HierarchicalDataTemplate x:Key="AlienTemplate" ItemsSource="{Binding Children}">

    <StackPanel Orientation="Horizontal">

        <Image Source="{Binding Path=Picture}"/>

        <TextBlock Text="{Binding Path=Name}"/>

    </StackPanel>

</controls:HierarchicalDataTemplate>

 

Let’s run the sample:

image

 

Here’s our XAML code for the TreeView (hasn’t changed during this sample):

<controls:TreeView FontSize="22" Height="350" Width="250" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}"/>

Here’s our updated ItemTemplate:

<UserControl.Resources>

    <controls:HierarchicalDataTemplate x:Key="AlienTemplate" ItemsSource="{Binding Children}">

        <StackPanel Orientation="Horizontal">

            <Image Source="{Binding Path=Picture}"/>

            <TextBlock Text="{Binding Path=Name}"/>

        </StackPanel>

    </controls:HierarchicalDataTemplate>

</UserControl.Resources>

Here’s our updated Alien class:

public class Alien

{

    public Alien(string name, string pictureUrl, params Alien[] children)

    {

        Name = name;

        Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));

        Children = children;

    }

 

    public string Name { get; set; }

    public BitmapImage Picture { get; set; }

    public Alien[] Children { get; set; }

}

And our updated Code-behind:

void TreeViewPage_Loaded(object sender, RoutedEventArgs e)

{

    trvAliens.ItemsSource = new List<Alien>()

                {

        new Alien("Sally", "Alien1.png",

            new Alien("John", "Alien2.png"),

           new Alien("Greg", "Alien3.png",

                new Alien("Linda", "Alien4.png"),

                new Alien("Darren", "Alien5.png")

                    ),

            new Alien("Alice", "Alien6.png",

                new Alien("Neil", "Alien7.png")

                    )

                  )

                };

}

 

 

 

Syncing a TreeView SelectedItem with an External Control

We’d like to have a big bold TextBlock on top of our page which says “Selected Alien: <Name of Alien>”.  like so:

image

 

So, first, we’ll add a big TextBlock saying “Selected Alien:” in big bold letter.

image

And another empty TextBlock called “txtSelectedAlienName” with the same font properties.

image

 

Next we’d like to register to the SelectedItemChanged event.

        public TreeViewPage()

        {

            // Required to initialize variables

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(TreeViewPage_Loaded);

            trvAliens.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(trvAliens_SelectedItemChanged);

        }

 

        void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)

        {

 

        }

Here are the EventArgs for this event:

image

We’ll convert e.NewValue to Alien CLR type, and use it’s Name to fill our TextBlock.

void trvAliens_SelectedItemChanged(object sender,

                                    RoutedPropertyChangedEventArgs<object> e)

{

    txtSelectedAlienName.Text = ((Alien) e.NewValue).Name;

}

 

Let’s run our sample:

 image

And if we select “John”:

image

 

Here’s our updated XAML:

<controls:TreeView FontSize="22" Height="350" Width="250" Margin="84,101,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}"/>

<TextBlock Height="46" Margin="22,38,0,0" VerticalAlignment="Top" FontWeight="Bold" TextWrapping="Wrap" Width="257" HorizontalAlignment="Left"><Run FontSize="30" Text="Selected Alien:"/></TextBlock>

<TextBlock Height="46" Margin="279,38,204,0" VerticalAlignment="Top" TextWrapping="Wrap" FontSize="30" FontWeight="Bold" FontFamily="Portable User Interface" TextDecorations="Underline" x:Name="txtSelectedAlienName"/>

And our updated code-behind:

public TreeViewPage()

{

    // Required to initialize variables

    InitializeComponent();

    this.Loaded += new RoutedEventHandler(TreeViewPage_Loaded);

    trvAliens.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(trvAliens_SelectedItemChanged);

}

 

void trvAliens_SelectedItemChanged(object sender,

                                    RoutedPropertyChangedEventArgs<object> e)

{

    txtSelectedAlienName.Text = ((Alien)e.NewValue).Name;

}

 

 

 

Using the TreeView SelectedValue and SelectedValueMember

Let’s refactor our previous code sample so instead of using e.NewValue it would use the TreeView’s SelectedValue.

SelectedValue reflects a specific property on the current SelectedItem based on SelectedValuePath. complicated? not really.

 

this handler:

void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)

{

    txtSelectedAlienName.Text = ((Alien)e.NewValue).Name;

}

 

is equivalent to this code:

void trvAliens_SelectedItemChanged(object sender,

                                    RoutedPropertyChangedEventArgs<object> e)

{

    txtSelectedAlienName.Text = ((Alien)trvAliens.SelectedValue).Name;

}

in stead of asking the what’s the new selected Item, we can just ask the TreeView what’s the currently selected item.
By default, SelectedValue is the CLR type behind the currently selected TreeViewItem. 

 

Instead of converting our SelectedValue back to Alien and getting Name, we can set SelectedValuePath to “Name”.

image

And now with this change, SelectedValue isn’t Alien anymore, but the value of Alien.Name.

so, we can re-write our handler to:

void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)

{

    txtSelectedAlienName.Text = trvAliens.SelectedValue.ToString();

}

 

And we get the exact same UI:

image

 

Here’s the XAML Blend generated for us:

<controls:TreeView FontSize="22" Height="350" Width="250" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}" SelectedValuePath="Name"/>

And here’s our updated Event Handler:

void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)

{

    txtSelectedAlienName.Text = trvAliens.SelectedValue.ToString();

}

 

 

 

Changing Visual States – Changing the Collapsed and Extended Visual States

Looking at this last Print screen a bit more closely we can see It uses to types of Icons:

 image  - Expanded Icon

image  - Collapsed Icon

We’d like to change those to:

image - Collapsed Icon

image  - Expanded Icon

 

In order to do that, we’ll have to edit the Template of the TreeViewItem generated by the TreeView.

To do that, we’ll change the TreeView ItemContainerStyle that gets applied on to each generated TreeViewItem.

We’ll select the TreeView, go to “Object –> Edit Other Styles –> Edit ItemContainerStyle –> Edit Copy”.

image

And we’ll call the new Style “AlienItemStyle”.

image

Next, we’ll need to drill into editing the template for the TreeViewItems.

We’ll do that through “Edit Template –> Edit Controls Parts (Template) –> Edit Template”.

image

 

Here’s what we see:

image

 

There are a few VisualStateManager states here. And we’ve got a template that has a few visual elements in it.

One of those is the Expander button.

image

 

In order to change the TreeViewItem Icons we’ll need to edit the Template for the ExpanderButotn.

Right Click on the ExpanderButton –> Edit Template –> Edit Template.

image

 

And here’s what we see:

image

 

Apparently, the TreeViewItem ExpanderButton has two visuals: “CheckedVisual” image  and “UncheckedVisual” image .

We’ll need to replace those with our new Visuals.

 

First, I’ll draw a whole ellipse.

image

And on top of it I’ll draw a rectangle.

image

I’ll select the Ellipse with the Rectangle. Right Click –> Combine –> Subtract.

image

And we’ll get this path:

image

I’ll repeat the process and we’ll also get this path:

image

 

Now, In XAML we’ll cut & paste the names of “CheckedVisual” and “UncheckedVisual” to these new elements.

image

becomes

image

 

Next, we’ll delete the old CheckVisual and UncheckedVisual and place our new ones into the correct position.

image

 

Now, one last thing we have to change before this runs, is making sure the “UncheckedVisual” is hidden during the “Checked” state.

We’ll go the the “Checked” state.

image

Select the “UncheckedVisual” and set it’s opacity to 0.

image

 

Now if we run our sample:

image

 

Here’s the XAML Blend generated for our ListBox:

<controls:TreeView FontSize="22" :Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}" SelectedValuePath="Name" ItemContainerStyle="{StaticResource AlienItemStyle}" />

 

Here’s the the Style that was generated for us:

(it’s quite verbose, so the important parts are highlighted)

<Style x:Key="AlienItemStyle" TargetType="controls:TreeViewItem">

    <Setter Property="IsTabStop" Value="True"/>

    <Setter Property="Padding" Value="3"/>

    <Setter Property="HorizontalContentAlignment" Value="Left"/>

    <Setter Property="VerticalContentAlignment" Value="Top"/>

    <Setter Property="Background" Value="Transparent"/>

    <Setter Property="BorderThickness" Value="1"/>

    <Setter Property="Cursor" Value="Arrow"/>

    <Setter Property="Template">

        <Setter.Value>

            <ControlTemplate TargetType="controls:TreeViewItem">

                <Grid Background="{TemplateBinding Background}">

                    <Grid.ColumnDefinitions>

                        <ColumnDefinition Width="15"/>

                        <ColumnDefinition Width="Auto"/>

                        <ColumnDefinition Width="*"/>

                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>

                        <RowDefinition Height="Auto"/>

                        <RowDefinition Height="*"/>

                    </Grid.RowDefinitions>

                    <vsm:VisualStateManager.VisualStateGroups>

                        <vsm:VisualStateGroup x:Name="CommonStates">

                            <vsm:VisualState x:Name="Normal"/>

                            <vsm:VisualState x:Name="Disabled">

                                <Storyboard>

                                    <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Header" Storyboard.TargetProperty="Foreground">

                                        <DiscreteObjectKeyFrame KeyTime="0">

                                            <DiscreteObjectKeyFrame.Value>

                                                <SolidColorBrush Color="#FF999999"/>

                                            </DiscreteObjectKeyFrame.Value>

                                        </DiscreteObjectKeyFrame>

                                    </ObjectAnimationUsingKeyFrames>

                                </Storyboard>

                            </vsm:VisualState>

                        </vsm:VisualStateGroup>

                        <vsm:VisualStateGroup x:Name="SelectionStates">

                            <vsm:VisualState x:Name="Unselected"/>

                            <vsm:VisualState x:Name="Selected">

                                <Storyboard>

                                    <DoubleAnimation Duration="0" Storyboard.TargetName="select" Storyboard.TargetProperty="Opacity" To=".75"/>

                                </Storyboard>

                            </vsm:VisualState>

                            <vsm:VisualState x:Name="SelectedInactive">

                                <Storyboard>

                                    <DoubleAnimation Duration="0" Storyboard.TargetName="inactive" Storyboard.TargetProperty="Opacity" To=".2"/>

                                </Storyboard>

                            </vsm:VisualState>

                        </vsm:VisualStateGroup>

                        <vsm:VisualStateGroup x:Name="HasItemsStates">

                            <vsm:VisualState x:Name="HasItems"/>

                            <vsm:VisualState x:Name="NoItems">

                                <Storyboard>

                                    <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="Visibility">

                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>

                                    </ObjectAnimationUsingKeyFrames>

                                </Storyboard>

                            </vsm:VisualState>

                        </vsm:VisualStateGroup>

                        <vsm:VisualStateGroup x:Name="ExpansionStates">

                            <vsm:VisualState x:Name="Collapsed"/>

                            <vsm:VisualState x:Name="Expanded">

                                <Storyboard>

                                    <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ItemsHost" Storyboard.TargetProperty="Visibility">

                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

                                    </ObjectAnimationUsingKeyFrames>

                                </Storyboard>

                            </vsm:VisualState>

                        </vsm:VisualStateGroup>

                    </vsm:VisualStateManager.VisualStateGroups>

                    <ToggleButton HorizontalAlignment="Stretch" x:Name="ExpanderButton" VerticalAlignment="Stretch">

                        <ToggleButton.Template>

                            <ControlTemplate TargetType="ToggleButton">

                                <Grid x:Name="Root" Background="Transparent">

                                    <vsm:VisualStateManager.VisualStateGroups>

                                        <vsm:VisualStateGroup x:Name="CommonStates">

                                            <vsm:VisualState x:Name="Normal"/>

                                            <vsm:VisualState x:Name="MouseOver">

                                                <Storyboard>

                                                    <ColorAnimation Duration="0" Storyboard.TargetName="UncheckedBrush" Storyboard.TargetProperty="Color" To="#FF1BBBFA"/>

                                                </Storyboard>

                                            </vsm:VisualState>

                                            <vsm:VisualState x:Name="Disabled">

                                                <Storyboard>

                                                    <DoubleAnimation Duration="0" Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To=".7"/>

                                                </Storyboard>

                                            </vsm:VisualState>

                                        </vsm:VisualStateGroup>

                                        <vsm:VisualStateGroup x:Name="CheckStates">

                                            <vsm:VisualState x:Name="Unchecked">

                                                <Storyboard>

                                                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="CheckedVisual" Storyboard.TargetProperty="(UIElement.Opacity)">

                                                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>

                                                    </DoubleAnimationUsingKeyFrames>

                                                </Storyboard>

                                            </vsm:VisualState>

                                            <vsm:VisualState x:Name="Checked">

                                                <Storyboard>

                                                    <DoubleAnimation Duration="0" Storyboard.TargetName="UncheckedVisual" Storyboard.TargetProperty="Opacity" To="0"/>

                                                    <DoubleAnimation Duration="0" Storyboard.TargetName="CheckedVisual" Storyboard.TargetProperty="Opacity" To="1"/>

                                                </Storyboard>

                                            </vsm:VisualState>

                                        </vsm:VisualStateGroup>

                                    </vsm:VisualStateManager.VisualStateGroups>

                                    <Grid HorizontalAlignment="Right" Margin="2 2 5 2">

                                        <Path x:Name="CheckedVisual" Margin="-10.409,15.842,-5.159,6.095" Width="15.568" Fill="#FFFFF6F6" Stretch="Fill" Stroke="#FF000000" StrokeLineJoin="Miter" Data="M0.5,0.5 L15.067377,0.5 L14.694302,2.2247162 C13.935259,3.899652 12.497602,5.241466 10.703027,5.9499054 C9.8057394,6.3041248 8.8192225,6.5 7.7836885,6.5 C5.7126212,6.5 3.8376207,5.7164989 2.4803879,4.449748 C1.8017712,3.8163719 1.2525964,3.0621843 0.87307578,2.2247162 z"/>

                                        <Path x:Name="UncheckedVisual" Margin="-2.875,9.312,-5.125,6.688" Fill="#FFFFF6F6" Stretch="Fill" StrokeLineJoin="Miter" Data="M0.5,0.5 C4.3659902,0.50000048 7.5,3.634006 7.5,7.5 C7.5,11.365994 4.3659887,14.5 0.5,14.5 z">

                                                        <Path.Stroke>

                                                <SolidColorBrush Color="#FF989898" x:Name="UncheckedBrush"/>

                                            </Path.Stroke>

                                            </Path>

                                    </Grid>

                                </Grid>

                            </ControlTemplate>

                        </ToggleButton.Template>

                    </ToggleButton>

                    <Rectangle x:Name="select" Grid.Column="1" IsHitTestVisible="False" Opacity="0" Fill="#FFBADDE9" Stroke="#FF6DBDD1" StrokeThickness="1" RadiusX="2" RadiusY="2"/>

                    <Rectangle x:Name="inactive" Grid.Column="1" IsHitTestVisible="False" Opacity="0" Fill="#FF999999" Stroke="#FF333333" StrokeThickness="1" RadiusX="2" RadiusY="2"/>

                    <Button Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" x:Name="Header" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Grid.Column="1" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" ClickMode="Hover">

                        <Button.Template>

                            <ControlTemplate TargetType="Button">

                                <Grid Background="{TemplateBinding Background}">

                                    <vsm:VisualStateManager.VisualStateGroups>

                                        <vsm:VisualStateGroup x:Name="CommonStates">

                                            <vsm:VisualState x:Name="Normal"/>

                                            <vsm:VisualState x:Name="Pressed">

                                                <Storyboard>

                                                    <DoubleAnimation Duration="0" Storyboard.TargetName="hover" Storyboard.TargetProperty="Opacity" To=".5"/>

                                                </Storyboard>

                                            </vsm:VisualState>

                                            <vsm:VisualState x:Name="Disabled">

                                                <Storyboard>

                                                    <DoubleAnimation Duration="0" Storyboard.TargetName="content" Storyboard.TargetProperty="Opacity" To=".55"/>

                                                </Storyboard>

                                            </vsm:VisualState>

                                        </vsm:VisualStateGroup>

                                    </vsm:VisualStateManager.VisualStateGroups>

                                    <Rectangle x:Name="hover" IsHitTestVisible="False" Opacity="0" Fill="#FFBADDE9" Stroke="#FF6DBDD1" StrokeThickness="1" RadiusX="2" RadiusY="2"/>

                                    <ContentPresenter Cursor="{TemplateBinding Cursor}" HorizontalAlignment="Left" Margin="{TemplateBinding Padding}" x:Name="content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>

                                </Grid>

                            </ControlTemplate>

                        </Button.Template>

                    </Button>

                    <ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" Visibility="Collapsed"/>

                </Grid>

            </ControlTemplate>

        </Setter.Value>

    </Setter>

</Style>

I think it’s an important exercise for us to see how much Expression Blend helps us in not dealing with complex XAML syntaxes like this one.

 

 

Changing Visual States – Changing the Disabled Visual State

We’re going to a CheckBox to the form that says '”Allow Alien Selection?” and if it’s not Checked than we’ll disable our TreeView.

image

And here’s the XAML blend generated for this Checkbox:

<CheckBox Content="Enable Alien Selection?" x:Name="cbxEnableTreeView" IsChecked="True"/>

In our code-behind, we’ll change the TreeView IsEnabled (which drills down to TreeView IsEnabled).

        public TreeViewPage()

        {

            // Required to initialize variables

            InitializeComponent();

           

            cbxEnableTreeView.Checked += new RoutedEventHandler(cbxEnableTreeView_Checked);

            cbxEnableTreeView.Unchecked += new RoutedEventHandler(cbxEnableTreeView_Checked);

        }

 

        void cbxEnableTreeView_Checked(object sender, RoutedEventArgs e)

        {

           trvAliens.IsEnabled = cbxEnableTreeView.IsChecked.Value;

        }

 

Let’s run our sample:

image image

Well, I personally don’t like this IsEnabled=False display for our TreeViewItems.
Our aliens are very bright and still take a lot of attention.

Let’s bring down the TreeViewItem opacity to 50% while it’s Disabled.

Select the Disabled VSM state.

image

 

And change the Grid’s opacity to 70%.

image

 

Let’s run our sample:

image

 

Side-by-side with the original disabled state:

image image

 

Here’s the XAML Blend generated for our Disabled VSM state:

<vsm:VisualState x:Name="Disabled">

    <Storyboard>

        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Header" Storyboard.TargetProperty="Foreground">

            <DiscreteObjectKeyFrame KeyTime="0">

                <DiscreteObjectKeyFrame.Value>

                    <SolidColorBrush Color="#FF999999"/>

                </DiscreteObjectKeyFrame.Value>

            </DiscreteObjectKeyFrame>

        </ObjectAnimationUsingKeyFrames>

        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.Opacity)">

            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.7"/>

        </DoubleAnimationUsingKeyFrames>

    </Storyboard>

</vsm:VisualState>

 

 

 

Changing Visual States – Changing the HasItems and No Items Visual States

We’d like to change the Size of TreeViewItems based on whether or not they have children.

image

 

We’ll Select the NoItems state.

image

 

Select the Header and change it’s scaling to 0.7.

image

 

We’ll select the HasItems VSM State and set the Header scaling to 1.1.

image

 

Let’s run on our sample:

image

 

Here’s a side-by-side with the TreeView from the previous sample:

image image

 

Here’s the XAML Blend generated for our VSM states:

<vsm:VisualStateGroup x:Name="HasItemsStates">

    <vsm:VisualState x:Name="HasItems">

        <Storyboard>

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">

                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.1"/>

            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">

                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.1"/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

    </vsm:VisualState>

    <vsm:VisualState x:Name="NoItems">

        <Storyboard>

            <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="Visibility">

                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>

            </ObjectAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">

                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.7"/>

            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">

                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.7"/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

    </vsm:VisualState>

</vsm:VisualStateGroup>

 

 

 

Changing Visual States – Changing the Selected and SelectedInactive Visual State

Our aliens don’t like that the selected Alien has blue background. Being aliens, they asked us to change the background color to green and we’ll oblige.

Here’s the new Select background side by side with the old select background.

image image

 

To achieve this effect, we’ll start editing the “Selected” VSM State.

image

 

We’ll select the “select” element.

image

 

And change it’s Fill from Blue

image

to green

image

 

Let’s run our sample:

image

Here’s he XAML blend generated for our VSM state:

    <vsm:VisualState x:Name="Selected">

        <Storyboard>

            <DoubleAnimation Duration="0" Storyboard.TargetName="select" Storyboard.TargetProperty="Opacity" To=".75"/>

            <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="select" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">

                <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFBFE9BA"/>

            </ColorAnimationUsingKeyFrames>

        </Storyboard>

    </vsm:VisualState>

 

 

 

Virtualizing the TreeView with TreeViewItem’s Expanded & Collapsed Events

Let’s start from adding a new TreeView.

We want this TreeView to display all combinations of letters in the world.

image

Problem is, it’s quite a few Items and TreeView has no built in virtualization. So let’s start from scratch here.

We’ll start from displaying a TreeView of all single charcters.

        public TreeViewCharsPage()

        {

            // Required to initialize variables

            InitializeComponent();

            this.Loaded +=new RoutedEventHandler(TreeViewCharsPage_Loaded);

        }

 

        private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)

        {

            for (int i = 97; i < 122; i++)

            {

                TreeViewItem newItem = new TreeViewItem() {Header = (char) i};

                trvChars.Items.Add(newItem);

            }

        }

 

Let’s run our sample:

image

 

OK, that’s a good start. Now, we’d like to “pretend” that all combinations of “a” + all letters between A to Z are under “a”. Same goes to the rest of the characters.

So, at this point, we can start nesting loops. So the first loop would create 26 characters between A-Z.  And the next one would create another 26*26 TreeViewItems, and the one in that would create 26*26*26 ad so on.

We don’t want to waste that much memory because that would burden our application and make it sluggish.

So we’ll use a different strategy – we’ll pretend all TreeViewItems are already there and when the user Expends a TreeViewItem we’ll create them.

First move here is create a mock-TreeViewItem to show the TreeViewItem Icon.

        private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)

        {

            for (int i = 97; i < 122; i++)

            {

                TreeViewItem newItem = new TreeViewItem() {Header = (char) i};

                newItem.Items.Add(new TreeViewItem());

                trvChars.Items.Add(newItem);

            }

        }

Let’s run our sample:

image

But when we expand our TreeViewItem, it’s still empty:

image

So let’s use the Expanded event to add the new TreeViewItems.

        private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)

        {

            for (int i = 97; i < 122; i++)

            {

                TreeViewItem newItem = new TreeViewItem() {Header = (char) i};

                newItem.Items.Add(new TreeViewItem());

                newItem.Expanded += new RoutedEventHandler(newItem_Expanded);

                trvChars.Items.Add(newItem);

            }

        }

 

        void newItem_Expanded(object sender, RoutedEventArgs e)

        {

        }

The first thing we need to do here is convert sender to a TreeViewItem (which is always the sender of this event)

void newItem_Expanded(object sender, RoutedEventArgs e)

{

    TreeViewItem expanded = (TreeViewItem) sender;

}

 

Next, we’ll loop through the letters in the ABC and add them to the Expanded TreeViewItem:

void newItem_Expanded(object sender, RoutedEventArgs e)

{

    TreeViewItem expanded = (TreeViewItem) sender;

    for (int i = 97; i < 122; i++)

    {

        TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };

    }

}

 

And we’ll make sure to add this new TreeViewItem to the expanded TreeViewItem.

void newItem_Expanded(object sender, RoutedEventArgs e)

{

    TreeViewItem expanded = (TreeViewItem) sender;

    for (int i = 97; i < 122; i++)

    {

        TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };

        expanded.Items.Add(newItem);

    }

}

 

Let’s run our sample:

image

 

And now let’s add the final piece of puzzle which is a demi-object and link the new TreeViewItem’s Expanded event to the same handler.

void newItem_Expanded(object sender, RoutedEventArgs e)

{

    TreeViewItem expanded = (TreeViewItem) sender;

    for (int i = 97; i < 122; i++)

    {

        TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };

        newItem.Items.Add(new TreeViewItem());

        newItem.Expanded += new RoutedEventHandler(newItem_Expanded);

        expanded.Items.Add(newItem);

    }

}

 

Let’s run our sample and navigate all the way down to “Jhonny”.

image image image

image image

If we were to create all this TreeViewItems up front we’ll need to create than 25^1+25^2+25^3+25^4+25^5+25^5 items =  254,313,150 TreeViewItems.

So through using the Expanded event (and the Collapsed event) we can delay creating TreeViewItems until their necessary.

 

Here’s the XAML Blend generated for us:

<controls:TreeView x:Name="trvChars" />

 

Here’s the code we wrote:

private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)

{

    for (int i = 97; i < 122; i++)

    {

        TreeViewItem newItem = new TreeViewItem() {Header = (char) i};

        newItem.Items.Add(new TreeViewItem());

        newItem.Expanded += new RoutedEventHandler(newItem_Expanded);

        trvChars.Items.Add(newItem);

    }

}

 

void newItem_Expanded(object sender, RoutedEventArgs e)

{

    TreeViewItem expanded = (TreeViewItem) sender;

    for (int i = 97; i < 122; i++)

    {

        TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };

        newItem.Items.Add(new TreeViewItem());

        newItem.Expanded += new RoutedEventHandler(newItem_Expanded);

        expanded.Items.Add(newItem);

    }

}

(I’m tempted to refactor this code, but let’s leave it like this for example sake)

 

 

--- Justin-Josef Angel

Microsoft Silverlight Program Manager

Posted by JustinAngel | 15 comment(s)
Filed under:

Silverlight Design Time Extensibility

Hi Folks,

Whenever someone speaks about developers & designers working together on Silverlight/WPF they talk about the mythical “Designer-Developer workflow”.
Even if you’re your own designer, that workflow is always ever present when you switch between “designer” and “developer” hats.

I’m a huge supporter of using Tools in order to build your UI. HUGE.
I can’t for the life of me understand why someone would choose to be non-productive and XAML things manually.

One of the things you’ll find out when working with these UI Building tools (Expression Blend & Visual Studio WPF/Silverlight Designer) is that they can be a huge boost to productivity when used correctly.
This blog post however, is not about using tools, it’s about helping those who use your controls with tools.
It’s about the developers landing a hand to make the designer experience easier and frictionless.

Control vendors and people who author custom controls often find themselves wishing they could give a better experience for their custom controls.
However, there’s a huge lack of public information on this topic. And I’ve decided to correct this situation with this short 50+ pages article.

This is also relevant for any developer working with a designer on his own project.

The solution we’ve created here is available at: SilverlightControls.zip

 

 

Introduction

We’ll start by looking at the way design time assemblies work.

Consider the following class:

    public class myControl : Control

    {

        public string MyStringProperty { get; set;  }

    }

Now, consider the following class with some design-time attributes:

    [Description("I am a control")]

    public class myControl : Control

    {

        [Description("I am a property")]

        public string MyStringProperty { get; set;  }

    }

The way design-time assemblies work is they separate design time attributes from the actual class. So our class would look like so:

    public class myControl : Control

    {

        public string MyStringProperty { get; set;  }

    }

And in our design-time assembly there would be code equivalent to this one:

   AddCallback(typeof(myControl), builder =>

       builder.AddCustomAttributes(new DescriptionAttribute("I am a control 2")));

 

   AddCallback(typeof(myControl), builder =>

       builder.AddCustomAttributes("MyStringProperty", new DescriptionAttribute("I am a property")));

 

Without delving too much into the API we can see that the first line says “myControl has the following DescriptionAttribute”, and the second line says “myControl.MyStringProperty has this DescriptionAttribute”.

We separate out the Design Time code from the Runtime code.

Let’s see how this looks like when we run it in Expression Blend:
image image  


What are the advantages of this approach?

  1. Decoupling design time code from runtime code helps create better code. (Separation of concerns, POCO, single responsibility and what not)
  2. Changing design time properties based on tooling.  This approach allows supporting different design time attributes based on which tool they are run at.
  3. Design time changes do no require recompiling the runtime assemblies.
  4. Adding design time properties can be done by someone who isn’t the runtime assemblies author.
  5. advanced design-time support doesn’t require us to register visual studio packages with weird GUIDs.

 

 

Reference Architecture

There’s a lot of steps here. Essentially we’d like to create the following architecture:

image

Not confusing at all, Is it? Simply speaking, it’s just the reference model.

We’ll create 3 new projects.

image

*.Design.dll – will contain design time attributes that work for both Visual Studio WPF/Silverlight designer and Expression Blend.
*.VisualStudio.design.dll – Will contain design time features that only work in Visual studio.
*.Expression.design.dll – will contain design time features that only work in Expression blend.

This naming conventions are mandatory.

 

The first step is adding a reference to the project which we’re adding design-time support for from the design-time assemblies:

image

Add a reference to the Shared design time DLLs:

image

And add a reference between the Blend design-time project to blend specific assemblies:

image

 

 

Setup

1. Add a Silverlight Class Library that will contain our Controls. We’ll call it SilverlightControls.

image

2. Add a myControl class.

public class myControl : Control

{

    public string MyStringProperty

    {

        get { return GetValue(MyStringPropertyProperty) as string; }

        set { SetValue(MyStringPropertyProperty, value); }

    }

 

    public static readonly DependencyProperty MyStringPropertyProperty =

        DependencyProperty.Register("MyStringProperty",typeof(string), typeof(myControl), null);  

}

 

3. Create the shared design time DLL. Since we named the control assembly “SilverlightControls” this assembly must be called “SilverlightControls.Design”.
I’ve chosen to create it of type “WPF Custom Control library” since I’ll be using some some WPF features later on. 
You can just add references manually later on to a normal Class Library project if you’d like.

image

 

4. Add a reference from “SilverlightControls.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and the Silverlight System.Windows.dll which holds our base classes.
image

image

(Silverlight 2 Reference directory – defaults on my machine to c:\Program Files\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies)

image

And here are our references: 

image

 

5. Create the visual studio design time DLL.
Since we named the assembly it’s adding design-time for “SilverlightControls” this assembly must be called “SilverlightControls.VisualStudio.Design”.
image

 

6. Add a reference from “SilverlightControls.VisualStudio.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and the Silverlight System.Windows.dll.
image

image
image

And here are our references:

image

 

7. Create the expression blend design time DLL.
Since we named the assembly it’s adding design-time for “SilverlightControls” this assembly must be called “SilverlightControls.Expression.Design”.
image

 

8. Add a reference from “SilverlightControls.Expression.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and Blend specific dlls.
image

image

(From the Blend application directory, default on my machine is: c:\Program Files\Microsoft Expression\Blend 2\)
 image

And here are our references:

image

 

9. We need to add a class that’s Implements the IReigsterMetadata interface in all 3 design-time assemblies.

I use the following template for all three.

public class MetadataRegistration : IRegisterMetadata

{

    private static AttributeTable _customAttributes;

    private static bool _initialized;

 

    public void Register()

    {

        if (!_initialized)

        {

            MetadataStore.AddAttributeTable(CustomAttributes);

            _initialized = true;

        }

    }

 

    public static AttributeTable CustomAttributes

    {

        get

        {

            if (_customAttributes == null)

            {

                _customAttributes = new CustomMetadataBuilder().CreateTable();

            }

            return _customAttributes;

        }

    }

 

    private class CustomMetadataBuilder : AttributeTableBuilder

    {

        public DeveloperMetadataBuilder()

        {

            // TODO: Add Design time code here!

        }

 

        private void AddTypeAttributes(Type type, params Attribute[] attribs)

        {

            base.AddCallback(type, builder => builder.AddCustomAttributes(attribs));

        }

 

        private void AddMemberAttributes(Type type, string memberName, params Attribute[] attribs)

        {

            base.AddCallback(type, builder => builder.AddCustomAttributes(memberName, attribs));

        }

    }

}

I won’t go into what the class does and what IRegisterMetadata & AttributeTableBuilder are, you’ve got MSDN for that. Suffice to say, that they are the classes from the extensibility framework that allow us to add design-time support for our classes.
As well, We won’t go into what my template does. Basically it ensures that Metadata is only created once, and provides easy access methods we’ll be using throughout this article.

So here’s out current project structure:

image

Note that we have one metadata class per project.

 

10. Setup design time files copy.
The design time DLL files must be located in the same directory as the runtime DLL. 
I like having this step of the process of copying the *design.dll files to the runtime debug directory automated. You can choose not to automate this step and do it manually.

The way I’ve chosen to do the automated copying is add a post-build action to each of the design-time project.

Right Click on our project file –> Properties –> Build Events.
Paste this into the Post Build Events: copy "$(TargetPath)" "$(SolutionDir)\SilverlightControls\Bin\Debug"

image

Repeat this step for all 3 design-time projects.

It’s important to note that you can do this step in multiple ways, I’ve chosen to use Post-Build action.

 

 

Creating our testing project
1. We’ll create a new project in Expression blend that uses our DLL. Throughout this article we’ll use this project to preview our design-time improvements. 

image

2. Add a reference to our “SilverlightControls.dll”.

image

image

 

 

What just happened here?

We have 2 restrictions on the design time DLLs. Because of those 2 restrictions they get automatically loaded up by the tools:

  1. Design time DLLs are in the directory as the runtime DLL.
  2. Design time DLLs follow a naming convention.
    • <RuntimeName>.Design.dll – for the shared design time
    • <RuntimeName>.Expression.Design.dll – for expression blend design time
    • <RuntimeName?.VisualStudio.Design.dll – for Visual studio wpf/silverlight designer design time

 

Now that we have all the infrastructure in place, let’s start using it.

 

 

 

DescriptionAttribute (Shared)

Description is a way for us to show “Intellisense-like” comments in our design-time tools. 
This attribute works for both Classes and properties.

When we say an attribute is “Shared” it means that it works in both tools and should be placed in the shared design-time dll.
All though Visual studio currently  has a read-only Silverlight design surface, once it gets an editable design surface, those would be supported by both tools.

So let’s add the DescriptionAttribute to both myControl and MyControl.MyStringProperty in the SilverlightControls.Design.MetadataRegistration type.

            public CustomMetadataBuilder()

            {

                AddTypeAttributes(typeof(myControl),

                    new DescriptionAttribute("I am a control"));

 

                AddMemberAttributes(typeof(myControl),"MyStringProperty",

                    new DescriptionAttribute("I am a property"));

            }

We’ll compile our application and see those descriptions in Expression Blend.

image

image

 

 

DisplayNameAttribute (shared)

Display names are used to show friendlier names of type members.

    AddMemberAttributes(typeof(myControl),"MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"));

And this is how it looks like in Blend:

image

 

 

 

CategoryAttribute (shared)

Each property has a category by default. Blend features the following categories for our control:

image

By default our property gets added to the “Miscellaneous” Category. And if we’re fine with it, we can just keep it there.

We might want to add our property to an existing category, like “Common Properties”.

AddMemberAttributes(typeof(myControl),"MyStringProperty",

    new DescriptionAttribute("I am a property"),

    new DisplayNameAttribute("My String Property"),

    new CategoryAttribute("Common Properties"));

 

And we can see in Blend that it did move to that Category:

image

 

We might also like to create our own category called “My Category”.

AddMemberAttributes(typeof(myControl),"MyStringProperty",

    new DescriptionAttribute("I am a property"),

    new DisplayNameAttribute("My String Property"),

    new CategoryAttribute("My Category"));

image

 

Blend would even group properties in the same custom category together.

Let’s add another property of type Integer to our control:

public class myControl : Control

{

    public string MyStringProperty

    {

        get { return GetValue(MyStringPropertyProperty) as string; }

        set { SetValue(MyStringPropertyProperty, value); }

    }

 

    public static readonly DependencyProperty MyStringPropertyProperty =

        DependencyProperty.Register("MyStringProperty",typeof(string), typeof(myControl), null);

 

    public int MyIntProperty

    {

        get { return (int)GetValue(MyIntPropertyProperty) ; }

        set { SetValue(MyIntPropertyProperty, value); }

    }

 

    public static readonly DependencyProperty MyIntPropertyProperty =

        DependencyProperty.Register("MyIntProperty", typeof(int), typeof(myControl), null);  

}

We’ll add it to our custom category.

    AddMemberAttributes(typeof(myControl),"MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"),

        new CategoryAttribute("My Category"));

 

    AddMemberAttributes(typeof(myControl), "MyIntProperty",

        new CategoryAttribute("My Category"));

And we can see both properties in ‘My Category’ in Blend:

image

 

 

BrowsableAttribute (shared)

Browsable attribute allows us to hide properties that should not be available in Blend’s Data pane or Visual Studio’s properties windows.

    AddMemberAttributes(typeof(myControl),"MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"),

        new CategoryAttribute("My Category"));

 

    AddMemberAttributes(typeof(myControl), "MyIntProperty",

        new CategoryAttribute("My Category"),

        BrowsableAttribute.No);

When we run this in Blend we can see the property isn’t on the Data Pane:

image

We can use the browsable attribute to hide away inherited properties as well as our own defined properties.

 

 

EditorBrowsableAttribute (Blend)

I’ve marked this property as Blend only, but that’s just from my personal guesses as to the Visual Studio Silverlight Designer. This might be supported in some fashion in VS, but I’d suggest looking into it before using it there.

Editor Browsable Advanced state allows you in Blend to hide properties in a category unless the user decided he’d like to view those.
Basically, Hiding away seldom used properties to keep that category clear.

Let’s open up the Metadata file in the expression blend metadata project and add the following design-time:

image

    AddMemberAttributes(typeof (myControl), "MyIntProperty",

            new EditorBrowsableAttribute(EditorBrowsableState.Advanced));

 

Here’s the Blend display for that category:

image

We can see there’s a little arrow hidden on the Category:

image

If we click it, It’ll open up and reveal all Advanced properties in that category:

image

If the user loses focus from our control and selects it again, the category would show us as closed again:

image

So the assumption here is “we’ll hide it from you until you need it specifically for something”.

 

Where is this attribute used for by Silverlight Framework properties? A lot of places.

Here’s the “Layout” category in Silverlight:

image

And if we click it:

image

We can see why we would want to use this attribute to avoid an explosion of properties in the same category.

 

 

TypeConverterAttribute (Shared)

TypeConverters don’t work the same in blend as they do in Visual Studio for the full .Net framework.

Originally, they were meant to provide value transitions from the UI to the underlying model and back. That doesn’t really work in Blend 2 SP1 today.
Which if fine, because it does a few other nifty stuff.

One thing TypeConverters do in Blend (and I’m guessing Visual Studio Silverlight Designer) is support standard values. Let’s see that in action. 

Let’s add another property to our class of Type object called “MyObject”.

public class myControl : Control

{

    public string MyStringProperty

    {

       

    }

 

    public int MyIntProperty

    {

       

    }

 

    public object MyObjectProperty

    {

        get { return (object)GetValue(MyObjectPropertyProperty); }

        set { SetValue(MyObjectPropertyProperty, value); }

    }

 

    public static readonly DependencyProperty MyObjectPropertyProperty =

        DependencyProperty.Register("MyObjectProperty", typeof(object), typeof(myControl), null);  

}

Here’s the default design time for that property:

image

We’ll start off by creating this simple TypeConverter:

public class myTypeConverter : TypeConverter

{

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)

    {

        return true;

    }

 

    public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)

    {

        return new StandardValuesCollection(new string[] { "Hello" ,"World", "foo", "bar"});

    }

}

All this type converter does is write some code that suggests possible values for any property using this TypeConverter.

Next in the shared design-time metadata we’ll add a reference to this TypeConverter:

    AddMemberAttributes(typeof(myControl), "MyObjectProperty",

        new CategoryAttribute("My Category"),

        new TypeConverterAttribute(typeof(myTypeConverter)));

And when we run this in Blend:

image

We get a list of those values suggested by code that we can select from.

 

Similarly we can use any of the built-in TypeConverters, like Boolean Converter:

    AddMemberAttributes(typeof(myControl), "MyObjectProperty",

        new CategoryAttribute("My Category"),

        new TypeConverterAttribute(typeof(BooleanConverter)));

And here is how it looks like in Blend:

image

 

Some TypeConverters even give the tooling visual cues on how to deal with the current property.
A good example of that is ExpandleTypeConverter shown here:

    AddMemberAttributes(typeof(myControl), "MyObjectProperty",

        new CategoryAttribute("My Category"),

        new TypeConverterAttribute(typeof(ExpandableObjectConverter)));

And here is how it looks like in Blend:

image

And clicking the “new” button causes the object dialog to pop-up:

image

image

 

 

PropertyOrderAttribute (Shared)

Property order attribute allows us to order properties in an existing category in before/after other properties.

We’ll need 3 properties for this demonstration, so I’m taking MyIntProperty which is “Advanced” out of the advanced area. (in the blend design time metadata file)

    //AddMemberAttributes(typeof (myControl), "MyIntProperty",

    //        new EditorBrowsableAttribute(EditorBrowsableState.Advanced));

After we cancel out hiding the MyIntProperty, here’s our default property ordering:

image

It’s alphabetical order based.

First “MyIntProperty”, second “MyObjectProperty” and third “My String Property”.

We’ll start by saying that we want MyStringProperty to be the first property.

    AddMemberAttributes(typeof(myControl), "MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"),

        new CategoryAttribute("My Category"),

        new PropertyOrderAttribute(PropertyOrder.Early));

And here is how that looks like in Blend:

image

 

I kinda snuck in that PropertyOrder class. Let’s take a closer look a PropertyOrder static members.

image

OK, now we want to have MyIntProperty to show up last.

    AddMemberAttributes(typeof(myControl), "MyIntProperty",

        new CategoryAttribute("My Category"),

        new PropertyOrderAttribute(PropertyOrder.Late));

And we can see in Blend that the int property is now the last one:

image

 

What happens now if we define MyObjectProperty and “My String Property” as both PropertyOrder.Early?

    AddMemberAttributes(typeof(myControl), "MyObjectProperty",

        new CategoryAttribute("My Category"),

        new TypeConverterAttribute(typeof(ExpandableObjectConverter)),

        new PropertyOrderAttribute(PropertyOrder.Early));

They both get sorted by property order first and than alphabetical order:

image 

Since both “MyObjectProperty” and “My String Property” have equal property order, they get sorted out internally by alphabetical order.

Now, for exercise sake, I’d like for us to keep the current Property Ordering equality and sort those property manually internally.
We’ll try and move “My String Property” to show up before “MyObjectProperty”.

    AddMemberAttributes(typeof(myControl), "MyObjectProperty",

        new CategoryAttribute("My Category"),

        new TypeConverterAttribute(typeof(ExpandableObjectConverter)),

        new PropertyOrderAttribute(PropertyOrder.CreateAfter(PropertyOrder.Early)));

And here we can see that MyObjectProperty is ordered after “My String property”:

image

 

It’s important to note that PropertyOrder.CreateBefore/After methods return a PropertyOrder, so we could keep ordering manually with as much granularity as we’d like.

 

 

 

NumberRangeAttribute, NumberIncrementAttribute, NumberFormatAttribute (Blend)

It’s important to note that this attributes live in the Expression DLLs, and therefore can only be used in Blend.

Let’s have a look at a property we’ve all used that have these attributes declared on it: Opacity

image

The first thing we can notice is that there’s a ratio issue here. Opacity has a 100 time multiplier in display formatting.

Next we notice that there’s a ‘%’ symbol next to our multiplied number.

Now, we can start dragging with our mouse and increment and decrement the current value.

image image

image

if we drag with our mouse pressed and the SHIFT key pressed we start seeing larger increments:

image image

or we drag and the CTRL + SHIFT keys are pressed, we’ll see much smaller increments:

image image

We can see two things here:

1. There’s a 3 types of increments/decrements – Small, Large and normal

2. There’s a range of number between 0-100

Couple that that with the formatting which we’ve previously seen, that’s basically what these properties do.

 

Let’s take a look at the constructors for these attributes:

image

NumberRangeAttribute allows us to specify the Minimum & Maximum for the drag operation.

 

image

NumberIncrementAttribute allows us to specify the small, default and large increments.

 

image

NumberFormatAttribute allows us to specify how to format the displayed text, what numeric precision we’d like to show and what is the display ratio we’d like to use.

 

In our blend metadata file, let’s add the following attributes for MyIntProperty:

We’d like to limit it’s underlying range between 1-100, specify increments of 0.5, 1 and 5, have a display ratio of x10 and format as “X%”.

We’ll start by limiting the range between 1-100:

                AddMemberAttributes(typeof(myControl), "MyIntProperty",

                    new NumberRangesAttribute(null, 1, 100, null, null));

Add the 0.5, 1 and 5 increments for small, default and large increments (respectively):

                AddMemberAttributes(typeof(myControl), "MyIntProperty",

                    new NumberRangesAttribute(null, 1, 100, null, null),

                    new NumberIncrementsAttribute(0.5, 1, 5));

And finish off by adding a ‘X%’ format with a display ratio of 10.

                AddMemberAttributes(typeof(myControl), "MyIntProperty",

                    new NumberRangesAttribute(null, 1, 100, null, null),

                    new NumberIncrementsAttribute(0.5, 1, 5),

                    new NumberFormatAttribute("0'%'", null, 10));

Let’s run this sample and start dragging normally: :

image image

And drag with the SHIFT key pressed:

image image

Here’s the actual underlying value of MyIntProperty:

image

So we got everything we wanted: Range between 0-100, display ratio, formatting, and increments.

 

 

ToolboxBrowsableAttribute (Shared)

Say we have an assembly with a few controls and we’re adding that assembly to the Visual studio Silverlight Toolbox or the Asset Library in Blend.

If we’d like to hide one of these controls, we could use ToolboxBrowsableAttribute for that. A good example of other controls that do that are ComboBox that is visible but ComboBoxItem is hidden, Calender is visible by DayButton isn’t and so on.

This is the only design-time attribute that as of today works in both tools (Visual Studio 2008 SP1, Blend 2 SP1).

Let’s add another control and build.

    public class myOtherControl : Control

    {

 

    }

In Blend we can see the new control in the asset gallery:

image 

In Visual studio we’ll go to the “Choose Items” dialog, browse to the “SIlverlightControls.dll” and click OK.

image image

image

 

Now, we’ve stated that we want to hide myOtherControl from these dialogs. So we’ll add a ToolboxBrowsableAttribute to it in the shared design-time metadata.

AddTypeAttributes(typeof(myOtherControl),

        new ToolboxBrowsableAttribute(false));

 

After compiling and restarting blend, we can see that blend no longer shows the control in the Blend Asset Gallery:

image

And in Visual studio’s “Choose Items” dialog we won’t see myOtherControl anymore:

image image

 

 

Inline Editors (Shared)

Editors are custom elements that are found in the Visual Studio Property window and the Blend Data Pane.

Here’s a few examples of well know editors:

image

image

image

We’ll put all of our custom editors in a custom project to hold them and avoid Silverlight dlls ambiguous references.

image

Naming convention and the very existence of this project are optional.

We’ll add the necessary design-time references.

image

We’ll also make sure to add reference from “SilverlightControls.Design.dll” to “SilverlightControls.Design.Editors.dll”.

 

 

We’ll start off by adding a ResourceDictionary in our Editors project called “EditorDictionary”.

image

In the EditorResources.xaml we’ll add the following DataTemplate:

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

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

    <DataTemplate x:Key="ColorsComboBox">

        <ComboBox Text="{Binding StringValue}" IsEditable="True">

            <ComboBoxItem Background="Red">Red</ComboBoxItem>

            <ComboBoxItem Background="Blue">Blue</ComboBoxItem>

            <ComboBoxItem Background="Green">Green</ComboBoxItem>

            <ComboBoxItem Background="Yellow">Yellow</ComboBoxItem>

            <ComboBoxItem Background="Black">Black</ComboBoxItem>

        </ComboBox>

    </DataTemplate>

</ResourceDictionary>

It’s a straight forward DataTemplate.
We’ve got a ComboBox with a few ComboBoxItems. The only important thing is making sure there’s a b binding to either StringValue or Value which are sent to the DataTemplate by the design tool.

Next we’ll add a static class which will enable easy access to our Resource dictionary. (not mandatory, but I think it’s a good pattern)

    public static class EditorDictionary

    {

        private static ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(new Uri("SilverlightControls.Design.Editos;component/EditorDictionary.xaml", UriKind.Relative));

 

        public static DataTemplate ColorsComboBox

        {

            get

            {

                return dictionary["ColorsComboBox"] as DataTemplate;

            }

        }

    }

All this class does is load the Resource dictionary and has a static property to return our DateTemplate.

Now let’s get to the interesting part, we’ll create a custom inline editor that shows the DataTemplate.

    public class CustomInlineEditor : PropertyValueEditor

    {

        public CustomInlineEditor()

        {

            this.InlineEditorTemplate = EditorDictionary.ColorsComboBox;

        }

    }

Very straight forward. PropertyValueEditor has one interesting thing about it - the InlineEditorTemplate which we’ve set to the DataTemplate from the resource dictionary.

One last thing we have to do is say that MyControl.MyStringProperty has a custom inline editor.

    AddMemberAttributes(typeof(myControl), "MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"),

        new CategoryAttribute("My Category"),

        new PropertyOrderAttribute(PropertyOrder.Early),

        PropertyValueEditor.CreateEditorAttribute(typeof(CustomInlineEditor)));

 

Now if we run this in Blend:

image

And if we choose “Green”.

image 

The driving force behind the back-and-forth between the UI and the underlying XAML is the binding on the ComboBox to StringValue.

I think that the best takeaway from this exercise shouldn’t be “rainbow colored ComboBoxes are cool” rather “we can put any element as an inline editor”.

 

 

Property editors?

Now that we’ve seen how an Inline property editor looks like and how it works, we should get acquainted with the 3 types of Property editors.

Inline Property Editor – follows the “one line with a property name and editor” schema.
clip_image002 

clip_image004

 

Extended Property Editor – has an inline editor, but can also have a popup or some additional area opened by user interaction.
clip_image006

clip_image008 

clip_image010

 

Dialog property editor – An Inline property editor with a button that triggers a dialog editor.
clip_image012
clip_image014

clip_image016

 

 

 

Expended Property Editor

As we’ve seen, an expended property editor is just an inline editor that based on user interaction can grab more screen real estate. Let’s see how to build one.

We’ll start of by creating another DataTemplate in our EditorResources.xaml file for the InlineValueEditorTemplate:

xmlns:PropertyEditing="clr-namespace:Microsoft.Windows.Design.PropertyEditing;assembly=Microsoft.Windows.Design"

    <DataTemplate x:Key="SimpleTextBox">

        <StackPanel Orientation="Horizontal">

            <TextBox Text="{Binding StringValue}" />

            <PropertyEditing:EditModeSwitchButton />

        </StackPanel>

    </DataTemplate>

We can see it’s just a TextBox with a binding to StringValue we’ve seen before in the inline property editor.

Interesting to note is that there’s an “EditModeSwitchButton”.
This is a built-in extensibility button that toggles the extended template in & out of view. All that button does is hook-up to extensibility’s PropertyValueEditorCommands which take care of showing and hiding the extended view.

Now that we have our InlineValueEditorTemplate, we’ll add another DataTemplate for ExtendedValieEditorTemplate:

    <DataTemplate x:Key="HelloWorldListBox">

        <ListBox SelectedItem="{Binding StringValue}">

            <ListBox.ItemsSource>

                <x:Array Type="{x:Type System:String}">

                    <System:String>Hello</System:String>

                    <System:String>World</System:String>

                    <System:String>foo</System:String>

                    <System:String>bar</System:String>

                </x:Array>

            </ListBox.ItemsSource>

            <ListBox.ItemTemplate>

                <DataTemplate>

                    <TextBlock Text="{Binding}" />

                </DataTemplate>

            </ListBox.ItemTemplate>

        </ListBox>

    </DataTemplate>

While this DataTemplate might look interesting, it’s really not. It’s just a bunch of strings shown up in a as TextBlock in a Listbox. The only interesting thing about it is that we bound the ListBox’s SelectedItem to StringValue

Next, we’ll create the strongly typed & named resources for these two new templates in our EditorDictionary class:

            public static DataTemplate SimpleTextBox

            {

                get

                {

                    return dictionary["SimpleTextBox"] as DataTemplate;

                }

            }

 

            public static DataTemplate HelloWorldListBox

            {

                get

                {

                    return dictionary["HelloWorldListBox"] as DataTemplate;

                }

            }

And finally we’ll create an ExtendedValuePropertyEditor that uses our two new DataTemplates:

    public class CustomExtendedEditor : ExtendedPropertyValueEditor

    {

        public CustomExtendedEditor()

        {

            this.InlineEditorTemplate = EditorDictionary.SimpleTextBox;

            this.ExtendedEditorTemplate = EditorDictionary.HelloWorldListBox;

        }

    }

 

Last thing we’ll do is hook-up our MyStringProperty with the CustomExtendedEditor:

    AddMemberAttributes(typeof(myControl), "MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"),

        new CategoryAttribute("My Category"),

        new PropertyOrderAttribute(PropertyOrder.Early),

        PropertyValueEditor.CreateEditorAttribute(typeof(CustomExtendedEditor)));

This is how it looks like in Blend:

image

One click on the button we’ll get our ExtendedEditorTemplate in a pop-up:

image

Another click we can see that our ExtendedEditorTemplate is has taken up more space in the data pane:

image

Selecting anyone of the items will cause the Textbox and the underlying property to update:

image image

And one more click on the button will close the extended editor:

image

 

 

Dialog Property Editor

Instead of showing our Extended template under the existing inline editor, we’d like to have it show up in dialog window.

We’ll create a dialog editor with our existing resource dictionary.

    public class CustomDialogEditor : DialogPropertyValueEditor

    {

        public CustomDialogEditor()

        {

            this.InlineEditorTemplate = EditorDictionary.SimpleTextBox;

            this.DialogEditorTemplate = EditorDictionary.HelloWorldListBox;

        }

    }

We’ll need to hook this custom dialog editor up to MyStringProperty:

    AddMemberAttributes(typeof(myControl), "MyStringProperty",

        new DescriptionAttribute("I am a property"),

        new DisplayNameAttribute("My String Property"),

        new CategoryAttribute("My Category"),

        new PropertyOrderAttribute(PropertyOrder.Early),

        PropertyValueEditor.CreateEditorAttribute(typeof(CustomDialogEditor)));

 

When we run this in Blend it’ll look like so:

image

image

And changing the value in the dialog would cause the Textbox and underlying property value to change as well:

 

image 

 

image 

 

 

While Dialog property editors have a similar API to that of extended property editors there’s one main difference – they support transactions.

You can see the “OK” and “Cancel” buttons which basically just fire CommitTransaction or AbortTransaction commands defined in the design time extensiblity DLLs. So, if we click cancel after selecting “World”, the property will revert back to it’s original value of “foo”:
image

image

 

 

Hopefully you’ve learned from this tutorial, there’s a lot more knocks and crannies, but this should definitely get you on your way,

-- Justin Angel

Microsoft Silverlight Toolkit Program Manager

Silverlight Toolkit: HeaderedContentControl & HeaderedItemsControl

 

 

Hi Folks,

 

It’s very common to find ourselves creating a UI that is composed of a “Header” part and “something else”.
In order to better encapsulate that logic and allow more flexability when reusing it, WPF and the Silverlight Toolkit (codeplex.com/Silverlight) introduce two classes: HeaderedContentControl and HeaderedItemsControl.

Let’s deep dive into these controls and see how we can use them to create a more streamlined UI.

 

Setup

1. Create a new project.

image2_thumb

 

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

image10_thumb   image_thumb33[1]

 

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

image13_thumb

 

4. Add a HeaderedContentControl 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.HeaderedPage"

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

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

    <Grid Background="#FFFFFFFF">

        <controls:HeaderedContentControl HorizontalAlignment="Left" VerticalAlignment="Top" Content="HeaderedContentControl"/>

    </Grid>

</UserControl>

 

 

Setting Header & Content Properties

HeaderedContentControl is a very straight-forward control. It’s a Control with two Content areas: Header & Content.

image 

If we don't use the Header property of a HeaderedContentControl – we’re better off using a ContentControl.

So let’s set the Header & Content properties.

Select our HeaderedContentControl and set it’s Header property to “Cow”.

image

Next, Double Click our HeaderedContentControl (or edit the Content property, or even Right click and select “Edit Text”) and put in “Betsy, Age 32”.

image

Here’s the control display we’ll get:

image

 

here’s the XAML Blend created for this HeaderedContentControl:

<controls:HeaderedContentControl HorizontalAlignment="Left" VerticalAlignment="Top" Content="Betsy, Age 32" Header="Cow"/>

 

As a side-note, we could have also put-in more advanced controls into both Header & Content. Expression blend will only support doing that for the Content property, which we’ll change to a short video.

Let’s go back to editing our Content.

image

We’ll Add a MediaElement instead of the existing text.

image

And change it’s MediaElement.Source property to “http://www.silverlight.net/quickstarts/silverlight10/thebutterflyandthebear.wmv” and set MediaElement.AutoPlay to true.

image

Now if we run our sample we’ll see:

image

While a bear isn’t a cow, it’s close enough for us.

Here’s the XAML Blend generated for us:

<controls:HeaderedContentControl HorizontalAlignment="Left" VerticalAlignment="Top" Margin="32,32,0,0" Header="Cow">

    <MediaElement Height="119" Width="216" AutoPlay="True" Source="http://www.silverlight.net/quickstarts/silverlight10/thebutterflyandthebear.wmv"/>

</controls:HeaderedContentControl>

 

If we manually edit the XAML, we could have also used the Header property in the same way and it would look like this:

<controls:HeaderedContentControl HorizontalAlignment="Left" VerticalAlignment="Top" Margin="32,32,0,0" Content="Betsy, Age 32">

    <controls:HeaderedContentControl.Header>

        <MediaElement Height="119" Width="216" AutoPlay="True" Source="http://www.silverlight.net/quickstarts/silverlight10/thebutterflyandthebear.wmv"/>

    </controls:HeaderedContentControl.Header>

</controls:HeaderedContentControl>

The main takeaway from this exercise should be – Header & Content can contain any XAML content.

 

 

 

Styling the Content & Header by setting Template

Let’s try to create a basic GroupBox using our HeaderedContentControl. Something that looks like this:

image

A good first step for us is changing the Header font from Inside the control Template. (we’ll edit the Template directly because we can’t edit the Header property from Expression Blend)

Right click on our HeaderContentControl –> Edit Controls Parts (Template) –> Edit A copy.

image

We’ll call our new ControlTemplate “CowTemplate”.

image

And this is what we’ll see:

image

 

Now we’d like to set the first ContentPresenter’s (which is the Header) Font Size to 22.
However, ContentPresenter doesn’t have a FozeSize property, so we’ll wrap it a ContentControl which would allow us to set it’s FontSize.

So, first we’ve changed the Control Hierachy to:

image

And next we’ll set the ContentControl’s Font size to 22 and Bold.

image

And this is what we got:

image

 

Next, we’ll add a Grey Border that has round corners around our StackPanel.

Right click on our StackPanel ––> Group Into  –> Broder.

image

We’ll set it’s BorderBrush to solid grey, BorderThickness to 2 and CornerRadius to 1.

image

 

And here’s how our GroupBox looks like:

image

here’s the XAML blend created for our HeaderedContentControl:

<controls:HeaderedContentControl HorizontalAlignment="Left" VerticalAlignment="Top"Header="Cow" Content="Betsy, Age 32" Template="{StaticResource CowTemplate}"/>

And for our new DataTemplate:

<UserControl.Resources>

    <ControlTemplate x:Key="CowTemplate" TargetType="controls:HeaderedContentControl">

         <Border BorderBrush="#FFC8C8C8" BorderThickness="2,2,2,2" CornerRadius="1,1,1,1">

            <StackPanel>

                <ContentControl FontSize="22" FontWeight="Bold" FontFamily="Portable User Interface">

                    <ContentPresenter Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" Cursor="{TemplateBinding Cursor}" />

                </ContentControl>

                <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Cursor="{TemplateBinding Cursor}" Margin="{TemplateBinding Padding}" VerticalAlignment="Bottom"  />

            </StackPanel>

        </Border>

    </ControlTemplate>

</UserControl.Resources>

 

 

 

 

Binding the Content & Header by binding in ContentTemplate & HeaderTemplate

Let’s say we have this Cow class:

    public class Cow

    {

        public Cow(string name, int age)

        {

            Name = name;

            Age = age;

        }

 

        public string Name { get; set;  }

        public int Age { get; set;  }

    }

This class is pretty straight forward – one numeric property for Age and one string property for Name.

And we have this form:

image

We’d like to Bind 4 Cows to each of these HeaderedIContentControls.

void HeaderedPage_Loaded(object sender, RoutedEventArgs e)

{

    hdrCow1.DataContext = new Cow("Betsy", 3);

    hdrCow2.DataContext = new Cow("Martha", 1);

    hdrCow3.DataContext = new Cow("Flossy", 5);

    hdrCow4.DataContext = new Cow("Hoss", 2);

}

Eventually we want the page to look like this:

     image 

 

We’ll start off by editing the HeaderTemplate of each HeaderedContentControls and Binding it to Name.

Right Click on HeaderedContentControl –> Edit Other Templates –> Edit HeaderTemplate –> Create empty.

image

And we’ll call this new Template “CowHeaderTemplate”.

image

And here’s what we can see:

image

Our HeaderTemplate is empty (since we chose we want to create an empty template).

We’ll add a ContentControl.

image

And than Bind this ContentControl.Content to Name. We’ll do that by setting it’s Custom Expression to “{Binding Name"}”. 

image

 

 

Next, we’d like to edit our ContentTemplate to show “Cow, Age <Age>”.

Right click on HeaderedContentControl –> Edit Generated Content (ContentTemplate) –> Create Empty.

image

And we’ll call it CowContentTemplate.

image

Again, this DataTemplate is going to be empty as well.

image

We’ll add a TextBlock that says “Cow, Age “ and a ContentPresenter.

image

We’ll Bind the content with a Custom Expression to “{Binding Age}”.

image

 

Additionally, We’ll need to set the Header and Content to pass the DataContext straight to their respective template.

We’ll do that by setting both Header and Contentb properties to “{Binding}”.

image  image

image  image 

 

Now, we’ll run our Application:

image

 

We need to set all templates on all HeaderedContentControls exactly like the templates on the 1st HeaderedContentControl.

Right click on each HeaderedContentControl –> Edit Control Parts (Templates) –> Apply Resource –> Cow Template

image

Right click on each HeaderedContentControl –> Edit HeaderTemplate –> Apply Resource –> CowHeaderTemplateimage

Right click on each HeaderedContentControl –> Edit Generated Content (ContentTemplate)–> Apply Resource –> CowContentTemplate

image

Next we’ll need to set their Header and Content properties to “{Binding}”.

image

image

 

Now we we run we’ll see that all HeaderedContentControls have the same templates:

image 

 

Here’s the XAML blend generated for us:

        <controls:HeaderedContentControl x:Name="hdrCow1" Template="{StaticResource CowTemplate}" HeaderTemplate="{StaticResource CowHeaderTemplate}" ContentTemplate="{StaticResource CowContentTemplate}" Header="{Binding}" Content="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" />

        <controls:HeaderedContentControl x:Name="hdrCow2" Template="{StaticResource CowTemplate}" HeaderTemplate="{StaticResource CowHeaderTemplate}" ContentTemplate="{StaticResource CowContentTemplate}" Header="{Binding}" Content="{Binding}"  HorizontalAlignment="Left" VerticalAlignment="Top" />

        <controls:HeaderedContentControl x:Name="hdrCow3" Template="{StaticResource CowTemplate}" HeaderTemplate="{StaticResource CowHeaderTemplate}" ContentTemplate="{StaticResource CowContentTemplate}" Header="{Binding}" Content="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" />

        <controls:HeaderedContentControl x:Name="hdrCow4" Template="{StaticResource CowTemplate}" HeaderTemplate="{StaticResource CowHeaderTemplate}" ContentTemplate="{StaticResource CowContentTemplate}" Header="{Binding}" Content="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" />

here’s the content for our templates:

    <UserControl.Resources>

        <ControlTemplate x:Key="CowTemplate" TargetType="controls:HeaderedContentControl">

            <Border BorderBrush="#FFC8C8C8" BorderThickness="2,2,2,2" CornerRadius="1,1,1,1">

                <StackPanel>

                    <ContentControl FontSize="22" FontWeight="Bold" FontFamily="Portable User Interface">

                        <ContentPresenter Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" Cursor="{TemplateBinding Cursor}" />

                    </ContentControl>

                    <ContentControl Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Cursor="{TemplateBinding Cursor}" Margin="{TemplateBinding Padding}" VerticalAlignment="Bottom" d:LayoutOverrides="Width" />

                </StackPanel>

            </Border>

        </ControlTemplate>

        <DataTemplate x:Key="CowHeaderTemplate">

            <Grid>

                <ContentControl HorizontalAlignment="Left" VerticalAlignment="Top" Content="{Binding Path=Name}"/>

            </Grid>

        </DataTemplate>

        <DataTemplate x:Key="CowContentTemplate">

            <Grid>

                <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text="Cow, Age " TextWrapping="Wrap"/>

                <ContentControl HorizontalAlignment="Left" VerticalAlignment="Top" Content="{Binding Path=Age}" Margin="60,0,0,0"/>

            </Grid>

        </DataTemplate>

    </UserControl.Resources>

And our code-behind:

void HeaderedPage_Loaded(object sender, RoutedEventArgs e)

{

    hdrCow1.DataContext = new Cow("Betsy", 3);

    hdrCow2.DataContext = new Cow("Martha", 1);

    hdrCow3.DataContext = new Cow("Flossy", 5);

    hdrCow4.DataContext = new Cow("Hoss", 2);

}

 

 

Binding a list of strings to a HeaderedItemsControl

There’s a special case of HeaderedContentControl – HeaderedItemsControl. 
In concept – it’s  basically a a HeaderedContentControl with a list of items inside of it.
(off-beat comment: API-wise though, it’s an ItemsControl that happens to have a Header)

image

Let’s add a HeaderedItemsControl to our page.

image

 

Let’s name it “Cows” and Set it’s Header property to “Cows”.

image

image

 

From our code-behind we’ll set the ItemsSource to a collection of strings.

private void HeaderedItemsPage_Loaded(object sender, RoutedEventArgs e)

{

    Cows.ItemsSource = new string[] { "Betsy", "Martha", "Flossy", "Hoss" };

}

 

And if we’ll run our application we can see this:

image

“Cows” being the Header for our HeaderedItemsControl.

 

Here’s the XAML blend generated for us:

<controls:HeaderedItemsControl x:Name="Cows" Header="Cows"/>

 

 

 

Binding custom CLR types to a HeaderedItemsControl

We’d like to Bind our HeaderedItemsControl to a collection of Cows and have it wind up looking like this:

image

from our code-behind we’ll set the ItemsSource to a collection of cows.

void HeaderedItemsPage_Loaded(object sender, RoutedEventArgs e)

{

    Cows.ItemsSource = new Cow[]

                           {

                               new Cow("Betsy", 3), new Cow("Martha", 1),

                               new Cow("Flossy", 5), new Cow("Hoss", 2)

                           };

}

 

And if we run our sample we’ll see:

image

Not very impressive is it?

 

 

We’d should edit the template for each of our items.

Right click on our HeaderedItemsControl –> Edit Other Templated –> Edit Generated Items (ItemTemplate) –> Create Empty.

image

We’ll name it “CowItemTemplate”.

image

And here’s what we’ll see:

image

We can see that our DataTemplate is empty. So no we need to Add a TextBlock for the name of the Cow.

We’ll set the TextBlock.Text to be databound to Name. We’ll do that by setting it’s value to a Custom Expression of “{Binding Name}”.

image

We’ll add another TextBlock with the Text “, Age ”.

image

And another TextBlock with TextBlock.Text bound to Age.

image

 

 

If we run our example now we can see:

image

 

Here’s the XAML Blend generated for our HeaderedItemsControl:

<controls:HeaderedItemsControl x:Name="Cows" Header="Cows" ItemTemplate="{StaticResource CowItemTemplate}"/>

here’s our DataTemplate XAML:

<UserControl.Resources>

    <DataTemplate x:Key="CowItemTemplate">

        <StackPanel Orientation="Horizontal">

            <TextBlock TextWrapping="Wrap" Text="{Binding Path=Name}"/>

            <TextBlock Text=", Age " TextWrapping="Wrap"/>

            <TextBlock Text="{Binding Path=Age}" TextWrapping="Wrap"/>

        </StackPanel>

    </DataTemplate>

</UserControl.Resources>

And our code-behind:

Cows.ItemsSource = new Cow[]

                       {

                           new Cow("Betsy", 3), new Cow("Martha", 1),

                           new Cow("Flossy", 5), new Cow("Hoss", 2)

                       };

 

 

Styling the HeaderedItemsControl

As you might have noticed our HeaderedItemsControl doesn’t achieve it’s full visual potential right now. (managerial speak for: looks fugly)

Let’s try and make it look a little bit more like this:

image

 

First step is to edit the Template of our HeaderedItemsControl.

image

We’ll call this new ControlTemplate “CowItemsTemplate”.

image

And here’s what we would see:

image

 

First, we’d like to make give the HeaderContent a font size of 22 and set it to bold.

image

Next, we’d like to have a Left Margin of 10 for our Items.

image

We’ll “group into“ our Grid to a Border.

image

Set the Border’s BorderBrush to light silver, BorderThinckness to 2 and CornerRadius to 2.

image

One last thing I’d like us to do here is set Background with a light silver gradient.

First, we’ll select Background.

image

Next, we’ll select Gradient Brush.

image

 

We’ll move our last Gradient stop from the End, to about 10% near the beginning.

 

image

We’ll select the last Gradient stop and change it’s Color to “#FFB5B5B5” (light gray).

image

 

Now, let’s run our sample.

image

 

Here’s the XAML Blend generated for our HeaderedItemsControl:

<controls:HeaderedItemsControl VerticalAlignment="Top" Height="Auto" x:Name="Cows" Header="Cows" ItemTemplate="{StaticResource CowItemTemplate}" Template="{StaticResource CowItemsTemplate}"  />

And the DataTemplate that is our ItemsTemplate:

<ControlTemplate x:Key="CowItemsTemplate" TargetType="controls:HeaderedItemsControl">

    <Border Height="Auto" Width="189" BorderBrush="#FFE0E0E0" BorderThickness="2,2,2,2" CornerRadius="2,2,2,2">

        <Border.Background>

            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

                <GradientStop Color="#FFB5B5B5"/>

                <GradientStop Color="#FFFFFFFF" Offset="0.253"/>

            </LinearGradientBrush>

        </Border.Background>

        <Grid x:Name="Root" Height="Auto" Width="189">

            <Grid.RowDefinitions>

                <RowDefinition Height="Auto" />

                <RowDefinition Height="*" />

            </Grid.RowDefinitions>

            <ContentControl x:Name="HeaderContent" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" FontSize="22" FontWeight="Bold" FontFamily="Portable User Interface" />

            <ItemsPresenter x:Name="Items" Grid.Row="1" Margin="10,0,0,0" />

        </Grid>

    </Border>

</ControlTemplate>

And our code-behind:

void HeaderedItemsPage_Loaded(object sender, RoutedEventArgs e)

{

    Cows.ItemsSource = new Cow[]

               {

                   new Cow("Betsy", 3), new Cow("Martha", 1),

                   new Cow("Flossy", 5), new Cow("Hoss", 2)

               };

}

public class Cow

{

    public Cow(string name, int age)

    {

        Name = name;

        Age = age;

    }

 

    public string Name { get; set;  }

    public int Age { get; set;  }

}

 

 

-- Justin Angel

Microsoft SIlverlight Program Manager

 

(Acknowledgment: Shawn Oster Rules! He has the best Midwestern cow names ever!)

Posted by JustinAngel | 3 comment(s)
Filed under:

Silverlight Toolkit: ViewBox

Hi Folks,

 

I’d like for us to take some time and talk about the new Silverlight Toolkit Viewbox control.

We’ll deep dive on what Viewbox does, how it does it and the few nooks and crannies it has.

 

Setup

1. Create a new project.

image2_thumb

 

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

image10_thumb   image_thumb3

 

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

image13_thumb

 

4. Add a ViewBox 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.ViewBoxPage"

    Width="640" Height="480" xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls" xmlns:SilverlightControlsNovember2008="clr-namespace:SilverlightControlsNovember2008">

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

        <controls:Viewbox Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100"/>

    </Grid>

</UserControl>

 

 

 

Introducing Dobby The Penguin

ViewBox is meant to resize SIlverlight XAML to fit the available screen realestate.

As such, it’s optimal to be used in 2D graphics and less relevant for ordinary controls.

We’ll demo ViewBox with some 2D content. However, my drawing talent is limited to that of a 3yr old.
So, with the gracious approval of Jose Fajardo we’ll be using one of his XAML drawings – Dobby the penguin. 

image

Dobby enjoys snow-boarding, long walks on the beach, and is 320x300 pixels big. (approx. Width x Height)

 image

Dobby has a pretty complex XAML file (which you can find here). In order to reuse Dobby without copy-pasting his XAML all over the place, we’ll create Custom Penguin control.

I’ve pasted the XAML into Blend, and now we’ll “right click –> Make Control” on the penguin canvas.

image

We’ll call our now control “PenguinControl”.

image

 

And here’s the XAML behind our Penguin Control:(you can copy the full XAML from here)

<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.PenguinControl" Width="323" Height="301"

    >

 

    <Grid x:Name="LayoutRoot">

        <Canvas Height="301" x:Name="Penguin" Margin="0,0,317,179" Width="323"> 

            … <!-- Full Xaml Here -->

        </Canvas>

    </Grid>

</UserControl>

 

 

ViewBox with Fill Stretch

We’ve mentioned that ViewBox resizes it’s content based on it’s own size. In order to test that theory we’ll create 3 ViewBoxes:

1. The First (100,100) pixels

2. The second (250,250) pixels.

3. The Third (400,400) pixels.

image

We’ll also set our Viewboxes to use Stretch to Fill.

image

Next, we’ll put Each ViewBox in a Border that has a BorderBrush off Black and a BorderThickness of 1.

image

We’ll click on BorderBrush and than “Solid Color Brush”.

image --> image

And we’ll set BorderThickness to 1.

image

And here’s our ViewBoxes

 image

 

Here’s the XAML blend generated for us up until now:

<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.ViewBoxPage"

    Width="800" Height="480" xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls" xmlns:SilverlightControlsNovember2008="clr-namespace:SilverlightControlsNovember2008">

 

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

        <Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

            <controls:Viewbox Height="100" Width="100" Stretch="Fill" />

        </Border>

        <Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

            <controls:Viewbox Height="200" Width="200" Stretch="Fill" />

        </Border>

        <Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

            <controls:Viewbox Height="400" Width="400"Stretch="Fill" />

        </Border>

    </Grid>

</UserControl>

 

 

Now let’s place Dobby the Penguin into each of our Viewboxes.

image

And we can see Dobby has been resized to Fill the ViewBox.

Stretch.Fill tells the ViewBox –" “make sure this content takes up the entire width & height of the Viewbox”.  

 

 

ViewBox with Uniform Stretch

Fill Stretch makes sure the element fills up both Height & Width.

The only way to scale up an item to fill those both is to distort the ratio between Height & Width.

image

As you can see in this exciting table, Dobby’s fill ratio is equal to that of the viewbox – 1.
However, Dobby’s original ration was 1.07. That means that making Dobby scale to fill distorts him.

If we set the Viewbox Stretch to Uniform we’ll get the same ratio though.

image 

 

And this is how Dobby likes like now:

image

 

Let me crank up mspaint and highlight the new whitespaces in our Viewboxes.

image

We can see that Dobby is now filling all the Width of all the Viewboxes, but not all height.

The reason is that the Viewbox is smart enough how to scale up/down the controls in it without distorting them or changing their ratio.

 

Here’s the XAML Blend generated for us:

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

    <controls:Viewbox Height="100" Width="100" Stretch="Uniform">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

    <controls:Viewbox Height="200" Width="200" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

    <controls:Viewbox Height="400" Width="400" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

 

 

ViewBox with Uniform Stretch, HortizontalAlignment and VerticalAlignment

As we’ve seen a Viewbox with Stretch to Uniform might have some Whitespace left on the Top & Bottom or the Left & Right.

By default, Viewbox places the control in HorizontalAlignment and VerticalAlignment of Center.

We’ll select our Uniform Viewboxes and  set their VerticalAlignment to Top.

image

Let’s see how Dobby looks like now:

image

We can see Dobby still has the same amount of Vertical Whitespace, But now Dobby is aligned to the Top and the Whitespace is all at the Bottom.

 

Here’s the XAML we created here:

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

    <controls:Viewbox Height="100" Width="100" Stretch="Uniform" VerticalAlignment="Top">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

    <controls:Viewbox Height="200" Width="200" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform" VerticalAlignment="Top">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

    <controls:Viewbox Height="400" Width="400" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform" VerticalAlignment="Top">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

 

Similarly if we set VerticalAlignment to Bottom, the opposite of what we’ve just seen would be true.

image

Here’s how Dobby looks like now:

image

And we can see that Dobby is aligned to the Bottom and the Whitespace is on the Top.

 

Here’s the XAML blend generated for us:

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

    <controls:Viewbox Height="100" Width="100" Stretch="Uniform" VerticalAlignment="Bottom">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

    <controls:Viewbox Height="200" Width="200" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform" VerticalAlignment="Bottom">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

    <controls:Viewbox Height="400" Width="400" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform" VerticalAlignment="Bottom">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

 

In a very similar fashion, we could have set HorizontalAlignment for placement of Horizontal whitespace.

 

 

 

ViewBox with UniformToFill Stretch

 UniformToFill is very interesting in that it Fills the Viewbox, but in the same time doesn’t change the Height-Width ration of the Control nested in it.

Let’s see that.

image

Here’s our design-surface looks like:

image

We can see that Dobby fills all the space given to it and yet keeps his original Height-Width ratio.

However, Dobby is clipped on the right side to allow that. Similarly Dobby might have been clipped on the bottom if it was necessary.

 

Here’s the XAML Blend generated for us:

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

    <controls:Viewbox Height="100" Width="100" Stretch="UniformToFill" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

    <controls:Viewbox Height="200" Width="200" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="UniformToFill" VerticalAlignment="Bottom">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

    <controls:Viewbox Height="400" Width="400" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="UniformToFill" VerticalAlignment="Bottom">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

 

 

ViewBox with No Stretch

One more option of ViewBox is to just Clip to original content without resizing it.

Let’s have a look at that.

image

Here’s how page looks like:

image

We can see see in the first & second Viewbox that Dobby is clipped based on his top-right corner.

The third Viewbox shows Dobby aligned to the Center in it’s original size.

 

Here’s the XAML Blend generated for us:

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

    <controls:Viewbox Height="100" Width="100" Stretch="None" HorizontalAlignment="Stretch">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

    <controls:Viewbox Height="200" Width="200" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="None">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

    <controls:Viewbox Height="400" Width="400" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="None">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

 

 

ViewBox with StretchDirection Up & Down

We might sometime want to limit the scaling Viewbox does to only enlarge or shrink the controls in it.

Setting the StretchDirection to UpOnly says the Viewbox can only enlarge controls in it.

Setting the StretchDirection to DownOnly says the Viewbox can only shrink controls in it.

By default StretchDirection is set to Both which states that both Up & Down are acceptable.

 

image

 

Let’s examine how Dobby looks like in each of the 3 Stretch options in each of the StretchDirections.

 

Stretch=Fill, StretchDirection=Both

image

we’ve seen this already, this is standard Fill.

 

Stretch=Fill, StretchDirection=UpOnly

image

Viewbox #1 & Viewbox #2 have not been resized, because they should have scaled down, but we’re only allowed to scale up.
So Viewbox #1 & Viewbox #2 are equivalent to Stretch=None.

Viewbox #3 is still sized to Fill, because it’s been scaled up and that’s allowed.

 

 

Stretch=Fill, StretchDirection=DownOnly

image

Viewbox #1 & Viewbox #2 that need to be scale down their content have done so.

Viewbox #3 that needs to scale up has not done so, and is now equivalent to Stretch=None.

 

Stretch=Uniform, StretchDirection=Both <—Default for viewbox

image

Normal Uniform fill that we’ve previously seen.

 

Stretch=Uniform, StretchDirection=UpOnly

image

Viewbox #1 & Viewbox #2 that needed to be scaled down, have not done so because it’s not allowed. So they’re equivalent to having Stretch=None.

Viewbox #3 has been stretched to uniform with scaling up as expected.

 

Stretch=Uniform, StretchDirection=DownOnly

image

Viewbox #1 & Viewbox #2 that need to scale down in a Uniform Stretch have done so.

Viewbox #3 that needs to scale up has not done so, and is not equivalent of Stretch=None.

 

Stretch=UniformToFill, StretchDirection=Both

image

Normal UniformToFill stretching.

 

 

Stretch=UniformToFill, StretchDirection=DownOnly

image

Viewbox #1 & Viewbox #2 that needs to scale down in a UniformToFill Stretch have done so.

Viewbox #3 that needs to scale up has not done so, and is not equivalent of Stretch=None.

 

Stretch=UniformToFill, StretchDirection=UpOnly

image

Viewbox #1 & Viewbox #2 that needed to be scaled down have not done so because it’s not allowed.
So Viewbox #1 & Viewbox #2 equivalent to having Stretch=None.

Viewbox #3 has been stretched to uniform with scaling up as expected.

 

 

And here’s the XAML Blend generated for us for Stretch=Uniform & StretchDirection=Both: (the defaults)

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top">

    <controls:Viewbox Height="100" Width="100" Stretch="Uniform" HorizontalAlignment="Stretch" StretchDirection="Both">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" HorizontalAlignment="Left" Margin="120,0,0,0" VerticalAlignment="Top" >

    <controls:Viewbox Height="200" Width="200" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform" StretchDirection="Both">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

<Border BorderBrush="#FF000000" BorderThickness="1,1,1,1" Margin="340,0,60,80">

    <controls:Viewbox Height="400" Width="400" BorderBrush="#FF000000" BorderThickness="1,1,1,1" Stretch="Uniform" StretchDirection="Both">

        <SilverlightControlsNovember2008:PenguinControl/>

    </controls:Viewbox>

</Border>

 

 

ViewBox with Button

ViewBox might not be appropriate for all controls, as it’s primary use is 2D vector graphics.

You would probably not have a lot of scenarios to use it in a LOB app.

 

Viewbox essentially does a lot of math for you and sets the appropriate ScaleTransform on the nested Child.

 

Scenario: we have a Button sized 50x50 pixels and we’d like to scale it to 250x250 pixels.

Here’s the original button and it’s XAML:

 image

<Button Height="50" Width="50" Content="Button"/>

 

Option #1: Just change the Height & Width of the button!

image 

<Button Height="250" Width="250" Content="Button"/>

 

Let’s also set an acceptable fontsize for this situation:

 image

<Button Height="250" Width="250" Content="Button" FontSize="48" />

 

 

Option #2: Use a Viewbox

Hey, After all, we’ve just spent time learning about it.

image

<controls:Viewbox Height="250" Width="250">

    <Button Height="50" Width="50" Content="Button"/>

</controls:Viewbox>

 

Looks kinda weird, doesn’t it?

All the lines are very thick and not really subtle, the spacing looks absurd, the color schema which used to look slick now looks overblown.

 

 

Option #3: set a ScaleTransform of times 5

image

<Button Height="50"Width="50" Content="Button" RenderTransformOrigin="0.5,0.5">

    <Button.RenderTransform>

        <TransformGroup>

            <ScaleTransform ScaleX="5" ScaleY="5"/>

            <SkewTransform/>

            <RotateTransform/>

            <TranslateTransform/>

        </TransformGroup>

    </Button.RenderTransform>

</Button>

 

We can see that the result of option #2 and option #3 are completely equivalent.

That’s because, as we said, ViewBox just does a lot of complex math on what ScaleTransform to use.

 

The question of using a ViewBox or not is better articulated by asking “Would I use a Scale trasnform here? or just set Layout properties?”.

 

 

-- Justin Angel

Microsoft Silverlight Program Manager

Posted by JustinAngel | 8 comment(s)
Filed under:

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

Posted by JustinAngel | 9 comment(s)
Filed under: