MVVM + multilevel/nested databinding - c#

The Structure
The scenario is that I have a Pivot where each item is the menu of a certain day. Inside that PivotItem I need to display the dishes on the menu, grouped by category (e.g. soups, desserts, ...).
I have implemented this with the MVVM-model.
I thus have the folowing models:
public class Menu{
public string Date;
public List<DishList> Categories;
}
public class DishList
{
public string Category;
public List<Dish> Dishes;
}
public class Dish
{
public string Name;
public string Price;
}
Edit: these are simplified here, the actual structure for each field is like this:
private string _date;
//Get_Set
public string Date
{
get
{
return _date;
}
set
{
if (value != _date)
{
_date = value;
}
}
}
So the structure would be like this: a PivotElement containt a Menu-object. Inside it, I show Categories, a number of DishLists. Inside that Dishlist, I show the Category and the different Dishes, each with it's Name and Price. A mockup to make things a bit more clear (pdf-file on SkyDrive); http://sdrv.ms/12IKlWd
I have the following viewmodel
public class MenuViewModel : INotifyPropertyChanged
{
private ObservableCollection<Menu> _Menus;
}
The view that should implement (the structure) of the desired layout is as following:
<phone:Pivot
ItemsSource="{Binding Menus}">
<phone:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}" />
</DataTemplate>
</phone:Pivot.HeaderTemplate>
<phone:Pivot.ItemTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding Categories}">
<TextBlock
Text="{Binding Category}"/>
<ItemsControl
x:Name="Dishes"
ItemsSource="{Binding Dishes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="1"
Text="{Binding Name}"/>
<TextBlock
Grid.Column="2"
Text="{Binding Price}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ItemsControl>
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
As you can see, there's nested databinding to get to the data it needs.
The only code I then do on the page is
DataContext = App.ViewModel;
The Problem
When I launch it up, I get the correct Data displayed in the PivotItems header, and the correct number of PivotItems. Ufortunately, that's it; the content of the PivotItem stays empty. The first level of databinding thus works, but no further.
The way I was thinking
1st level: each PivotItem is linked to an object of type Menu, with header Menu.Date
2nd level: Inside that PivotItem I set the ItemsSource of a ItemsControl to that Menu.Categories
3rd level : The ItemsSource now is Menu.Categories.Dishes
I have already searched quite a bit and fiddled around with Datacontext and ItemsSource and different kinds of "{Binding ...}", but I can't get it to work. I would like to do the binding in xaml, and not in the code-behind.
Notes
The only function is to display data. The data is stationary and loaded once from a file.
That's why I chose ItemsControl instead of ListBox, there is no need to select anything.
This is a MVVM-approach to my previous question: Databinding + Dynamic Pivot

First off, you're trying to access a private field in the title so that won't work. These also have to be properties, not fields.
<TextBlock Text="{Binding Date}" />
private string Date;
Next you're binding to Category, which is also private and not a property.
<TextBlock Text="{Binding Category}"/>
private string Category;
And you're binding to the Categories field which needs to be a property.
<ItemsControl ItemsSource="{Binding Categories}">
public List<DishList> Categories;
Even if you correct these issues, you still won't get what you want because your outer ItemsControl doesn't have an ItemTemplate.
<ItemsControl ItemsSource="{Binding Categories}">
<TextBlock Text="{Binding Category}"/>
...
</ItemsControl>
Added: Your new error is because the DataTemplate can only have one child element so you need to use a container like a StackPanel.
<ItemsControl ItemsSource="{Binding Categories}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Category}"/>
<ItemsControl x:Name="Dishes" ItemsSource="{Binding Dishes}">
...
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Related

TextBlock not showing up in ListView

This is my ListView:
<ListView ItemsSource="{Binding ModuleList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<Image Source="{Binding ModuleImage}" Stretch="UniformToFill"/>-->
<TextBlock Text="{Binding ModuleName}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is the code in WPF (MVVM):
public ItemListViewVM() : base()
{
ModuleList = new List<Module>();
modulLoader.LoadAllModules();
tempModulList = modulLoader.GetAllModules();
foreach (var module in tempModulList)
{
ModuleImage = module.ModuleImage;
ModuleName = module.Name;
ModuleList.Add(module);
}
}
Long story short: The List tempModulList contains Objects of type Module, which has an ImageSource Image and a string Name. Then the ModuleList gets one item after another. When I uncomment the Image in xaml, you can see it. But the TextBlock won't show up no matter what. I checked the string module.Namefor every item, it is not empty.
EDIT: Add Module Class
The Module Class just contains Name and Image:
public class Module
{
public ImageSource ModuleImage { get; set; }
public string Name { get; set; }
}
The Object gets created by deserializing a Json
The Bindings in the ItemTemplate use the properties of the data item class as their source properties. Hence you should write
Text="{Binding Name}"
instead of
Text="{Binding ModuleName}"
Unless you set the View property of a ListView (to e.g. a GridView) you could also better use a ListBox, which is the base class of ListView, and simpler:
<ListBox ItemsSource="{Binding ModuleList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ModuleImage}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Initializing the ModuleList property in the view model constructor would be as simple as this:
public ItemListViewVM()
{
modulLoader.LoadAllModules();
ModuleList = modulLoader.GetAllModules();
}

XAML: Simple ItemsControl Binding to TextBox does not work: how to set the source property?

I have an ObservableCollection that I try to bind to a list of text boxes. The textboxes do show but the content of the text does not.
The XAML:
<Grid>
<Grid.RowDefinitions >
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding Path=ListOfMessages}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Message, ElementName=ListOfMessages}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The Code:
In the ViewModel:
public ObservableCollection<ApplicationLog> ListOfMessages { get; set; }
In the Model:
public class ApplicationLog
{
public string Code { get; set; }
public string Message { get; set; }
}
When I run this, the app shows the text boxes (for example 4 text boxes, one below the other), but the text in the text boxes (ie the Message property) is not shown. I think my Binding expression for the Text Box is wrong.
Context: I am new to XAML and WPF. More generally: how does one debug Binding issues similar to this one.
Thanks.
Remove ElementName=ListOfMessages. The DataContext for each item will be the items in the ListOfMessages bound to the ItemsSource.
<Grid>
<Grid.RowDefinitions >
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding Path=ListOfMessages}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- DataContext will be ListOfMessages[0], [1], ..., [n] -->
<TextBox Text="{Binding Path=Message}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
ElementName is used to bypass the DataContext and point to a specific named item in your scoped XAML.

Binding in ListBox using MVVM

I have a problem binding all array entries to my ListBox in XAML.
XAML:
<ListBox ItemsSource="{Binding ResultFlag}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TypeInfo}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ResultFlag property in my ViewModel (which is the DataContext of the XAML file):
private ObservableCollection<DataField> _resultFlag;
public ObservableCollection<DataField> ResultFlag
{
get { return _resultFlag; }
set
{
_resultFlag = value;
OnPropertyChanged();
}
}
TypeInfo in the DataField class:
public string[] TypeInfo { get; set; }
I would like to show all string entries from above array in the ListBox - how should I do this? I've tried several things including nested Listbox and binding the ItemsSource of the ListBox directly to the array (didn't work, BTW)
Cheers!
What you have in your scenario is a List of List. In order to show that in your list box you need to have nested ListBoxes like this.
<ListBox ItemsSource="{Binding ResultFlag}" >
<ListBox.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding TypeInfo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF Binding on Nested ItemControls with Sub Collection

I have the following xaml:
<ItemsControl ItemsSource="{Binding ResearchLanguageViewModel.Filters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Type}"></TextBlock>
<TextBox Text="Search...."></TextBox>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Values}"></CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
With the following objects/collection(s):
public class FilterViewModel
{
public string Type { get; set; }
public ObservableCollection<string> Values { get; set; }
}
public class ResearchLanguageViewModel
{
public int FirmCount { get; set; }
public ObservableCollection<FilterViewModel> Filters { get; set; }
}
I'm trying to bind on the Filters property and the Type property comes out fine. However, I am having trouble getting the Values collection of strings to show as a group of checkboxes. Not sure what I'm doing wrong here...
Values in your View Model is an array, and you are trying (I'm assuming) to create an array of Checkboxes with the values of each checkbox being the index value into that array.
In your model, you are passing in the array of values into the content of one single checkbox control. What you need to do is bind the array to the parent's ItemsSource and bind the new value to the Checkbox control's content. Something like this:
<ItemsControl ItemsSource="{Binding ResearchLanguageViewModel.Filters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Type}"></TextBlock>
<TextBox Text="Search...."></TextBox>
<ItemsControl>
<ItemsControl.ItemTemplate ItemsSource={Binding Path=Values}>
<DataTemplate>
<CheckBox Content="{Binding Mode=TwoWay}"></CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Bind your item controls, ItemSource property to values.
For a string, you won't even need a template. In your model, you have a collection of strings, so binding to a checkbox template does provide some UI enhancement but my betting is you want the checked status represented in the view model too.
You might want to consider creating an object to encapsulate your label and Checked status and defining your collection based on that.
public class CheckModel
{
public string Label {get; set;}
public bool Checked { get; set; }
}
Then bind your checkbox to:
<CheckBox IsChecked="{Binding Checked}" Content="{Binding Label}" />
I hope this helps.
You will want to bind Values to the ItemsControl not the Checkbox,
<StackPanel>
<TextBlock Text="{Binding Path=Type}"></TextBlock>
<TextBox Text="Search...."></TextBox>
<ItemsControl ItemsSource="{Binding Values}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"></CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>

Bounded element's ItemsSource is not updated in view

I have a simple ListView What is bound to an object's (TaxonDescription) list.
When I select another TaxonDescription, then the ListView's element are doesnt updated.
Maybe I need somewhere the NotifyPropertyChanged, but i tried everywhere.
There are my classes.
At the page's codebehind I check the ItemSource, and it is have the right list element, just dont updated to the View.
<ListView ItemsSource="{Binding Descriptions}"
SelectedItem="{Binding ActualSelectedDescription, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DescriptionName}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// this not updated
<ListView ItemsSource="{Binding ActualSelectedDescription.Images}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding NormalUri}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Text="{Binding ActualSelectedDescription.Name}"/> //This is works well
The ActualSelectedDescription is changed by an event.
public TaxonDescription ActualSelectedDescription
{
get{return actualSelectedDescription;}
set { actualSelectedDescription = value;
RaisePropertyChanged("ActualSelectedDescription"); }
//In the setter the Images are in the list
}
And there is the list element in the Images list
There is the Description class, with the list.
class TaxonDescription
{
public List<BaseImage> Images { get; private set; }
public string Name { get; private set; }
public TaxonDescription(string taxonName, string descriptionName)
{
Name = taxonName;
Images = new List<BaseImage>();
//Adding some element
}
}
Any accurate idea can help, i will try them all ;)
ActualSelectedDescription.Images has no change notification.
Try adding notification for Images property.
If that's not possible, make Images an ObservableCollection<BaseImage>.
If that's an issue, try setting data context to a notifying property:
// this should start updating
<ListView DataContext="{Binding ActualSelectedDescription}"
ItemsSource="{Binding Images}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding NormalUri}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT:
Just noticed that TaxonDescription is not public - is that a typo here in SO or is the class private in your actual code?
Since if it's private, the ItemsSource="{Binding ActualSelectedDescription.Images}" will not work since there's no access to Images property.

Categories