Freigeben über


Silverlight ListBox: Part I - Using multiple templates in a ListBox

Introduction

The listbox is a very important control in any technology like ASP.NET or Sliverlight. Normally, we use it to show single line records where the user has the option to choose one of them. I am not going to discuss about how to use listbox in Silverlight as there are many articles available on the topic in the Internet. In this article, I will discuss about how we can use a template to show the items in a listbox. Although there are many articles available on using a single template in a listbox, there are very few which discuss about using multiple templates in a listbox. Sometimes we might have a requirement where we want to show items which have a few common properties and a few specific properties, and we might want to show a different UI for each type of item and in the same listbox.

Let’s take an example to get a better understanding.

It is a very common scenario. There is an organization which has software developers, team leaders, and managers. All of them have a common properties like EmployeeName and ManagerName, but team leaders and managers will have direct reporters to them.

I am going to create a list box which will have software developers, team leaders, and mangers in a list box. Each will have common properties like Employee Name and Manager Name. Team leader and manager items will have listbox which will have a list of reporters. Following is the list of background colors that I used for each type of item to distinguish them.

Item Type Background Color
Software Developer Yellow
Team Leader Cyan
Manager Green

Background

There is a very good sample on listbox which uses a Panel as a container of the listbox. Normally, we use a listbox with the listbox items arranged vertically even if there is enough horizontal space. This sample implements a wrap panel for the listbox; if you are using a wrap panel, then you need not worry about the list box item positions. If there is enough horizontal space, then multiple items can rendered in the same line, and if there is not enough space for the next item, then the next item would be rendered in the next line. Apart from it, its also takes care of resizing the listbox so if the end user is resizing the listbox, then the items would be rendered again and will take new positions corresponding to the space available. This sample also describes about an animation but I am not going to use that in my sample. Following is the link for the article:

You can directly follow up my example even without reading the above article, but I would highly recommend reading it.

What is the Idea?

Following are the steps that we will follow to achieve our goal. The steps would be more clear in the code section.

  1. Create a template class which will have properties for all the possible templates in the listbox.
  2. Create a new class which will derive from ListBox. It will have a new dependency property to select the template for a given ListBoxItem. Override the PrepareContainerForItemOverride method as it actually prepares the specified element to display the given ListBoxItem. It should call the base class (ListBox) PrepareContainerForItemOverride method and set the template of the item from the TemplateSelecter property.
  3. Define the templates for each type of item in the XAML resource.
  4. Define a TemplateSelecter in the XAML resource which will refer to the data template for the item types in the ListBox.
  5. Refer this TemplateSelector in the list box declaration in the User Control XAML.

Using the Code

Step1

Define the classes for each type of item. All the classes should have a common base class. Here, Employee is the common base class.

 public class Employee
{
    public string Name { get; set; }
    public string ImageSource { get; set; }
}

public class Developer : Employee
{
    public string ManagerName { get; set; }
}

public class TeamLeader : Employee
{
    public List<Employee> DirectReports { get; set; }
    public string ManagerName { get; set; }
}

public class Manager : Employee
{
    public List<Employee> DirectReports { get; set; }
}

Step 2

Create a EmployeeTemplateSelecter class which should have all the used templates in the ListBox and should have a SelectTemplate method which would return the template.

 public class EmployeeTemplateSelector : DataTemplateSelector
{
    public DataTemplate EmployeeTemplate { get; set; }
    public DataTemplate DeveloperTemplate { get; set; }
    public DataTemplate TeamLeaderTemplate { get; set; }
    public DataTemplate ManagerTemplate { get; set; }
    public override DataTemplate SelectTemplate(object item,
        DependencyObject container)
    {
        if (item != null)
        {
            if (item is Manager)
            {
                return this.ManagerTemplate;
            }

            if (item is TeamLeader)
            {
                return this.TeamLeaderTemplate;
            }

            if (item is Developer)
            {
                return this.DeveloperTemplate;
            }

            if (item is Employee)
            {
                return this.EmployeeTemplate;
            }
        }

        return null;
    }
}

Step 3

Create a class MultiTemplateListBox which will be derived from ListBox. It will have a dependency property which will refer to TemplateSelector to get the template for the given ListBoxItem. We have to override the virtual method PrepareContainerForItemOverride so that we can apply the template corresponding to the given ListBoxItem. Here is the code for MultiTemplateListBox.

 public class MultiTemplateListBox : ListBox
{
    public static readonly DependencyProperty TemplateSelectorProperty = 
        DependencyProperty.Register("TemplateSelector",
        typeof(DataTemplateSelector), typeof(MultiTemplateListBox),
        new PropertyMetadata(new PropertyChangedCallback(OnTemplateChanged)));

    public DataTemplateSelector ItemTemplateSelector
    {
        get { return (DataTemplateSelector)this.GetValue(TemplateSelectorProperty); }
        set { this.SetValue(TemplateSelectorProperty, value); }
    }

    protected override void PrepareContainerForItemOverride(
        DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);

        ListBoxItem listBoxItem = element as ListBoxItem;

        if (listBoxItem != null)
        {
            listBoxItem.ContentTemplate = this.ItemTemplateSelector.SelectTemplate(
                item, this);
        }
    }

    private static void OnTemplateChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
    }
}

public class DataTemplateSelector
{
    public virtual DataTemplate SelectTemplate(object item,
        DependencyObject container)
    {
        return null;
    }
}

Step 4

I created three different controls for three types of items. You can see the controls in the attached solution. Now, we can create the templates for each type of item, which will have different views for different types of items. Here is the code which defines the templates:

 <DataTemplate x:Key="DeveloperTemplate">
<CustomControls:DeveloperView />

    </DataTemplate>
    <DataTemplate x:Key="LeaderTemplate">
<CustomControls:TeamLeaderView />
</DataTemplate>
<DataTemplate x:Key="ManagerTemplate">
    <CustomControls:ManagerView />

</DataTemplate>

Step 5

Define an EmployeeTemplateSelector in the User Control resource which will refer each template defined in the resources.

 <CustomControls:EmployeeTemplateSelector x:Key="EmployeeTemplateSelector"
                                DeveloperTemplate=
                                "{StaticResource DeveloperTemplate}" 
                                TeamLeaderTemplate=
                                "{StaticResource LeaderTemplate}"

                                ManagerTemplate=
                                "{StaticResource ManagerTemplate}"/>

Step 6

Include an object of MultiTemplateListBox which will have a ItemTemplateSelector property as a static resource and refers to the EmployeeTemplateSelector key. Bind the ListBox to the Employees collection property.

 <CustomControls:MultiTemplateListBox x:Name="EmployeeList"
                                         ItemsSource="{Binding Employees}"       
                                         ItemTemplateSelector=
                                         "{StaticResource EmployeeTemplateSelector}" 
                                         Width="800" Height="600" 
                                         Canvas.Top="20"> 
    <ListBox.Template>

        <ControlTemplate>
            <Grid>D:\Personal\Submit Article\MultipleDataTemplateInListBox\
                  MultipleDataTemplateInListBox\EmployeeContainer.xaml
                <ScrollViewer>
                    <ItemsPresenter />
                </ScrollViewer>
            </Grid>

        </ControlTemplate>
    </ListBox.Template>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <CustomControls:WrapPanel Width="Auto"/>

        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</CustomControls:MultiTemplateListBox>

The only thing remaining is to populate the data and set the context. Here is the code for it:

 public class EmployeeContext
{
    public List<employee> Employees { get; set; }
    public EmployeeContext()
    {
        Employees = GetEmployeeList();
    }
    private List<employee> GetEmployeeList()
    {
        Developer dev1 = new Developer() { Name = "Mike", ManagerName = "Peterson" ,
            ImageSource = @"/Images/image1.jpg"};
        Developer dev2 = new Developer() { Name = "John", ManagerName = "Peterson" };
        TeamLeader leader1 = new TeamLeader() { Name = "Peterson",
            ManagerName = "Anderson", DirectReports = new List<employee> { dev1,
            dev2 } };
        Developer dev3 = new Developer() { Name = "Steave Mollenkopf",
            ManagerName = "Tomi Swartz" };
        Developer dev4 = new Developer() { Name = "Han Tran",
            ManagerName = "Tomi Swartz" };
        Developer dev5 = new Developer() { Name = "Parth Sarthi",
            ManagerName = "Tomi Swartz" };
        Developer dev6 = new Developer() { Name = "Shivank Nayak",
            ManagerName = "Tomi Swartz" }; 
        TeamLeader leader2 = new TeamLeader() { Name = "Allona Cholnika",
            ManagerName = "Tomi Swartz", DirectReports = new List<employee> { dev3,
            dev4, dev5, dev6 } };
        Manager manager1 = new Manager() { Name = "Tomi Swartz",
            DirectReports = new List<employee>() { leader1, leader2 } };
        return new List<employee>()
           {
               dev1, dev2, dev3, dev4, dev5, dev6, leader1, leader2, manager1
           };
    }
}
   
EmployeeContext context = new EmployeeContext();
        this.DataContext = context;

Now, if you run the code, you will find the items in three different colors (yellow, cyan, and green). I am not very good at UI designing, so the UI might not look very good. The ListBox contains three different types of items, and different types of items have different UI. We can deal with them separately. Here, the team leader ListBoxItem has a list of developers and the manager ListBoxItem has a list of team leaders.

MultipleDataTemplateInListBox.zip

Comments