Tricky WPF binding - wpf

I'm unable to do a simple but yet tricky WPF binding in Silverlight 4 (WP7 development)
I have the following code:
Class People{
public string firstname;
public string lastname;
}
Class DataSource{
public static List<People> people; // consider this as a list filled with objects already
}
I'm trying to put the list of people into a ListBox, here's the xaml I've tried:
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0" ItemsSource="{Binding DataSource.people}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding firstname}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding lastname}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But unfortunately my listbox remains empty. What am I doing wrong ?
Thank you in advance :)
Cheers,
Miloud B.

Firstly you are using fields, where you should use public property (i.e. people, firstname and lastname). Convert people to a public property, like this:
public static List<People> people { get; set; }
Then, you need to bind the ItemsSource using x:Static markup, like this:
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0">
<ListBox.ItemsSource>
<Binding Source="{x:Static local:DataSource.people}"/>
<ListBox.ItemsSource/>
...
PS: local is the xml namespace pointing to your DataSource class's namespace. Also, your class too needs to be a public class.
EDIT:
For WP7, you need to declare the instance of the class in the resources and then you can use Path to point to the source. Like this:
<phone:PhoneApplicationPage.Resources>
<local:DataSource x:Key="dataSource"/>
</phone:PhoneApplicationPage.Resources>
...
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0" ItemsSource="{Binding Source={StaticResource dataSource}, Path=people}">
PS: Again, your class needs to be public and must have a default constructor.
EDIT:
Here is a example which is working perfectly on my system. Check and see where are you making the mistake:
namespace WindowsPhoneApplication1
{
public class People
{
public string firstname { get; set; }
public string lastname { get; set; }
}
public class DataSource
{
public static List<People> people { get; set; }
public DataSource() { }
static DataSource()
{
people = new List<People> {new People {firstname = "Foo", lastname = "Bar"}};
}
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
}
}
Xaml (only the relevant portions):
...
...
xmlns:local="clr-namespace:WindowsPhoneApplication1"
...
...
<phone:PhoneApplicationPage.Resources>
<local:DataSource x:Key="dataSource"/>
</phone:PhoneApplicationPage.Resources>
...
...
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0" ItemsSource="{Binding Source={StaticResource dataSource}, Path=people}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding firstname}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding lastname}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

1.FirstName and LastName need to be public properties with at least getters.
2.Your list should also be a public property unless you explicitly set the DataContext of your Window
3.Need to either set the DataContext or reference the source otherwise.
4.You cannot bind to static properties like that, use {x:Static ...}.
5.This is not a tricky binding -.-
As devdigital said you might want to implement those interfaces as well.

You can only bind to properties, so change your public fields to properties.
Also, if you want your UI to update on programmatic changes to your people instances, then implement INotifyPropertyChanged on your People type (really that type should be called Person).
If you want your UI to update when items are added/removed from your DataSource people collection, then use ObservableCollection<T> rather than List<T>.

Related

XAML Binding to child collection

In a Windows UWP project I'm trying to bind to the following properties in this class
using System;
using System.Collections.ObjectModel;
namespace IAmOkShared.Models
{
public class Client
{
public Guid clientId { get; set; }
public string lastname { get; set; }
public DateTime timestamp { get; set; }
//- List af addresses of this client
public ObservableCollection<Address> clientaddresses;
public Client ()
{
clientId = Guid.Empty;
lastname = string.Empty;
timestamp = DateTime.Today;
clientaddresses = new ObservableCollection<Address>();
}
}
}
Binding to clientId and lastname is no problem, but can't get it right to bind to one or more of the properties of clientaddresses (e.g city, country)
My XAML:
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="AddressTextBlock" Text="{Binding clientaddresses[0].city}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Any idea how to solve this?
Steven
You are binding to a field instead of a property.
public ObservableCollection<Address> clientaddresses;
Change this to
public ObservableCollection<Address> Clientaddresses { get; private set; }
So it cannot be instantiated outside the viewmodel then the binding should work.
Also you could create additional data template for the Address and just use the entire collection in your datatemplate of the Client, because then you would not get possible Index out of bounds exception if your ClientAddresses collection is empty.
<DataTemplate x:DataType="models:Address">
<TextBlock x:Name="AddressTextBlock" Text="{Binding city}" />
<DataTemplate>
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ClientAddresses}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Also note your model is not implementing the INotifyPropertyChanged so your UI will not be updated when the model properties change.
Also the convention for back-end private fields is to start with lower case character and for the properties that utilize the INotifyPropertyChanged you should start the property with upper case.
private int myProperty;
public int MyProperty { get { ... } set { ... }}
that's why we create ViewModel and additional property in it
public Address ClientFirstAddress
{
get {return clientaddresses[0].city;}
}
and then Bind it to View,
remember to call NofityPropertyChanged for this property when you set clientaddresses collection

How to bind ComboBox found inside of a nested in DataGrid

I'm attempting to learn WPF and MVVM at the same time, and need some help with a binding. I'm still trying to get my head wrapped around binding controls to properties of a class.
Here is my Malfunctions ViewModel:
public class Malfunctions : ViewModelBase {
public ObservableCollection<Model.PartMalfunction> AllPartMalfunctions {
get;
private set;
}
public ObservableCollection<Model.Part> AllParts {
get;
private set;
}
}
Here is the Model.PartMalfunction.cs:
public class PartMalfunction{
public ObservableCollection<Model.PartSerial> AllPartSerials {
get;
set;
}
}
Here is the Model.Part.cs:
public class Part {
public string Label { get; set; }
public string Value { get; set; }
}
I have a DataGrid that binds to the AllPartMalfunctions ObservableCollection in the Malfunctions ViewModel. This binding is working just great.
I have another DataGrid nested in the RowDetailsTemplate, and it is binding to AllPartSerials in the PartMalfunction model. This binding is working just fine as well.
My problem is with a combobox that is inside of the nested DataGrid. I want to bind this combobox to the AllParts ObservableCollection in the Malfunctions ViewModel. How do I do this?
<DataGrid ItemsSource="{Binding AllPartMalfunctions}" AutoGenerateColumns="False" Width="Auto"
RowDetailsVisibilityMode="Visible">
<DataGrid.Columns>
<!--removed for brevity-->
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding AllPartSerials }" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="cboPart" VerticalAlignment="Center" ItemsSource="{Binding AllParts}" DisplayMemberPath="Label" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
A similar approach has been stuck in my case. I realized one with a different view model structure.
public class PartMalfunction{
public ObservableCollection<Model.PartSerial> AllPartSerials {
get;
set;
}
public ObservableCollection<Model.Part> AllParts {
get { return SomeStaticClass.AllParts; }
}
}
Binding naturally worked even in DataTemplate. Hope this fits to your domain.
Give the element that binds to Malfunctions a Name and bind using a relative path, like shown below in the line containing the ComboBox. I assume for my example that a StackPanel contains the DataGrid and the StackPanel is bound to a Malfunctions property of the container's view model.
<StackPanel x:Name="MalfunctionsGrid" DataContext={Binding Malfunctions}" Orientation="Vertical">
...
<DataGrid ItemsSource="{Binding AllPartMalfunctions}" AutoGenerateColumns="False" Width="Auto"
RowDetailsVisibilityMode="Visible">
...
...
<ComboBox Name="cboPart" VerticalAlignment="Center" ItemsSource="{Binding Path=DataContext.AllParts, ElementName=MalfunctionsGrid}" DisplayMemberPath="Label" />
...
...

what is the difference between DisplayDataMember and ItemTemplate and when to use one over the other?

As mentioned in the title, I want to know the difference between DisplayDataMember and ItemTemplate. I tried to use both of them together and I got a comiler error that both cannot be used at the same time. I also want to know when to use one over the other.
I am a newbie. If this is not a good question to ask then please forgive me.
DisplayMemberPath and ItemTemplate are two ways for representing data.
First one only lets you allow string representation whereas other lets you customise combobox content as per your need (not only string representation). As error states you can't define both at same time.
Suppose you have TestClass with property say Name.
public class TestClass
{
public string Name { get; set; }
}
Now you bind to ItemsSource of your combobox with collection of objects of this class.
Without DisplayMemberPath and ItemTemplate
<ComboBox ItemsSource="{Binding Objects}"/>
With DisplayMemberPath
<ComboBox ItemsSource="{Binding Objects}" DisplayMemberPath="Name"/>
With ItemTemplate
<ComboBox ItemsSource="{Binding Objects}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<Rectangle Margin="15,0,0,0" Fill="Red"
Width="10" Height="10"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I hope images are self explanatory. Let me know if more clarification required.
Also you can achieve DisplayMemberPath functionality by simply overriding ToString() method on your class since internally it calls ToString() on data item.
public class TestClass
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}

How can I use a collection class as a static resource in silverlight

I have a simple class named Customer with 2 properties.
public Name {get;set;}public LastName {get;set}
Then I made a collection class named CustomerList with only one public property named Customers
public class CustomerList
{
public List<Customer> Customers { get; set; }
public CustomerList()
{
Customers = new List<Customer>();
Customers.Add(new Customer() { Name = "Foo", LastName = "Bar" });
Customers.Add(new Customer() { Name = "Foo1", LastName = "Bar1" });
}
}
Now I want to use this class as a static resouce in XAML.
<UserControl.Resources>
<customers:CustomerList x:Key="CustomersKey">
</UserControl.Resources>
and then use it in a ListBox
<ListBox x:Name="lvTemplate" ItemsSource="{Binding Source={StaticResource CustomersKey}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
if I set the ItemsSource in code behide, after instantiating the class, all work fine. If I try to set it from XAML and the static resource Nothing happens. not even if I use the {Binding Path=Customer.Name} or {Binding Path=Name}.
Clearly I miss something...
Since the CustomerList isn't actually the list of items (does not implement IEnumerable), you need to specify what property inside the object you want to use as ItemsSource.
<ListBox ItemsSource="{Binding Path=Customers, Source={StaticResource CustomersKey}}">

How to bind silverlight datagrid combo box itemSource to viewModel

We're using Caliburn.Micro/Silverlight 4 and life is good.
I'm trying to bind a combobox's itemsSource to a viewModel, but this doesn't seem possible since the combobox is already bound to its own row's dataItem. The logic which fills the combo changes with other data on the screen so I can't really use a static list like I've been using.
Is there a way to bind directory to the viewModel somehow??? I've tried element to element binding but this never appears to work within the grid.
<Controls:DataGridTemplateColumn x:Name="FooNameCol" Header="Foo" MinWidth="200">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Foo.ShortName}"
Style="{StaticResource DataGridTextColumnStyle}"/>
</StackPanel>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
MinWidth="200" MinHeight="25"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding Officers, Source={StaticResource ReferenceListRetriever}}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
Within a DataTemplate, the DataContext is bound to each single item of the corresponding list; since all Bindings implicitly refers to DataContext, you have to ensure that the path is valid, starting from the single data item.
In your scenario, for the indicated binding to work, you should have a VM shaped this way:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
public IEnumerable<Officer> Officers {get;}
}
It may seem an overkill, but in some scenarios each combo can actually contain different choices for each data item, based on some business rule.
In simpler cases MyItem can just expose a common list coming from the parent MyVM:
public class MyItem {
...
public IEnumerable<Officer> Officers {
get { return _parent.AvailableOfficers; }
}
}
If you really can't live with it and prefer to keep the available Officers list in the root VM only, you can use a Xaml side trick:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
public IEnumerable<Officer> Officers {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
}
Xaml:
<UserControl ...>
...
<AnyFrameworkElementAtThisLevel Name="bridge" />
...
<Controls:WhateverGrid>
...
<Controls:DataGridTemplateColumn ...>
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
...
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding DataContext.Officers, ElementName=bridge}" />
</DataTemplate>

Resources