Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

ListBox Empty Template – Using Control Templates And Behaviors (Part 2)

DZone's Guide to

ListBox Empty Template – Using Control Templates And Behaviors (Part 2)

· Mobile Zone ·
Free Resource

In the previous post I have shown how to fake “empty ListBox template” using visual states. The proposed solution is very intrusive since it requires editing the built-in (or customized) control template. You have to design the template carefully to avoid any visual state corruption.

And if we want to introduce the third template, let’s say we want to have also “download in progress” template (similarly to the way Marketplace search works), the control template would become unmanageable. It would be best to completely separate each template from normal template, and this is exactly what we are going to do today.

The final result will give us something like this:
All states for list box; from left to right: progress, default, empty

First of all, we are going to define empty and progress control template in the following way:

<ControlTemplate TargetType="ListBox" x:Key="EmptyListBoxTemplate">
    <Grid>
        <TextBlock x:Name="txtNoItems"
                    Style="{StaticResource PhoneTextLargeStyle}"
                    Text="No items found" />
    </Grid>
</ControlTemplate>

<ControlTemplate TargetType="ListBox" x:Key="ProgressListBoxTemplate">
    <Grid>
        <StackPanel VerticalAlignment="Center">
            <TextBlock Text="Downloading"
                        HorizontalAlignment="Center" />
            <ProgressBar IsIndeterminate="True" Height="15" />
        </StackPanel>
    </Grid>
</ControlTemplate>

Our ListBox has its own template defined somewhere, we don’t care now. What we need now is a way to detect that the source data has changed and then switch template accordingly. I will add a property named IsWorking to the view model. If it is set to true, we are retrieving our results, if set to false, it means that the items collection is properly set. If there are no items, use the empty template, otherwise, use default template.

But this logic touches UI and view model simultaneously, where should we put it? And who will subscribe to view model to keep track of ongoing operation? Since we do not want to add significant amount of code to our page and we want to achieve maximum reusability, there is only one way to do it – using behaviors.

Behaviors are fragments of code that are aware of view model specifics but are still in view part of the MVVM framework. To use them, first add the necessary reference using the Add Reference dialog. Add the System.Windows.Interactivity.dll found under Assemblies/Extensions page (see picture below).
Add reference to System.Windows.Interactivity.dll

Let’s create a class and name it BoolControlBehavior (not really an inventive name) and inherit Behavior<ListBox> class. This restricts using this behavior only on the ListBox class instances (or any derived classes’ instances for that matter). Now add the following fragment inside it:

private ListBox listBox = null;
private ControlTemplate defaultTemplate = null;

protected override void OnAttached()
{
    base.OnAttached();
    listBox = (ListBox)this.AssociatedObject;
    if (listBox == null)
        return;
    defaultTemplate = listBox.Template;
}

This particular override is called whenever this behavior is attached to ListBox and you can retrieve the object on which it is attached. We will need it for easier reference later (although you can always cast AssociatedObject again). The override named OnDetaching complements the function above and is called when the behavior is removed from the associated object. Use it to cleanup whatever event handlers you have attached before.

Add three dependency properties to the class (for complete code please refer to the source code linked at the bottom of this post):

  • bool IsWorking
  • ControlTemplate EmptyTemplate
  • ControlTemplate ProgressTemplate

Also define the property changed callback for the IsWorking property, we will write all our logic inside it. We want to change templates when or view model starts working or when it is completed. Here is the implementation for the callback:

private static void IsWorking_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var bvsb = d as BoolControlBehavior;
    if (bvsb != null && bvsb.listBox != null)
    {
        var progressTemplate = bvsb.ProgressTemplate;

        if ((bool)e.NewValue == true && progressTemplate != null)
        {
            bvsb.listBox.Template = progressTemplate;
        }
        else
        {
            var src = bvsb.listBox.ItemsSource as ICollection;
            if ((src == null || src.Count == 0) && bvsb.EmptyTemplate != null)
                bvsb.listBox.Template = bvsb.EmptyTemplate;
            else
                bvsb.listBox.Template = bvsb.defaultTemplate;
        }
    }
}

We can now add the last piece – attaching the behavior to our ListBox. We need to add reference in XAML to the correct namespace first. We then attach behavior by adding it to the list of all behaviors attached to particular framework element, in our case ListBox. Here is the snippet:

// XAML namespace references
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:EmptyListBox_ControlTemplate"

<!--Attaching behaviors-->
<ListBox ItemsSource="{Binding Items}"
            Grid.ColumnSpan="2"
            x:Name="lbItems">
    <i:Interaction.Behaviors>
        <local:BoolControlBehavior IsWorking="{Binding IsWorking}"
                                    EmptyTemplate="{StaticResource EmptyListBoxTemplate}"
                                    ProgressTemplate="{StaticResource ProgressListBoxTemplate}"/>
    </i:Interaction.Behaviors>
</ListBox>

We can now easily alternate between three templates by simply updating our view model, oblivious to the actual presentation. This is actually quite similar to what you already have – a property indicating if work is in progress (usually bound to shell:ProgressIndicator) and a list of results. Since it is not intrusive, you can easily upgrade your existing code with little actual change. The code itself is quite portable and can be put in shared assembly.

You can download the sample project from the following location: http://sdrv.ms/Qxetgq [SkyDrive, 20kB].

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}