How to bind to a property of a property in WPF - wpf

I have a listview who's itemssource is a ObservableCollection of MyModel. I am trying to figure how to bind a textbox text's property to the Name property of the model's Owner property
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
//...
}
public class MyModel
{
public string Title { get; set; }
public Person Owner { get; set; }
//...
}
I tried:
<TextBlock Text="{Binding Owner.Name}" />
but that leaves the textblock blank. Whats the proper syntax?

The binding looks fine. I assume that you put the TextBlock into a DataTemplate and attached this to the ListView. If yes, that should work.
To find the error, replace the Binding through a literal to see if you have some rows (The literal must be shown in every line). If not, check the ItemsSource. If yes, check that you have really a Person-object attached to your MyModel-instances and that the Name-property is not null or empty. Check also the output window of VS. There you will see binding-errormessages.
If you have no DataTemplate, here an example:
<ListView ItemsSource="[Your ItemsSource]">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Owner.Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Check that the DataContext is set properly and implement INotifyPropertyChanged (raise the event it defines when the value of the property changes.).

Try to use Data Source Wizard form VS 2010
I just add these classes and click, clik, clik
<TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="nameTextBox" Text="{Binding Path=Owner.Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />

<Slider Margin="81,161.66,66,0" Name="Height" Minimum="0" Maximum="10" Width="400"></Slider>
<Slider Margin="81,1.66,66,0" Name="Width" Minimum="0" Maximum="10" Width="400"/>
<Ellipse Height="{Binding ElementName=Height}" Width="{Binding ElementName=Width}" Fill="Blue" ></Ellipse>

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

Are data templates obsolete in MVVM?

I have created the following model (the code is simplified to illustrate the situation):
public abstract class Account
{
public string Name { get; set; }
}
public class Person : Account
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Company : Account
{
public string Owner { get; set; }
}
Next I have created a view model:
public class ViewModel
{
public Account Model { ... }
public string Name { ... }
public string FirstName { ... }
public string LastName { ... }
public string Owner { ... }
...
}
And finally, the view:
<UserControl>
<UserControl.Resources>
<!-- Person data template -->
<DataTemplate x:Key="personTemplate" DataType="{x:Type model:Person}">
<Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
</Grid>
</DataTemplate>
<!-- Company data template -->
<DataTemplate x:Key="companyTemplate" DataType="{x:Type model:Company}">
<Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Owner}" />
</Grid>
</DataTemplate>
<!-- Data template selector for different account types -->
<local:AccountTemplateSelector x:Key="templateSelector"
PersonTemplate="{StaticResource personTemplate}"
CompanyTemplate="{StaticResource companyTemplate}" />
</UserControl.Resources>
<StackPanel Name="rootLayout" DataContext="{Binding Path=viewModel}">
<ContentControl Content="{Binding Path=Model}"
ContentTemplateSelector="{StaticResource templateSelector}"/>
<Button Content="Save" />
<Button Content="Close" />
</StackPanel>
</UserControl>
So, when the model that is loaded is of type Person the personTemplate is shown; vice versa, when the model is Company the companyTemplate is shown.
My questions are:
Does this approach make sense at all? Would it be smarter to delete the Model
property in the ViewModel class and to introduce an enum or just a simple bool
which would show person if true, or company if `false?
While defining the data templates, I specified DataTypes to Person and Company
types (it was natural to me to do it this way). Do I need it at all because in the very
next line I am setting a new data context to be the one from the UserControl?
Should the DataTypes of the data templates be different view models, something like
PersonViewModel and CompanyViewModel? Does it make sense to create them?
How can I, and can I at all, make data template inherit the data context from the
ContentControl automatically?
I know that all this is a matter of a personal choice in the end, but since I am learning MVVM (I am using MVVM Light), I am wondering which approach would be the most recommendable one? I still do not fully understand when should the classes from models be used as data types for data templates and when should view models be used for that purpose. Should the assembly that represents the model even be referenced in the view assembly (assuming that view, model and view model all reside in separate assemblies)?
Thanks for all the clarifications!
UPDATE:
This update should explain the problem of having classes of the model as DataTypes in the data templates when the property of the model class is not directly binded to just one control in the view.
There is an enum and a new property in the Person, so now it looks like this:
public class Person : Account
{
public enum GenderType { Female, Male, NotSpecified }
public string FirstName { get; set; }
public string LastName { get; set; }
public GenderType Gender {get; set; }
}
And in the view, the data template of the person is changed as well of course:
<!-- Person data template -->
<DataTemplate x:Key="personTemplate" DataType="{x:Type model:Person}">
<Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
<RadioButton Name="Female" />
<RadioButton Name="Male" />
<RadioButton Name="NotSpecified" />
</Grid>
</DataTemplate>
If the Content of the ContentControl is set to Model property of the ViewModel, how would I resolve the gender/radio buttons situation; because, now they do not match in the way one control/one property?
I would change it to this:
<UserControl>
<UserControl.Resources>
<!-- Person data template -->
<DataTemplate DataType="{x:Type model:Person}">
<Grid>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
<RadioButton Name="Female" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=Female}" />
<RadioButton Name="Male" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=Male}" />
<RadioButton Name="NotSpecified" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=NotSpecified }" />
</Grid>
</DataTemplate>
<!-- Company data template -->
<DataTemplate DataType="{x:Type model:Company}">
<Grid>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Owner}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<StackPanel DataContext="{Binding viewModel}">
<ContentControl Content="{Binding Model}" />
<Button Content="Save" />
<Button Content="Save" />
<Button Content="Close" />
</StackPanel>
</UserControl>
like this you define implicit styles for your classes and you don't have to use a templateselector. Also then you don't need all your string properties in the ViewModel class:
public class ViewModel
{
public Account Model { ... }
...
}
Disclaimer, the binding in the RadioButtons uses a Converter from here.
Absolutely positively not (are they obsolete).
Yes, it makes perfect sense, however your bindings, not so much. Those issues can be handled in a number of different ways. For instance, the Account could have a Parent property which exposes the ViewModel it is contained in (I didn't say it was the best approach).
This is an issue with your ViewModel design, where you must bind not against the Account but the ViewModel. It might be possible to change the design so that you don't need to do this; hard to tell with the snapshot you have provided.
I don't think that will help you at this point. I'd see if there was a way to keep your ViewModel related UI out of the DataTemplate.
Here's one good solution: Create a DataTemplateSelector that chooses the template based on the Account property. That way, you can bind the ItemsSource directly to the DataContext, and it will be available within the DataTemplate.

Does StringFormat work with this.DataContext

I am binding a TextBlock with a collection in code-behind via this.DataContext = SellerList;
The output is correct but when i apply StringFormat, i see no result. Following is the code for TextBlock on xaml page
<TextBlock Name="dateDTKey" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Text="{Binding Path=Date, StringFormat={}{0:dd-MM-yyyy}}"
Style="{StaticResource textStyleTextBlock}"/>
The source for the Binding is a string, if detailsSellerListingTemplate is a resource you should use {StaticResource detailsSellerListingTemplate}. Also, the TextBlock doesn't need a DataContext for this Binding to work since it's using Source.
<Window.Resources>
<local:DetailsSeller x:Key="detailsSellerListingTemplate"/>
</Window.Resources>
<TextBlock Name="dateDTKey"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="{Binding Source={StaticResource detailsSellerListingTemplate},
Path=Date,
StringFormat={}{0:dd-MM-yyyy}}"/>
This will work if DetailsSeller looks similar to this
public class DetailsSeller
{
public DetailsSeller()
{
Date = DateTime.Now;
}
public DateTime Date
{
get;
set;
}
}
You talked about a collection but I can't see how that fits with the binding, so maybe I missunderstood something in the question
I am thinking its because you have way to many braces in your string format. try this:
StringFormat={0:dd-MM-yyyy}

How to get TextBox inside DataTemplate in a ListBox to notify the ViewModel on value change

What I need to find is when a textbox's value is changing or the dropdown's value changes inside my datatemplate item, I need to be notified in my ViewModel.cs.
So basically as a user edits a textbox inside the listbox, the viewmodel will be notified as the values are changing.
The reason is I need to go through all my Entries and update something as items inside the listbox's datatemplate change.
Any suggetion?
I have the following in my XAML.
<ListBox x:Name="EntriesListBox"
ItemsSource="{Binding Path=Entries}"
Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="EntriesPropertyName"
Width="215"
Margin="0,0,5,0"
SelectedItem="{Binding Path=Property, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource DataContextProxy},Path=DataSource.EntityTypeProperties}" />
<TextBox x:Name="EntriesPropertyValue"
Width="215"
Margin="0,0,5,0"
Text="{Binding Path=Value, Mode=TwoWay, BindsDirectlyToSource=True}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The following is in my VM (ViewModel.cs)
public ObservableCollection<Entry> Entries { get; set; }
The following is in my business object (Entry.cs)
public class Entry
{
public PropertyItem Property { get; set; }
public string Value { get; set; }
}
On your binding, set the UpdateSourceTrigger... Also implement INotifyPropertyChanged
Provided that you have setup your view model class properly (by implementing INotifyPropertyChanged), following is what you may want to do:
<TextBox x:Name="EntriesPropertyValue"
Width="215"
Margin="0,0,5,0"
Text="{Binding Path=Value, Mode=TwoWay, BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged}" />
This seems to work. Any reason not to do it this way?
private void EntriesPropertyValue_TextChanged(object sender, TextChangedEventArgs e)
{
(sender as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
this.ViewModel.UpdateFinalQuery();
}

WPF binding to Listbox selectedItem

Can anyone help with the following - been playing about with this but can't for the life of me get it to work.
I've got a view model which contains the following properties;
public ObservableCollection<Rule> Rules { get; set; }
public Rule SelectedRule { get; set; }
In my XAML I've got;
<ListBox x:Name="lbRules" ItemsSource="{Binding Path=Rules}"
SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox x:Name="ruleName">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Now the ItemsSource works fine and I get a list of Rule objects with their names displayed in lbRules.
Trouble I am having is binding the SelectedRule property to lbRules' SelectedItem. I tried binding a textblock's text property to SelectedRule but it is always null.
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
The error I'm seeing in the output window is:
BindingExpression path error: 'SelectedRule' property not found.
Can anyone help me with this binding - I can't see why it shouldn't find the SelectedRule property.
I then tried changing the textblock's text property as bellow, which works. Trouble is I want to use the SelectedRule in my ViewModel.
<TextBlock Text="{Binding ElementName=lbRules, Path=SelectedItem.Name}" />
Thanks very much for your help.
First off, you need to implement INotifyPropertyChanged interface in your view model and raise the PropertyChanged event in the setter of the Rule property. Otherwise no control that binds to the SelectedRule property will "know" when it has been changed.
Then, your XAML
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
is perfectly valid if this TextBlock is outside the ListBox's ItemTemplate and has the same DataContext as the ListBox.
Inside the DataTemplate you're working in the context of a Rule, that's why you cannot bind to SelectedRule.Name -- there is no such property on a Rule.
To bind to the original data context (which is your ViewModel) you can write:
<TextBlock Text="{Binding ElementName=lbRules, Path=DataContext.SelectedRule.Name}" />
UPDATE: regarding the SelectedItem property binding, it looks perfectly valid, I tried the same on my machine and it works fine. Here is my full test app:
XAML:
<Window x:Class="TestWpfApplication.ListBoxSelectedItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSelectedItem" Height="300" Width="300"
xmlns:app="clr-namespace:TestWpfApplication">
<Window.DataContext>
<app:ListBoxSelectedItemViewModel/>
</Window.DataContext>
<ListBox ItemsSource="{Binding Path=Rules}" SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Code behind:
namespace TestWpfApplication
{
/// <summary>
/// Interaction logic for ListBoxSelectedItem.xaml
/// </summary>
public partial class ListBoxSelectedItem : Window
{
public ListBoxSelectedItem()
{
InitializeComponent();
}
}
public class Rule
{
public string Name { get; set; }
}
public class ListBoxSelectedItemViewModel
{
public ListBoxSelectedItemViewModel()
{
Rules = new ObservableCollection<Rule>()
{
new Rule() { Name = "Rule 1"},
new Rule() { Name = "Rule 2"},
new Rule() { Name = "Rule 3"},
};
}
public ObservableCollection<Rule> Rules { get; private set; }
private Rule selectedRule;
public Rule SelectedRule
{
get { return selectedRule; }
set
{
selectedRule = value;
}
}
}
}
Yocoder is right,
Inside the DataTemplate, your DataContext is set to the Rule its currently handling..
To access the parents DataContext, you can also consider using a RelativeSource in your binding:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ____Your Parent control here___ }}, Path=DataContext.SelectedRule.Name}" />
More info on RelativeSource can be found here:
http://msdn.microsoft.com/en-us/library/system.windows.data.relativesource.aspx
For me, I usually use DataContext together in order to bind two-depth property such as this question.
<TextBlock DataContext="{Binding SelectedRule}" Text="{Binding Name}" />
Or, I prefer to use ElementName because it achieves bindings only with view controls.
<TextBlock DataContext="{Binding ElementName=lbRules, Path=SelectedItem}" Text="{Binding Name}" />
There is a shorter version to bind to a selected item's property:
<TextBlock Text="{Binding Rules/Name}" />
since you set your itemsource to your collection, your textbox is tied to each individual item in that collection. the selected item property is useful in this scenario if you were trying to do a master-detail form, having 2 listboxes. you would bind the second listbox's itemsource to the child collection of rules. in otherwords the selected item alerts outside controls that your source has changed, internal controls(those inside your datatemplate already are aware of the change.
and to answer your question yes in most circumstances setting the itemsource is the same as setting the datacontext of the control.

Resources