Page view counter

Justin Angel's Silverlight Weblog

This blog has moved to a Silverlight powered weblog on @ http://JustinAngel.net

Read the Silverlight weblog features overview at @ http://justinangel.net/SilverlightWeblogFeaturesOverview

Posted by JustinAngel

Silverlight TreeView Advanced Scenarios (TreeViewExtended)

Hi Folks,

One of the most important tenants of Silverlight Controls development we picked up in Microsoft is WPF Compatibility.

That means, that if we end up building FooControl in Silverlight and FooControl exists in WPF we’ll end up with the same API and behaviour.

Why is that good for you? It allows you to reuse to code from Silverlight in WPF. If we spin up new API or have differentiating behaviours you really won’t be able to to reuse your XAML and Code in WPF.

Why could this introduce problems? Well, The WPF API and Behaviours aren’t a divine decree. Some scenarios might not be covered by the WPF control.

We’ve hit this issue pretty hard when it comes to TreeView. The WPF TreeView doesn’t fully meet all user requirements, but we still want to be WPF Subset compat.
Hard choice, eh?

So, allow me to introduce a TreeViewExtended class that solves some of this issues.

 

Download code @ http://silverlight.net/blogs/justinangel/BlogStorage/TreeViewExtended.zip 
(To get TreeViewExtended and extension methods to work, include TreeViewExtended.cs in your project and go crazy.)

 

Prologue: Setting up a Hello World TreeView

Prerequisites for this article:
1. Silverlight Toolkit: TreeView, TreeViewItem & HierarchalDataTemplate 

If you don’t know how TreeView works, please read the afore mentioned article.

 

Here’s our basic TreeView:

<UserControl x:Class="TreeViewExtended.MainPage"

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

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

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

   xmlns:common="clr-namespace:System.Windows;assembly=System.Windows.Controls"

   Width="400" Height="300">

    <UserControl.Resources>

        <common:HierarchicalDataTemplate x:Key="myHierarchicalTemplate" ItemsSource="{Binding Items}" >

            <TextBlock Text="{Binding myString}" />

        </common:HierarchicalDataTemplate>

    </UserControl.Resources>

    <StackPanel x:Name="LayoutRoot" Background="White" VerticalAlignment="Stretch">

        <controls:TreeView VerticalAlignment="Stretch" 

                          x:Name="trv"

                          ItemTemplate="{StaticResource myHierarchicalTemplate}" />

    </StackPanel>

</UserControl>

Nothing too fancy, Just a TreeView with a HierarchichalDataTemplate.

Here’s some sample data we’ll use in this solution:

public partial class MainPage : UserControl

{

    ObservableCollection<myItem> itemsSource =

      new ObservableCollection<myItem>()

      {

          new myItem("Hello",

              new myItem("World"),

              new myItem("Foo"),

              new myItem("Bar")),

          new myItem("Moo",

              new myItem("Boo",

                new myItem("Goo"))),

      };

 

    public MainPage()

    {

        InitializeComponent();

 

        trv.ItemsSource = itemsSource;

    }

}

And here’s the C# type we’ll use:

public class myItem

{

    public myItem(string myString, params myItem[] myItems)

    {

        ObservableCollection<myItem> itemsObservableCollection = new ObservableCollection<myItem>();

        foreach (var item in myItems)

            itemsObservableCollection.Add(item);

        Items = itemsObservableCollection;

    }

 

    public string myString { get; set; }

    public ObservableCollection<myItem> Items { get; set; }

}

 

When we run this simple TreeView sample we’ll see the following TreeView:

image

And Fully expanded:

image

 

 

Chapter #1: Finding a TreeViewItem based on a Business Class instance

Let’s create a Button that once Clicked it changes the “Moo” TreeViewItem to FontSize=20.

To do that, we’ll use the TreeView.ItemContainerGenerator shown here:

image

The ContainerFromItem allows us to find a TreeViewItem based on a specific Business class.

image

image

So, Let’s add the button:

<Button VerticalAlignment="Bottom" Content="Change Moo TreeViewItem FontSize" Click="Button_Click" />

And Let’s write the code needed to get the TreeViewItem:

ObservableCollection<myItem> itemsSource =

  new ObservableCollection<myItem>()

  {

      new myItem("Hello",

          new myItem("World"),

          new myItem("Foo"),

          new myItem("Bar")),

      new myItem("Moo",

          new myItem("Boo",

            new myItem("Goo"))),

  };

 

private void Button_Click(object sender, RoutedEventArgs e)

{

    myItem mooElement = itemsSource[1];

    TreeViewItem mootreeViewItem =

        (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

 

    mootreeViewItem.FontSize = 20;

}

Let’s run this sample:

image

And click the sample:

image 

Now let’s change the sample to change the TreeViewItem for ‘Boo’

ObservableCollection<myItem> itemsSource =

  new ObservableCollection<myItem>()

  {

      new myItem("Hello",

          new myItem("World"),

          new myItem("Foo"),

          new myItem("Bar")),

      new myItem("Moo",

          new myItem("Boo",

            new myItem("Goo"))),

  };

 

private void Button_Click(object sender, RoutedEventArgs e)

{

    myItem booElement = itemsSource[1].Items[0];

    TreeViewItem bootreeViewItem =

        (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(booElement);

 

    bootreeViewItem.FontSize = 20;

}

What do you think that’ll happen when we run this sample?

image

 

But why is BooTreeViewItem empty? Well, it’s because an ItemContainerGenerator only knows the Items of the same level.

image

So, The TreeViewItem itself has an ItemContainerGenerator we’ll need to access.

private void Button_Click(object sender, RoutedEventArgs e)

{

    myItem mooElement = itemsSource[1];

    TreeViewItem mootreeViewItem =

        (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

 

    myItem booElement = mooElement.Items[0];

    TreeViewItem bootreeViewItem =

        (TreeViewItem)mootreeViewItem.ItemContainerGenerator.ContainerFromItem(booElement);

 

    bootreeViewItem.FontSize = 20;

}

And when we run this sample:

image 

So, first we had to get the ‘Moo’ TreeViewItem and than the ‘Boo’ TreeViewItem..

You can see how this gets a bit tricky if you’re just looking for any element in the TreeView.

 

Introducing TreeViewWorkarounds.
Based on the principles shown in this article up until now, you can easily create a method to iterate over all TreeViewItem and search for items.

public static class TreeViewWorkarounds

{

    public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)

    {

        TreeViewItem containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);

        if (containerThatMightContainItem != null)

            return containerThatMightContainItem;

        else

            return ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);

    }

 

    private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

            if (parentContainer == null)

                return null;

            TreeViewItem containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);

            if (containerThatMightContainItem != null)

                return containerThatMightContainItem;

            TreeViewItem recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);

            if (recursionResult != null)

                return recursionResult;

        }

        return null;

    }

 

    public static object ItemFromContainer(this TreeView treeView, TreeViewItem container)

    {

        TreeViewItem itemThatMightBelongToContainer = (TreeViewItem)treeView.ItemContainerGenerator.ItemFromContainer(container);

        if (itemThatMightBelongToContainer != null)

            return itemThatMightBelongToContainer;

        else

            return ItemFromContainer(treeView.ItemContainerGenerator, treeView.Items, container);

    }

 

    private static object ItemFromContainer(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, TreeViewItem container)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

            if (parentContainer == null)

                return null;

            TreeViewItem itemThatMightBelongToContainer = (TreeViewItem)parentContainer.ItemContainerGenerator.ItemFromContainer(container);

            if (itemThatMightBelongToContainer != null)

                return itemThatMightBelongToContainer;

            TreeViewItem recursionResult = ItemFromContainer(parentContainer.ItemContainerGenerator, parentContainer.Items, container) as TreeViewItem;

            if (recursionResult != null)

                return recursionResult;

        }

        return null;

    }

}

Using these extension methods isn’t hard at all:

private void Button_Click(object sender, RoutedEventArgs e)

{

    //myItem mooElement = itemsSource[1];

    //TreeViewItem mootreeViewItem =

    //    (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

 

    //myItem booElement = mooElement.Items[0];

    //TreeViewItem bootreeViewItem =

    //    (TreeViewItem)mootreeViewItem.ItemContainerGenerator.ContainerFromItem(booElement);

 

    //bootreeViewItem.FontSize = 20;

 

    trv.ContainerFromItem(itemsSource[1].Items[0]).FontSize = 20;

}

Let’s run this sample:

image

Much easier to use, eh?

Don’t worry about the code for the extension methods, you can freely use it.

 

 

Chapter #2: Searching for any TreeViewItems based on predicates

Based on the code from the previous extension methods, we can build a TreeView search based on predicates.

Essentially, we’d like to enable using search lambda predicates:

trv.FindContainer(t => ((myItem) t.Header).myString == "Boo").FontSize = 20;

 

We’ll add the FindContainer extension method to iterate over all TreeView and check the predicate.

public static class TreeViewWorkarounds

{

 

    public static TreeViewItem FindContainer(this TreeView treeView, Predicate<TreeViewItem> condition)

    {

        return FindContainer(treeView.ItemContainerGenerator, treeView.Items, condition);

    }

 

    private static TreeViewItem FindContainer(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, Predicate<TreeViewItem> condition)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem containerThatMightMeetTheCondition= (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

 

            if (containerThatMightMeetTheCondition == null)

                return null;

 

            if (condition(containerThatMightMeetTheCondition))

                return containerThatMightMeetTheCondition;

 

            TreeViewItem recursionResult = FindContainer(containerThatMightMeetTheCondition.ItemContainerGenerator, containerThatMightMeetTheCondition.Items, condition);

            if (recursionResult != null)

                return recursionResult;

        }

        return null;

    }

 

Let’s run this code sample:

image

 

We can also use this method to look for parent TreeViewItems.

trv.FindContainer(t => ((myItem)t.Header).Items.Any(i => i.myString == "World")).FontSize = 20;

In this code, we’re looking for any TreeViewItem that has a Child with the word “World” in it.

image

 

Really, once you get the power of lambda search predicates, you can do a lot with the FindContainer extension method.

 

 

Chapter #3: Exposing a TreeView.Containers collection

Sometimes, you just want to have a collection of all the TreeViewItems in the TreeView.
Maybe you want to check something, or manually search those items.

We’ll inherit from the normal TreeView to create TreeViewExtended:

public class TreeViewExtended : TreeView

{

    public TreeViewExtended()

    {

        this.DefaultStyleKey = typeof(TreeView);

    }

}

Next, we’ll change our sample to work with this new TreeView:

<UserControl x:Class="TreeViewExtended.MainPage"

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

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

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

   xmlns:common="clr-namespace:System.Windows;assembly=System.Windows.Controls"

   xmlns:TreeViewExtended="clr-namespace:System.Windows.Controls"

   Width="400" Height="300">

    <UserControl.Resources>

        <common:HierarchicalDataTemplate x:Key="myHierarchicalTemplate" ItemsSource="{Binding Items}" >

            <TextBlock Text="{Binding myString}" />

        </common:HierarchicalDataTemplate>

    </UserControl.Resources>

    <StackPanel x:Name="LayoutRoot" Background="White" VerticalAlignment="Stretch">

        <!--<controls:TreeView VerticalAlignment="Stretch"  x:Name="trv" ItemTemplate="{StaticResource myHierarchicalTemplate}"  />-->

        <TreeViewExtended:TreeViewExtended VerticalAlignment="Stretch" x:Name="trv" ItemTemplate="{StaticResource myHierarchicalTemplate}" />

        <Button VerticalAlignment="Bottom" Content="Do something" Click="Button_Click" />

    </StackPanel>

</UserControl>

 

And obviously, everything works as we expected:

image

 

Now, let’s implement our collection.

 

public class TreeViewExtended : TreeView

{

    public TreeViewExtended()

    {

        this.DefaultStyleKey = typeof(TreeView);

    }

 

    public IEnumerable<TreeViewItem> Containers

    {

        get

        {

            return GetTreeViewItems(this.ItemContainerGenerator, this.Items);

        }

    }

 

    private static IEnumerable<TreeViewItem> GetTreeViewItems(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem container = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

 

            if (container != null)

                yield return container;

 

            foreach (var treeViewItem in GetTreeViewItems(container.ItemContainerGenerator, container.Items))

                yield return treeViewItem;

        }

    }

}

 

And let’s print out the TreeViewItems when clicking the button:

    foreach (TreeViewItem treeViewItem in trv.Containers)

    {

        Debug.WriteLine("TreeViewItem " + ((myItem)treeViewItem.Header).myString);

    }

 

Let’s run this sample app and click the button:

image

And in Visual Studio “Output” window we can see:

image

 

Chapter #4: Aggregating the TreeViewItem Expanded & Collapsed on TreeView

We’d like to get an event to fire on our TreeView whenever a TreeViewItem.Expanded/Collapsed event fires.

Think with me here, if we want to do that – the TreeViewItem needs to know who TreeView is so it can tell the TreeView “Hey, the TreeViewItem event fired!”.

For that, we’ll need to create our own TreeViewItemExtended.

Now, look closely, this is how we override the 2 methods on an ItemsControl/TreeView to do that.

public class TreeViewExtended : TreeView

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    …

}

 

public class TreeViewItemExtended : TreeViewItem

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

}

 

We’ll override these 2 methods so our TreeView and TreeViewItems will always generated our TreeVieWItemExtended.

Next, we’ll create a public property on TreeViewItem that points to the TreeView it belongs to.

public class TreeViewItemExtended : TreeViewItem

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    public TreeViewExtended ParentTreeView { set; get; }

}

 

We’ll populate that property on the prepare method.

public class TreeViewExtended : TreeView

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended) element;

        treeViewItemExtended.ParentTreeView = this;

 

        base.PrepareContainerForItemOverride(element, item);

    }

 

And in the TreeViewItem for each child TreeViewItem:

public class TreeViewItemExtended : TreeViewItem

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    public TreeViewExtended ParentTreeView { internal set; get; }

 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended)element;

        treeViewItemExtended.ParentTreeView = this.ParentTreeView;

 

        base.PrepareContainerForItemOverride(element, item);

    }

}

 

Next, we’ll expose a TreeViewExtended event for Expanded/Collapsed.

public class TreeViewExtended : TreeView

{

   …

 

    public event RoutedEventHandler ContainerExpanded;

    public event RoutedEventHandler ContainerCollapsed;

}

 

And we’ll add appropriate methods for TreeViewItemExtended to invoke these events.

    public event RoutedEventHandler ContainerExpanded;

    internal void InvokeContainerExpanded(object sender, RoutedEventArgs e)

    {

        RoutedEventHandler expanded = ContainerExpanded;

        if (expanded != null) expanded(sender, e);

    }

 

    public event RoutedEventHandler ContainerCollapsed;

    internal void InvokeContainerCollapsed(object sender, RoutedEventArgs e)

    {

        RoutedEventHandler collapsed = ContainerCollapsed;

        if (collapsed != null) collapsed(sender, e);

    }

 

Finally, we’ll have the TreeViewItem invoke these event invoke methods.

public class TreeViewItemExtended : TreeViewItem

{

    public TreeViewItemExtended()

    {

        this.Expanded += new RoutedEventHandler(TreeViewItemExtended_Expanded);

        this.Collapsed += new RoutedEventHandler(TreeViewItemExtended_Collapsed);

    }

 

    void TreeViewItemExtended_Collapsed(object sender, RoutedEventArgs e)

    {

        ParentTreeView.InvokeContainerCollapsed(sender, e);

    }

 

    void TreeViewItemExtended_Expanded(object sender, RoutedEventArgs e)

    {

        ParentTreeView.InvokeContainerExpanded(sender, e);

    }

 

Let’s use these events:

public MainPage()

{

    InitializeComponent();

 

    trv.ItemsSource = itemsSource;

 

    trv.ContainerExpanded += (s, e) => PrintTreeViewMessage(s, "Expanded");

    trv.ContainerCollapsed += (s, e) => PrintTreeViewMessage(s, "Collapsed");

 

}

 

private void PrintTreeViewMessage(object sender, string description)

{

    TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended) sender;

    myItem item = (myItem) treeViewItemExtended.Header;

 

    textBlock.Text = string.Format("{0} TreeView was {1}", item.myString, description);

}

And when we run this sample, we can see that:

image

image

 

image

 

 

Chapter #5: Exposing the TreeViewItem.MouseLwftButtonDown event

For reasons of WPF subset Compatibility the Silverlight TreeViewItem does not raise the MouseLeftButtonDown event when clicked.
Normally, you should use the “Selected” event for that.

But, we can now expose a new TreeViewExtended.ContainerMouseLeftButtonDown event that does just that.

public class TreeViewExtended : TreeView

{

    public event MouseButtonEventHandler ContainerMouseButtonEventHandler;

    internal void InvokeContainerMouseButtonEventHandler(object sender, MouseButtonEventArgs e)

    {

        MouseButtonEventHandler handler = ContainerMouseButtonEventHandler;

        if (handler != null) handler(sender, e);

    }

public class TreeViewItemExtended : TreeViewItem

{

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)

    {

        base.OnMouseLeftButtonDown(e);

        ParentTreeView.InvokeContainerMouseButtonEventHandler(this, e);

    }

 

And now, we’ll sign up for that event.

public MainPage()

{

    InitializeComponent();

 

    trv.ItemsSource = itemsSource;

 

    trv.ContainerMouseButtonEventHandler += (s, e) => PrintTreeViewMessage(s, "MouseLeftButtonDown");

}

 

And when we run this sample:

image

image

 

 

Chapter #6: Exposing a PreparingContainer event

So, one of the biggest thing you should know about TreeView – it’s virtualized (in a way).
It won’t create TreeViewItem until they’re really needed.

Which makes a lot of the work we’ve done up until now for not. Let’s have a look at this code snippet:

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            trv.ContainerFromItem(itemsSource[1].Items[0].Items[0]).FontSize = 20;

        }

Basically, we’re looking for the TreeViewItem with the word ‘Goo’.

What do you think happens if we click the button when TreeView is fully collapsed?

image

We’ll get a “OMG, This TreeView doesn’t exist” exception:

image

But, if we do the same thing when the TreeView is expanded:

image

It will work:

image

 

Why? Because the ‘Goo’ TreeViewItem doesn’t exist until it’s needed.

And the TreeViewItem isn’t available until that Visual Tree has been built, which happens at somewhat random points.

So, let’s start by exposing an event that’ll happen every time a TreeViewItem is created.

public class TreeViewExtended : TreeView

{

    public event EventHandler<ContainerPreparedEventArgs> ContainerPrepared;

    internal void InvokeContainerPrepared(TreeViewItemExtended sender, object item)

    {

        EventHandler<ContainerPreparedEventArgs> prepared = ContainerPrepared;

        if (prepared != null) prepared(sender, new ContainerPreparedEventArgs(sender, item));

    }

 

 

public class ContainerPreparedEventArgs : EventArgs

{

    public ContainerPreparedEventArgs(TreeViewItemExtended container, object item)

    {

        Container = container;

        Item = item;

    }

 

    public TreeViewItemExtended Container { get; set; }

    public object Item { get; set; }

}

 

Next, whenever a TreeViewItem is prepared we’ll invoke this event.

public class TreeViewExtended : TreeView

{

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended) element;

        treeViewItemExtended.ParentTreeView = this;

 

        base.PrepareContainerForItemOverride(element, item);

 

        InvokeContainerPrepared(treeViewItemExtended, item);

    }

 

    public event EventHandler<ContainerPreparedEventArgs> ContainerPrepared;

    internal void InvokeContainerPrepared(TreeViewItemExtended sender, object item)

    {

        EventHandler<ContainerPreparedEventArgs> prepared = ContainerPrepared;

        if (prepared != null) prepared(sender, new ContainerPreparedEventArgs(sender, item));

    }

 

}

 

 

public class TreeViewItemExtended : TreeViewItem

{

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended)element;

        treeViewItemExtended.ParentTreeView = this.ParentTreeView;

 

        base.PrepareContainerForItemOverride(element, item);

 

        ParentTreeView.InvokeContainerPrepared(treeViewItemExtended, item);

    }

 

 

}

 

Now, Let’s print out when each TreeViewItem has been created:

public MainPage()

{

    InitializeComponent();

 

    trv.ItemsSource = itemsSource;

 

    trv.ContainerPrepared += (s, e) => AddTreeViewMessage(s, "Created");

}

 

 

Let’s start our application:

image

We can clearly that only TreeViewItems have been created up until now.

Let’s expand the ‘Hello’ TreeViewItem:

image

And once that TreeViewItem has expanded the TreeViewItems were created.

We’ll Expand the rest of the Tree:

image image

 

So the PreparingContainer event is pretty useful considering it lets us know when TreeViewItems are ready.

 

 

Chapter #7: Implementing Delayed Expanded TreeViewItems

This feature isn’t 100% useful before we get to the next Chapter.
What I’d like for us to do (and take me on faith here) is implement a method that :
1. Will expand TreeViewItem corresponding to Items
2. Will be able to work in a delayed manner (even if they are created after the method was called)

Expanding TreeViewItems creates more TreeViewItems, so this might be very interesting.

 

Here’s our method:

    private List<object> itemsToDelayExpand = new List<object>();

    public void ExpandDelayItems(params object[] ItemsCorrespondingToTreeViewItemsToExpand)

    {

        itemsToDelayExpand.AddRange(ItemsCorrespondingToTreeViewItemsToExpand);

 

        foreach (object itemtoTryAndExpand in ItemsCorrespondingToTreeViewItemsToExpand)

        {

            TreeViewItem treeViewItem = this.ContainerFromItem(itemtoTryAndExpand);

            if (treeViewItem != null)

            {

                treeViewItem.IsExpanded = true;

                itemsToDelayExpand.Remove(itemtoTryAndExpand);

            }

        }

    }

We’ll take a collection of Items and add those to a bigger list of all Items we’re waiting for.
Than we’ll go over the TreeView looking for these items and if they have TreeViewItems – we’ll expand those.

If they don’t have TreeViewItems, we’ll listen to the prepared event and wait for them to be created.

    internal void InvokeContainerPrepared(TreeViewItemExtended sender, object item)

    {

        EventHandler<ContainerPreparedEventArgs> prepared = ContainerPrepared;

        if (prepared != null) prepared(sender, new ContainerPreparedEventArgs(sender, item));

 

        if (itemsToDelayExpand.Contains(item))

        {

            sender.IsExpanded = true;

            itemsToDelayExpand.Remove(item);

        }

    }

 

    private List<object> itemsToDelayExpand = new List<object>();

    public void ExpandDelayItems(params object[] ItemsCorrespondingToTreeViewItemsToExpand)

    {

        itemsToDelayExpand.AddRange(ItemsCorrespondingToTreeViewItemsToExpand);

 

        foreach (object itemtoTryAndExpand in ItemsCorrespondingToTreeViewItemsToExpand)

        {

            TreeViewItem treeViewItem = this.ContainerFromItem(itemtoTryAndExpand);

            if (treeViewItem != null)

            {

                treeViewItem.IsExpanded = true;

                itemsToDelayExpand.Remove(itemtoTryAndExpand);

            }

        }

    }

 

Let’s test our method:

trv.ExpandDelayItems(itemsSource[1], itemsSource[1].Items[0]);

 

And if we run our application:

image

Let’s click the button:

image

You can see that ‘Moo’ TreeViewItem was expanded, and than the ‘Boo’ TreeViewItem was expanded.

 

 

Chapter #8: Set TreeView.SelectedItem with TreeView.SetSelectItem method

By now, you can see how using the ExpandDelayItems with the ContainerPrepared event let’s us set a selected item.

    private object SelectedItemDelayed = null;

    public void SetSelectedItem(object SelectedItem, params object[] SelectedItemParents)

    {

        ContainerPrepared += new EventHandler<ContainerPreparedEventArgs>(ContainerPrepared_LookForSelectedItem);

        SelectedItemDelayed = SelectedItem;

        ExpandDelayItems(SelectedItemParents);

    }

 

    private void ContainerPrepared_LookForSelectedItem(object sender, ContainerPreparedEventArgs e)

    {

        if (e.Item == SelectedItemDelayed)

        {

            e.Container.IsSelected = true;

            SelectedItemDelayed = null;

            ContainerPrepared -= new EventHandler<ContainerPreparedEventArgs>(ContainerPrepared_LookForSelectedItem);

        }

    }

Basically, all we did here is expand all Parent TreeViewItems and sit comfortably waiting for our TreeViewItem to create itself.

 

Let’s test our SetSelectedItem method.

trv.SetSelectedItem(itemsSource[1].Items[0].Items[0],

                    itemsSource[1].Items[0],

                    itemsSource[1]);

And when we’ll run the app we can see:

image

image

 

Now, the TreeView could start looking around for the Parent Items itself, but consider a scenario with millions of nested elements.
So if you call SetSelectedItem with the correct parents, it’ll end up setting your SelectedItem.

 


Chapter #9: Adding a Parent TreeViewItem property

One last tiny feature we’ll add is a TreeViewItem.ParentTreeViewItem property.

 

We’ll start by adding the property.

public class TreeViewItemExtended : TreeViewItem

{

    public TreeViewItemExtended ParentTreeViewItem { internal set; get; }

 

And during the Prepare Container for it’s nested elements we’ll set their Parent property to this TreeViewItem.

public class TreeViewItemExtended : TreeViewItem

{

 

    public TreeViewExtended ParentTreeView { internal set; get; }

    public TreeViewItemExtended ParentTreeViewItem { internal set; get; }

 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended)element;

        treeViewItemExtended.ParentTreeView = this.ParentTreeView;

        treeViewItemExtended.ParentTreeViewItem = this;

 

        base.PrepareContainerForItemOverride(element, item);

 

        ParentTreeView.InvokeContainerPrepared(treeViewItemExtended, item);

    }

 

Let’s use this to change the parent background of any TreeViewItem with children:

            trv.ContainerExpanded += (s, args) =>

                {

                    TreeViewItemExtended parent = ((TreeViewItemExtended) s).ParentTreeViewItem;

                    if (parent != null)

                    {

                        parent.Background = new SolidColorBrush(Colors.Blue);

                    }

                };

Let’s run this application:

image

And once we expand ‘Hello’ TreeViewItem:

image

And ‘Moo’ TreeViewItem:

image

 

 

Epilogue

The TreeView control has a lot of extensibility points to overcome some innate WPF compatible features.

If you’d like to see TreeViewItemExtended ship in the Silverlight Toolkit, please feel free to vote on the issue here:
http://silverlight.codeplex.com/WorkItem/View.aspx?WorkItemId=3039

 

Sincerely,

-- Justin Angel

Microsoft Silverlight Program Manager

Silverlight Unit Testing, RhinoMocks, Unity and Resharper.

Hi Folks,

As most of you who’re familiar with my work know – I’m a huge supporter of the open source community.  
I also hold firm to a belief that the Microsoft .Net ecosystem is truly great due to community and vendors innovation.

During this article I’d like for us to go over 4 open source initiatives: The Microsoft’s Silverlight Unit Testing framework, RhinoMocks , Microsoft Unity and the Silverlight unit testing plug-in for Resharper.

 

Here are some links to get you started:

1. Microsoft Silverlight Unit testing framework
2. RhinoMocks
3. Microsoft Unity
4. Silverlight Unit Test Runner for Resharper 


You can grab the application we’ve developed from: SL2_UnitTestingRhinoMocksUnityResharper.zip

 

The obligatory Hello World sample

In order to introduce these 4 unit testing oriented projects we’ll start of from the canonical hello world sample.
After that we’ll move to something a bit more Interesting.

We’ll start a new Silverlight project which we’ll for our sample:
image

We’ll use a Textblock with “Hello World!” as text:

<TextBlock Text="Hello World!" x:Name="txt" x:FieldModifier="public" />

When we run our application we’ll see:

image

 

 

 

Microsoft Silverlight Unit Testing framework – “Hello World”

Since Silverlight can’t load Desktop .Net CLR assemblies and the Desktop CLR can’t load Silverlight controls – We can’t use existing unit testing frameworks.
So, no MbUnit, no NUnit, no xUnit. Sad.

In comes developer extraordinaire Jeff Wilcox and develops the Microsoft Silverlight Unit testing framework.

1. Download the latest Binaries from Microsoft Silverlight Unit Test Framework.
image
2. Create a project used for unit testing.
image

3. Add a reference to the 2 Unit testing DLLs to our testing project.
image 


4. From the testing project we’ll add a reference to the runtime project.
image image

 

5. Set your Application.Root in App.xaml.cs to a unit testing runner:

namespace SL2_UnitTestingRhinoMocksUnityRes_Tests

{

    public partial class App : Application

    {

        private void Application_Startup(object sender, StartupEventArgs e)

        {

            this.RootVisual = UnitTestSystem.CreateTestPage();

        }

Now we’ve got a basic unit testing project setup.

 

Next, we’ll create a new class to test our actual Silverlight “Page” class.

image

 

And we’ll put the appropriate attributes to run a test:

using Microsoft.VisualStudio.TestTools.UnitTesting;

using SL2_UnitTestingRhinoMocksUnityResharper;

 

namespace SL2_UnitTestingRhinoMocksUnityRes_Tests

{

    [TestClass]

    public class Page_Tests : SilverlightTest

    {

        [TestMethod]

        public void Page_Initialized_ShouldHaveAextBlock()

        {

            Page page = new Page();

 

            Assert.IsTrue(???);

        }

    }

}

 

In this test, we’d like to make sure that when Page has been initialized. And there is indeed a textblock with the Text hello world in there.

The problem with that is that we’ll need to add Page to the visual tree and wait for the loaded event.
Well, we can’t do that from a synchronous test. We’ll have to use an Async test format.

        [TestMethod]

        [Asynchronous]

        public void Page_Initialized_ShouldHaveATextBlock()

        {

            Page page = new Page();

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => Assert.IsTrue(page.txt != null));

 

            EnqueueTestComplete();

        }


Let’s go line by line here.

We’ll declare an async test method.

        [TestMethod]

        [Asynchronous]

        public void Page_Initialized_ShouldHaveATextBlock()

We’ll create a new page class to be tested.

            Page page = new Page();

We’ll wait for the Page.Loaded event after it has been added to the visual tree.

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

Next, we’ll tell the async testing engine that “after loaded has happened” we need to check if the page.txt public field (the TextBlock) has been initialized.

            EnqueueCallback(() => Assert.IsTrue(page.txt != null));

And than we’ll tell the async testing engine that we’re done testing.

            EnqueueTestComplete();

 

When we run our testing project we’ll see this:

image

 

Now, I’d like to confess that the “WaitFor” method isn’t something the comes with the Silverlight unit testing framework. It’s a little helper method I use. 
Here’s the syntax, though it’s not important:

protected void WaitFor<T>(T objectToWaitForItsEvent, string eventName)

{

    EventInfo eventInfo = objectToWaitForItsEvent.GetType().GetEvent(eventName);

 

    bool eventRaised = false;

 

    if (typeof(RoutedEventHandler).IsAssignableFrom(eventInfo.EventHandlerType))

        eventInfo.AddEventHandler(objectToWaitForItsEvent, (RoutedEventHandler)delegate { eventRaised = true; });

    else if (typeof(EventHandler).IsAssignableFrom(eventInfo.EventHandlerType))

        eventInfo.AddEventHandler(objectToWaitForItsEvent, (EventHandler)delegate { eventRaised = true; });

 

    EnqueueConditional(() => eventRaised);

}

There are plans to address the need for a WaitFor method in one form or another in future versions of the unit testing framework. 

 

Next, we’d like to test that the TextBlock has the correct text.

        [TestMethod]

        [Asynchronous]

        public void PageWithTextBlock_Initialized_ShouldHaveHelloWorldText()

        {

            Page page = new Page();

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => Assert.IsTrue(page.txt.Text == "Hello World!"));

 

            EnqueueTestComplete();

        }

 

And if we can run our test we’ll see that this test succeeded:

image

 

 

RhinoMocks – Hello World

1. Download RhinoMocks for silverlight.
image
2. Add a reference to these files from the Testing project:

image

As we move more towards real-world examples we need better tools to handle testing.
We’ll start off by creating a ViewModel for page:

    public class PageViewModel

    {

        private string _helloText = "Hello World!";

        public virtual string HelloText

        {

            get { return _helloText; }

            set { _helloText = value; }

        }

    }

It’s important to note that this is not a ViewModel article and I’m not following best MVVM practices.
MVVM and Unit testing are complimentary. but in the interest of keeping this article simple I’ll keep the ViewModel simple.

Next, we’ll create a ViewModel property on Page:

    public partial class Page : UserControl

    {

        private PageViewModel _viewModel = new PageViewModel();

        public PageViewModel ViewModel

        {

            get { return _viewModel; }

            set { _viewModel = value; }

        }

 

        public Page()

        {

            InitializeComponent();

        }

    }

And we’ll set the Page.DataContext to the ViewModel.

    public partial class Page : UserControl

    {

        private PageViewModel _viewModel = new PageViewModel();

        public PageViewModel ViewModel

        {

            get { return _viewModel; }

            set { _viewModel = value; }

        }

 

        public Page()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(Page_Loaded);

        }

 

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

            this.DataContext = ViewModel;

        }

    }

 

Let’s run our tests first to make sure we didn’t break anything:

image

We didn’t break anything, we’re good.

 

Now, let’s add another test class for PageViewModel.

    [TestClass]

    public class PageViewModel_Tests : SilverlightTest

    {

        [TestMethod]

        public void PageViewModel_ByDefault_ShouldHaveHelloWorldTest()

        {

            PageViewModel pageViewModel = new PageViewModel();

 

            Assert.IsTrue(pageViewModel.HelloText == "Hello World!");

        }

    }

And we’ll run our tests:

image

All green.

 

Now it’s time to hook up our TextBlock to the ViewModel.

<UserControl x:Class="SL2_UnitTestingRhinoMocksUnityResharper.Page"

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

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

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <TextBlock x:Name="txt" x:FieldModifier="public" Text="{Binding HelloText}" />

    </Grid>

</UserControl>

And let’s run our test again to make sure everything still works:

image

 

Now, we’d like to test that PageViewModel.HelloText does get invoked by Page.
Finally, we can use RhinoMocks.

Here’s one way to write that test:

        [TestMethod]

        [Asynchronous]

        public void Page_Loaded_ShouldUsePageViewModelHelloTextOnce()

        {

            Page page = new Page();

            page.ViewModel = new PageViewModel() { HelloText = "Bonjour Monde!" };

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => Assert.IsTrue(page.txt.Text == "Bonjour Monde!"));

 

            EnqueueTestComplete();

        }

Right now this test would run, but it has 2 main issues:
1. We’re testing PageViewModel when really we want to test Page invoking PageViewModel.
2. We’re testing the entire Page Lifecycle in the UI, which we don’t want to do.

image

This test is supposed to test the red arrow “Invokes”, but instead we’re testing this entire diagram.
Which makes our test brittle and poorly written.

Let’s write a test that only checked the “Invokes” arrow.

        [TestMethod]

        [Asynchronous]

        public void Page_Loaded_ShouldUsePageViewModelHelloTextOnce()

        {

            Page page = new Page();

            MockRepository mocks = new MockRepository();

            PageViewModel pageViewModel = (PageViewModel) mocks.StrictMock(typeof(PageViewModel));

            page.ViewModel = pageViewModel;

 

            using(mocks.Record())

            {

                pageViewModel.Expect(p => p.HelloText).Return(string.Empty);

            }

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => mocks.VerifyAll());

 

            EnqueueTestComplete();

        }

Let’s go line-by-line here.

            MockRepository mocks = new MockRepository();

            PageViewModel pageViewModel = (PageViewModel) mocks.StrictMock(typeof(PageViewModel));

We’ve initialized a mock repository which is a magical doohickey that creates mocks and checks expectations.
We used it to create a mock for PageViewModel.

            using(mocks.Record())

            {

                pageViewModel.Expect(p => p.HelloText).Return(string.Empty);

            }

We’ve recorded an expectation that someone should invoke PageViewModel.HelloText. (And it should return a String.Empty)

            EnqueueCallback(() => mocks.VerifyAll());

And in the end of the test, in stead of checking the UI we’ll check the expectation have been met.

 

Let’s run our test:

image

And now let’s run it again if we temporarily delete the binding:

<TextBlock x:Name="txt" x:FieldModifier="public" Text="{Binding HelloText}" Text="Hello World!" />

image

We can clearly see that if the PageViewModel.HelloText property’s getter isn’t called, RhinoMocks will fail the test.

image

 

Let’s add another test to test the whole workflow without testing the PageViewModel itself:

        [TestMethod]

        [Asynchronous]

        public void Page_Loaded_ShouldUsePageViewModelHelloTextInTextBlock()

        {

            Page page = new Page();

            MockRepository mocks = new MockRepository();

            PageViewModel pageViewModel = (PageViewModel)mocks.StrictMock(typeof(PageViewModel));

            page.ViewModel = pageViewModel;

 

            using (mocks.Record())

            {

                pageViewModel.Expect(p => p.HelloText).Return("Bonjour Monde!");

            }

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => Assert.IsTrue(page.txt.Text == "Bonjour Monde!"));

            EnqueueCallback(() => mocks.VerifyAll());

 

            EnqueueTestComplete();

        }

 

And again, all of our tests pass:

image

 

The advantage of testing with Mocks – we test only what we need to test – we didn’t the actual PageViewModel class in this test.

 

 

Microsoft Unity – Hello World

Unity is an Inversion of Control framework which let’s us easily reduce the complexity of our object model.
Big words, eh? Let’s get down to the code.

 

1. Download and install Unity for Silverlight.

image

2. Add references to the Unity DLL to both our runtime and testing project.

image image

 

Now, we’ll remove the Page.ViewModel property.

    public partial class Page : UserControl

    {

        private PageViewModel _viewModel = new PageViewModel();

        public PageViewModel ViewModel

        {

            get { return _viewModel; }

            set { _viewModel = value; }

        }

And we’ll add a property that will get automatically populated by Unity:

    public partial class Page : UserControl

    {

        [Dependency]

        public PageViewModel ViewModel { get; set; }

Basically the [DependencyAttribute] says “Hey, Unity! I’m here! put a value into this property.”

 

Now, we’ll create a static IoC used from everywhere in our App:

    public static class IoC

    {

        static IoC()

        {

            Current = new UnityContainer();

        }

 

        public static UnityContainer Current { get; set; }

    }

In our runtime project App.xaml.cs we’ll register “PageViewModel” type as the correct type to fill for any property of type PageViewModel.

    public static class IoC

    {

        static IoC()

        {

            Current = new UnityContainer();

            Current.RegisterType<PageViewModel, PageViewModel>();

        }

 

        public static UnityContainer Current { get; set; }

    }

Not the best of examples, I’ll admit that. But if we used a IPageViewModel this piece of code would have more meaning, and would look like this:

    public static class IoC

    {

        static IoC()

        {

            Current = new UnityContainer();

            Current.RegisterType<IPageViewModel, PageViewModel>();

        }

 

        public static UnityContainer Current { get; set; }

    }

Which basically tells “Hey, Unity! Whenever you need to fill a property of type IPageViewModel use PageViewModel.”
Since we’re not using such an interface, we’ll go back to the original syntax.

 

One last thing we have to do is tell Unity to populate our type:

    public partial class Page : UserControl

    {

        [Dependency]

        public PageViewModel ViewModel { get; set; }

 

        public Page()

        {

            InitializeComponent();

           IoC.Current.BuildUp(this);

            this.Loaded += new RoutedEventHandler(Page_Loaded);

        }

 

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

            this.DataContext = ViewModel;

        }

    }

 

Now, let’s run our app:

image

It still shows “Hello world!”.
Let’s go over how that happened:

image

How does that help us write better code?
Well, right now we only have a dependency between 2 classes. So it doesn’t help us a lot.
image

Becomes:

image

But when dealing with real-world object graphs with a more complicated Object graph it becomes obvious why IoC containers are good:

image

When using IoC containers:

image

In a real-world app, everyone registers with the IoC container and resolve dependencies with it.

 

Now, let’s use Unity in on our unit tests and re-write our existing tests to use Unity to resolve these dependencies:

        [TestMethod]

        [Asynchronous]

        public void Page_Loaded_ShouldUsePageViewModelHelloTextOnce()

        {

            Page page = new Page();

            MockRepository mocks = new MockRepository();

            PageViewModel pageViewModel = (PageViewModel) mocks.StrictMock(typeof(PageViewModel));

            page.ViewModel = pageViewModel;

 

            using(mocks.Record())

            {

                pageViewModel.Expect(p => p.HelloText).Return(string.Empty);

            }

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => mocks.VerifyAll());

 

            EnqueueTestComplete();

        }

we’ll re-write the test to use our Unity container:

        [TestMethod]

        [Asynchronous]

        public void Page_Loaded_ShouldUsePageViewModelHelloTextOnceWithUnity()

        {

            MockRepository mocks = new MockRepository();

            IoC.Current.RegisterInstance(typeof(PageViewModel), mocks.StrictMock(typeof(PageViewModel)));

            Page page = IoC.Current.Resolve<Page>();

 

            using (mocks.Record())

            {

                IoC.Current.Resolve<PageViewModel>().Expect(p => p.HelloText).Return(string.Empty);

            }

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => mocks.VerifyAll());

 

            EnqueueTestComplete();

        }

 

Let’s go line-by-line here:

IoC.Current.RegisterInstance(typeof(PageViewModel), mocks.StrictMock(typeof(PageViewModel)));

Basically we’re telling Unity that whenever it needs to resolve PageViewModel it should use a PageViewModel mock from RhinoMocks.

Page page = IoC.Current.Resolve<Page>();

Instead of initializing a type with “new myClass();” we’re using Unity to get a class that has all of it’s dependencies resolved. In our case, a Page with a PageViewModel Mock.

            using (mocks.Record())

            {

                IoC.Current.Resolve<PageViewModel>().Expect(p => p.HelloText).Return(string.Empty);

            }

In stead of keeping a local copy of the Mock we created for Unity, we’re using Unity’s Resolve method to test get the mock back.

 

And when we run our tests:

image

 

Let’s rewrite this test with Unity:

[TestMethod]

[Asynchronous]

public void Page_Loaded_ShouldUsePageViewModelHelloTextInTextBlock()

{

    Page page = new Page();

    MockRepository mocks = new MockRepository();

    PageViewModel pageViewModel = (PageViewModel)mocks.StrictMock(typeof(PageViewModel));

    page.ViewModel = pageViewModel;

 

    using (mocks.Record())

    {

        pageViewModel.Expect(p => p.HelloText).Return("Bonjour Monde!");

    }

 

    WaitFor(page, "Loaded");

    TestPanel.Children.Add(page);

 

    EnqueueCallback(() => Assert.IsTrue(page.txt.Text == "Bonjour Monde!"));

    EnqueueCallback(() => mocks.VerifyAll());

 

    EnqueueTestComplete();

}

 

Becomes:

[TestMethod]

[Asynchronous]

public void Page_Loaded_ShouldUsePageViewModelHelloTextInTextBlockWithUnity()

{

    MockRepository mocks = new MockRepository();

    IoC.Current.RegisterInstance(typeof(PageViewModel), mocks.StrictMock(typeof(PageViewModel)));

    Page page = IoC.Current.Resolve<Page>();

 

    using (mocks.Record())

    {

        IoC.Current.Resolve<PageViewModel>().Expect(p => p.HelloText).Return("Bonjour Monde!");

    }

 

    WaitFor(page, "Loaded");

    TestPanel.Children.Add(page);

 

    EnqueueCallback(() => Assert.IsTrue(page.txt.Text == "Bonjour Monde!"));

    EnqueueCallback(() => mocks.VerifyAll());

 

    EnqueueTestComplete();

}

 

And when we run our tests:

image

 

Some slight of hand I used here is to change this test:

        [TestMethod]

        [Asynchronous]

        public void PageWithTextBlock_Initialized_ShouldHaveHelloWorldText()

        {

            Page page = IoC.Current.Resolve<Page>(); //new Page();

 

            WaitFor(page, "Loaded");

            TestPanel.Children.Add(page);

 

            EnqueueCallback(() => Assert.IsTrue(page.txt.Text == "Hello World!"));

 

            EnqueueTestComplete();

        }

We no longer initialize types with “new” rather through the IoC container.

And I added this Piece of code to initialize our IoC between each test:

[TestInitialize]

public void Init()

{

    IoC.Current = new UnityContainer();

}

 

 

 

Silverlight Unit Test Runner for Resharper – Hello World

Running our tests breaks the flow of development.

We have to exit Visual studio, run the test harness and wait for it to run.
In comes Einar Ingebrigtsen’s Resharper SIlverlight Unit test runner.

1. Install R# (Resharper) from http://www.jetbrains.com/resharper/download/index.html 
R# is a commercial plugin for Visual studio, but has 30 first days free.

2. Download the latest version of the R# Silverlight Unit test runner from Einar’s blog.
image

3. Drag & Drop the 3 DLLs into the machine’s GAC. (On my computer at: c:\Windows\Assembly)
image 

4. Copy the 3 DLLs and the “Website” directory into a subdirectory under the Resharper plugin directory.
(On my computer: C:\Program Files\JetBrains\ReSharper\v4.1\Bin\Plugins\C:\Program Files\JetBrains\ReSharper\v4.1\Bin\Plugins\)

image

5. Restart Visual studio and when prompted for a website address put in the file path for the ‘website’ directory.
image

 

with our testing project open, open up the Resharper Unit Testing explorer.

image

And you can see our 7 Silverlight unit tests In the R# Unit test explorer.
We’ll press the “run” button to run all the tests.

image

A new R# Test session starts up and we’ll have to wait 5 seconds the first time for tests to start running. 

image

And we can see that all test pass: 

image

 

And finally, it’s just easier to run our tests by pressing “Run all tests from solution” (and maybe assigning a shortcut for it).

image

 

 

Summary – Hello World

Hopefully this short blog post is enough to get you on your way in the exciting world of unit testing.

None of these solutions are the sole frameworks in their respective field, but they are a good start.
The article doesn’t talk about best practices like Unity AutoMocker or ViewModels, but it does give you a good start.
I’ll try to cover other Silverlight IoC containers and Silverlight Mocking solutions in future blog posts.

 

Sincerely,

-- Justin Angel

Microsoft Silverlight Toolkit Program Manager

Tour around Silverlight Contrib

I’m a huge supporter of open source software development and I'd like to take this opportunity to go over the Silverlight Contrib O/S project.

Fair disclosure first though, my current role is Program Manager for the Silverlight Toolkit team which ships on codeplex (at http://www.codeplex.com/Silverlight). 
I’ve got a very high quality bar and I expect nothing less from any O/S project that I ask customers to put into their technology stack.

Standard Microsoft disclaimer: KGB Assassins will be hired toWait, other Disclaimer: I do not speak for my employer.

 

Getting Started

OK, this is straight forward and well-organized.

Codeplex website @ http://www.codeplex.com/SilverlightContrib.
I went to the Release Tab and downloaded the latest Alpha 3 release from December 15th.

Official Website @ http://SilverlightContrib.org.

And live demo @ http://silverlightcontrib.org/demo/.

Silverlight Contrib Demo App

First impressions is really important, especially in a open source projects. A real problem that plagues open source initiatives is the lack of proper documentation and support.
Silverlight Contrib has an excellent first impression. Excellent website organization, solid sample app, and it even comes with a CHM file!

image

Unfortunately some of the subjects in the CHM file clearly say they’re missing documentation:

image

 

OK, Let’s start a new SIlverlight Application and add the (3?) Silverlight Contrib DLLs.

image

image 

image

And here’s how our solution looks like after we added the DLLs:

image

 

Inside the XAML editor, we’ll go to “Choose Items” and add the “SilverlightContrib.Controls.dll” to the Visual Studio Toolbox:

image    --> image

 

 

Color Picker

Ok, enough fooling around. Let’s build something. We’ll start from the Color Picker.

Based on the CHM file here’s what we’ve got In ColorPicker:

image

We’ve got a ColorSelected event and a SelectedColor property.

Let’s build a simple app that has a color picker and a rectangle that uses that colour as fill.

<UserControl x:Class="SL_Contrib.Page"

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

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

   xmlns:SilverlightContrib="clr-namespace:SilverlightContrib.Controls;assembly=SilverlightContrib.Controls"

   Width="400" Height="200">

    <StackPanel x:Name="LayoutRoot" Background="White">

        <SilverlightContrib:ColorPicker x:Name="colorPicker" Width="200" Height="200" SelectedColor="Green" />

        <Rectangle x:Name="myRect" Fill="Green" Width="100" Height="100" />

    </StackPanel>

</UserControl>

Hmm… I was writing the event registration there for a second and got confused… Why is the Property called “SelectedColor” and the change event called “ColorChanged”? Shouldn’t it be “SelectedColorChanged”? Minor annoyance, but still.

    public partial class Page : UserControl

    {

        public Page()

        {

            InitializeComponent();

            colorPicker.ColorSelected += new ColorSelectedHandler(colorPicker_ColorSelected);

        }

 

        private void colorPicker_ColorSelected(object sender, ColorSelectedEventArgs e)

        {

            myRect.Fill.As<SolidColorBrush>().Color = e.SelectedColor;

        }

    }

 

    public static class Extensions

    {

        public static T As<T>(this object obj)

        {

            return (T) obj;

        }

    }

(I’ve added my As<T> extension method)

Let’s run this sample:

image

Truly a master-piece app.

Yep, and when we select another color the rectangle gets updated.

image image image

It looks like the ColorPicker isn’t invoking the ColorChanged event when you just drag & drop…

image image

You can see that the in the first picture I’ve started dragging and the second picture is while still dragging.
The control visuals get updated, but there’s no external event you can use to pick up on that until the user drops and release the mouse capture.

 

The TextBlock on the bottom left isn’t editable, so we’ll create another TextBox and try to change the selected colour.

<SilverlightContrib:ColorPicker x:Name="colorPicker" Width="200" Height="200" SelectedColor="Green" />

<TextBox x:Name="tbxColour" />

<Button x:Name="btnCommit" Content="Change colour" />

<Rectangle x:Name="myRect" Fill="Green" Width="100" Height="100" />

   

    public Page()

    {

        InitializeComponent();

        colorPicker.ColorSelected += new ColorSelectedHandler(colorPicker_ColorSelected);

        btnCommit.Click += new RoutedEventHandler(btnCommit_Click);

    }

 

    void btnCommit_Click(object sender, RoutedEventArgs e)

    {

        byte alpha = Convert.ToByte(tbxColour.Text.Substring(0, 2), 16);

        byte red = Convert.ToByte(tbxColour.Text.Substring(2, 2), 16);

        byte green = Convert.ToByte(tbxColour.Text.Substring(4, 2), 16);

        byte blue = Convert.ToByte(tbxColour.Text.Substring(6, 2), 16);

        colorPicker.SelectedColor = Color.FromArgb(alpha, red, green, blue);

    }

And when we run our sample:

imageimage

Works like a charm.

 

I’ve opened the application in Blend to try and change the default layout of the ColorPicker.

image

Now, I changed the default visual tree for this control to move the Hue selector from the left to the right and viola:

image

Still works like a charm:

image

One cool thing though that I’ve noticed in Blend is how the nice spectrum is built:

image

It’s basically a Gradient with a bunch of GradientStops. cool stuff. I keep being amazed by how people use the basic 2d drawing abilities of Silverlight.

 

 

 

All and all, this is a very cool control.

Pros:

  1. Very cool visuals appearance. Main color selection palette, side spectrum selection palette and an area that shows the currently selected color and it’s Hex.
  2. Solid control.

Minor cons:

  • The event naming for “ColorChanged” is not consistent with the property name “SelectedColor”.
  • No event “ColorChanging” that fired while the eye dropper is being dragged.
  • The Hex textbox isn’t editable

 

 

Cool Menu

From the sample app:

image

 

Here’s the CHM file doc on this control:

image

So we’ve got Items, MaxItemHeight, MaxItemWidth, MenuIndexChanged, MenuItemClicked  & MenuItemClickEffect.

 

Let’s build a quick cool menu with some items.
I’ll use Dobby the penguin from Jose Farajdo’s blog - http://advertboy.wordpress.com/blendcandy/.

   <SilverlightContrib:CoolMenu Height="200">

            <SilverlightContrib:CoolMenu.Items>

                <SilverlightContrib:CoolMenuItemCollection>

                    <SilverlightContrib:CoolMenuItem>

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem>

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem>

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem>

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem>

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem>

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                </SilverlightContrib:CoolMenuItemCollection>

            </SilverlightContrib:CoolMenu.Items>

        </SilverlightContrib:CoolMenu>

I’m not crazy about this syntax.

First, I have to specify I’m using the Items collectiom, than the specific collection CollMenuItemCollection, than a CoolMenuItem, than Content and finally I can specify the actual Items. Why not support a much simpler syntax?

        <SilverlightContrib:CoolMenu Height="200">

            <SL_Contrib:PenguinControl />

            <SL_Contrib:PenguinControl />

            <SL_Contrib:PenguinControl />

            <SL_Contrib:PenguinControl />

            <SL_Contrib:PenguinControl />

        </SilverlightContrib:CoolMenu>

 

Anyway, let’s run our app with the extended syntax:

image

When we mouse over one of the items:

image

image

This menu is pretty cool.

 

Oh, this is cool: When I click Dobby he drops down and up in a nice elastic bounce motion.

image

image

image

image

Pretty cool.

 

OK, Now I’d like to grab a click on one of the menu items.
We’ll name the menu and add a Text property to the MenuItem.

        <SilverlightContrib:CoolMenu Height="200" x:Name="menu">

            <SilverlightContrib:CoolMenu.Items>

                <SilverlightContrib:CoolMenuItemCollection>

                    <SilverlightContrib:CoolMenuItem Text="Penguin #1">

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem Text="Penguin #2">

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem Text="Penguin #3">

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem Text="Penguin #4">

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem Text="Penguin #5">

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                    <SilverlightContrib:CoolMenuItem Text="Penguin #6">

                        <SilverlightContrib:CoolMenuItem.Content>

                            <SL_Contrib:PenguinControl />

                        </SilverlightContrib:CoolMenuItem.Content>

                    </SilverlightContrib:CoolMenuItem>

                </SilverlightContrib:CoolMenuItemCollection>

            </SilverlightContrib:CoolMenu.Items>

        </SilverlightContrib:CoolMenu>

 

    public partial class CoolMenuPage : UserControl

    {

        public CoolMenuPage()

        {

            InitializeComponent();

            menu.MenuItemClicked += new SilverlightContrib.Controls.MenuIndexChangedHandler(menu_MenuItemClicked);

        }

 

        void menu_MenuItemClicked(object sender, SilverlightContrib.Controls.SelectedMenuItemArgs e)

        {

            txt.Text = e.Item.Text;

        }

    }

 

image

image

 

Nice.

 

Pros:

  1. Pretty cool all and all. The resizing effect, the nice elastic bounce on click and how the rest of the items move. All of these create a solid User eXprience.

Cons:

  • The menu only ships with 2 types of effects “Bounce” and “None”. There’s no extensible mechanisms to add your own effect. Not even through VSM. 
  • The menu should inherit from ItemsControl, which it doesn’t. I can’t use any Item templating goodness with this control. I’d like to be able to run this code:

menu.ItemSource = new List<Person>()

           {

               new Person(Name = "Justin", Picture = "Awesomeness.jpg"),

               new Person(Name = "RJ", Picture = "YaImADutch.jpg"),

               new Person(Name = "Dobby", Picture = "Penguin.jpg"),

           };

        <SilverlightContrib:CoolMenu>

            <SilverlightContrib:CoolMenu.ItemTemplate>

                <DataTemplate>

                    <StackPanel>

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

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

                    </StackPanel>

                </DataTemplate>

            </SilverlightContrib:CoolMenu.ItemTemplate>

        </SilverlightContrib:CoolMenu>

 

 

 

Tweening

From Wikipedia:

“Inbetweening or tweening is the process of generating intermediate frames between two images to give the appearance that the first image evolves smoothly into the second image. Inbetweens are the drawings between the keyframes which help to create the illusion of motion. Inbetweening is a key process in all types of animation, including computer animation.” – copied from http://en.wikipedia.org/wiki/Tweening 

Basically, Tweening is creating cool effects :)

Let’s see a basic example of this.

Here’s a basketball I draw in a few seconds in Blend:

image

(Yes, It’s a Basketball. Prove that it isn’t.)

Now, I’d like for it to bounce down and up and down again in a realistic fashion.

image

 

In order to do that, We’ll add a storyboard in Blend.

image

 

At 0.0 we want the basketball to be at the initial position.

image

At 0.5 seconds we want it at the bottom.

image

At 0.7 seconds we want it to back up a bit.

image

And down again in 0.8 seconds:

image

And on load we’ll start the stortboard:

        void SilverlightControl1_Loaded(object sender, RoutedEventArgs e)

        {

            Resources["Storyboard1"].As<Storyboard>().Begin();

        }

 

 

Than once we run the page we’ll see the full animation.

However, we might not want to specify each possible frame to build realistic effects.
It’s hard, and it’s a very hard task.

In comes the Tweening library.
First, we’ll delete all keyframes besides the last one.

image

Than, back in Visual studio we’ll have a look at that last keyframe.

        <Storyboard x:Name="Storyboard1">

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="canvas" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">

                <SplineDoubleKeyFrame KeyTime="00:00:00.8000000" Value="186"/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

We’ll start by deleting even that last animation and copying it’s Keyframe time to the Duration.

        <Storyboard x:Name="Storyboard1">

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"

                                          Duration="00:00:00.8000000"

                                          Storyboard.TargetName="canvas"

                                          Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" />

        </Storyboard>

Next, We’ll add some Tweening attached properties.

image

Now, let’s set the initial value, the target value, the number of frames we’d like generated for us and the Transition type. .

image image image

And here’s our final storyboard XAML:

<Storyboard x:Name="Storyboard1">

    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"

                                  Duration="00:00:00.8000000"

                                  Storyboard.TargetName="canvas"

                                  Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)"

                                  Tweener:Tween.From="0"

                                  Tweener:Tween.To="186"

                                  Tweener:Tween.Fps="20"

                                  Tweener:Tween.TransitionType="EaseInOutBounce"

                                  />

</Storyboard>

 

And once we run our app we can see the ball falls down, bounces up a bit and falls back down.

 

Pro:

  1. Tweening is a well established concept in the designer audience and a very productive way of creating complex animations quickly.

Con:

  • No blend support.

 

 

Clipboard support

At times you might want to have Silverlight access directly into your clipboard. Which isn’t natively supported in Silverlight.

<StackPanel x:Name="LayoutRoot" Background="White">

    <TextBox x:Name="tbx" />

    <Button Click="Copy_Click" Content="Copy TextBox to clipboard" />

    <Button Click="Paste_Click" Content="Paste clipboard to TextBox" />

    <Button Click="Clear_Click" Content="Clear clipboard" />

</StackPanel>

 

Here’s how this page looks like when we run it.

image

 

We’d like the copy button to copy the Text from the TextBox to the clipboard.

First, we’ll create a property that will use lazy initialization to Initialize a ClipboardHelper class.

private ClipboardHelper _clipboard = null;

ClipboardHelper Clipboard

{

    get

    {

        try

        {

            if (_clipboard == null)

                _clipboard = new ClipboardHelper();

        }

        catch (InvalidOperationException)

        {

            MessageBox.Show("Current browser isn't supported for clip board opearations.");

        }

        return _clipboard;

    }

}

This class has resource implications and browser support implications, so it’s best to cache it once initialized.

 

Now, once the Copy button is clicked we’d like to copy the contents of the TextBox into the Clipboard.

        private void Copy_Click(object sender, RoutedEventArgs e)

        {

            if (Clipboard != null)

                Clipboard.SetData(tbx.Text);

        }

Let’s run this application and put some text into the TextBox:

image

 

Once we click the button the user will be prompted to allow the browser app to access the clipboard:

image

The user must click “Allow Access”.

 

Now let’s paste this outside our browser:

image

 

So we can use the Clipboard helper from SIlverlight contrib to cross through browsers boundaries.

 

Now, let’s implement all buttons.

        private void Copy_Click(object sender, RoutedEventArgs e)

        {

            if (Clipboard != null)

                Clipboard.SetData(tbx.Text);

        }

 

        private void Paste_Click(object sender, RoutedEventArgs e)

        {

            if (Clipboard != null && !string.IsNullOrEmpty(Clipboard.GetData()))

                tbx.Text = Clipboard.GetData();

        }

 

        private void Clear_Click(object sender, RoutedEventArgs e)

        {

            if (Clipboard != null)

                Clipboard.ClearData();

        }

And once we run our application we see that we can copy information from outside the browser inside our Silverlight Application.

image

image

 

And we can clear the clipboard and paste again to see it’s been emptied out.

image

 

Pros:

  1. Clipboard support is something that can make or break certain apps.
  2. This is an excellent API for something that is basically a huge managed wrapper around Javascript. Having a Dispose method on the ClipboardHelper shows the Contrib team really did a good job here.
  3. This could enable a range of scenarios (copying whole files from outside the browser?)

Con:

  • The current implementation is not supported in FireFox.

image

 

 

Sum up

The Silverlight Contrib is a top notch open source project with some excellent controls, utilities and helpers you should definitely take a look at.

Go get it at http://codeplex.com/SilverlightContrib.

 

 

-- Justin Angel 

Microsoft Silverlight Toolkit Program Manager

Silverlight DLLs on the desktop CLR

I’m a dinosaur and it’s possible I’m the only who didn’t know this – But you can use Silverlight DLLs on the full .Net desktop CLR.

See, I’ve been working with Silverlight since Mix07 (March 2007) when we were all excited about Silverlight 1.0 and Silverlight 1.1. 
First I was working as a senior .Net consultant on a variety of projects, and for the last 6 months I’ve been working on the Silverlight Toolkit for the evil empire Microsoft. 

So believe me when I say – I’m an aging dinosaur to miss something this big. In Silverlight 2.0 you can share DLLs between the desktop and the Silverlight CLR.
I know for sure it wasn’t possible in Silverlight 1.1, and just assumed it wasn’t possible in Silverlight 2.

From all the articles I’ve read in the last few months, It seems I’m not the only one who doesn’t know this:
http://pagebrooks.com/archive/2008/10/11/sharing-code-between-.net-and-silverlight-platforms.aspx
http://petesbloggerama.blogspot.com/2008/12/referencing-non-silverlight-assembly-in.html
http://stackoverflow.com/questions/208123/what-is-the-best-practice-for-compiling-silverlight-and-wpf-in-one-project

All of these fairly recent articles say the same – You need to use the “Add as link” hackery to share source between Silverlight CLR and the full .Net CLR.

image

Well, Apparently that’s not true. You can add a reference from a full .Net CLR to a Silverlight assembly.

 

Let’s see how to do that:

1. Let’s start a new Silverlight Application:
image

Here’s the Solution we got:

image

2. Let’s add a Silverlight Class Library to hold our Business logic.

image

Here’s our solution now:

image

3. Let’s add a class with some business logic.

public class Person

{

    private string _firstName;

    public string FirstName

    {

        get { return _firstName; }

        set { _firstName = UpperCaseFirstLetter(value); }

    }

 

    private string _lastName;

    public string LastName

    {

        get { return _lastName; }

        set { _lastName = UpperCaseFirstLetter(value); }

    }

 

    private string UpperCaseFirstLetter(string str)

    {

        if (string.IsNullOrEmpty(str) || str.Length == 0)

            return str;

 

        return str[0].ToString().ToUpper() + str.Substring(1, str.Length - 1);

    }

 

    public string FullName

    {

        get

        {

            return string.Format("{0} {1}", FirstName, LastName);

        }

    }

}

This class has 3 interesting things in it:

  1. It has 2 properties called “FirstName” and “LastName” that have a backing field.
  2. When setting the First and last name, we store these with the first character as uppercase. Property setter business logic.
  3. We have a property that combines that value of the first two properties. Pure business logic.

 

4. We’ll add a new WPF project that runs of the desktop .Net CLR. (We could have used the ASP.Net project already in the solution, but I’d like to demo WPF)

image

5. Now, we’ll add a reference from the WPF project to the Silverlight Class Library.

image

image

image

 

Using the Business logic in the Silverlight application

First, I’ll add a reference from the Silverlight Application to the Silverlight Class Library.

image

Next, I’ll add a reference to the Silverlight Toolkit (http://codeplex.com/Silverlight) mainly because we need the WrapPanel.

image

Next, we’ll add this little form to our default page:

<Border x:Name="LayoutRoot" Background="White" Margin="20" CornerRadius="5" BorderBrush="LightGray" BorderThickness="1" >

    <controls:WrapPanel Width="150" >

        <TextBlock Width="75" Text="First Name:" />

        <TextBox Width="75" Text="{Binding FirstName, Mode=TwoWay}" TabIndex="1" />

        <TextBlock Width="75" Text="Last Name:" />

        <TextBox Width="75" Text="{Binding LastName, Mode=TwoWay}" TabIndex="2" />

        <TextBlock Width="75" Text="Full Name:" />

        <TextBox Width="75" x:Name="txtFullName" IsReadOnly="True" InputMethod.IsInputMethodEnabled="False" />

        <Button Content="Update full name" Click="Button_Click" />

    </controls:WrapPanel>

</Border>

This might look lie a lot, but it’s really not.
It’ just the following form:

image

It’s only 3 textblocks, 2 editable textboxes, 1 readonly TextBox and a button.

Now let’s add some code to our code behind:

using BL;

 

namespace SilverlightApp

{

    public partial class Page : UserControl

    {

        public Page()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(Page_Loaded);

        }

 

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

            this.DataContext = new Person();

        }

 

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            txtFullName.Text = ((Person) this.DataContext).FullName;

        }

    }

}

Basically, we set the DataContext for the whole form to a Person class and once the button is clicked we manually update the txtFullName.
We could have used Dependency Properties to update this for us, but I’d rather keep this demo simple :)

 

If we run our application we’ll see the following form:

image

We’ll put a very sexy and rugged first name and last name.

image

Next we’ll click the Update button:

image

Well, that’s pretty straight forward Silverlight. (And if we added Dependency Properties it would have been much better, but this isn’t a Silverlight demo)

 

 

Using the Silverlight Class Library / Business Logic in the WPF application

We’ll create a similar yet not identical version of the form in WPF:

<Window x:Class="WPFApp.Window1"

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

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

   Title="Window1" Height="300" Width="300">

    <Grid>

        <WrapPanel Width="150" >

            <TextBlock Width="75" Text="First Name:" />

            <TextBox Width="75" Text="{Binding FirstName}" TabIndex="1" />

            <TextBlock Width="75" Text="Last Name:" />

            <TextBox Width="75" Text="{Binding LastName}" TabIndex="2" />

            <TextBlock Width="75" Text="Full Name:" />

            <TextBox Width="75" x:Name="txtFullName" IsReadOnly="True" InputMethod.IsInputMethodEnabled="False" />

            <Button Content="Update Full Name" Click="Button_Click" />

        </WrapPanel>

    </Grid>

</Window>

And here’s the pretty almost identical code behind:

using BL;

 

namespace WPFApp

{

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(Window1_Loaded);

        }

 

        void Window1_Loaded(object sender, RoutedEventArgs e)

        {

            this.DataContext = new Person();

        }

 

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            txtFullName.Text = ((Person) this.DataContext).FullName;

        }

    }

}

Let’s run this app.

image

We’ll put in the ruggedly manly name we used before.

image

Click the update button.

 

image

And yep, the same result – the Silverlight code runs on both the desktop CLR and the SIlverlight CLR.

 

One picture is worth an entire blog post

image

 

How does this even work? 

The System.String from Silverlight is a completely different System.String from the desktop .Net. So how does this even work? I’m still looking into this, but before I got the results of my research I thought it’s definitely worth sharing.

 

This has limitations, probably a lot

Using the full abilities of the Silverlight runtime is obviously not possible from our Silverlight class library when it runs on the desktop. There’s no Application.Current, no dispatcher and a host of other things are missing. This limit us to really sharing only business logic in this method, not UI.
For one, I’m not sure it’s not a good thing that we’re being forced to keep best practices of layer separation.

 

Source code available at: http://silverlight.net/blogs/justinangel/BlogStorage/SharingCode.zip

 

-- Justin Angel

Microsoft Silverlight Toolkit Program Manager

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

Custom VSM VisualStateManagers in Silverlight 2.0

Hi Folks

I’ve been getting some questions through the usual backchannels (Twitter, my J@JustinAngel.Net email and mail pigeons) about creating custom VSM managers.

If you’re unfamiliar with VSM I strongly suggest you take 30 minutes and watch Steve White talking in his ‘ohhh-so-British' voice about it:

Watch these 4 videos on the Blend 2.5 website which should provide you with deep technical insight into the inner workings of Templating Parts & VSM States from Blend: http://expression.microsoft.com/en-ca/cc643423.aspx

Let’s try and conceptually sum up what VSM does:

image 

Well, we could have just said this instead:

image

 

 

Point is – the VisualStateManager class is the driving force behind the actual change between states.

 

Let’s see how that comes into play.

Here are all the states for a Button: (snapshot from Blend)

image

Let’s put a normal button on our form, run the app and see it’s MouseOver state:

<Button x:Name="myButton" Content="myButton" Width="100" Height="100" />

image image

We can see that once the mouse enters the button area, the MouseOver state is fired, a storyboard executes and everyone’s happy.

But how executes the state change? VSM of course.

Let’s see how we can “interfere” with the normal operations of VSM.

        public SilverlightControl2536()

        {

            InitializeComponent();

            myButton.MouseEnter += new MouseEventHandler(myButton_MouseEnter);

        }

 

        private void myButton_MouseEnter(object sender, MouseEventArgs e)

        {

            VisualStateManager.GoToState(myButton, "Disabled", true);

        }

Over here I’ve registered to the MouseEnter event and told VSM that “On MouseEnter go to the Disabled State”.

image

We can definitely see that mouseovering the button clearly puts it in Disabled state.

And we caused this little change through talking directly to the VSM static class.
I’ll just point out that the button isn’t really disabled and is still clickable. All we did here is cause a VSM state change, which caused a storyboard to begin, which changed the Visual UI of the button.

 

As for demos – the disabled button one was short, to the point and utterly useless. Let’s get our hands dirty with some custom VSM goodness.

 

Writing a Custom VisualStateManager – Getting the current states

We just demonstrated and explained that the VisualStateManager is an actual class which drives state management at the control level.
We can also create our own VisualStateManager that has additional functionality and logic in that stage.

public class CustomVisualStateManager : VisualStateManager

{

    protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)

    {

this.

        return base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);

    }

}

This basic class doesn’t do a lot. We inherit from VisualStateManager and override the only method we can – GoToStateCore.

We can see that we get some important data:

  1. What control is changing it’s state
  2. What’s it’s root template element
  3. What group are we changing state to
  4. What state are we changing state to
  5. And should we use Transitions

Now, if we were the original VisualStateManager class this is the place to actually do state changes, fire storyboards, raise events and what not.
Luckily, we’re not. So we’ll call the base method and that would take care of actually running states. Which leaves us as free to do whatever we want.

And here’s what we want – We want to be able to query the VisualStateManager and get a string the represents the control’s current state in each state group.

public class QueryableVisualStateManager : VisualStateManager

{

    protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)

    {

        return base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);

    }

 

    public static string QueryState(Control controlToFindItsState)

    {

 

    }

}

Now, at this point we’ll add a lot of plumbing to store the last fired states in each group for each control.

public class QueryableVisualStateManager : VisualStateManager

{

    /// <summary>

    /// Dictionary of controls --> Dictionary of group names and state names

    /// </summary>

    private static Dictionary<WeakReference, KeyValuePair<string, string>> controlStates =

        new Dictionary<WeakReference, KeyValuePair<string, string>>();

 

    protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)

    {

        bool ReturnValue = base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);

 

        if (ReturnValue)

        {

            var existingPair = controlStates.SingleOrDefault(pair => pair.Key.IsAlive

                                                          && pair.Key.Target == control

                                                          && pair.Value.Key == group.Name);

            if (existingPair.Key != null)

            {

                controlStates.Remove(existingPair.Key);

            }

 

            controlStates.Add(new WeakReference(control), new KeyValuePair<string, string>(group.Name, stateName));

        }

        return ReturnValue;

    }

 

    public static string QueryState(Control controlToFindItsState)

    {

        var existingValues = controlStates.Where(pair => pair.Key.IsAlive

                                               && pair.Key.Target == controlToFindItsState);

 

        string ReturnValue = string.Empty;

        foreach (var existingValue in existingValues)

        {

            ReturnValue += existingValue.Value.Key + "." + existingValue.Value.Value + " ";

        }

        return ReturnValue;

    }

}

This code really isn’t interesting. All it does is store controls, state groups and last fired states in that state group. Than, it queries against that data and returns the current state in a string form.

There’s really nothing interesting about the implementation, just the fact that we’re getting some data, we’re storing it and later on query it.

 

Next, we’ll want to use this custom Visual State Manager with the Button, CheckBox and Expander control.

So, we’ll start by putting these on our form:

    <StackPanel x:Name="LayoutRoot" Background="White">

        <Button x:Name="myButton" />

        <CheckBox x:Name="myCheckBox" />

        <controls:Expander x:Name="myExpander" />

    </StackPanel>

In order to change the VSM manager, we need to edit the template for each of these controls.

We’ll open up Blend on this form.

image

We’ll right click on each of the controls –> “Edit Control Parts (Template)” –> Edit Copy –> Ok.

image

After getting local copies of the templates for all the controls, we’ll need to set the VisualStateManager.CustomVisualStateManager Attached property in their templates:

                           <vsm:VisualStateManager.CustomVisualStateManager>

                                <SL_RTM_VS:QueryableVisualStateManager />

                            </vsm:VisualStateManager.CustomVisualStateManager>

 

image

image

image

 

Here’s a method that uses the static method we defined on our custom visual state manager and updates their content:

       private void updateContentFromVSM(object sender, EventArgs e)

        {

            myButton.Content = QueryableVisualStateManager.QueryState(myButton);

            myCheckBox.Content = QueryableVisualStateManager.QueryState(myCheckBox);

            myExpander.Header = QueryableVisualStateManager.QueryState(myExpander);

        }

Let’s sign this method up for some events that may cause visual state changes:

        public SilverlightControl2536()

        {

            InitializeComponent();

            myButton.MouseEnter += new MouseEventHandler(updateContentFromVSM);

            myButton.MouseLeave += new MouseEventHandler(updateContentFromVSM);

            myButton.MouseLeftButtonDown += new MouseButtonEventHandler(updateContentFromVSM);

            myButton.MouseLeftButtonUp += new MouseButtonEventHandler(updateContentFromVSM);

            myCheckBox.MouseEnter += new MouseEventHandler(updateContentFromVSM);

            myCheckBox.MouseLeave += new MouseEventHandler(updateContentFromVSM);

            myCheckBox.MouseLeftButtonDown += new MouseButtonEventHandler(updateContentFromVSM);

            myCheckBox.MouseLeftButtonUp += new MouseButtonEventHandler(updateContentFromVSM);

            myCheckBox.Checked += new RoutedEventHandler(updateContentFromVSM);

            myCheckBox.Unchecked += new RoutedEventHandler(updateContentFromVSM);

            myExpander.MouseEnter += new MouseEventHandler(updateContentFromVSM);

            myExpander.MouseLeave += new MouseEventHandler(updateContentFromVSM);

            myExpander.MouseLeftButtonDown += new MouseButtonEventHandler(updateContentFromVSM);

            myExpander.MouseLeftButtonUp += new MouseButtonEventHandler(updateContentFromVSM);

            myExpander.Expanded += new RoutedEventHandler(updateContentFromVSM);

            myExpander.Collapsed += new RoutedEventHandler(updateContentFromVSM);

        }

Now, let’s run our app.

image

This is how the app looks like initially once we mouseover elements and cause them to fire their initial states.

Now, What happens if we mouseover the Button?

image

And Click it?

image

What happens if we mouse over the CheckBox?

image

And click the CheckBox?

image

And than Click the Expander?

image

All and all, we can see that through a Custom VisualStateManager we can store the state changes and query them at a later phase.

 

 

One last change – Getting the initial state

Through some sleight of hand I was able to hide the fact that our form look like so when it first load up:

image

If we want to have the controls initialized with their states, we need to make sure the Visual Tree (which setups the custom VSM) is up and running.
In order to do that, we’ll register for a one-time call to LayoutUpdated on each control and initialize state there.

myButton.LayoutUpdated += new EventHandler(myButton_LayoutUpdated);

void myButton_LayoutUpdated(object sender, EventArgs e)

{

    myButton.LayoutUpdated -= myButton_LayoutUpdated;

    myButton.Content = QueryableVisualStateManager.QueryState(myButton);

}

And now in our UI we can see the following initial state:

image

Now, let’s add this initialization step for our two other controls:

 

            myButton.LayoutUpdated += new EventHandler(myButton_LayoutUpdated);

            myCheckBox.LayoutUpdated += new EventHandler(myCheckBox_LayoutUpdated);

            myExpander.LayoutUpdated += new EventHandler(myExpander_LayoutUpdated);

 

void myButton_LayoutUpdated(object sender, EventArgs e)

{

    myButton.LayoutUpdated -= myButton_LayoutUpdated;

    myButton.Content = QueryableVisualStateManager.QueryState(myButton);

}

 

void myCheckBox_LayoutUpdated(object sender, EventArgs e)

{

    myCheckBox.LayoutUpdated -= myCheckBox_LayoutUpdated;

    myCheckBox.Content = QueryableVisualStateManager.QueryState(myCheckBox);

}

 

void myExpander_LayoutUpdated(object sender, EventArgs e)

{

    myExpander.LayoutUpdated -= myExpander_LayoutUpdated;

    myExpander.Content = QueryableVisualStateManager.QueryState(myExpander);

}

image

And you can get the source at: http://silverlight.net/blogs/justinangel/BlogStorage/SilverlightCustomVSM.zip 

 

 

-- Justin Angel
Microsoft Silverlight Toolkit Program Manager

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 | 17 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 | 4 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 | 9 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 | 11 comment(s)
Filed under:

Silverlight Toolkit: DockPanel

Hi Folks,

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

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

 

 

Setup

1. Create a new project.

File --> New --> Project

Select project type

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

image    image

3. Add a Silverlight Toolkit controls xmlns reference to the XAML page.

image

4. Add a DockPanel to the Page with LastChildFill=False. (we’ll talk about this property later in this article)

<controls:DockPanel LastChildFill="False" />

Here’s the XAML we just wrote:

<UserControl x:Class="SilverlightControlsNovember2008.Page"

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

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

   Width="400" Height="300"

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

    <Border x:Name="LayoutRoot" Background="White" BorderBrush="Black" BorderThickness="1">

        <controls:DockPanel LastChildFill="False" />

    </Border>

</UserControl>

(for the purposes of this specific demo, we changed LayoutRoot to be a <Border> and not a <Grid>. That is so we could see the edge of our visual area)

 

 

Docking a single Item

Let’s add a <Button> to the top our DockPanel.

 

<controls:DockPanel LastChildFill="False">

    <Button Content="#1" />

</controls:DockPanel>

 

And here’s a preview through Visual Studio XAML designer:

image

<Button> Fills all the available space given to the control inside our DockPanel.
So actually Button is perfect for us to learn about DockPanel, since it lets us see exactly where the control is and what the available size for it is.

As we can, the <Button> is Docked to the Left of our DockPanel.

Let’s specify a Controls:DockPanel.Dock Attached property value that would allow us to dock items to areas of the DockPanel.

Dock Enum values

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Top" Content="#1" />

</controls:DockPanel>

image

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Right" Content="#1" />

</controls:DockPanel>

image

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

</controls:DockPanel>

image

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Left" Content="#1" />

</controls:DockPanel>

image

 

 

Docking two items to opposite docking areas

Looking at the previous example it’s pretty easy to figure out what the following XAML would look like.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Left" Content="#1" />

    <Button controls:DockPanel.Dock="Right" Content="#2" />

</controls:DockPanel>

image

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Top" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

</controls:DockPanel>

image

 

 

Docking the last item to Center

In our first DockPanel example we’ve set some mysterious LastChildFill property to false.

Let’s set LastChildFill=True and put an undocked button as the last child of the DockPanel.

<controls:DockPanel LastChildFill="True">

    <Button controls:DockPanel.Dock="Top" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

    <Button Content="Filler" />

</controls:DockPanel>

image

It’s important to note that Even if we do set DockPanel.Dock property on the last child of a DockPanel, it would still get filled out if LastChildFill=True.

<controls:DockPanel LastChildFill="True">

    <Button controls:DockPanel.Dock="Top" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

    <Button controls:DockPanel.Dock="Left" Content="Filler" />

</controls:DockPanel>

image

You can see the “DockPanel.Dock=Left” is completely ignored and the Last child is still treated as a filler.

 

 

Docking Defaults (LastChildFill=True, Dock=Left)

By default a DockPanel would always treat the last child as a Filler.

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Top" Content="#1" />

    <Button Content="Filler" />

</controls:DockPanel>

image

And by default, any item not Docked to any area, will be docked to the Left.

<controls:DockPanel>

    <Button Content="#1" />

    <Button Content="Filler" />

</controls:DockPanel>

image

Let’s add a few more items so the Default Left docking would be a bit more visible.

<controls:DockPanel>

    <Button Content="#1" />

    <Button Content="#2" />

    <Button Content="#3" />

    <Button Content="Filler" />

</controls:DockPanel>

image

Essentially, DockPanel by default mimics a Horizontal StackPanel besides the fact that it does treat the last item as a filler.

<StackPanel Orientation="Horizontal">

    <Button Content="#1" />

    <Button Content="#2" />

    <Button Content="#3" />

    <Button Content="Filler" />

</StackPanel>

image

 

 

Docking Multiple elements to the same Docking area

You’ve probably noticed in the previous example that the DockPanel stacks items on the same Area.

Let’s have a look at stacking multiple items on all sides.

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Top" Content="#1" />

    <Button controls:DockPanel.Dock="Top" Content="#2" />

    <Button controls:DockPanel.Dock="Top" Content="#3" />

    <Button Content="Filler" />

</controls:DockPanel>

image

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Right" Content="#1" />

    <Button controls:DockPanel.Dock="Right" Content="#2" />

    <Button controls:DockPanel.Dock="Right" Content="#3" />

    <Button Content="Filler" />

</controls:DockPanel>

image

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

    <Button controls:DockPanel.Dock="Bottom" Content="#3" />

    <Button Content="Filler" />

</controls:DockPanel>

image

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Left" Content="#1" />

    <Button controls:DockPanel.Dock="Left" Content="#2" />

    <Button controls:DockPanel.Dock="Left" Content="#3" />

    <Button Content="Filler" />

</controls:DockPanel>

image

The order in which items are stacked is based on the first rule of DockPanel.

image

The First Rule of Fight Club: You Don’t talk about Fight Club

image

The First Rule of DockPanel: The order of DockPanel Children’s determines their placement based on precedence

 

Admittedly it’s not as dramatic as fight club, but it’s still true.

Consider these trivial DockPanel samples:

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Left" Content="#1" />

    <Button controls:DockPanel.Dock="Left" Content="#2" />

    <Button Content="Filler" />

</controls:DockPanel>

<controls:DockPanel>

    <Button controls:DockPanel.Dock="Left" Content="#2" />

    <Button controls:DockPanel.Dock="Left" Content="#1" />

    <Button Content="Filler" />

</controls:DockPanel>

image image

The #1 and #2 Items are placed based on the order of the DockPanel children.

Although that on the surface they’re both docked to the Left exactly, one has precedence over the other.

The order of DockPanel Children’s determines their placement based on precedence.

 

 

Docking Two elements with intersecting Docking areas

We mentioned Precedence, That is also relevant for Intersecting items.

Have a look at these two DockPanels. 

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Top" Content="#1" />

</controls:DockPanel>

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Left" Content="#1" />

</controls:DockPanel>

 image image

Let’s illustrate how this collision would look like: (done with mspaint, not an actual print-screen)

image

Now, it’s obvious that one of these items would need to get the Top-left corner, but which one?
Let’s write some code, run it and we’ll see.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Top" Content="#1" />

    <Button controls:DockPanel.Dock="Left" Content="#2" />

</controls:DockPanel>

image

And if we change the order of the buttons:

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Left" Content="#2" />

    <Button controls:DockPanel.Dock="Top" Content="#1" />

</controls:DockPanel>

image

So, as we can see – In the first example Button #1 was placed first and it got precedence over Button #2 for the top-left corner.

And in the second example Button #2 was placed first and it got precedence over Button #1 for the top-left corner.

Let’s see this at work with other docking areas.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Right" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

</controls:DockPanel>

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

    <Button controls:DockPanel.Dock="Right" Content="#2" />

</controls:DockPanel>

image image

So, we can clearly see that the order of items in the DockPanel determines who is docked first to where.

 

 

Docking Multiple elements to intersecting Docking areas

Let’s follow up on the last sample with more than one item per docking area.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

    <Button controls:DockPanel.Dock="Bottom" Content="#3" />

    <Button controls:DockPanel.Dock="Right" Content="#4" />

</controls:DockPanel>

image

So, Button #1 is docked first to the bottom area, followed by buttons #2 and #3, and finally Button #4 is docked to the Right.

Let’s put more items on the Right docking area.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

    <Button controls:DockPanel.Dock="Bottom" Content="#3" />

    <Button controls:DockPanel.Dock="Right" Content="#4" />

    <Button controls:DockPanel.Dock="Right" Content="#5" />

    <Button controls:DockPanel.Dock="Right" Content="#6" />

</controls:DockPanel>

image

Continuing for where the last sample left off, Button #4 is the first control docked to the right, followed by Button #5 and finally Button #6.

And we’ll finish this sample by adding Top docked buttons and Left docked buttons.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

    <Button controls:DockPanel.Dock="Bottom" Content="#2" />

    <Button controls:DockPanel.Dock="Bottom" Content="#3" />

    <Button controls:DockPanel.Dock="Right" Content="#4" />

    <Button controls:DockPanel.Dock="Right" Content="#5" />

    <Button controls:DockPanel.Dock="Right" Content="#6" />

    <Button controls:DockPanel.Dock="Top" Content="#7" />

    <Button controls:DockPanel.Dock="Top" Content="#8" />

    <Button controls:DockPanel.Dock="Top" Content="#9" />

    <Button controls:DockPanel.Dock="Left" Content="#10" />

    <Button controls:DockPanel.Dock="Left" Content="#11" />

    <Button controls:DockPanel.Dock="Left" Content="#12" />

</controls:DockPanel>

image

First, Buttons #1, #2 & #3 Get placed on the bottom.

Second, Buttons #4, #5 & #6 get placed on the right, using up remaining height in the DockPanel.

Third, Buttons #7, #8 & #9 get placed on the top, using up remaining width in the DockPanel.

Lastly, Buttons #10, #11 & #12 get placed on the Left, using up remaining Height in the DockPanel (after placing both Bottom and Top buttons).

 

So again – The order of DockPanel Children’s determines their placement based on precedence.

Let’s mix it up a bit by alternating top buttons and left buttons.

<controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Bottom" Content="#1" />

    <Button controls:DockPanel.Dock="Right" Content="#2" />

    <Button controls:DockPanel.Dock="Bottom" Content="#3" />

    <Button controls:DockPanel.Dock="Right" Content="#4" />

    <Button controls:DockPanel.Dock="Bottom" Content="#5" />

    <Button controls:DockPanel.Dock="Right" Content="#6" />

</controls:DockPanel>

image

image 

We can see that items are weaved into the DockPanel, based on their order.

 

 

Spiral Sample

One last final sample for DockPanel is the Spiral sample by Ted Glaza (a developer on the Silverlight Toolkit team) and slightly changed for this blog post.

 <controls:DockPanel LastChildFill="False">

    <Button controls:DockPanel.Dock="Left" Background="LightBlue" Content="#1" />

    <Button controls:DockPanel.Dock="Top" Background="LightCoral" Content="#2" />

    <Button controls:DockPanel.Dock="Right" Background="LightGreen" Content="#3" />

    <Button controls:DockPanel.Dock="Bottom" Background="LightYellow" Content="#4" />

    <Button controls:DockPanel.Dock="Left" Background="LightGreen" Content="#5" />

    <Button controls:DockPanel.Dock="Top" Background="LightYellow" Content="#6" />

    <Button controls:DockPanel.Dock="Right" Background="LightBlue" Content="#7" />

    <Button controls:DockPanel.Dock="Bottom" Background="LightCoral" Content="#8" />

    <Button controls:DockPanel.Dock="Left" Background="LightGray" Content="#9" />

    <Button controls:DockPanel.Dock="Top" Background="LightCoral" Content="#10" />

    <Button controls:DockPanel.Dock="Right" Background="LightGreen" Content="#11" />

    <Button controls:DockPanel.Dock="Bottom" Background="LightBlue" Content="#12" />

    <Button controls:DockPanel.Dock="Left" Background="LightCoral" Content="#13" />

    <Button controls:DockPanel.Dock="Top" Background="LightGoldenrodYellow" Content="#14" />

    <Button controls:DockPanel.Dock="Right" Background="LightYellow" Content="#15" />

    <Button controls:DockPanel.Dock="Bottom" Background="LightGreen" Content="#16" />

    <Button controls:DockPanel.Dock="Left" Background="LightYellow" Content="#17" />

    <Button controls:DockPanel.Dock="Top" Background="LightCoral" Content="#18" />

    <Button controls:DockPanel.Dock="Right" Background="LightBlue" Content="#19" />

    <Button controls:DockPanel.Dock="Bottom" Background="LightGreen" Content="#20" />

    <Button controls:DockPanel.Dock="Left" Background="LightBlue" Content="#21" />

    <Button controls:DockPanel.Dock="Top" Background="LightGreen" Content="#22" />

    <Button controls:DockPanel.Dock="Right" Background="LightCoral" Content="#23" />

    <Button controls:DockPanel.Dock="Bottom" Background="LightYellow" Content="#24" />

</controls:DockPanel>

image

image

Pretty, isn’t it?

 

-- Justin Angel

Microsoft Silverlight Program Manager

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

Silverlight Toolkit: Label

Hi Folks,

 

I’d like for us to talk a bit a seemingly simple control in the Silverlight Toolkit – Label.

In the following article we’ll deep dive into the many nooks and crannies Label has and unleash some of it’s true power.

 

Setup

1. Create a new project.

image

 

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

image   image

 

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

image

 

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

    x:Class="SilverlightControlsNovember2008.Page"

    Width="640" Height="480"

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

    <Grid x:Name="LayoutRoot" Background="White">

        <slctls:Label HorizontalAlignment="Left" Margin="98,179,0,220" Width="148" Content="Label"/>

    </Grid>

</UserControl>

 

 

Setting the content for Label

One way of editing a Label's content is double clicking on it.

image

image

And here's the XAML we just got:

<slctls:Label HorizontalAlignment="Left" Margin="98,179,0,220" Width="148" Content="Hello World!"/>

 

Another way is editing the Content property directly through the Blend Data pane:

 image

 

 

Changing the Label’s Border

We can set the BorderBrush & BorderThinckness properties for Label in order to change it’s border.

image

 

Let’s start by changing the BorderBrush to a SoliderColorBrush with a light grey color. Click on BorderBrush and select SolidColorBrush.

image --> image

Than we’ll select a light gray color.

image

 

Next, we’ll change the BorderThickness to 1 on all sides.

image

 

And we can see our Label has a nice subtle light grey border around itself:

image

 

Here’s the XAML we just generated:

<slctls:Label HorizontalAlignment="Left" Margin="98,179,0,0" Width="79" Content="I am a Label!" VerticalAlignment="Top" Height="20" BorderBrush="#FFC6C4C4" BorderThickness="1,1,1,1"/>

 

 

Changing Label’s Background and Foreground

Let’s do what the title says!

In case you’re wondering TextBlock does not have a Background. So it’s a bit different than what we would have normally done with TextBlock.

 

We’ll select Background and set it to SolidColorBrush with the color Black.

image -->image

image

 

Next we’ll select the Foreground property and set it to SolidColorBrush with the color white.

image -->image

 

here’s our Label:

image

And the xaml we generated:

<slctls:Label HorizontalAlignment="Left" Margin="98,179,0,0" Width="79" Content="I am a Label!" VerticalAlignment="Top" Height="20" BorderBrush="#FFC6C4C4" BorderThickness="1,1,1,1" Background="#FF000000" Foreground="#FFF5F3F3"/>

 

Changing the Label’s Template

Let’s say we're unhappy with the default border for out Text inside the Label. Through the power of templating we can change it.
So let’s create a RadialLabelTemplate.

 

We’ll start of by adding a new label to form.

image

Than Right Click on it, select “Edit Control Parts(Template) –> Create Empty…”.

image

We’ll change the Resource name to RadialPanelTemplate.

image

And here’s the drawing area of the new template:

image

Let’s add the Ellipse for the radial background.

image

image

Now we want to add a TextBlock to display the content of the Label. Before we can do that, we’ll need to add a container that will contain both our Ellipse and our TextBlock. We’ll Group our Ellipse into a Canvas.

image

image

 

Finally we can add our TextBlock that has the Label Content.

image

image

 

We’ll use TemplateBinding to connect this TextBlock.Text to the Label.Content property. Click the “Advanced Options” next to the Text property.

image

Select “Custom Expression”.

image

And put in the correct TemplateBinding.

image

 

To preview our form we can go back to our form.

image

 

And here it is:

image

 

Now we can we add 3 more label to our page and just apply this new template to them.

image

We’ll select each of our new Labels, Right click, select “Edit Control Parts (Template) –> Apply Resource –> Radial Label Template”.

 image -

And here’s our new form:

image

 

Here’s the XAML we just generated for the RadialLabelTemplate:

    <UserControl.Resources>

       <ControlTemplate x:Key="RadialLabelTemplate" TargetType="slctls:Label">

            <Canvas Height="Auto" Width="Auto">

                <Ellipse Height="35" Width="125" Fill="#FFFFFFFF" Stroke="#FF000000"/>

                <TextBlock Height="20" Width="82.667" Canvas.Left="22.667" Canvas.Top="7.333" Text="{TemplateBinding Content}" TextWrapping="Wrap"/>

            </Canvas>

        </ControlTemplate>

    </UserControl.Resources>

And the Xaml for our labels:

<slctls:Label Template="{StaticResource RadialLabelTemplate}" Content="Radial Label!" Width="124"  Height="34"/>

<slctls:Label Template="{StaticResource RadialLabelTemplate}" Height="19.333" Content="Radial  == good" FontSize="10"/>

<slctls:Label Template="{StaticResource RadialLabelTemplate}" Height="29.333" Width="144" Content="I R Radial" />

<slctls:Label Template="{StaticResource RadialLabelTemplate}" Height="28.667" Width="130.334" Content="U R Radial"/>

 

 

Editing a Label’s ContentTemplate

Templates are used with TemplateBindings that emanate for general Control properties. But we might do bind our Label to CLR property from any Data Source. like, Cows.

image

We’ll create (in Visual studio) our Cow class that has Age and Name.

    public class Cow

    {

        public Cow(string Name, int Age)

        {

            Name = Name;

            Age = Age;

        }

 

        public string Name { get; set;  }

        public int Age { get; set;  }

    }

A very basic CLR class that has 2 properties: a numeric age and a string that represents the age of the cow.

Now let’s use this class and create a cow we’ll DataBind our label to.

    public partial class Page : UserControl

    {

        public Page()

        {

            // Required to initialize variables

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(Page_Loaded);

        }

 

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

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

        }

    }

 

Eventually I’d like for us to see a Label that looks like this:

image

We’d like to change the thickness of the head line based on Cow.Age and change the text based on Cow.Name.

 

Let’s start by adding a new Label to the page and changing it’s name to myLabel.

image

We’d like to edit the ContentTemplate of this label, so we’ll Right Click on the label select “Edit Other Templates –> Edit Generated Content (ContentTemplate) –> Create Empty…”

 

image

We’ll call our new ContentTemplate – CowContentTemplate.

image

As always we start off with an empty ContentTemplate.

image

I’ll spare you my third grade drawing skills and we’ll magiclly add some lines that are our cow.

image

Truly a masterpiece.

Single cowIf you squint really hard, they look alike 

Next we’d like to add the appropriate bindings.

We’ll select the TextBlock in the middle of the cow.

 

image

Click “Advanced Options –> Custom Expression”.

image

image

And add a Binding to the Cow Name.

image

 

Next, we’ll select the Cow’s head.

image

And set a StrokeThickness to a Custom expression that’s bound to the Cow’s age.

image

 

One last thing we have to do before we can run the sample is set the Cow’s Content to it’s DataContext.

image

We’ll ad a Custom expression to the Content property of the label.

image

 

Finally we can run the sample.

image

Let’s add a few more Labels with that ContentTemplate and with more Cows.

public partial class Page : UserControl

{

    public Page()

    {

        // Required to initialize variables

        InitializeComponent();

        this.Loaded += new RoutedEventHandler(Page_Loaded);

    }

 

    void Page_Loaded(object sender, RoutedEventArgs e)

    {

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

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

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

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

    }

}

We’ll make sure to add those label to our page and re-run this sample.

image

And our Cow Herd is now complete.

 

Here’s our DataTemplate XML.

<UserControl.Resources>

    <DataTemplate x:Key="CowContentTemplate">

        <Grid>

            <TextBlock Margin="21.75,9.833,27.25,9.167" TextWrapping="Wrap" FontSize="8" Text="{Binding Path=Name}"/>

            <Ellipse HorizontalAlignment="Right" Margin="0,2.167,4.583,13.833" Width="15.084" Fill="#FFFFFFFF" Stroke="#FF000000" StrokeThickness="{Binding Path=Age}"/>

            <Rectangle Margin="18.75,8,25.25,8" Fill="#FFFFFFFF" Stroke="#FF000000" RadiusX="1" RadiusY="1"/>

            <Path Height="2.334" HorizontalAlignment="Right" Margin="0,12.25,19.417,0" VerticalAlignment="Top" Width="6.667" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" Data="M65.166664,9.833333 L59.5,11.166667"/>

            <Path Height="6" HorizontalAlignment="Right" Margin="0,0,23.75,2.667" VerticalAlignment="Bottom" Width="3.667" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" Data="M58.833332,19.666666 L61.5,24.666666" d:LayoutOverrides="Width"/>

            <Path Height="4.5" HorizontalAlignment="Left" Margin="27.585,0,0,3.833" VerticalAlignment="Bottom" Width="2.166" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" Data="M35.333332,19.666666 L34.166668,23.166666"/>

            <Path Height="4.834" HorizontalAlignment="Right" Margin="0,0,33.751,4.333" VerticalAlignment="Bottom" Width="1.833" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" Data="M51.833332,22.666666 L51,18.833334" d:LayoutOverrides="Width"/>

            <Path Height="5" HorizontalAlignment="Left" Margin="18.917,0,0,4.333" VerticalAlignment="Bottom" Width="4" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" Data="M27.333334,19.35417 L24.833334,23.187502"/>

        </Grid>

    </DataTemplate>

</UserControl.Resources>

 

And our Cow labels:

<slctls:Label Height="32.5" Margin="311.5,56.75,235.5,0" VerticalAlignment="Top" x:Name="myLabel" ContentTemplate="{StaticResource CowContentTemplate}" Content="{Binding}"  />

<slctls:Label Height="32.5" Margin="311,129.5,236,0" VerticalAlignment="Top" x:Name="myCowLabel1" ContentTemplate="{StaticResource CowContentTemplate}" Content="{Binding}" />

<slctls:Label Height="32.5" Margin="311,198.75,236,0" VerticalAlignment="Top" x:Name="myCowLabel2" ContentTemplate="{StaticResource CowContentTemplate}" Content="{Binding}"  />

<slctls:Label Height="32.5" Margin="311.5,0,235.5,184.5" x:Name="myCowLabel3" ContentTemplate="{StaticResource CowContentTemplate}" Content="{Binding}"  />

 

 

-- Justin-Josef Angel

Microsoft Silverlight Program Manager

Silverlight Toolkit November 2008 Overview

Hi folks,

 

We’ve just shipped the first release of the Silverlight Toolkit’s on http://codeplex.com/Silverlight.

HelloWorld1

HelloWorld2

HelloWorld3  

HelloWorld4

 

I’d like to do a 101 technical overview of these controls in this blog post.

During the next few days I’ll publish technical deep dives on each control, but for now, we’re sticking to an overview.

 

TreeView 

TreeView

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

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

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

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

        </controls:TreeViewItem>

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

            <controls:TreeViewItem Header="Family"/>

            <controls:TreeViewItem Header="Friends"/>

        </controls:TreeViewItem>

    </controls:TreeViewItem>

</controls:TreeView>

 

Let’s review what’s going on here.

We’ve nested some TreeViewItems in one another (Which implicitly set the TreeView.Items property) and set TreeView.IsExpanded=True to show those children up once the Treeview loads. One more interesting thing we’ve done is set the TreeView.Header property (which could have been populated with any element since it’s just Content).

 

AutoCompleteBox

AutoCompleteBox

<controls:AutoCompleteBox Width="100" x:Name="autoComplete" />

 

autoComplete.ItemsSource = new string[]

        {"You", "Life", "Work", "Silverlight", "Friends", "Family"};

 

By Setting the AutoComplete.ItemsSource we’ve told the AutoComplete to filter out items from this list of items.

In future blog posts we’ll see how we can change the display of the drop down and the filtering behavior.

 

NumericUpDown

NumericUpDown

<input:NumericUpDown Width="100" Height="30" Increment="1" IsEditable="True" Maximum="100" Minimum="0" Value="33" DecimalPlaces="1"/>

 

The property names are pretty self explanatory. Interesting to note is the NumericUpDown.DecimalPlaces property that let’s you define how many decimal places after the dot you’d like to show in your NumericUpDown.

 

DockPanel

DockPanel 

<Border Height="Auto" Width="Auto" BorderBrush="#FF000000" BorderThickness="1,1,1,1" >

    <controls:DockPanel Height="150" Width="200" LastChildFill="True">

        <Image Source="You.png" controls:DockPanel.Dock="Left" Stretch="None"/>

        <Image Source="Work.png" controls:DockPanel.Dock="Right" Stretch="None"/>

        <Image Source="Family.png" controls:DockPanel.Dock="Top" Stretch="None"/>

        <Image Source="Friends.png" controls:DockPanel.Dock="Bottom" Stretch="None"/>

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

    </controls:DockPanel>

</Border>

To begin with, you can see that the DockPanel is surrounded with a <Border /> just so we could see the edges of the DockPanel.

Next you can see that each Image is placed in it’s original size on the DockPanel and each Docked with the DockPanel.Dock Attached Dependency property to a different side.

And last, the DockPanel.LastChildFill is set to true. So the last item will be aligned to the “Center” dock. Since the image is marked as “No Stretch” it won’t expand, but it’s still aligned to center.

 

 

WrapPanel

WrapPanel

<controls:WrapPanel Orientation="Horizontal" Width="200">

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

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

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

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

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

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

</controls:WrapPanel>

 

For those of you familiar with WrapPanel it’s pretty obvious what’s going on here – there’s a bunch of items being written sequentially  left-to-right and than to the next row.

 

Let’s test this WrapPanel out in different widths: 50, 100 and 200 pixels width.

Same WrapPanel Width=50

image

Width=100

image

 

Width=150

image

And finally Width=200.

WrapPanel

The interesting thing about this sample is that it’s aligned Left-to-Right because we’ve set WrapPanel.Orientation=Horizontal.
We could have changed it to Vertical so the WrapPanel would place items top-to-bottom.

 

 

ViewBox

Viewbox 

<controls:Viewbox Height="50" Width="20">

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

</controls:Viewbox>

<controls:Viewbox Height="100" Width="40">

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

</controls:Viewbox>

<controls:Viewbox Height="150" Width="100">

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

</controls:Viewbox>

 

A ViewBox essentially resized content to fit it by applying a ScaleTransform.

So in the first sample, where we have a 40x100 pixels image in a 20x50 pixels viewbox – The Image would get a ScaleTransform of 0.5. (50%)

In the second sample, where the container is exactly 40x100 pixels wide, we’ll get a ScaleTransform of 1 (100%, meaning none).

And in the Third sample, where the container is 150x100 pixels wide we’ll get a ScaleTansform of all more than 1.5 (more than 150%).

 

Label

Label

<controls:Label FontSize="18">

    <StackPanel Orientation="Horizontal">

        <Image Source="You.png" />

        <TextBlock Text="I'm a Stickperson!"/>

    </StackPanel>

</controls:Label>

 

Label isn’t a really intriguing control, but it has one very appealing property. Instead of plain text, it has Label.Content property.
Which is one of the strengths of WPF/Silvelright based model in comparison to more mature ASP.Net/Winforms based models.

In this Label we’ve decided to place a TextBlock and an Image horizontally aligned inside a Label.

The beauty here is that we’ve declared the Label as Font 18 and that tunneled down all the way to TextBlock.

 

HeaderedContentControl

HeaderedContentControl

<controls:HeaderedContentControl HorizontalContentAlignment="Center">

    <controls:HeaderedContentControl.Header>

        <TextBlock Text="Your Stickfigure life" FontSize="18" TextDecorations="Underline" />

    </controls:HeaderedContentControl.Header>

    <controls:HeaderedContentControl.Content>

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

    </controls:HeaderedContentControl.Content>

</controls:HeaderedContentControl>

The HeaderedContentControl has two interesting properties we’re showing off here – HeaderedContentControl.Content and HeaderedContentControl.Header.

Both of which follow the “content” model and can be set to any element.

In this sample we’ve placed a TextBlock into the Header and an Image in the Content.

 

 

Expander

image

<controls:Expander>

    <controls:HeaderedContentControl.Header>

        <TextBlock Text="Your Stickfigure life" FontSize="18" TextDecorations="Underline" />

    </controls:HeaderedContentControl.Header>

    <controls:HeaderedContentControl.Content>

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

    </controls:HeaderedContentControl.Content>

</controls:Expander>

 

The Expander control inherits from HeaderedContentcontrol so it has roughly the same API.

But, it renders out a completely different control.

This is how Expander looks like once it’s loaded:

image

And with one click on the Expand button, it drops down.

image

Another click brings it back to it’s collapsed state.

image

 

HeaderedItemControl

HeaderedItemsControl

headeredItemControl.ItemsSource = new string[]

    { "You", "Life", "Work", "Silverlight", "Friends", "Family" };

 

<controls:HeaderedItemsControl x:Name="headeredItemControl">

    <controls:HeaderedItemsControl.Header>

        <TextBlock Text="Your life" FontSize="18" TextDecorations="Underline" />

    </controls:HeaderedItemsControl.Header>

    <controls:HeaderedItemsControl.ItemTemplate>

        <DataTemplate>

            <TextBlock Text="{Binding}" Margin="10 0 0 0"/>

        </DataTemplate>

    </controls:HeaderedItemsControl.ItemTemplate>

</controls:HeaderedItemsControl>

The HeaderedItemsControl is an ItemsControl that has a HeaderedItemscontrol.Header property.

we’ll set the Header property with a TextBlock.

Now, we’ve defined a HeaderedItemsControl.ItemsTemplate that basically shows us whatever you bind to it in a TextBlock with some margin.

We’ve given the sample a list of value as the HeaderedItemsControl.ItemsSource and it rendered all of those into the TextBlocks found in the ItemTemplate.

 

 

Chart - ColumnSeries

ColumnSeries

columnSeries.DataContext = new KeyValuePair<string, int>[]

                               {

               new KeyValuePair<string, int>("Work", 9),

               new KeyValuePair<string, int>("Sleep", 8),

               new KeyValuePair<string, int>("Driving", 2),

               new KeyValuePair<string, int>("Family", 4),

               new KeyValuePair<string, int>("Friens", 1),

                               };

<charting:Chart x:Name="columnSeries" Height="200" Margin="8,8,492,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="300">

    <charting:Chart.Series>

        <charting:ColumnSeries ItemsSource="{Binding}"

                              DependentValueBinding="{Binding Value}"

                              IndependentValueBinding="{Binding Key}"

                              Title="Time"  />

    </charting:Chart.Series>

</charting:Chart>

 

Let’s explain what we’re seeing and how we got to it.

First, we’ve got a Chart with a Chart.Series set to ColumnSeries. That by itself is enough t add a new ColumnSeries to the chart.

Now, we want some data in our Chart and we’ll use DataBinding magic to get it.

We’ll start off by specifying a Chart.DataContext with a collection of KeyValuePairs. Each pair has a Key and a Value.
Next, we’ll tell the ColumnSeries.DependentValue (which is the numerical value being followed on the columns) that it’s DataBound to Value.
And DataBind ColumnSeries.IndependentValue to Key.

It’s important to remember that, IndependentValue is the text that shows up on under the Column. And DependentValue is the value of the actual Column.

We’ll put the Cherry on top by specifying ColumnSeries.Title as ‘Time’. 

 

 

Chart - BarSeries

BarSeries

barSeries.DataContext = new KeyValuePair<string, int>[]

                               {

               new KeyValuePair<string, int>("Work", 9),

               new KeyValuePair<string, int>("Sleep", 8),

               new KeyValuePair<string, int>("Driving", 2),

               new KeyValuePair<string, int>("Family", 4),

               new KeyValuePair<string, int>("Friens", 1),

                               };

<charting:Chart x:Name="barSeries" Width="350" Height="250">

    <charting:Chart.Series>

        <charting:BarSeries ItemsSource="{Binding}"

                           DependentValueBinding="{Binding Path=Value}"

                           IndependentValueBinding="{Binding Path=Key}"

                           Title="Life" />

    </charting:Chart.Series>

</charting:Chart>

Very similar to the ColumnSeries example, just that in this example we’ll use a BarSeries.

 

 

Chart – PieSeries

PieSeries

pieSeries.DataContext = new KeyValuePair<string, int>[]

                               {

               new KeyValuePair<string, int>("Work", 9),

               new KeyValuePair<string, int>("Sleep", 8),

               new KeyValuePair<string, int>("Driving", 2),

               new KeyValuePair<string, int>("Family", 4),

               new KeyValuePair<string, int>("Friens", 1),

                               };

<charting:Chart x:Name="pieSeries" Width="350" Height="250">

    <charting:Chart.Series>

        <charting:PieSeries ItemsSource="{Binding}"

                           DependentValueBinding="{Binding Path=Value}"

                           IndependentValueBinding="{Binding Path=Key}"

                           Title="Life"/>

    </charting:Chart.Series>

</charting:Chart>

Again, very similar to the BarColumn Sample. We’re just using a PieSeries here.

 

 

Chart - ScatterSeries

ScatterSeries

scatterSeries.DataContext = new KeyValuePair<DateTime, int>[] {

       new KeyValuePair<DateTime, int>(DateTime.Now, 9),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(1), 8),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(2), 9),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(3), 6),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(4), 8)

    };

<charting:Chart x:Name="scatterSeries" Width="400" Height="250" >

    <charting:Chart.Series>

        <charting:ScatterSeries ItemsSource="{Binding}"

                               DependentValueBinding="{Binding Path=Value}"

                               IndependentValueBinding="{Binding Path=Key}"

                               Title="Work" />

    </charting:Chart.Series>

</charting:Chart>

 

Let’s look at the chart. It shows how many hours we’ve worked each day in the week.

The reason this Chart is different than the previous charts is that a ScatterSeries.IndepdenValue needs to be comparable.
The Chart orders items based on their relative “size” in comparison to other items. In previous examples, the order of items determined final horizontal placement. However, In this type of series, The value of the IndepdentValue determines that.

So Instead of using a string which isn’t comparable and does not work as an IndependentValue, we’re using a DataTime.

The rest is pretty similar to how we’ve worked with other charts.

 

 

Chart – Line Series

LineSeries 

lineSeries.DataContext = new KeyValuePair<DateTime, int>[] {

       new KeyValuePair<DateTime, int>(DateTime.Now, 9),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(1), 8),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(3), 6),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(2), 9),

       new KeyValuePair<DateTime, int>(DateTime.Now.AddDays(4), 8)

    };

<charting:Chart x:Name="lineSeries" Width="400" Height="250" >

    <charting:Chart.Series>

        <charting:LineSeries ItemsSource="{Binding}"

                            DependentValueBinding="{Binding Path=Value}"

                            IndependentValueBinding="{Binding Path=Key}"

                            Title="Work"/>

    </charting:Chart.Series>

</charting:Chart>

This is pretty similar to how we’ve worked with ScatterSeries, just that in this case we’re using a LineSeries.

A LineSeries is visually equivalent to a ScatterSeries, minus the fact that it has lines drawn between it’s DataPoints.

 

 

ImplicitStyleManager

ImplicitStyleManager

// in myPage.xaml

<UserControl theming:ImplicitStyleManager.ApplyMode="OneTime"

   theming:ImplicitStyleManager.ResourceDictionaryUri="projectName;compoent/Theme.xaml" >

    <Button Content="Hello Stickperson!" Canvas.Top="170" Canvas.Left="191" />

</UserControl>

// in Theme.xaml

<ResourceDictionary

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

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

    <Style TargetType="Button">

        <Setter Property="FontSize" Value="22" />

        <Setter Property="Foreground" Value="Blue" />

    </Style>

</ResourceDictionary>

 

As you can see, in Theme.xaml we’ve got a default style for Button. A default style basically means “we want all controls of type X to receive style Y as default”.

In this specific sample we want all Buttons by default to be Font size 22 and foreground color Blue.

 

Now in this sample ImplicitStyleManager takes the Theme.xaml file, loads it up and applies it to all the children of UserControl.
We instructed it to do that by specifying an ImplicitStyleManager.ResourceDictionaryUri property with the full resource URI for Theme.xaml. And we made sure Theme.xaml has a built action set to Resource.

We’ve also specified ImplicitStyleManager.ApplyMode=OneTime which just means that we want the styles to be applied on the UserControl.Loaded event and not apply styles to new controls which are added later on.

 

 

Expression Light Theme

ExpressionDarkTheme 

<expressionDark:ExpressionDarkTheme>

    <StackPanel Margin="5">

        <Button Margin="5" Content="Hello Stickperson!"/>

        <ComboBox Margin="5">

            <ComboBoxItem Content="Mr. Stickman" IsSelected="True"/>

        </ComboBox>

        <CheckBox Content="Are you a StickPerson?"/>

        <Slider Margin="5"/>

        <input:NumericUpDown Width="50" Margin="5"/>

    </StackPanel>

</expressionDark:ExpressionDarkTheme>

This is a pretty straight forward sample. We’ve shipped 6 Silverlight Themes with the Silverlight Toolkit.
ExpressionDarkTheme is one of those.

After adding a reference to the correct DLL we can just set our top level container as the Theme Control and all nested controls would get the Expression Dark Theme.

 

ExpressionLightTheme

ExpressionLightTheme

<expressionLight:ExpressionLightTheme>

    <StackPanel Margin="5">

        <Button Margin="5" Content="Hello Stickperson!"/>

        <ComboBox Margin="5">

            <ComboBoxItem Content="Mr. Stickman" IsSelected="True"/>

        </ComboBox>

        <CheckBox Content="Are you a StickPerson?"/>

        <Slider Margin="5"/>

        <input:NumericUpDown Width="50" Margin="5"/>

    </StackPanel>

</expressionLight:ExpressionLightTheme>

 

 

RainierOrangeTheme

RainierOrangeTheme 

<rainerOrange:RainierOrangeTheme>

    <StackPanel Margin="5">

        <Button Margin="5" Content="Hello Stickperson!"/>

        <ComboBox Margin="5">

            <ComboBoxItem Content="Mr. Stickman" IsSelected="True"/>

        </ComboBox>

        <CheckBox Content="Are you a StickPerson?"/>

        <Slider Margin="5"/>

        <input:NumericUpDown Width="50" Margin="5"/>

    </StackPanel>

</rainerOrange:RainierOrangeTheme>

 

 

RainierPurpleTheme

RainierPurpleTheme 

<rainerPurple:RainierPurpleTheme>

    <StackPanel Margin="5">

        <Button Margin="5" Content="Hello Stickperson!"/>

        <ComboBox Margin="5">

            <ComboBoxItem Content="Mr. Stickman" IsSelected="True"/>

        </ComboBox>

        <CheckBox Content="Are you a StickPerson?"/>

        <Slider Margin="5"/>

        <input:NumericUpDown Width="50" Margin="5"/>

    </StackPanel>

</rainerPurple:RainierPurpleTheme>

 

 

ShinyBlueTheme

ShinyBlueTheme 

<shinyBlue:ShinyBlueTheme>

    <StackPanel Margin="5">

        <Button Margin="5" Content="Hello Stickperson!"/>

        <ComboBox Margin="5">

            <ComboBoxItem Content="Mr. Stickman" IsSelected="True"/>

        </ComboBox>

        <CheckBox Content="Are you a StickPerson?"/>

        <Slider Margin="5"/>

        <input:NumericUpDown Width="50" Margin="5"/>

    </StackPanel>

</shinyBlue:ShinyBlueTheme>

 

 

ShinyRedTheme

ShinyRedTheme 

<shinyRed:ShinyRedTheme>

    <StackPanel Margin="5">

        <Button Margin="5" Content="Hello Stickperson!"/>

        <ComboBox Margin="5">

            <ComboBoxItem Content="Mr. Stickman" IsSelected="True"/>

        </ComboBox>

        <CheckBox Content="Are you a StickPerson?"/>

        <Slider Margin="5"/>

        <input:NumericUpDown Width="50" Margin="5"/>

    </StackPanel>

</shinyRed:ShinyRedTheme>

 

 

 

-- Justin Angel

Microsoft Silverlight Program Manager

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

Cake, Politics, Religion and Money - or Why I work for Microsoft

image9 I Once saw a great man speak.

 

It was on the front lawn of the White House more than decade ago and Israeli Prime Minister Yitzhak Rabin managed to inspire me.

He talked about hope, about peace and about a new Middle-east where everyone could live and prosper together.

 

 

 

 

I wanted to say that that was truly the only time I was moved by a politician.
That changed about a three months ago when Israeli President Shimon Paras spoke at the official opening of a new Israeli Microsoft development center.

image
"A single man, two man, three man, can create an empire
Without firing a single cartridge,
Without oppressing a single man,
But through discovering,
Opening the windows to see the horizons of the world,
which was so closed to us,
not because the world was closed but because we were blind."
- Israeli President Shimon Paras, 2008

 

Why am I going to work for Microsoft on Silverlight & WPF?

It's not about money.
It isn't even about possible career advancement.

 

It's all about passion.
I've taken the Silverlight position from the worst possible reason - I actually believe in the technology.

 

Silverlight is going to change the world. 

 

image18

I was looking for a picture of myself passionate about something to put up here.
In this picture I'm passionate and excited about Cake, Not Silverlight & WPF.
But still, you get the point - Cake good, Silverlight & WPF even better.

 

 

It's my honest opinion that Silverlight is just plain amazing and has huge potential.
Hopefully, I could help out by making it just a bit better.

 

 

Posted by JustinAngel | 5 comment(s)
Filed under: , ,