Bind ListBox to a List in Windows Phone

Feb 8, 2012 at 6:46 PM
Edited Feb 9, 2012 at 3:29 PM

Hello,

I have a model that returns List<Items> and a ListBox in my ItemsView.xaml. How should the property look like in my ViewModel to create the binding?

I tried something like:

public List<Items> LeftList
{
    get {
        return MyModel.CreateList();
    }
}

But my ListBox shows "Cannot find view for Name.Space.Stuff.Items". However, when I access the property "SelectedItem" of my ListBox (from code behind) I get the correct data.

Is there an extra step I have to take to make it work?

Essam

Feb 8, 2012 at 11:54 PM
Edited Feb 8, 2012 at 11:55 PM

this occurred cause you gave the listbox the name of "Items" change it to LeftList and it should bind.  but remember if you change the contents of the list it will not see the changes because List<> doesn't respond to INotifyPropertyChange events, therefore you should consider using ObservableCollection (in the .NET framework) or use the BindableCollection provided in CM.  BindableCollection does implement ObservableCollection.

Feb 9, 2012 at 3:21 PM

Not really. My ListBox x:Name is LeftList. Like I said, my list is getting populated correctly with the items I have in the ViewModel; my problem is with the displayed text of the ListItems.

I'm aware of the "static" nature of List<>; however, my list is also static and gets populated once the page is loaded.

Thanks


Feb 9, 2012 at 4:26 PM

Set the DisplayMemberPath = the property you want to show up or create a View for the Item being displayed.  

Feb 9, 2012 at 4:34 PM

I didn't quite understand your answer. My ListBox is bound to List<Item> which means each ListBoxItem has an object of Item and the displayed text is the value of ToString() method of Item... shouldn't it work like that?

Feb 9, 2012 at 4:40 PM

Right, you're ListBox has it's name set to x:Name="Items".

So,

"In addition to binding the ItemsSource on an ItemsControl, the ApplyBinding func also inspects the ItemTemplate, DisplayMemberPath and ItemTemplateSelector (WPF) properties. If none of these are set, then the framework knows that since you haven’t specified a renderer for the items, it should add one conventionally."

but...

"If your item is a ValueType or a String we don’t generate a default DataTemplate. We assume that you want to use the default ToString rendering that the platform provides."

 

The value of ToString() for that type is what you're seeing.  Which would probably do what you're expecting if it was List<string>();

Feb 9, 2012 at 4:53 PM
Edited Feb 9, 2012 at 4:53 PM

Excuse me if for some reason I wasn't clear enough. My ListBox x:Name is "LeftList" and not "Items", and "LeftList" is also the name of the property that returns List<Item> in my ViewModel.

My Item ToString() is:

public override string ToString()
{
    return this.ItemName;
}

and ItemName is defined as:

public string ItemName { get; set; }

Also, no, I tried List<string> too and the result was similar; i.e. something like "Cannot find view for System.String"

Like I said in my question, the data is there in the ListBox, but it's displayed incorrectly.

Feb 9, 2012 at 5:09 PM
Edited Feb 9, 2012 at 5:11 PM

Here's the code where I think that happens:

https://gist.github.com/1781678

Feb 9, 2012 at 5:14 PM

I don't have any DataTemplate.

My code is as straightforward as:

1. Model returns List<Item>

2. VeiwModel List<Item> property called LeftList

3. View that has a ListBox called LeftList

What is wrong here?

Feb 9, 2012 at 5:31 PM

I was able to re-create this, don't know why ToString isn't being called.  I normally create a template or use DisplayMemberPath, curious as to why it doesn't work though.

Feb 9, 2012 at 5:38 PM

Not sure why code inserting isn't working on codeplex, tried 2 browsers.  Anyway. 

#if !WP71

            var itemType = property.PropertyType.GetGenericArguments().First();   

        if (itemType.IsValueType || typeof(string).IsAssignableFrom(itemType)) {                return;            }

#endif

Does that mean on WP7 its not doing the ToString() ?

Feb 9, 2012 at 5:44 PM

I'm not sure. Maybe one of the developers passes by and tell us what we are missing?

Feb 10, 2012 at 3:42 AM

what does your file structure look like?

Feb 10, 2012 at 6:45 AM
Edited Feb 10, 2012 at 6:47 AM

It's something like:

MyProject
    Data
        MyData.txt
    Framework
        AppBootstrapper.cs
    Models
        Item.cs
    ViewModels
        ItemSelectViewModel.cs
    Views
        ItemSelectView.xaml
    App.xaml

MyData.txt is where I read the list from, and my namespaces follow my folder structure where the base namespace is: TheBlueSky.MyProject.UI

Feb 10, 2012 at 11:43 PM

have you implemented any Logging to tap into the framework to see any binding errors?

LogManager.GetLog = type => new Logging(type);

Logging is a class that has a constructor that takes a param of Type and inherits from ILog, there is an example in the GameLibrary demo and I use it to send information to the debug console.

It might help you see the problem from the viewmodel perspective.

 

Feb 11, 2012 at 8:49 AM

Thanks for pointing this out.

I created a log for my app similar to the one in GameLibrary, and this what I got:

WARN: ViewLocator : View not found. Searched: .
INFO: ViewModelBinder : Binding System.Windows.Controls.TextBlock and Item 1.
INFO: Action : Setting DC of System.Windows.Controls.TextBlock to Item 1.
INFO: Action : Attaching message handler Item 1 to System.Windows.Controls.TextBlock.

This part is repeated for every item in my list (I've got 5 of them). 'Item 1' of course is the ToString() value of one of the items in the list (others will return 'Item 2', 'Item 3' ... etc.).

I can see from the log that ViewLocator search nothing and the TextBox got the value of ToString() correctly; still, the end result is incorrect.

Any clue?

Feb 11, 2012 at 4:26 PM

Based on information from that debugging, the view isn't ever located therefor nothing should be bound at all.  I would suspect there is a typo somewhere in the name of the view / viewmodel and it's causing the unusual behavior of the application....  What is the name of the TextBlock?

How are you defining the ViewModels in your Bootstrapper?

Feb 11, 2012 at 4:51 PM
Edited Feb 11, 2012 at 4:52 PM

I don't think it's a typo thing and I don't have any TextBlock defined anywhere in my View.

The TextBlock which is shown in the log probably is the default ItemTemplate for the ListBoxItem. dbeattie who replied to this post earlier was able to reproduce the scenario with an application he created... it's very less likely that we did the same type of typo :)

In my bootstrapper, my ViewModel defined as the following:

this.container.PerRequest<ItemSelectViewModel>();

And below is my view

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="72"/>
    </Grid.RowDefinitions>

    <!--Pivot Control-->
    <controls:Pivot Title="{StaticResource AppNameUpper}" Grid.Row="0">
        <!--Pivot item one-->
        <controls:PivotItem Header="mass">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.5*"/>
                    <ColumnDefinition Width="0.5*"/>
                </Grid.ColumnDefinitions>

                <ListBox x:Name="LeftList" Grid.Column="0" FontSize="29.333">
                    <!-- Will be filled dynamically -->
                </ListBox>
                <ListBox x:Name="RightList" Grid.Column="1" FontSize="29.333">
                    <!-- It's empty now -->
                </ListBox>
            </Grid>
        </controls:PivotItem>

        <!--Pivot item two-->
        <controls:PivotItem Header="temperature">
            <Grid>
                <!-- It's empty now -->
            </Grid>
        </controls:PivotItem>
    </controls:Pivot>
    <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
        <Button Content="Okay" Width="192" VerticalAlignment="Center"/>
        <Button Content="Not Now" Width="192" VerticalAlignment="Center"/>
    </StackPanel>
</Grid>

If you can you may create an app similar to mine and see how it behaves in your side.

Note again that the total number of items visible in my list is the expected number and when I select an item I can cast the SelectedItem to Item, which means the ListBox is populated correctly, however, the text displayed for the ListBosItem is not correct.

Feb 12, 2012 at 11:50 PM
Edited Feb 12, 2012 at 11:55 PM

dbeattie is absolutely correct... What you are seeing is "DEFAULT" convention for String data type.  So if you want to see Item 1 Item 2 etc... you will 1) Set the DisplayMemberPath, or 2) DataTemplate

Sorry what you were running into is expected behavior now that I have actually played with the code.  Basically the framework doesn't know how to bind your data to the listbox so it defaults to crap output (aka hard to debug).  As a result we are seeing TextBlock being bound because it is a part of the ListBox.

 <ListBox  x:Name="LeftList" Grid.Column="0" FontSize="29.333">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding ItemName}" />
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>

 


Feb 13, 2012 at 2:13 AM

And this is why I'm confused. If you're binding a ListBox to List<Item> without Caliburn.Micro (e.g. code behind) you'll see that the binding is smart enough to know that there is something to be displayed and that thing is the ToString output of Item... I expect CM to default to this too; okay. try to find a view for the model, but there is no view, just display the ToString.

I'm thinking to open an bug report for this, but before that, can you think of any technical reason why this is not the behaviour of CM?

Thanks.

Feb 13, 2012 at 3:48 AM

http://devlicio.us/blogs/rob_eisenberg/archive/2010/12/16/caliburn-micro-soup-to-nuts-part-7-all-about-conventions.aspx

Feb 13, 2012 at 5:49 PM

Wow... that's one hell of an article :) I guess I have some reading to do first :)

Feb 14, 2012 at 5:36 AM

basically there is a part in one of hte paragraphs that talks about this exact issue you are seeing...  If you don't datatemplate or use displaymemberpath then it defaults to a datatemplate that is ultra generic...  And as you ahve seen doesn't work very well for all cases

 

Feb 14, 2012 at 11:04 AM
Edited Feb 14, 2012 at 11:05 AM

Then I guess I'll come back soon to open issue/feature request :)

The idea is I have no problem with the default generic DataTemplate, it's just it should take into account the default behaviour of the control when they are bound to different object (when not using CM); e.g. ListBoxItem text is the value of ToString() of the object it's bound too.

Thanks a lot for your help.