We have a client-server application which has the requirement of building the View dynamically. The server will send the XAML string together with the data (Dctionary< string, string>) to the client, which will then build the View from received Xaml string and bind the data to the View.
Here is the sample XAML string:
<StackPanel>
<TextBox>
<TextBox.Text>
<Binding RelativeSource="{{RelativeSource Self}}" Path="DataContext"
Converter="{{StaticResource fieldBindingConverter}}" ConverterParameter="ID_Id"
UpdateSourceTrigger="PropertyChanged">
</Binding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<Binding RelativeSource="{{RelativeSource Self}}" Path="DataContext"
Converter="{{StaticResource fieldBindingConverter}}" ConverterParameter="ID_Name"
UpdateSourceTrigger="PropertyChanged">
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
The data would look like:
new Dictionary<string, string>
{
{"ID_Id", "1"},
{"ID_Name", "John"}
};
The client will build the View by using XamlReader.Load(), and create a Window to host it as the Content. The client also assigns the received data to Window.DataContext
window.DataContext = dictionaryData;
As the two TextBox's inherit the DataContext from Window, the Text property binds to the Dictionary.
The binding converter "fieldBindingConverter" fetches the correct value out of the dictionary by using ConverterParameter which has the key.
So the Two TextBox's will disply "1" and "John" correspondingly when the View is first built.
The problem arises when a new data arrives at client side
new Dictionary<string, string>
{
{"ID_Id", "2"},
{"ID_Name", "Peter"}
};
By resetting the DataContext of the hosting Window will not make the binding on the TextBox refresh itself
window.DataContext = newDictionaryData;
In fact the DataContext of the TextBox still cache the old data value.
It seems that TextBox only takes a copy of its parent DataContext when it's first initialized, and then only works with that local copy afterwards.
It also appears that it not easy to have a ViewModel and implement INotifyPropertyChanged in this scenario, as the key "ID_XX" can vary among different Views and it;s hard to have a Model class defined for this dynamic nature (I could be wrong).
It does work correctly if a new hosting Window is created (and DataContext is set) every time a new data arrives, because the DataContext of all TextBox's will have the new data derived for the new hosting window.
Does anyone know how to get the TextBox "refresh" its DataContext to take the new one set on the parent Window and "refresh" the binding?
Best Answer
In WPF, we don't generally set the
DataContext
of aWindow
to one data type object like this... however it is possible. Instead, we normally create a specific class that contains all of the properties required to be displayed and as you alluded to, implements theINotifyPropertyChanged
interface. In your case, we would have a property of typeStaff
that we could bind to in the UI:Then in XAML:
In this small example, this is not strictly necessary, but in larger projects, it is likely that there would be other data to display as well. If you set the
Window.DataContext
to just one instance of yourStaff
data type, then you would find it tricky to display other data, such as a collection ofStaff
objects. Likewise, it is better to update theStaff
property, which will inform the interface to update the UI, rather than theDataContext
which will not update the UI as it is not 'connected' to the interface.It is the notification of property changes through the
INotifyPropertyChanged
interface that updates, or 'refreshes as you call it, the values in the UI controls when property changes are made. So your answer is to implement this interface and make sure that you call theINotifyPropertyChanged.PropertyChangedEventHandler
when property value changes are made.UPDATE >>>
Wow! You really aren't listening, are you? In WPF, we don't 'refresh' the
DataContext
, theINotifyPropertyChanged
interface 'refreshes' the UI once properties have been changed. This is one possible way that you could fix this problem:You could then fill your
CustomObject
like this:Now you could argue that you can't do this for this reason, or you don't want to do that for that reason, but at the end of the day, you have to do something like this to solve your problem.