Update binding when nested object changes - wpf

I have a some XAML as follows (a simple Label and Button):
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Test="clr-namespace:WpfApplication2">
<StackPanel DataContext="{Binding Path=TestPerson}">
<Label Content="{Binding Path=Name}"></Label>
<Button Content="Button" Click="button1_Click" />
</StackPanel>
</Window>
in the code behind I have:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Person _person = new Person();
public Person TestPerson { get { return _person; } }
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_person.Name = "Bill";
//_person = new Person() { Name = "Bill" };
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TestPerson"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and the class Person is:
public class Person
{
string _name = "Bob";
public string Name
{
get { return _name; }
set { _name = value; }
}
}
As it is, firing the Propertychanged event does not cause the Label's contents to change to Bill.
I've found I am able to overcome this by either:
Assigning a new object to _person (as in the commented out line)
Removing the DataContext from the StackPanel and have Label bind to Path=TestPerson.Name
I don't understand why I have to actually assign a NEW object to _person for the Label to update, or use the FULL path in the binding.
Is there a way to update the Label without supplying the full path (relying on the DataContext), and without assigning a new object to _person?

You raise PropertyChanged for the Person instance TestPerson. However, TestPerson hasn't changed, it is the Name property of TestPerson that has changed and that is the property the Label is binding to.
Edit: To answer why your first two versions work
Assigning a new object to _person (as in the commented out line)
Here you are actually changing the value of TestPerson and because DataContext is inherited by the children, the Label gets a new DataContext as well so that's why the Binding is updated.
Removing the DataContext from the StackPanel and have Label bind to
Path=TestPerson.Name
This is something I've never seen. The same binding subscribes to PropertyChanged for both TestPerson and Name in Person so raising PropertyChanged for any of these properties will work.
If you want to overcome this without implementing INotifyPropertyChanged for Person, you can change set UpdateSourceTrigger to Explicit
<Label Name="label"
Content="{Binding Path=Name, UpdateSourceTrigger=Explicit}"/>
And update the Binding manually whenever Name changes
private void button1_Click(object sender, RoutedEventArgs e)
{
_person.Name = "Bill";
BindingExpression be = label.GetBindingExpression(Label.ContentProperty);
be.UpdateTarget();
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TestPerson"));
}
}
Otherwise, just implement INotifyPropertyChanged for Person as well and it will work
public class Person : INotifyPropertyChanged
{
string _name = "Bob";
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

You need a little change in your XAML...
In your code behind, instead of setting DataContext as this, set it in XAML via Binding...
<StackPanel DataContext="{Binding RelativeSource={RelativeSource
AncestorType= {x:Type Window},
Mode=FindAncestor},
Path=TestPerson}">
<Label Content="{Binding Path=Name}"></Label>
<Button Content="Button" Click="button1_Click" />
</StackPanel>
Remove the
DataContext = this;
from your code behind.
Let me know if this helps.

Related

WPF - Binding Textbox Text to a class property

I am doing some changes in my WPF project to make it less deprecated.
One of the things I am trying to do is Binding my Textbox.Text value to a simple Class as shown below.
<TextBox x:Name="txtNCM"
Grid.Column="1"
Margin="5"
MaxLength="8"
Text="{Binding Path=Name}"
</TextBox>
public partial class wCad_NCM : UserControl, INotifyPropertyChanged
{
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public wCad_NCM()
{
InitializeComponent();
}
}
Everytime I use the Immediate Window to display the Name's value, it is shown as null. I am really new to this, so I had to search for a similar situation to adapt, but I don't know how to make this work :(
You need to set the DataContext and give Name a value.
To do that, change your constructor to include this:
public wCad_NCM()
{
InitializeComponent();
DataContext = this; // Sets the DataContext
Name = "Test";
}
This should make it work, but is typically bad practice. See http://blog.scottlogic.com/2012/02/06/a-simple-pattern-for-creating-re-useable-usercontrols-in-wpf-silverlight.html for more details.
Additionally, I tried running this and ran into a name hiding problem. Try using a variable name other than Name as FrameworkElement already contains it.

WPF: bind to the first item in the sorted ListView

I have a ListView with items, that contain string field Name among others. Items in the ListView are sorted by this field:
SortDescription descr = new SortDescription("Name", ListSortDirection.Ascending);
list.Items.SortDescriptions.Add(descr);
I also have a TextBlock in which I want to display the Name of the first item in the sorted ListView. Items can be added, removed and edited at runtime, so I would like to use some kind of binding like the following one (doesn't work, just for example):
<TextBlock Text="{Binding ElementName=list, Path=Items[0].Name}"/>
1) How can I achieve the desired behavior using a binding?
2) If such binding can't be created, which is the most convenient way to succeed?
Any thoughts and hints would be appreciated.
UPDATE
Content of the main window:
<StackPanel>
<TextBlock Name="nameFirst" Text="{Binding ElementName=list, Path=Items[0].Name}"/>
<ListView Name="list" ItemsSource="{Binding ElementName=mainWnd, Path=List}" DisplayMemberPath="Name" Loaded="list_Loaded"/>
</StackPanel>
Code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Item(string name)
{
Name = name;
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public ObservableCollection<Item> _list;
public ObservableCollection<Item> List
{
get { return _list; }
set
{
_list = value;
OnPropertyChanged("List");
}
}
public MainWindow()
{
InitializeComponent();
List = new ObservableCollection<Item>();
List.Add(new Item("1"));
List.Add(new Item("2"));
List.Add(new Item("3"));
}
private void list_Loaded(object sender, RoutedEventArgs e)
{
SortDescription descr = new SortDescription("Name", ListSortDirection.Descending);
list.Items.SortDescriptions.Add(descr);
}
}
When the application is started, items in the ListView are sorted in the descending order: "3", "2", "1", but nameFirst TextBox still displays "1", though now it should display "3".
Actually, what you wrote should do the work. The problem is probably not in the xaml.
You said the items contains a field Name. WPF can only bind to public properties make sure to use them, and not public members. It's also possible you're seeing old values if you changed them after the window was opened. Make sure your items implement the INotifyPropertyChanged interface, and the properties invoke OnPropertyChanged.
If you're still having problems, please post more of your code, specifically your data items class and the place where you set the SortDescriptor.
Lastly, though it's not what you asked about. You can bind to the current item in an array by using the / operator. To use your code:
<TextBlock Text="{Binding ElementName=list, Path=Items/Name}"/>
You control the "current" item using CollectionViews and the IsSynchronizedWithCurrentItem property.

Binding not working in Checkbox isChecked WPF

I have a xaml file named MyWindow.xaml, and this xaml has a checkbox declared as..
<CheckBox Name="chkView" IsChecked="{Binding Path=IsChkChecked, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Checked="chkView_Checked" Unchecked="chkView_Checked" />
In MyWindow.xaml.cs,
public partial class MyWindow: UserControl,INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
}
private bool isChkChecked;
public bool IsChkChecked
{
get { return isChkChecked; }
set
{
isChkChecked= value;
OnPropertyChanged("IsChkChecked");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Now, Iam trying to access this property from another class and change the property, but the checkbox is not getting binded to bool property.
MyLib.MyWindow wnd;
wnd= (MyLib.MyWindow)theTabItem.Content;
wnd.IsChkChecked = true;
Any suggestions would be appreciated.
Your view doesn't bind to IsChkChecked as it doesn't live in the DataContext. Usually you would declare a ViewModel with the property and declare the DataContext to be an instance of this ViewModel. A quick fix would be to change the constructor of the view to set the DataContext to the View itself or change the Binding (as dkozl suggested):
public MyWindow()
{
InitializeComponent();
this.DataContext = this;
}
If you don't specify another binding source by default it will search in DataContext and I cannot see that you set it anywhere. One way is to set RelativeSource against binding to point to Window that publishes IsChkChecked property
<CheckBox Name="chkView" IsChecked="{Binding Path=IsChkChecked, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>

WPF Simple Binding to INotifyPropertyChanged Object

I've created the simplest binding. A textbox bound to an object in the code behind.
Event though - the textbox remains empty.
The window's DataContext is set, and the binding path is present.
Can you say what's wrong?
XAML
<Window x:Class="Anecdotes.SimpleBinding"
x:Name="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SimpleBinding" Height="300" Width="300" DataContext="MainWindow">
<Grid>
<TextBox Text="{Binding Path=BookName, ElementName=TheBook}" />
</Grid>
</Window>
Code behind
public partial class SimpleBinding : Window
{
public Book TheBook;
public SimpleBinding()
{
TheBook = new Book() { BookName = "The Mythical Man Month" };
InitializeComponent();
}
}
The book object
public class Book : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private string bookName;
public string BookName
{
get { return bookName; }
set
{
if (bookName != value)
{
bookName = value;
OnPropertyChanged("BookName");
}
}
}
}
First of all remove DataContext="MainWindow" as this sets DataContext of a Window to a string MainWindow, then you specify ElementName for your binding which defines binding source as another control with x:Name="TheBook" which does not exist in your Window. You can make your code work by removing ElementName=TheBook from your binding and either by assigning DataContext, which is default source if none is specified, of a Window to TheBook
public SimpleBinding()
{
...
this.DataContext = TheBook;
}
or by specifying RelativeSource of your binding to the Window which exposes TheBook:
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=TheBook.BookName}"/>
but since you cannot bind to fields you will need to convert TheBook into property:
public partial class SimpleBinding : Window
{
public Book TheBook { get; set; }
...
}

Binding is not working with dynamic data updates

I'm new in WPF and I have the following problem.
I have the following class with many properties , but here is only one property for example:
public class StatusData : INotifyPropertyChanged
{
private string m_statusText = String.Empty;
public StatusData()
{
m_statusText = "1234";
}
public string StatusText
{
get
{
return m_statusText;
}
set
{
if (m_statusText != value)
{
m_statusText = value;
NotifyPropertyChanged("StatusText");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Another component of the project changes StatusData and calls Update() function in MainWindow.
So, m_statusData of this MainWindow has changed and I want update the textbox with m_statusText accordingly.
public class MainWindow
{
private StatusData m_statusData = new StatusData();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
grid1.DataContext = m_statusData ;
}
public void Update(StatusData newStatusData)
{
m_statusData = newStatusData;
}
}
Xaml code:
<Window x:Class="WpfApplicationUpdateTextBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWin"
xmlns:local="clr-namespace:WpfApplicationUpdateTextBox"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" >
<Grid Name="grid1">
<TextBox Text="{Binding Path=StatusText}" Name="textBox1" />
</Grid>
</Window>
The question is : why the textBox is not updated withnewStatusData.StatusText?
Here, you are assigning the grid's DataContext to m_statusData:
grid1.DataContext = m_statusData ;
And here, you are reassigning m_statusData to something else:
m_statusData = newStatusData;
The problem is that this has no effect on grid1.DataContext, which was set to the previous instance of m_statusData.
In this case, doing grid1.DataContext = newStatusData should solve your problem. However, a better solution would be to create a StatusData property which returns m_statusData. You can then do a RaisePropertyChanged() on it when m_statusData changes.
private void Update(StatusData newStatusData)
{
StatusData = newStatusData;
}
public StatusData StatusData
{
get
{
return m_statusData;
}
set
{
m_statusData = value;
RaisePropertyChanged("StatusData");
}
}
... and then in your XAML, bind your Grid's DataContext to the StatusData property
Edit:
To bind the grid's data context to the StatusData property, you can do this in your XAML:
<Grid Name="grid1" DataContext="{Binding StatusData}">
<TextBox Text="{Binding Path=StatusText}" Name="textBox1" />
</Grid>
You will also need to set the initial DataContext of your window, to make all of the other databindings work (this is a little strange and non-standard, but it will do the trick):
this.DataContext = this;
Your class StatusData has only 1 property: StatusText. If this property is the only thing you want to change with this code:
m_statusData = newStatusData;
You can change it to this:
m_statusData.StatusText = newStatusData.StatusText;
This code will fire the PropertyChangedEvent of StatusData class and that will change TextBox value.

Resources