Southerneer
Southerneer

Reputation: 2012

Problem with ItemsSource binding in Silverlight ListBox

I'm trying to list some strings in a Silverlight ListBox. I'm binding a vanilla List to the ItemsSource and then specifying the property of the List item to display in DisplayMemberPath. There is something specific to my implementation that causes the ListBox to display the templated items instead of the property specified inside those items.

Here's the scenario. I have a Parent class that derives from UserControl that adds a "Title" Dependency Property. I create a few Child controls that derive from Parent and specify that inherited Title property. For some reason, binding to that Title property in the ListBox causes the unexpected behavior. Here's the code:

public class Parent : UserControl
{
    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(Parent), new PropertyMetadata(String.Empty));
}

The Child XAML code (Child1 and Child2 are basically the same XAML with trivial codebehinds)

<v:Parent x:Class="TemplateBindingTest.Child1" 
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:v="clr-namespace:TemplateBindingTest"
       mc:Ignorable="d" Title="Baby 1" Height="41" Width="94">
<Grid x:Name="LayoutRoot" Background="#FFFFBBBB">
    <TextBlock HorizontalAlignment="Center" Text="I da baby" VerticalAlignment="Center" FontSize="14" />
</Grid>

The "ViewModel"

public class TheViewModel : INotifyPropertyChanged
{
    public List<Parent> Babies { get; set; }

    public TheViewModel()
    {
        Babies = new List<Parent>();
        Child1 baby1 = new Child1();
        Child2 baby2 = new Child2();
        Babies.Add(baby1);
        Babies.Add(baby2);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

MainPage.xaml

<UserControl x:Class="TemplateBindingTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" xmlns:my="clr-namespace:TemplateBindingTest" Height="268" Width="355">

<Grid x:Name="LayoutRoot" Background="White">
    <ListBox ItemsSource="{Binding Path=Babies}" DisplayMemberPath="Title" Height="219" HorizontalAlignment="Left" Margin="12,12,0,0" VerticalAlignment="Top" Width="180"  />
    <my:Child1 HorizontalAlignment="Left" Margin="203,26,0,0" x:Name="child1" VerticalAlignment="Top" />
    <my:Child2 HorizontalAlignment="Left" Margin="203,92,0,0" x:Name="child2" VerticalAlignment="Top" />
</Grid>

So ignoring the fact that it's a bit weird to maintain a list of UI controls in a "viewmodel" class, this is all fairly simple Silverlight. In the ListBox control on MainPage I would expect to see the title for each Child control. Instead, the Child controls themselves show up in the ListBox. What am I missing here? I find it very odd that Silverlight just decides to render the Children controls with no complaints in the Debug Output or other error messages. It's like the DisplayMemberPath attribute gets completely ignored. Could this be a bug in Silverlight?

error example

For ease of testing, here's a link to the full Visual Studio project containing the code above.

Upvotes: 1

Views: 1303

Answers (2)

Southerneer
Southerneer

Reputation: 2012

@AnthonyWJones is correct and the link he provided led to this answer: I would need to implement my own hacked up version of ListBox in order to achieve the desired functionality mentioned in my question.

public class MyListBox : ListBox
{
    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return false;
    }

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        ((ListBoxItem)element).ContentTemplate = ItemTemplate;
    }
}

I guess the moral of this story is don't try to deal with UI elements directly in your ViewModel classes. It's not best practice (and definitely not MVVM) for a reason.

Upvotes: 0

AnthonyWJones
AnthonyWJones

Reputation: 189437

This behaviour seems to be by design. If the listbox sees that the Content is a derivative of a UIElement then it makes the simple (and I think reasonable) assumption that you intend for that content to be displayed.

You are right what you are doing is "a bit weird" and you are paying the price. This answer may hold a solution for you. However I would recommend you review the choice to hold an instance of a UserControl in the viewmodel.

Upvotes: 1

Related Questions