Silverlight / WPF Binding Nested Objects - wpf

Instance of MyViewModel is set as DataContext of the .Xaml file. Data is bound as below, but the Text for {FName1, FName2, LName1, LName2} are not getting displayed. Only the Text for (ThePerson) is getting displayed. Appreciate if any one have a suggestion to fix it.
.xaml file
<StackPanel>
<TextBlock x:Name="ThePerson" Text="{Binding PersonOne}" />
<TextBlock x:Name="FName1" Text="{Binding PersonOne.FirstName}" />
<TextBlock x:Name="FName2" Text="{Binding Person.FirstName}" />
<TextBlock x:Name="LName1" Text="{Binding Path=PersonOne.LastName}" />
<TextBlock x:Name="LName2" Text="{Binding Path=Person.LastName}" />
</StackPanel>
ViewModel file
public class MyViewModel {
public MyViewModel()
{
PersonOne = new Person()
{
FirstName = "James",
LastName = "San"
};
}
public Person PersonOne { get; set; }
}
public class Person {
public string FirstName;
public string LastName;
public override string ToString()
{
return string.Format("{0}, {1}", LastName, FirstName);
}
}

You need to make the FirstName, etc as properties of the Person class instead of fields. Add a getter and setter to them. You can only bind to properties.

Related

WPF Binding property of an Object contained within Another Object contained in a List

I have a ListBox showing some information about Cars. So, I have ViewModel with:
private ObservableCollection<Car> _cars;
public ObservableCollection<Car> CarsList
{
get
{
return _cars;
}
set
{
_cars= value;
OnPropertyChanged("CarsList");
}
}
I have implemented PropertyChanged, and I get my list of cars just fine. I load my CarsList from within my ViewModel (not from Window).
My Car object has Name, Country, Url and Image object like below:
public class Car
{
public string Name { get; set; }
public string Country { get; set; }
public Image Image { get; set; }
}
public class Image
{
public string Url { get; set; }
}
I need to bind Name, Country and Image.Url to text boxes in my ListBox. I did this like:
<ListBox Name="lbEvents" ItemsSource="{Binding Path=CarsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<StackPanel>
<TextBlock Name="txtCarName" Text="{Binding Path=Name}" />
<TextBlock Name="txtCarCountry" Text="{Binding Path=Country}"/>
<!-- How do I bind this properly? -->
<Image Name="imgCarImage" Source="{Binding Path=Car.Image.Url}" />
</StackPanel>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Problem is that my Car Name and Country are properly displayed in ListBox for each Car. But Car.Image.Url shows nothing. I am new to WPF, how do I bind properly the Image.Url to the ImageBox?
Just as Name and Country are properties of the Car class, so is Image. Look at the way you are binding to Name and Country, you don't specify it as Car.Name, so why do that for Image? Just do it as Image instead of Car.Image.

Binding to collection in viewmodel with IsSynchronizedWithCurrentItem

I have a grid displaying the contents of an observableCollection of Persons, and two textboxes showing the properties of the selected row. A master-detail view if you will.
When assigning the observablecollection to the datacontext you can simply do this:
<Grid>
<Grid Background="Gray">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<igWPF:XamDataGrid Grid.Row="0" DataSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBox Height="21" Width="100" Margin="5,0,5,0" Text="{Binding Name}"></TextBox>
<TextBox Height="21" Width="100" Text="{Binding Age}"></TextBox>
</StackPanel>
</Grid>
</Grid>
The IsSynchronizedWithCurrentItem-property makes sure the selected item in the grid is the one that is treated in the textboxes.
I was wondering if it is possible to do this exact thing when the observablecollection is not in the datacontext directly, but rather is located in a viewmodel (which is assigned to the datacontext of the window).
public class TestViewModel: DependencyObject
{
public TestViewModel(){
Results = new ObservableCollection<Person>();
Results.Add(new Person { Age = "23", Name = "Able" });
Results.Add(new Person { Age = "25", Name = "Baker" });
}
public ObservableCollection<TagDlgmtEntity> Results
{
get { return (ObservableCollection<Person>)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
public static readonly DependencyProperty ResultsProperty =
DependencyProperty.Register("Results", typeof(ObservableCollection<Person>), typeof(TestViewModel), new PropertyMetadata(null));
}
I would like not to reassign a datacontext on a lower level in the visual tree, or bind with the selectedItem property of the grid.
Is it possible to use this mechanism this way?
Thanks!
Yes, that's possible. Declare your binding like this:
DataSource="{Binding Results}"
Example ViewModel
Assumption: ViewModelBase implements INotifyPropertyChanged (see my comment on the question)
public class MainWindowViewModel : ViewModelBase
{
private readonly List<Person> _results;
public MainWindowViewModel()
{
_results = new List<Person>
{
new Person {Age = "23", Name = "Able"},
new Person {Age = "25", Name = "Baker"}
};
ResultsView = CollectionViewSource.GetDefaultView(_results);
ResultsView.CurrentChanged += (sender, args) => RaisePropertyChanged(() => Name);
}
public ICollectionView ResultsView { get; private set; }
public string Name
{
get
{
return ((Person) ResultsView.CurrentItem).Name;
}
}
}

WPF IDataErrorInfo process error array

I´m using IDataErrorInfo for validation in WPF, But when the attribute is an array I can to process the error. ( in this case int[] Position)
My code is similar to this: http://codeblitz.wordpress.com/2009/05/08/wpf-validation-made-easy-with-idataerrorinfo/
public class Customer : IDataErrorInfo
{
public string FirstName { get; set; }
public int[] Position { get; set; }
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columName == "FirstName")
{
// Do something
}
if (columnName == "Position")
{
// Do something
}
return result;
}
}
#endregion
}
XAML
<TextBox x:Name="tbFirstName" Grid.Row="2" Grid.Column="1" Width="50" HorizontalAlignment="left"
Validation.Error="Validation_Error" MaxLength="2"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=FirstName, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
<TextBox x:Name="tbPosition1" Grid.Row="2" Grid.Column="1" Width="50" HorizontalAlignment="left"
Validation.Error="Validation_Error" MaxLength="2"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=Position[0], ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />
I have no problem to capture "Firstname" but if I do a change in the textbox of btPosition1 the program don´t through the function to process the errors.
You bind to an item in array, not to array itself.
In this case, I think, WPF looks for IDataErrorInfo in Array class.
So you should either expose positions as separate properties (if they are finite), or use your own collection class: inherit List<> and add IDataErrorInfo implementation.

ListBox DataTemplate for dynamically loaded type

I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.

Binding data to ComboBox WPF

I am newbie to WPF, and needs help to bind data into the ComboBox. The xaml file contains the tag below.
<UserControl x:Class="SKAT.Postfordeler.Client.UI.View.ChooseInboxView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="42" d:DesignWidth="598">
<Grid>
<StackPanel Orientation="Horizontal">
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" DataContext="{Binding}" />
<Label Content="Et job kører allerede i denne indbakke (1500 ud af 1700 poster behandlet)" Name="_currentProcess" Margin="5" Height="25" />
</StackPanel>
</Grid>
//Inbox class , this class was implemented in seperate project
namespace SKAT.Postfordeler.Shared.DataTypes
{
[DataContract]
public class Inbox
{
[DataMember]
public String Id { get; set; }
[DataMember]
public String Folder { get; set; }
[DataMember]
public Rule Rules { get; set; }
}
}
//This code is located in the controller, the Activate method will fire when the MainWindow was executed
public void Activate()
{
var configuration = _configurationManager.GetConfiguration();// this method gets the xaml file settings
_chooseInboxView.FillInboxes(configuration.Inboxes); // Inboxes data binds to combobox
}
and in the View code behind, I created a method to bind the data which contains a type of list
public void FillInboxes(List<Inbox> inboxes)
{
DataContext = inboxes;
}
But it won't works,Any help please?
I assume your Inbox class consists of two properties (for simplicity), but there may be any number of them:
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
You write a DataTemplate, for example:
<Grid.Resources>
<DataTemplate x:Key="InboxTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ID}"/>
<TextBlock>:</TextBlock>
<TextBlock Text="{Binding Path=Text}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
Then correct your ComboBox declaration like:
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" ItemsSource="{Binding}" ItemTemplate="{StaticResource InboxTemplate}" />
Finally you set DataContext of your ComboBox to your List<Inbox>:
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DataContext = inboxes;
}
EDIT: As you've asked for a simpler solution, you can just override ToString() method of your Inbox class:
protected override string ToString()
{
return ID.ToString() + ":" + Text;
}
Instead of DataContext={Binding} you should have ItemsSource={Binding}.
The data context for any frameworkelement in the visual tree is by default {Binding}.
<ComboBox Name="_currentInbox"
SelectedItem="Hoved"
Width="180"
Margin="5"
Height="22"
DisplayMemberPath="Name"
ItemSource="{Binding}" />
Also for the combobox to display text of the items correctly I suppose you need DisplayMemberPath too. I assumed the property from Inbox class that you need to display is Name. Please replace with your relevant property name.
If your Inbox class is like,
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
And if you do not want to change your xmal, the code behind method should be like this,
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DisplayMemberPath = "Text"; // To display the 'Text' property in the combobox dropdown
//_currentInbox.DisplayMemberPath = "ID"; // To display the 'ID' property in the combobox dropdown
_currentInbox.DataContext = inboxes;
}

Resources