Loading panel control for asynchronous requests

by Tom 29. November 2008 02:56

Wow. just realising that finding the time to write a blog is hard! I've had a busy couple of weeks, and with a big deadline looming in February I think time is only going to become more scarce. It's a good thing I really enjoy coding in Silverlight - unlike some other technologies I've been using lately - and being on the crest of this wave of Silverlight enthusiasm going around at the moment.

Last night I witnessed the first ever meeting of the Silverlight Designer and Developer Network - a new Silverlight user group here in Melbourne. Turnout was huge for a first time and the quality of the presented material was great. In particular, I have to say I was blown away by Jonas Follesø's presentation (a Norwegian Microsoft guy). Not only did he turn out *lots* of code by hand by he actually covered some really cool topics (which I didn't expect at this sort of event). He covered the Silverlight Toolkit (nothing new here), Live Mesh & Silverlight outside the browser (wow! this one got the gears of my brain turning) as well as the as-yet nameless Silverlight Business FX Framework which I'm thinking will be part of Silverlight 3 (we could take weeks/months off our project if this was available now!). So I walked away pretty happy and very positive about putting myself in the Silverlight camp for the foreseeable future.

 

Better get to some code....

A very quick post today. In the course of today I realised needed to unify our approach to managing the UI during asynchronous loads. Basically, the scenario is where you have a control that displays some piece of data that needs to be fetched, and the fetching may take several seconds (or more). Obviously you need to block any user interaction with the control before it is fully loaded, and to follow the convention, you should probably also throw a translucent layer over the control and display a snazzy loading graphic. And of course, a major consideration for me was to make a control I could re-use throughout my app.

Here's what I'm making:

04-01

The way I decided to approach this was to make a very minor extension to a ContentControl - adding an extra piece of content to hold the 'waiting' layer, and an IsLoading property to manage the visual state of the control (i.e. loading or normal). To manage the different visual states, Silverlight provides the handy Visual State Manager. Using the VSM, you can define the visual appearance of your control when it is in certain states (or combinations of states). The Loading Panel can only ever be in two states: Normal and Loading. When the control is in the loading state, the 'waiting' layer has it's visibility set to visible.

Here's the XAML:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    xmlns:loc="clr-namespace:LoadingPanel"
    >
    <Style TargetType="loc:LoadingPanel">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="loc:LoadingPanel">
                    <Grid>
                        <vsm:VisualStateManager.VisualStateGroups>
                            <vsm:VisualStateGroup x:Name="CommonStates">
                                <vsm:VisualState x:Name="Loading">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames 
                                            Storyboard.TargetName="frameworkelementLoadingContent" 
                                            Storyboard.TargetProperty="Visibility" Duration="0">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Normal"/>
                            </vsm:VisualStateGroup>
                        </vsm:VisualStateManager.VisualStateGroups>
                        <ContentPresenter Content="{TemplateBinding Content}"/>
                        <ContentPresenter x:Name="frameworkelementLoadingContent" 
                                          Content="{TemplateBinding LoadingContent}"
                                          Visibility="Collapsed"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>  

There's little that needs explaining here, since the control comprises little more than two ContentPresenters to hold the pieces of content. The VSM also enables you to se transitions between states, but since our designers haven't asked for anything I've purposely left this out.

In our case, when the control is put into 'Loading' mode, there's a basic Storyboard with a single animation occurring after 0 seconds (the change from collapsed to visible) targeted at the 'waiting' content presenter. This particular control uses only one VisualStateGroup, but with more complex controls, you can use several of these to describe different sets of visual characteristics that may or may not be valid in conjunction with other visual states.

The LoadingPanel.cs is a little more interesting. Here's the code:

using System;
using System.Windows;
using System.Windows.Controls;
 
namespace LoadingPanel
{
    public class LoadingPanel : ContentControl
    {
        public LoadingPanel()
        {
            DefaultStyleKey = typeof(LoadingPanel);
            IsLoading = false;
        }
 
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            GoToState();
        }
 
        private void GoToState()
        {
            if (IsLoading)
            {
                VisualStateManager.GoToState(this, "Loading", false);
            }
            else
            {
                VisualStateManager.GoToState(this, "Normal", false);
            }
        }
 
        public static DependencyProperty IsLoadingProperty =
            DependencyProperty.Register("IsLoading", typeof(bool), typeof(LoadingPanel),
            new PropertyMetadata(new PropertyChangedCallback((d, e) =>
            {
                LoadingPanel lp = d as LoadingPanel;
                lp.GoToState();
            })));
 
        public bool IsLoading
        {
            get { return (bool)GetValue(IsLoadingProperty); }
            set { SetValue(IsLoadingProperty, value); }
        }
 
        public static readonly DependencyProperty LoadingContentProperty =
            DependencyProperty.Register("LoadingContent", typeof(object), typeof(LoadingPanel), null);
 
        public object LoadingContent
        {
            get { return (object)GetValue(LoadingContentProperty); }
            set { SetValue(LoadingContentProperty, value); }
        }
    }
}

 

A few things worth pointing out:

  • The constructor is useful for setting default values for the control's properties
  • The GotoState() method is, by convention, a helper method that interacts with the VisualStateManager
  • I haven't touched the Control Contract at all. This is a series of class Attributes that describe the various parts and states of the control - consumed by Blend. If you don't plan to use the control in Blend then there's  no need for a Control Contract, but apparently it's good practice to leave it in anyway. Karen Corby wrote a good post about this.

Here's how to use the control in your XAML:

<UserControl 
    x:Class="LoadingPanelSL.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:lp="clr-namespace:LoadingPanel;assembly=LoadingPanel"
    >
    <Grid x:Name="LayoutRoot">
        
        <Grid Width="400" Height="400">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
 
            <lp:LoadingPanel IsLoading="{Binding IsLoading}">
                <lp:LoadingPanel.Content>
                    <data:DataGrid x:Name="datagridMyGrid" ItemsSource="{Binding MyData}" />
                </lp:LoadingPanel.Content>
                <lp:LoadingPanel.LoadingContent>
                    <Grid>
                        <Rectangle Fill="LightSlateGray" Opacity="0.3" />
                        <TextBlock Text="Loading..." 
                                   HorizontalAlignment="Center" 
                                   VerticalAlignment="Center" 
                                   Opacity="0.7" FontSize="16" />
                    </Grid>
                </lp:LoadingPanel.LoadingContent>
            </lp:LoadingPanel>
            
            <Button Content="Load Data" x:Name="buttonLoadData" Grid.Row="1" />
        </Grid>
        
    </Grid>
</UserControl>

Note the two attached properties: Content (inherited from ContentControl) and LoadingContent which is a dependency property I added. You should put the data control in the Content tag and the 'waiting' screen in the LoadingContent tag.

The thing that makes this technique very simple to use is being able to databind the IsLoading property. It becomes very simple to flick the IsLoading property from true to false in your presenter and know that the UI side of things is taken care of.

And finally, the code-behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.ComponentModel;
 
namespace LoadingPanelSL
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            this.DataContext = new MyDataContext(this);
        }
    }
 
    public class MyDataContext : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private readonly UserControl view;
 
        public MyDataContext(UserControl view)
        {
            this.view = view;
 
            MyDataService service = new MyDataService();
            service.DataLoaded += (object s, MyDataEventArgs e) =>
                {
                    IsLoading = false;
                    MyData = e.Data;
                };
 
            Button b = view.FindName("buttonLoadData") as Button;
            b.Click += (s, e) =>
                {
                    IsLoading = true;
                    service.GetData();
                };
        }
 
        private IEnumerable<string> myData;
        public IEnumerable<string> MyData
        {
            get { return myData; }
            set { myData = value; NotifyChange("MyData"); }
        }
 
        private bool isLoading = false;
        public bool IsLoading
        {
            get { return isLoading; }
            set { isLoading = value; NotifyChange("IsLoading"); }
        }
 
        private void NotifyChange(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
 
    public class MyDataService
    {
        public event EventHandler<MyDataEventArgs> DataLoaded;
        private string[] str = ("Microsoft Silverlight extends your existing " +
            "development skills empowering you to build new types of applications " +
            "for the Web regardless of target platform or browser Rapidly create " +
            "compelling rich Web applications using all the familiar features " +
            "languages and tools of the .NET framework").Split();
 
        public void GetData()
        {
            Random rand = new Random();
            Storyboard sb = new Storyboard();
            sb.Duration = new Duration(TimeSpan.FromSeconds(3));
            sb.Completed += new EventHandler((s, e) =>
                {
                    OnDataLoaded(new MyDataEventArgs(from st in str orderby rand.Next() select st));
                });
            sb.Begin();
        }
 
        private void OnDataLoaded(MyDataEventArgs e)
        {
            if (DataLoaded != null) DataLoaded(this, e);
        }
    }
 
    public class MyDataEventArgs : EventArgs
    {
        public IEnumerable<string> Data { get; set; }
        public MyDataEventArgs(IEnumerable<string> data)
        {
            Data = data;
        }
    }
}

 

There's a bit of junk code here to generate 'random' data and simulate an asynchronous request. Nevertheless, the code flow is somewhat similar to the way you find your self working with WCF services in a real LOB app.

If I had time there's any number of improvements I'd make to this, but what sort of software developer has time?  This is exactly the sort of quick and dirty solution that makes Silverlight so fun to work with.

Here's the code.

Categories: Work

Automatic hierarchies using Linq and IValueConverter

by Tom 9. November 2008 16:54

The big news in the last week or so has been the release of the Silverlight Toolkit, packed with some nice controls. At my work we were particularly hanging out for the TreeView and Expander controls, but the Charting stuff looks really nice as well, so I'm sure I'll find myself using most of the toolkit before long.

One of the first things I needed to do with the new control was rig up a TreeView for some data. Sounds pretty easy, and it is - if you data happen to be in the right structure for the data binding. Reading the docos I discovered that in order to display hierarchical data to the nth level, I need objects that contain collections of their child objects. Damn! For a moment I thought this would mean either changes to the business objects (unlikely) or some sort of conversion layer to covert my business objects into hierarchical objects.

Before we get any further, this is what I am making:

03-01

(Click on the screenshot to view a demo)

To explain what I mean, this is how my business objects were structured:

public class Person
{
    public Person() { PersonID = 0; }
    public int PersonID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string JobTitle { get; set; }
    public Person Manager { get; set; }
}

So, in my objects, I carry an instance of the parent, not a collection of the children. This is fairly standard stuff, so I figured there must be a neat way to make a collection of objects like this bind to a tree view.

 

Convert flat collection to hierarchy using LINQ

 

In my reading I came across a brilliant blog post at scip.be by a guy name Stefan Cruysberghs (LINQ AsHierarchy) which demonstrates how to use a Linq Extension method to dynamically convert flat objects into hierarchical ones. What a great idea! I highly recommend you read his post.

I needed to make a minor customization to the source provided by Stefan: I threw in an extra method that allows you to specify the parent ids that root nodes should have, but other than that, everything is the same. Here is my altered code:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
 
/// <summary>
/// Hierarchy node class which contains a nested collection of hierarchy nodes
/// </summary>
/// <typeparam name="T">Entity</typeparam>
public class HierarchyNode<T> where T : class
{
    public T Entity { get; set; }
    public IEnumerable<HierarchyNode<T>> ChildNodes { get; set; }
    public int Depth { get; set; }
    public T Parent { get; set; }
}
 
// Stefan Cruysberghs, July 2008, http://www.scip.be
/// <summary>
/// AsHierarchy extension methods for LINQ to Objects IEnumerable
/// </summary>
public static class LinqToObjectsExtensionMethods
{
    private static IEnumerable<HierarchyNode<TEntity>>
      CreateHierarchy<TEntity, TProperty>(
        IEnumerable<TEntity> allItems,
        TEntity parentItem,
        Func<TEntity, TProperty> idProperty,
        Func<TEntity, TProperty> parentIdProperty,
        object rootItemId,
        object rootParentId,
        int maxDepth,
        int depth) where TEntity : class
    {
        IEnumerable<TEntity> childs;
 
        if (rootItemId != null)
        {
            childs = allItems.Where(i => idProperty(i).Equals(rootItemId));
        }
        else if (rootParentId != null)
        {
            childs = allItems.Where(i => parentIdProperty(i).Equals(rootParentId));
        }
        else
        {
            if (parentItem == null)
            {
                childs = allItems.Where(i => parentIdProperty(i).Equals(default(TEntity)));
            }
            else
            {
                childs = allItems.Where(i => parentIdProperty(i).Equals(idProperty(parentItem)));
            }
        }
 
        if (childs.Count() > 0)
        {
            depth++;
 
            if ((depth <= maxDepth) || (maxDepth == 0))
            {
                foreach (var item in childs)
                    yield return
                      new HierarchyNode<TEntity>()
                      {
                          Entity = item,
                          ChildNodes =
                            CreateHierarchy(allItems.AsEnumerable(), item, idProperty, parentIdProperty, null, null, maxDepth, depth),
                          Depth = depth,
                          Parent = parentItem
                      };
            }
        }
    }
 
    /// <summary>
    /// LINQ to Objects (IEnumerable) AsHierachy() extension method
    /// </summary>
    /// <typeparam name="TEntity">Entity class</typeparam>
    /// <typeparam name="TProperty">Property of entity class</typeparam>
    /// <param name="allItems">Flat collection of entities</param>
    /// <param name="idProperty">Func delegete to Id/Key of entity</param>
    /// <param name="parentIdProperty">Func delegete to parent Id/Key</param>
    /// <returns>Hierarchical structure of entities</returns>
    public static IEnumerable<HierarchyNode<TEntity>> AsHierarchy<TEntity, TProperty>(
      this IEnumerable<TEntity> allItems,
      Func<TEntity, TProperty> idProperty,
      Func<TEntity, TProperty> parentIdProperty) where TEntity : class
    {
        return CreateHierarchy(allItems, default(TEntity), idProperty, parentIdProperty, null, null, 0, 0);
    }
 
    /// <summary>
    /// LINQ to Objects (IEnumerable) AsHierachy() extension method
    /// </summary>
    /// <typeparam name="TEntity">Entity class</typeparam>
    /// <typeparam name="TProperty">Property of entity class</typeparam>
    /// <param name="allItems">Flat collection of entities</param>
    /// <param name="idProperty">Func delegete to Id/Key of entity</param>
    /// <param name="parentIdProperty">Func delegete to parent Id/Key</param>
    /// <param name="rootItemId">Value of root item Id/Key</param>
    /// <returns>Hierarchical structure of entities</returns>
    public static IEnumerable<HierarchyNode<TEntity>> AsHierarchy<TEntity, TProperty>(
      this IEnumerable<TEntity> allItems,
      Func<TEntity, TProperty> idProperty,
      Func<TEntity, TProperty> parentIdProperty,
      object rootItemId) where TEntity : class
    {
        return CreateHierarchy(allItems, default(TEntity), idProperty, parentIdProperty, rootItemId, null, 0, 0);
    }
 
    /// <summary>
    /// LINQ to Objects (IEnumerable) AsHierachy() extension method
    /// </summary>
    /// <typeparam name="TEntity">Entity class</typeparam>
    /// <typeparam name="TProperty">Property of entity class</typeparam>
    /// <param name="allItems">Flat collection of entities</param>
    /// <param name="idProperty">Func delegete to Id/Key of entity</param>
    /// <param name="parentIdProperty">Func delegete to parent Id/Key</param>
    /// <param name="rootItemId">Value of root item Id/Key</param>
    /// <returns>Hierarchical structure of entities</returns>
    public static IEnumerable<HierarchyNode<TEntity>> AsHierarchy<TEntity, TProperty>(
      this IEnumerable<TEntity> allItems,
      Func<TEntity, TProperty> idProperty,
      Func<TEntity, TProperty> parentIdProperty,
      object rootItemId,
      object rootParentId) where TEntity : class
    {
        return CreateHierarchy(allItems, default(TEntity), idProperty, parentIdProperty, rootItemId, rootParentId, 0, 0);
    }
 
    /// <summary>
    /// LINQ to Objects (IEnumerable) AsHierachy() extension method
    /// </summary>
    /// <typeparam name="TEntity">Entity class</typeparam>
    /// <typeparam name="TProperty">Property of entity class</typeparam>
    /// <param name="allItems">Flat collection of entities</param>
    /// <param name="idProperty">Func delegete to Id/Key of entity</param>
    /// <param name="parentIdProperty">Func delegete to parent Id/Key</param>
    /// <param name="rootItemId">Value of root item Id/Key</param>
    /// <param name="maxDepth">Maximum depth of tree</param>
    /// <returns>Hierarchical structure of entities</returns>
    public static IEnumerable<HierarchyNode<TEntity>> AsHierarchy<TEntity, TProperty>(
      this IEnumerable<TEntity> allItems,
      Func<TEntity, TProperty> idProperty,
      Func<TEntity, TProperty> parentIdProperty,
      object rootItemId,
      object rootParentId,
      int maxDepth) where TEntity : class
    {
        return CreateHierarchy(allItems, default(TEntity), idProperty, parentIdProperty, rootItemId, rootParentId, maxDepth, 0);
    }
}

Using this technique, I quickly converted my flat collection in a hierarchical one and bound it to my treeview. Great!

 

Implementing the conversion via an IValueConverter

 

The solution still seemed a bit messy, so I hooked it up to an IValueConverter to make the conversion automatic and seamless. The less code the better, right? The IValueConverter interface is incredibly useful (I've been using it all week to display different ItemTemplate content in an ItemsControl depending what the data is). Basically, it grabs data during data binding and lets you examine / alter / replace data while it moves from the data source to the destination. In my case, I want to bind the ItemsSource of a TreeView to a flat collection of objects, but I wanted the TreeView to receive a hierarchical collection of objects. Here's my converter:

public class HierarchyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<Person> p = (ObservableCollection<Person>)value;
        var tree = p.AsHierarchy(e => e.PersonID, e => e.Manager.PersonID, null, 0);
        return tree;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 

Wrap Up

 

Using this technique, I took  a flat collection of business objects and dynamically converted it to a hierarchical collection of node objects at run-time. Once again this is an instance where I expected something to be painful and involve writing a lot of boring code, but Silverlight managed to give me a really nice, elegant solution that I can re-use throughout my app.

I have done  some experimentation, though, and if you want to get two-way data binding to work for you, then you'll have to drop the IValueConverter part. This is becuase the Convert() method is called when the initial binding takes place to the TreeView's ItemsSource, but at no time will the ConvertBack() method be called - even if you specify fields in your Hierarchical ItemTemplate to have two-way binding. I still use the AsHierarchy extension method for a quick conversion when I am using two-way data binding however.

Here's the Page.xaml:

<UserControl 
    x:Class="LinqTree.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
    xmlns:local="clr-namespace:LinqTree"
    >
    <Grid x:Name="LayoutRoot" Background="White" Width="400" Height="470">
        <Grid.Resources>
            <local:HierarchyConverter x:Key="HierarchyConverter" />
            <Style x:Key="NameStyle" TargetType="TextBlock">
                <Setter Property="FontSize" Value="14" />
            </Style>
            <Style x:Key="JobTitleStyle" TargetType="TextBlock">
                <Setter Property="FontSize" Value="12" />
                <Setter Property="Foreground" Value="LightGray" />
                <Setter Property="FontStyle" Value="Italic" />
            </Style>
            <Style x:Key="MainBorder" TargetType="Border">
                <Setter Property="Background" Value="WhiteSmoke" />
                <Setter Property="BorderThickness" Value="1" />
                <Setter Property="CornerRadius" Value="8" />
                <Setter Property="BorderBrush" Value="LightGray" />
                <Setter Property="Padding" Value="12" />
            </Style>
        </Grid.Resources>
        <Border Style="{StaticResource MainBorder}">
            <controls:TreeView 
            x:Name="treeview"
            ItemsSource="{Binding Converter={StaticResource HierarchyConverter}}">
                <controls:TreeView.ItemTemplate>
                    <controls:HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
                        <StackPanel Orientation="Vertical">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Style="{StaticResource NameStyle}" Text="{Binding Entity.FirstName}" />
                                <TextBlock Text=" " />
                                <TextBlock Style="{StaticResource NameStyle}" Text="{Binding Entity.LastName}" />
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Style="{StaticResource JobTitleStyle}" Text="{Binding Entity.JobTitle}" />
                            </StackPanel>
                        </StackPanel>
                    </controls:HierarchicalDataTemplate>
                </controls:TreeView.ItemTemplate>
            </controls:TreeView>
        </Border>
    </Grid>
</UserControl>

And here's  the code behind (Page.xaml.cs):

using System;
using System.Collections.ObjectModel;
using System.Windows.Controls;
 
namespace LinqTree
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            ObservableCollection<Person> Persons = PersonCollection.PersonList();
            treeview.DataContext = Persons;
        }
    }
}

As you can see, a nice clean solution to a common problem. Thanks to Stefan Cruysberghs for pointing me in the right direction!

 

Source

Source is here.

Categories: Work

Customizing a ListBox

by Tom 4. November 2008 09:13

I am quite new to Silverlight (like just about everyone I guess) and I spend a good proportion of each day reading various blogs and finding how to make stuff happen in new and better ways. A well written blog post on a pertinent topic can save me hours or even days of frustration - even if the topic is pretty basic stuff. Sometimes it helps to see the same old demos done slightly differently just to get a feel for how others are approaching the sorts of problems we all deal with on a daily basis. So, having spurned the vast majority of bloggers as self-important know-it-alls and vowing never to engage in the practice, I've decided to start a blog. Whether I continue to keep a blog depends largely on 1) how much of my time it consumes 2) how often I come across little tid-bits worthy of sharing 3) how many people read the posts 4) whether I decide to throw in my unhealthy software career and become a farmer or a fisherman (an idea I entertain each morning at 7am when my alarm goes off).

 

Now, to get to the code...

A fairly common scenario I encounter is needing to display a UI for objects with one-to-many relationships with other objects, for example a person who has one or more addresses (shipping, billing, etc). The other day I needed to whip up a really quick and dirty view in Silverlight to do this, and it occurred to me that it might be a good place to use the rich control templating features rather than making a whole new control by extending, say, ItemsControl. A key consideration is, of course, to make a reusable component that can be dropped anywhere in my app. The component also needs to support data binding, and fit in with the patterns & practises of the rest of the project (we are using Prism).

Here's a screenshot of what I am creating:

(Click on the screenshot to view a demo)

 

Overview

 

The widget depicted in the screenshot above comprises some basic form components and a templated ListBox. Each address is a ListBoxItem: the ListBox is templated in such a way as to make Selected ListBoxItems visible and Unselected ListBoxItems hidden.

The ItemsSource of the ListBox and the ItemsSource of the Addresses ComboBox are data bound to the same collection of Addresses. The SelectedIndex of the ListBox and the SelectedIndex of the Address(es) ComboBox are data bound to an integer Property in the DataContext of the UserControl: when the selection in the ComboBox changes, the corresponding ListBoxItem is made visible in the ListBox below.

Since re-use is a big goal, I want to create new styles and templates for ListBox and ListBoxItem, and store them somewhere accessible like my App.xaml. This way, I can use a ListBox anywhere in my app and use these  styles - the ItemTemplate of the ListBoxItem can be customized on each use.

 

Building a Data Source

 

For the purposes of this demo, I'll use the following dummy data source:

Person.cs

   1: using System;
   2: using System.Collections.ObjectModel;
   3:  
   4: namespace ListBoxTest
   5: {
   6:     public class Person
   7:     {
   8:         public Person()
   9:         {
  10:             Addresses = new AddressCollection();
  11:         }
  12:         public int PersonId { get; set; }
  13:         public string Title { get; set; }
  14:         public string FirstName { get; set; }
  15:         public string LastName { get; set; }
  16:         public AddressCollection Addresses { get; set; }
  17:     }
  18:  
  19:     public class PersonCollection : ObservableCollection<Person>
  20:     {
  21:         public PersonCollection LoadDefault()
  22:         {
  23:             PersonCollection pc = new PersonCollection();
  24:             Person p;
  25:  
  26:             p = new Person();
  27:             p.PersonId = 1;
  28:             p.FirstName = "George";
  29:             p.LastName = "Costanza";
  30:             p.Addresses = new AddressCollection().LoadByPersonId(1);
  31:             pc.Add(p);
  32:  
  33:             p = new Person();
  34:             p.PersonId = 2;
  35:             p.FirstName = "Elaine";
  36:             p.LastName = "Benis";
  37:             p.Addresses = new AddressCollection().LoadByPersonId(2);
  38:             pc.Add(p);
  39:  
  40:             p = new Person();
  41:             p.PersonId = 3;
  42:             p.FirstName = "Cosmo";
  43:             p.LastName = "Kramer";
  44:             p.Addresses = new AddressCollection().LoadByPersonId(3);
  45:             pc.Add(p);
  46:             return pc;
  47:         }
  48:     }
  49:  
  50:     public class Address
  51:     {
  52:         public string AddressDescription { get; set; }
  53:         public string StreetAddress1 { get; set; }
  54:         public string StreetAddress2 { get; set; }
  55:         public string StreetAddress3 { get; set; }
  56:         public string Suburb { get; set; }
  57:         public string State { get; set; }
  58:         public string Country { get; set; }
  59:         public string PostCode { get; set; }
  60:         public bool IsDefault { get; set; }
  61:     }
  62:  
  63:     public class AddressCollection : ObservableCollection<Address>
  64:     {
  65:         public AddressCollection LoadByPersonId(int personid)
  66:         {
  67:             AddressCollection ac = new AddressCollection();
  68:             Address a;
  69:             if (personid.Equals(1))
  70:             {
  71:                 a = new Address();
  72:                 a.AddressDescription = "Shipping";
  73:                 a.StreetAddress1 = "Unit XYZ";
  74:                 a.StreetAddress2 = "World Towers";
  75:                 a.StreetAddress3 = "100-104 Something St";
  76:                 a.Suburb = "Melbourne";
  77:                 a.State = "Victoria";
  78:                 a.PostCode = "3000";
  79:                 a.Country = "Australia";
  80:                 a.IsDefault = true;
  81:                 ac.Add(a);
  82:  
  83:                 a = new Address();
  84:                 a.AddressDescription = "Billing";
  85:                 a.StreetAddress1 = "PO Box xyz123";
  86:                 a.StreetAddress2 = "SomePostOffice";
  87:                 a.StreetAddress3 = "";
  88:                 a.Suburb = "Melbourne";
  89:                 a.State = "Victoria";
  90:                 a.PostCode = "3000";
  91:                 a.Country = "Australia";
  92:                 a.IsDefault = false;
  93:                 ac.Add(a);
  94:             }
  95:             else if (personid.Equals(2))
  96:             {
  97:                 a = new Address();
  98:                 a.AddressDescription = "Shipping";
  99:                 a.StreetAddress1 = "Apartment 4";
 100:                 a.StreetAddress2 = "Empire Gardens";
 101:                 a.StreetAddress3 = "150 Another St";
 102:                 a.Suburb = "Sydney";
 103:                 a.State = "New South Wales";
 104:                 a.PostCode = "2000";
 105:                 a.Country = "Australia";
 106:                 a.IsDefault = true;
 107:                 ac.Add(a);
 108:  
 109:                 a = new Address();
 110:                 a.AddressDescription = "Home";
 111:                 a.StreetAddress1 = "15 MyStreet Rd";
 112:                 a.StreetAddress2 = "";
 113:                 a.StreetAddress3 = "";
 114:                 a.Suburb = "SomeSuburb";
 115:                 a.State = "New South Wales";
 116:                 a.PostCode = "2123";
 117:                 a.Country = "Australia";
 118:                 a.IsDefault = false;
 119:                 ac.Add(a);
 120:  
 121:                 a = new Address();
 122:                 a.AddressDescription = "Billing";
 123:                 a.StreetAddress1 = "Apartment 4";
 124:                 a.StreetAddress2 = "Empire Gardens";
 125:                 a.StreetAddress3 = "150 Another St";
 126:                 a.Suburb = "Sydney";
 127:                 a.State = "New South Wales";
 128:                 a.PostCode = "2000";
 129:                 a.Country = "Australia";
 130:                 a.IsDefault = false;
 131:                 ac.Add(a);
 132:             }
 133:             else if (personid.Equals(3))
 134:             {
 135:                 a = new Address();
 136:                 a.AddressDescription = "Billing";
 137:                 a.StreetAddress1 = "5 Grand Avenue";
 138:                 a.StreetAddress2 = "";
 139:                 a.StreetAddress3 = "";
 140:                 a.Suburb = "SomeTown";
 141:                 a.State = "New South Wales";
 142:                 a.PostCode = "2033";
 143:                 a.Country = "Australia";
 144:                 a.IsDefault = true;
 145:                 ac.Add(a);
 146:             }
 147:             return ac;
 148:         }
 149:     }
 150: }

This gives me a Person object which (among other properties) carries a collection of Address objects.

 

Customizing the ListBox

 

In my experience, when customizing an existing control, it is best to start by looking at that control's default styles and templates. The source of all Silverlight's controls can be obtained from Codeplex, but I also find the documentation on MSDN to be pretty good. I found a particularly helpful article here: ListBox Styles and Templates that gave me a great start.

The two controls I'll need to customize are ListBox and ListBoxItem. Here are the default styles and templates for ListBox in XAML:

   1: <Style TargetType="ListBox">
   2:   <Setter Property="Padding" Value="1"/>
   3:   <Setter Property="Background" Value="#FFFFFFFF" />
   4:   <Setter Property="Foreground" Value="#FF333333" />
   5:   <Setter Property="HorizontalContentAlignment" Value="Left" />
   6:   <Setter Property="VerticalContentAlignment" Value="Top" />
   7:   <Setter Property="IsTabStop" Value="False" />
   8:   <Setter Property="BorderThickness" Value="1" />
   9:   <Setter Property="TabNavigation" Value="Once" />
  10:   <Setter Property="BorderBrush">
  11:     <Setter.Value>
  12:       <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  13:         <GradientStop Color="#FFA3AEB9" Offset="0"/>
  14:         <GradientStop Color="#FF8399A9" Offset="0.375"/>
  15:         <GradientStop Color="#FF718597" Offset="0.375"/>
  16:         <GradientStop Color="#FF617584" Offset="1"/>
  17:       </LinearGradientBrush>
  18:     </Setter.Value>
  19:   </Setter>
  20:   <Setter Property="Template">
  21:     <Setter.Value>
  22:       <ControlTemplate TargetType="ListBox">
  23:         <Border CornerRadius="2" 
  24:                     BorderBrush="{TemplateBinding BorderBrush}"
  25:                   BorderThickness="{TemplateBinding BorderThickness}">
  26:           <ScrollViewer x:Name="ScrollViewer" Padding="{TemplateBinding Padding}" 
  27:                         Background="{TemplateBinding Background}" 
  28:                         BorderBrush="Transparent" BorderThickness="0">
  29:             <ItemsPresenter />
  30:           </ScrollViewer>
  31:         </Border>
  32:       </ControlTemplate>
  33:     </Setter.Value>
  34:   </Setter>
  35: </Style>

Looking at ListBox, the major area of interest is the ControlTemplate; the rest is just very basic style stuff. The ControlTemplate lays out the various bits and pieces that go together to make a ListBox - and most notably it includes an ItemsPresenter which marks the place where the ListBoxItems will be positioned. For my purposes, I''ll need to strip out some of the style info and remove the ScrollViewer (which isn't needed in this case).

And here are the default styles and templates for ListBoxItem in XAML:

   1: <Style TargetType="ListBoxItem">
   2:   <Setter Property="Padding" Value="3" />
   3:   <Setter Property="HorizontalContentAlignment" Value="Left" />
   4:   <Setter Property="VerticalContentAlignment" Value="Top" />
   5:   <Setter Property="Background" Value="Transparent" />
   6:   <Setter Property="BorderThickness" Value="1"/>
   7:   <Setter Property="TabNavigation" Value="Local" />
   8:   <Setter Property="Template">
   9:     <Setter.Value>
  10:       <ControlTemplate TargetType="ListBoxItem">
  11:         <Grid Background="{TemplateBinding Background}">
  12:           <vsm:VisualStateManager.VisualStateGroups>
  13:             <vsm:VisualStateGroup x:Name="CommonStates">
  14:               <vsm:VisualState x:Name="Normal" />
  15:               <vsm:VisualState x:Name="MouseOver">
  16:                 <Storyboard>
  17:                   <DoubleAnimation Storyboard.TargetName="fillColor" Storyboard.TargetProperty="Opacity" Duration="0" To=".35"/>
  18:                 </Storyboard>
  19:               </vsm:VisualState>
  20:             </vsm:VisualStateGroup>
  21:             <vsm:VisualStateGroup x:Name="SelectionStates">
  22:               <vsm:VisualState x:Name="Unselected" />
  23:               <vsm:VisualState x:Name="Selected">
  24:                 <Storyboard>
  25:                   <DoubleAnimation Storyboard.TargetName="fillColor2" Storyboard.TargetProperty="Opacity" Duration="0" To=".75"/>
  26:                 </Storyboard>
  27:               </vsm:VisualState>
  28:             </vsm:VisualStateGroup>
  29:             <vsm:VisualStateGroup x:Name="FocusStates">
  30:               <vsm:VisualState x:Name="Focused">
  31:                 <Storyboard>
  32:                   <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Visibility" Duration="0">
  33:                     <DiscreteObjectKeyFrame KeyTime="0">
  34:                       <DiscreteObjectKeyFrame.Value>
  35:                         <Visibility>Visible</Visibility>
  36:                       </DiscreteObjectKeyFrame.Value>
  37:                     </DiscreteObjectKeyFrame>
  38:                   </ObjectAnimationUsingKeyFrames>
  39:                 </Storyboard>
  40:               </vsm:VisualState>
  41:               <vsm:VisualState x:Name="Unfocused"/>
  42:             </vsm:VisualStateGroup>
  43:           </vsm:VisualStateManager.VisualStateGroups>
  44:           <Rectangle x:Name="fillColor" Opacity="0" Fill="#FFBADDE9" IsHitTestVisible="False" RadiusX="1" RadiusY="1"/>
  45:           <Rectangle x:Name="fillColor2" Opacity="0" Fill="#FFBADDE9" IsHitTestVisible="False" RadiusX="1" RadiusY="1"/>
  46:           <ContentPresenter
  47:             x:Name="contentPresenter"
  48:             Content="{TemplateBinding Content}"
  49:             ContentTemplate="{TemplateBinding ContentTemplate}"
  50:             HorizontalAlignment="Left"
  51:             Margin="{TemplateBinding Padding}"/>
  52:           <Rectangle x:Name="FocusVisualElement" Stroke="#FF45D6FA" StrokeThickness="1" Visibility="Collapsed" RadiusX="1" RadiusY="1" />
  53:         </Grid>
  54:       </ControlTemplate>
  55:     </Setter.Value>
  56:   </Setter>
  57: </Style>

Things get a lot more interesting looking at the ListBoxItem styles and templates. The Template makes use of the VisualStateManager, which defines a different look-and-feel for each of the various states (or combination of states) the ListBoxItem might be in. The VisualStateManager can also describe transitions between states, but this functionality is not used by ListBoxItem by default.

States are divided into VisualStateGroups. At any moment, the control may be in no more than one state from each VisualStateGroup. The internal logic of the control instructs the VisualStateManager when a state needs to be activated (e.g. MouseOver state is activated when a use moves their mouse over the ListBoxItem).

The states used by ListBoxItem are:

  • Common States
    • Normal
    • MouseOver
  • Selection States
    • Unselected
    • Selected
  • Focus States
    • Focused
    • Unfocused

For my purposes, I want to simplify this a bit. I am only interested in two states: Selected (i.e. visible) and Unselected (i.e. invisible). I do not want any changes to occur on Normal, MouseOver, Focused or Unfocused, so I will remove the extra information in these VisualStates and remove the three Rectangles which these states manipulate. In the Selected VisualState, I am going use an ObjectAnimationUsingKeyFrames to change the Visibility property to Visible. Likewise, in the Unselected VisualState, I am going to use an ObjectAnimationUsingKeyFrames to to change the Visibility property to collapsed.

Here's the resultant styles and templates I came up with for ListBox and ListBoxItem:

   1: <Style x:Key="ListBoxStyle" TargetType="ListBox">
   2:     <Setter Property="HorizontalContentAlignment" Value="Left" />
   3:     <Setter Property="VerticalContentAlignment" Value="Top" />
   4:     <Setter Property="IsTabStop" Value="False" />
   5:     <Setter Property="TabNavigation" Value="Once" />
   6:     <Setter Property="Template">
   7:         <Setter.Value>
   8:             <ControlTemplate TargetType="ListBox">
   9:                 <ItemsPresenter />
  10:             </ControlTemplate>
  11:         </Setter.Value>
  12:     </Setter>
  13: </Style>
  14: <Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
  15:     <Setter Property="HorizontalContentAlignment" Value="Left" />
  16:     <Setter Property="VerticalContentAlignment" Value="Top" />
  17:     <Setter Property="TabNavigation" Value="Local" />
  18:     <Setter Property="Template">
  19:         <Setter.Value>
  20:             <ControlTemplate TargetType="ListBoxItem">
  21:                 <Grid Background="{TemplateBinding Background}">
  22:                     <vsm:VisualStateManager.VisualStateGroups>
  23:                         <vsm:VisualStateGroup x:Name="CommonStates">
  24:                             <vsm:VisualState x:Name="Normal" />
  25:                             <vsm:VisualState x:Name="MouseOver" />
  26:                         </vsm:VisualStateGroup>
  27:                         <vsm:VisualStateGroup x:Name="SelectionStates">
  28:                             <vsm:VisualState x:Name="Unselected">
  29:                                 <Storyboard>
  30:                                     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Visibility" Duration="0">
  31:                                         <DiscreteObjectKeyFrame KeyTime="0">
  32:                                             <DiscreteObjectKeyFrame.Value>
  33:                                                 <Visibility>Collapsed</Visibility>
  34:                                             </DiscreteObjectKeyFrame.Value>
  35:                                         </DiscreteObjectKeyFrame>
  36:                                     </ObjectAnimationUsingKeyFrames>
  37:                                 </Storyboard>
  38:                             </vsm:VisualState>
  39:                             <vsm:VisualState x:Name="Selected">
  40:                                 <Storyboard>
  41:                                     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Visibility" Duration="0">
  42:                                         <DiscreteObjectKeyFrame KeyTime="0">
  43:                                             <DiscreteObjectKeyFrame.Value>
  44:                                                 <Visibility>Visible</Visibility>
  45:                                             </DiscreteObjectKeyFrame.Value>
  46:                                         </DiscreteObjectKeyFrame>
  47:                                     </ObjectAnimationUsingKeyFrames>
  48:                                 </Storyboard>
  49:                             </vsm:VisualState>
  50:                         </vsm:VisualStateGroup>
  51:                         <vsm:VisualStateGroup x:Name="FocusStates">
  52:                             <vsm:VisualState x:Name="Focused"/>
  53:                             <vsm:VisualState x:Name="Unfocused"/>
  54:                         </vsm:VisualStateGroup>
  55:                     </vsm:VisualStateManager.VisualStateGroups>
  56:                     <ContentPresenter
  57:                         x:Name="contentPresenter"
  58:                         Content="{TemplateBinding Content}"
  59:                         ContentTemplate="{TemplateBinding ContentTemplate}"/>
  60:                 </Grid>
  61:             </ControlTemplate>
  62:         </Setter.Value>
  63:     </Setter>
  64: </Style>

 

Wrap Up

 

That's about it. I'll quickly include a listing of the various source files, and the whole project can be downloaded at the end of this post.

 

MyDataContext.cs

   1: using System;
   2: using System.ComponentModel;
   3: using System.Windows.Controls;
   4:  
   5: namespace ListBoxTest
   6: {
   7:     public class MyDataContext : INotifyPropertyChanged
   8:     {
   9:         public event PropertyChangedEventHandler PropertyChanged;
  10:  
  11:         public MyDataContext(UserControl view)
  12:         {
  13:             View = view;
  14:             PersonList = new PersonCollection().LoadDefault();
  15:             if (PersonList.Count > 0)
  16:             {
  17:                 PersonComboBoxSelectedIdx = 0;
  18:             }
  19:         }
  20:  
  21:         private UserControl view;
  22:         public UserControl View
  23:         {
  24:             get { return view; }
  25:             set { view = value; }
  26:         }
  27:  
  28:         private PersonCollection personList;
  29:         public PersonCollection PersonList
  30:         {
  31:             get { return personList; }
  32:             set { personList = value; NotifyChange("PersonList"); }
  33:         }
  34:  
  35:         private int personComboBoxSelectedIdx;
  36:         public int PersonComboBoxSelectedIdx
  37:         {
  38:             get { return personComboBoxSelectedIdx; }
  39:             set 
  40:             { 
  41:                 personComboBoxSelectedIdx = value; 
  42:                 NotifyChange("PersonComboBoxSelectedIdx");
  43:                 if (value.Equals(-1)) return;
  44:  
  45:                 SelectedPerson = PersonList[value];
  46:  
  47:                 AddressesComboBoxSelectedIdx = -1;
  48:                 ComboBox cb = (ComboBox)View.FindName("comboboxAddresses");
  49:                 cb.UpdateLayout();
  50:                 AddressesComboBoxSelectedIdx = 0;
  51:             }
  52:         }
  53:  
  54:         private int addressesComboBoxSelectedIdx;
  55:         public int AddressesComboBoxSelectedIdx
  56:         {
  57:             get { return addressesComboBoxSelectedIdx; }
  58:             set { addressesComboBoxSelectedIdx = value; NotifyChange("AddressesComboBoxSelectedIdx"); }
  59:         }
  60:  
  61:         private Person selectedPerson;
  62:         public Person SelectedPerson
  63:         {
  64:             get { return selectedPerson; }
  65:             set { selectedPerson = value; NotifyChange("SelectedPerson"); }
  66:         }
  67:  
  68:         private void NotifyChange(string name)
  69:         {
  70:             if (PropertyChanged != null)
  71:             {
  72:                 PropertyChanged(this, new PropertyChangedEventArgs(name));
  73:             }
  74:         }
  75:     }
  76: }

 

Page.xaml

   1: <UserControl 
   2:     x:Class="ListBoxTest.Page"
   3:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   5:     <Grid x:Name="LayoutRoot" Background="White" Width="400" Height="470">
   6:         <Border Style="{StaticResource MainBorder}">
   7:             <Grid>
   8:                 <Grid.ColumnDefinitions>
   9:                     <ColumnDefinition Width="140"/>
  10:                     <ColumnDefinition Width="*"/>
  11:                 </Grid.ColumnDefinitions>
  12:                 <Grid.RowDefinitions>
  13:                     <RowDefinition Height="auto" />
  14:                     <RowDefinition Height="auto" />
  15:                     <RowDefinition Height="auto" />
  16:                     <RowDefinition Height="auto" />
  17:                     <RowDefinition Height="auto" />
  18:                     <RowDefinition Height="auto" />
  19:                 </Grid.RowDefinitions>
  20:  
  21:                 <Border Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Style="{StaticResource InnerBorder}">
  22:                     <StackPanel Orientation="Horizontal">
  23:                         <TextBlock Text="Person Details" Width="200" />
  24:                         <ComboBox Width="150"
  25:                                   ItemsSource="{Binding PersonList}"
  26:                                   SelectedIndex="{Binding PersonComboBoxSelectedIdx, Mode=TwoWay}">
  27:                             <ComboBox.ItemTemplate>
  28:                                 <DataTemplate>
  29:                                     <StackPanel Orientation="Horizontal">
  30:                                         <TextBlock Text="{Binding LastName}"/>
  31:                                         <TextBlock Text=", "/>
  32:                                         <TextBlock Text="{Binding FirstName}"/>
  33:                                     </StackPanel>
  34:                                 </DataTemplate>
  35:                             </ComboBox.ItemTemplate>
  36:                         </ComboBox>
  37:                     </StackPanel>
  38:                 </Border>
  39:  
  40:                 <TextBlock Grid.Column="0" Grid.Row="1" Text="Person ID" Style="{StaticResource TextBlockStyle}" />
  41:                 <TextBlock Grid.Column="0" Grid.Row="2" Text="First Name" Style="{StaticResource TextBlockStyle}" />
  42:                 <TextBlock Grid.Column="0" Grid.Row="3" Text="Last Name" Style="{StaticResource TextBlockStyle}" />
  43:                 
  44:                 <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding SelectedPerson.PersonId}" Style="{StaticResource TextBoxStyle}" />
  45:                 <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding SelectedPerson.FirstName}" Style="{StaticResource TextBoxStyle}" />
  46:                 <TextBox Grid.Column="1" Grid.Row="3" Text="{Binding SelectedPerson.LastName}" Style="{StaticResource TextBoxStyle}" />
  47:                 
  48:                 <Border Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4" Style="{StaticResource InnerBorder}">
  49:                     <StackPanel Orientation="Horizontal">
  50:                         <TextBlock Text="Address(es)" Width="200" />
  51:                         <ComboBox x:Name="comboboxAddresses" 
  52:                                   Width="150"
  53:                                   ItemsSource="{Binding SelectedPerson.Addresses}"
  54:                                   SelectedIndex="{Binding AddressesComboBoxSelectedIdx, Mode=TwoWay}">
  55:                             <ComboBox.ItemTemplate>
  56:                                 <DataTemplate>
  57:                                     <StackPanel Orientation="Horizontal">
  58:                                         <TextBlock Text="{Binding AddressDescription}" />
  59:                                     </StackPanel>
  60:                                 </DataTemplate>
  61:                             </ComboBox.ItemTemplate>
  62:                         </ComboBox>
  63:                     </StackPanel>
  64:                 </Border>
  65:                 
  66:                 <ListBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="5"
  67:                          ItemsSource="{Binding SelectedPerson.Addresses}"
  68:                          SelectedIndex="{Binding AddressesComboBoxSelectedIdx, Mode=TwoWay}"
  69:                          Style="{StaticResource ListBoxStyle}"
  70:                          ItemContainerStyle="{StaticResource ListBoxItemStyle}">
  71:  
  72:                     <ListBox.ItemTemplate>
  73:                         <DataTemplate>
  74:                             <Grid>
  75:                                 <Grid.ColumnDefinitions>
  76:                                     <ColumnDefinition Width="140"/>
  77:                                     <ColumnDefinition Width="*"/>
  78:                                 </Grid.ColumnDefinitions>
  79:                                 <Grid.RowDefinitions>
  80:                                     <RowDefinition Height="auto"/>
  81:                                     <RowDefinition Height="auto"/>
  82:                                     <RowDefinition Height="auto"/>
  83:                                     <RowDefinition Height="auto"/>
  84:                                     <RowDefinition Height="auto"/>
  85:                                     <RowDefinition Height="auto"/>
  86:                                     <RowDefinition Height="auto"/>
  87:                                 </Grid.RowDefinitions>
  88:                                 
  89:                                 <TextBlock Grid.Column="0" Grid.Row="0" Text="Street Address 1" Style="{StaticResource TextBlockStyle}" />
  90:                                 <TextBlock Grid.Column="0" Grid.Row="1" Text="Street Address 2" Style="{StaticResource TextBlockStyle}" />
  91:                                 <TextBlock Grid.Column="0" Grid.Row="2" Text="Street Address 3" Style="{StaticResource TextBlockStyle}" />
  92:                                 <TextBlock Grid.Column="0" Grid.Row="3" Text="Suburb" Style="{StaticResource TextBlockStyle}" />
  93:                                 <TextBlock Grid.Column="0" Grid.Row="4" Text="State" Style="{StaticResource TextBlockStyle}" />
  94:                                 <TextBlock Grid.Column="0" Grid.Row="5" Text="Post Code" Style="{StaticResource TextBlockStyle}" />
  95:                                 <TextBlock Grid.Column="0" Grid.Row="6" Text="Country" Style="{StaticResource TextBlockStyle}" />
  96:  
  97:                                 <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding StreetAddress1}" Style="{StaticResource TextBoxStyle}" />
  98:                                 <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding StreetAddress2}" Style="{StaticResource TextBoxStyle}" />
  99:                                 <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding StreetAddress3}" Style="{StaticResource TextBoxStyle}" />
 100:                                 <TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Suburb}" Style="{StaticResource TextBoxStyle}" />
 101:                                 <TextBox Grid.Column="1" Grid.Row="4" Text="{Binding State}" Style="{StaticResource TextBoxStyle}" />
 102:                                 <TextBox Grid.Column="1" Grid.Row="5" Text="{Binding PostCode}" Style="{StaticResource TextBoxStyle}" />
 103:                                 <TextBox Grid.Column="1" Grid.Row="6" Text="{Binding Country}" Style="{StaticResource TextBoxStyle}" />
 104:                             </Grid>
 105:                         </DataTemplate>
 106:                     </ListBox.ItemTemplate>
 107:                 </ListBox>
 108:             </Grid>
 109:         </Border>
 110:     </Grid>
 111: </UserControl>

 

Page.xaml.cs

   1: using System;
   2: using System.Windows.Controls;
   3:  
   4: namespace ListBoxTest
   5: {
   6:     public partial class Page : UserControl
   7:     {
   8:         public Page()
   9:         {
  10:             InitializeComponent();
  11:             this.DataContext = new MyDataContext(this);
  12:         }
  13:     }
  14: }

 

App.xaml

   1: <Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   3:              x:Class="ListBoxTest.App"
   4:              xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
   5:              >
   6:     <Application.Resources>
   7:         <Style x:Key="TextBoxStyle" TargetType="TextBox">
   8:             <Setter Property="Margin" Value="5"/>
   9:             <Setter Property="Width" Value="180"/>
  10:         </Style>
  11:         <Style x:Key="TextBlockStyle" TargetType="TextBlock">
  12:             <Setter Property="Margin" Value="5"/>
  13:             <Setter Property="Width" Value="100"/>
  14:         </Style>
  15:         <Style x:Key="MainBorder" TargetType="Border">
  16:             <Setter Property="Background" Value="WhiteSmoke" />
  17:             <Setter Property="BorderThickness" Value="1" />
  18:             <Setter Property="CornerRadius" Value="8" />
  19:             <Setter Property="BorderBrush" Value="LightGray" />
  20:             <Setter Property="Padding" Value="12" />
  21:         </Style>
  22:         <Style x:Key="InnerBorder" TargetType="Border">
  23:             <Setter Property="Margin" Value="0,5,0,5" />
  24:             <Setter Property="Background" Value="LightGray" />
  25:             <Setter Property="CornerRadius" Value="5" />
  26:             <Setter Property="Padding" Value="8" />
  27:         </Style>
  28:         <Style x:Key="ListBoxStyle" TargetType="ListBox">
  29:             <Setter Property="HorizontalContentAlignment" Value="Left" />
  30:             <Setter Property="VerticalContentAlignment" Value="Top" />
  31:             <Setter Property="IsTabStop" Value="False" />
  32:             <Setter Property="TabNavigation" Value="Once" />
  33:             <Setter Property="Template">
  34:                 <Setter.Value>
  35:                     <ControlTemplate TargetType="ListBox">
  36:                         <ItemsPresenter />
  37:                     </ControlTemplate>
  38:                 </Setter.Value>
  39:             </Setter>
  40:         </Style>
  41:         <Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
  42:             <Setter Property="HorizontalContentAlignment" Value="Left" />
  43:             <Setter Property="VerticalContentAlignment" Value="Top" />
  44:             <Setter Property="TabNavigation" Value="Local" />
  45:             <Setter Property="Template">
  46:                 <Setter.Value>
  47:                     <ControlTemplate TargetType="ListBoxItem">
  48:                         <Grid Background="{TemplateBinding Background}">
  49:                             <vsm:VisualStateManager.VisualStateGroups>
  50:                                 <vsm:VisualStateGroup x:Name="CommonStates">
  51:                                     <vsm:VisualState x:Name="Normal" />
  52:                                     <vsm:VisualState x:Name="MouseOver" />
  53:                                 </vsm:VisualStateGroup>
  54:                                 <vsm:VisualStateGroup x:Name="SelectionStates">
  55:                                     <vsm:VisualState x:Name="Unselected">
  56:                                         <Storyboard>
  57:                                             <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Visibility" Duration="0">
  58:                                                 <DiscreteObjectKeyFrame KeyTime="0">
  59:                                                     <DiscreteObjectKeyFrame.Value>
  60:                                                         <Visibility>Collapsed</Visibility>
  61:                                                     </DiscreteObjectKeyFrame.Value>
  62:                                                 </DiscreteObjectKeyFrame>
  63:                                             </ObjectAnimationUsingKeyFrames>
  64:                                         </Storyboard>
  65:                                     </vsm:VisualState>
  66:                                     <vsm:VisualState x:Name="Selected">
  67:                                         <Storyboard>
  68:                                             <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Visibility" Duration="0">
  69:                                                 <DiscreteObjectKeyFrame KeyTime="0">
  70:                                                     <DiscreteObjectKeyFrame.Value>
  71:                                                         <Visibility>Visible</Visibility>
  72:                                                     </DiscreteObjectKeyFrame.Value>
  73:                                                 </DiscreteObjectKeyFrame>
  74:                                             </ObjectAnimationUsingKeyFrames>
  75:                                         </Storyboard>
  76:                                     </vsm:VisualState>
  77:                                 </vsm:VisualStateGroup>
  78:                                 <vsm:VisualStateGroup x:Name="FocusStates">
  79:                                     <vsm:VisualState x:Name="Focused"/>
  80:                                     <vsm:VisualState x:Name="Unfocused"/>
  81:                                 </vsm:VisualStateGroup>
  82:                             </vsm:VisualStateManager.VisualStateGroups>
  83:                             <ContentPresenter
  84:                                     x:Name="contentPresenter"
  85:                                     Content="{TemplateBinding Content}"
  86:                                     ContentTemplate="{TemplateBinding ContentTemplate}"/>
  87:                         </Grid>
  88:                     </ControlTemplate>
  89:                 </Setter.Value>
  90:             </Setter>
  91:         </Style>
  92:     </Application.Resources>
  93: </Application>

 

And Person.cs is displayed above.

 

Notes

 

  • In Line 49 of MyDataContext.cs, don't forget to call ComboBox.UpdateLayout() when changing the data binding of a ComboBox. Otherwise you'll encounter the insidious "Error: Sys.InvalidOperationException: ManagedRuntimeError error #4004 in control 'Xaml1': System.ArgumentException: Value does not fall within the expected range." error during runtime.
  • Don't forget to  add a namespace for the VisualStateManager in App.xaml (xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows")

 

Source

 

Source is here.

Categories: Work

Setting up IIS6 for Silverlight on shared hosting via PLESK

by Tom 1. November 2008 10:19

When I'm at home, I don't have access to high-end hosting products like dedicated servers and VPS, so I'm stuck with a shared hosting plan for my personal sites. Maybe Azure will make running a virtual environment in the cloud more accessible - and I hear Amazon EC2 is now offering Windows environments - but for the time being I am happy enough with things as they are.

I have noticed, however, that there's very little literature on how to set up IIS6 to run Silverlight when you don't have RDP access, but are limited to a control panel system. My hosting provider gives me PLESK, so that's what I'm looking at.

There are two basic steps for setting up IIS6 to run Silverlight (IIS 7 does all this out of the box I believe)

  1. Ensure that ClientBin directory is configured to run scripts
  2. Ensure that IIS6 understands what to do with XAP files\

Here's the process:

 

1. From the root of you domain or subdomain, click "Web Directories"

02-01

 

2. Click "Add New Virtual Directory"

02-02

 

3. Ensure scripts have Execute permissions. I also added write permission, but only do this if you need to.

02-03

 

4. Once the ClientBin directory is created and you are within it, click on MIME types.

02-04

 

5. Add the following MIME types:

  • .xaml application/xaml+xml
  • .xap application/x-silverlight-app
  • .xbap application/x-ms-xbap

 

6. You should see this:

02-08

 

And that's that. Your crappy shared server is setup to run Silverlight.

Categories: Work