Mobile Zone is brought to you in partnership with:

My name is Toni Petrina and I am a software developer and an occasional speaker. Although I primarily develop on the Microsoft stack, I like to learn new technologies. My hobbyist projects range from game development, regardless of the technology, to ALM. I spend most of my time with my girlfriend and someday I will learn how to play the guitar properly. Toni is a DZone MVB and is not an employee of DZone and has posted 69 posts at DZone. You can read more from them at their website. View Full User Profile

Windows Phone ListBox empty template – Using visual states (part 1)

05.25.2012
| 4219 views |
  • submit to reddit

ListBox can be customized in a lot of different ways: you can change the item template, the container or the control template. However, there are some common states which cannot be easily set. For example, if you want to display “No items match your query” as a property of the ListBox itself, there is no such template. Similarly, the template for “data is loading” does not exist. These are all common requirements for building a pleasant UX.

When you are searching the WP7 marketplace from your phone you will get a message when no applications can be found with the specified criteria. Also, before the search is complete, the progress indicator is displayed in the middle of the ListBox indicating that it is currently being filled. This is somewhat better UX than using the progress indicator in the system tray since the user knows that the list box is not ready rather than the generic “application is doing something so please wait.” We want to replicate such design easily. Luckily, you can achieve this functionality in several different ways:

  1. Place an invisible TextBlock element which will be visible when there are no elements found. Toggle the visibility in your OnCompleted handler.
  2. Create a user control which contains the logic and a dependency property for the “empty message”
  3. Inherit custom control from ListBox and create custom control template.

In the first solution you have to add elements to the page which should not belong there, maintenance is reduced and you have to c/p the same snippet all over the place. In the second solution you lose the ability to simply style the ListBox control inside the UserControl. There should be a simple, portable (in a sense that we can apply it to other ListBox-like control like controls), maintainable and customizable solution. The third solution prevents you from enhancing other inherited ListBox controls which might be detrimental.

In this post I will present one method for adding such “empty template” and “progress template” using visual states.

Basic setup

Create a simple WP7 project and add the ListBox control to the page. Add two buttons that will simulate fetching data from some source: one that will fill list with dummy items and one that will result in empty list. I have arranged controls as seen on the image below (you can download the project used in this post from the link at the bottom of the page).

Bind ItemsSource to the ViewModel property named Items (you can choose your own names if you want). I have simply bound the MainPage instance as the view model and added the necessary scaffolding required for the MVVM pattern.

Edit ControlTemplate

The default control template consists of the ScrollViewer control which contains ItemsPresenter. I will add a Grid< control as the root element which will contain both the original ScrollViewer control and the TextBlock control. The XAML code should have the following layout (attributes have been intentionally omitted):

<Grid>
    <ScrollViewer>
        <ItemsPresenter/>
    </ScrollViewer>
 
    <TextBlock />
</Grid>

You can bind the Text property to the appropriate property in ViewModel. But how and when do we show the text? It should be hidden at first and displayed only after our download/search has been completed and not before. To solve this, we will add two visual states in our template that will animate the opacity property for the text control. First add the following XAML code inside the Grid control:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup  x:Name="States">
        <VisualState x:Name="HasItems">
            <Storyboard>
                <DoubleAnimation To="0" Duration="0:0:0"
Storyboard.TargetName="txtNoItems" Storyboard.TargetProperty="Opacity"/>
            </Storyboard>
        </VisualState>
        <VisualState x:Name="HasNoItems">
            <Storyboard>
                <DoubleAnimation To="1" Duration="0:0:.5"
Storyboard.TargetName="txtNoItems" Storyboard.TargetProperty="Opacity"/>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

 Set the opacity, name, style and text properties for the text control:

<TextBlock x:Name="txtNoItems"
              Style="{StaticResource PhoneTextLargeStyle}"
              Opacity="0"
              Text="{Binding NoMessagesText}" />

Also name the ListBox control, I will presume that you named it lbItems.

So how do we change the state? We can do that via the VisualStateManager.GoToState method. In the code behind, use the following snippet for the button's click event handler:

// "Some items"
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
    var items = new ObservableCollection<string>();
    items.Add("Hello");
    items.Add("world");
    items.Add("2012");
    Items = items;
    VisualStateManager.GoToState(lbItems, "HasItems", false);
}
 
// "No items"
private void Button_Click_1(object sender, System.Windows.RoutedEventArgs e)
{
    Items = new ObservableCollection<string>();
    VisualStateManager.GoToState(lbItems, "HasNoItems", false);
}

Run the program in the emulator or on the device and click the buttons. Notice that the text is smoothly animated when it is appearing and it immediately disappears when you add items. This should be consistent with the asynchronous data fetching after which you would toggle the visual state.

You might have noticed that, while reusable, this solution requires naming controls (which is always bad) and does not rely on binding. We want to change the state automatically. Is there a way to make it less verbose and more in the spirit of MVVM? Luckily, the answer is yes. What we need is a behavior.

Adding behavior

Behaviors are pieces of reusable components that can be attached to any control. This allows altering the behavior, hence the name, of any framework element without depending on the ViewModel. First of all, you need a reference to the System.Windows.Interactivity assembly. Add it via the Add Reference dialog.

Add a new class to the project and name it BoolVisualStateBehaviour. If you are not familiar with the behaviors, they inherit the class Behavior which can be found in the previously added assembly. You can override two methods: OnAttached and OnDettached where you can get the framework element on which you have attached a behavior. The easiest way to attach a behavior to the element is via XAML. First add the following namespaces (the local namespace can differ depending on the project name):

xmlns:local="clr-namespace:EmptyListBox_VisualState"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Now you can add the behavior using the following snippet:

<ListBox>
    <i:Interaction.Behaviors>
        <local:BoolVisualStateBehaviour />
    </i:Interaction.Behaviors>
</ListBox>

 Back to the code. Since we will use this behavior for the ListBox controls, inherit from the Behavior<ListBox>. Since we want to make this code more MVVM friendly, the behavior should be invoked when some property changes in the view model. For simplicity, I will presume that is boolean property. The following code adds the dependency property:

public bool HasNoItems
{
    get { return (bool)this.GetValue(HasNoItemsProperty); }
    set { this.SetValue(HasNoItemsProperty, value); }
}
 
public static readonly DependencyProperty HasNoItemsProperty = DependencyProperty.Register(
    "HasNoItems",
    typeof(bool),
    typeof(BoolVisualStateBehaviour),
    new PropertyMetadata(false, HasNoItems_Changed));
 
private static void HasNoItems_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}

We can now bind the dependent property in the XAML. You will find in the attached project that I have added a property in the MainPage class of boolean type and the binding in XAML looks like this:

<local:BoolVisualStateBehaviour HasNoItems="{Binding HasNoItems}"/>

To get the actual ListBox instance in the behavior use the AssociatedObject in the OnAttached override:

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

Now we can react when the binding changes and the HasNoItems_Changed event handler is invoked:

private static void HasNoItems_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var bvsb = d as BoolVisualStateBehaviour;
    if (bvsb != null && bvsb.listBox != null)
    {
        if ((bool)e.NewValue == true)
            VisualStateManager.GoToState(bvsb.listBox, "HasNoItems", false);
        else
            VisualStateManager.GoToState(bvsb.listBox, "HasItems", false);
    }
}

Finally we can fix the view model and the view: remove the name for the ListBox and fix the click event handlers to signal change instead of manually changing the visual state.

private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
    // ... omitted...
    HasNoItems = false;
}
 
private void Button_Click_1(object sender, System.Windows.RoutedEventArgs e)
{
    Items = new ObservableCollection<string>();
    HasNoItems = true;
}
</string>

Conclusion and further work

Voila! If you run the program now, it still does the same thing. However, this is a drastic improvement since this is reusable solution. We still need to remove the hard coded strings in the behavior and use the dependency properties for that. You also need to enhance this code a bit using the SafeBehavior class from Joost van Schaik.

You can now extend this sample easily if you want to add the progress state by changing the control template, adding the necessary visual states and extending the behavior class. Even though this method is functional, it is still intrusive and it can be hard to adapt when you are already using a control template. In the next post I will show the less intrusive method.

You can download the sample project from the following link: http://tonicodes.net/downloads/EmptyListBox.7z.

 

Published at DZone with permission of Toni Petrina, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)