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

Comments

p90x
p90x United States on 2/9/2010 9:06:30 AM

I can see that you are putting a lot of time and effort into your blog and detailed articles! I am deeply in love with every single piece of information you post here. Reading this post reminds me of my old room mate! He always kept talking about this. I will forward this article to him. Pretty sure he will have a good read. Thanks for sharing!

jobs uk
jobs uk on 3/2/2010 4:00:27 PM

Thanks mate!I was looking for that solution nearly all day!There are few more questions that I want to ask you.Should I post them as comments?By the way -I love your blog,it is awesome and if you are looking for link exchange with quality job borad in UK,please drop an email-I will be glad to exchange link with your blog
Regards,
Chris

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading