
When developing
DXScheduler for WPF, we received a script from the user that used the
MVVM template.
The user object was assigned to the
DataContext property of our scheduler, and in the XAML markup we “bound” to the corresponding properties of the object using
Binding expressions .
But there was a problem - the scheduler contained some non-visual Storage object that stored a set of settings for the data. In the form in which the Binding expressions were written, the properties of the object stack were not updated.
How this problem was solved, you will find out below ...
The article presents a simplified solution that demonstrates the scenario described above. Therefore, I will not complicate the above code with full implementations of the MVVM template, pile up INotifyPropertyChanged interfaces, etc. Our task is that the example as simply as possible reflects the essence of the issue.
So, let's start with the visual control, which will be a representation of the model.
')
View class
A visual control that contains a property for a nested DataStore object. It will be created and assigned in XAML.
public class SomeVisualControl : Control { public static readonly DependencyProperty InnerDataStoreProperty = DependencyProperty.Register("InnerDataStore", typeof(DataStore), typeof(SomeVisualControl), new PropertyMetadata(null)); public DataStore InnerDataStore { get { return (DataStore)GetValue(InnerDataStoreProperty); } set { SetValue(InnerDataStoreProperty, value); } } }
Class DataStore
The non-visual data store is created in XAML and is contained as an internal property in SomeVisualControl.
In DXScheduler, a similar internal object was derived from DependencyObject and, by definition, did not contain a DataContext. As a result, Binding expressions on the properties of this object did not work. Therefore, the first thing that comes to mind is to inherit this object from the class containing the DataContext, and the problem will be solved.
Such a class is FrameworkElement, and we will use it as a base class.
public class DataStore : FrameworkElement { public static readonly DependencyProperty ConnectionStringProperty = DependencyProperty.Register("ConnectionString", typeof(string), typeof(DataStore), new PropertyMetadata(string.Empty)); public string ConnectionString { get { return (string)GetValue(ConnectionStringProperty); } set { SetValue(ConnectionStringProperty, value); } } }
Now we define the user level objects.
Model Class
Defines a user object. The
ConnectionString property will be “associated” with the property of the internal storage of the visual control.
public class DataStoreModel { public string ConnectionString { get; set; } public DataStoreModel(string connection) { ConnectionString = connection; } }
ModelView class
Defines the representation of the user object model. Following the requirements of the MVVM pattern, this class should implement
INotifyPropertyChanged , but in our example this is not necessary.
public class DataStoreViewModel { DataStoreModel dataStore; public DataStoreViewModel(DataStoreModel dataStore) { if (dataStore == null) throw new ArgumentNullException("dataStore"); this.dataStore = dataStore; } public string ModelConnectionString { get { return dataStore.ConnectionString; } } }
A diagram of the resulting classes is shown below:

So, we set the task: to
associate the property of an internal non-visual object with the property of the model .
Go to the application and create the necessary controls in the XAML markup.
< Window x:Class ="DataContextWpfSample.MainWindow"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:DataContextWpfSample"
Loaded ="Window_Loaded"
Title ="MainWindow" Height ="350" Width ="525" >
< Grid >
< local:SomeVisualControl x:Name ="MyVisualControl" >
< local:SomeVisualControl.InnerDataStore >
< local:DataStore ConnectionString ="{Binding ModelConnectionString}" />
</ local:SomeVisualControl.InnerDataStore >
< local:SomeVisualControl.Template >
< ControlTemplate >
< StackPanel >
< TextBlock Text ="DataStore Connection:" FontWeight ="Bold" />
< TextBlock Text ="{Binding Path=InnerDataStore.ConnectionString, RelativeSource={RelativeSource Mode=TemplatedParent}}" TextWrapping ="Wrap" />
</ StackPanel >
</ ControlTemplate >
</ local:SomeVisualControl.Template >
</ local:SomeVisualControl >
</ Grid >
</ Window >
* This source code was highlighted with Source Code Highlighter .
Let's set the name MyVisualControl to our control - it will be necessary to access it from the window's code-behind file. We define the display template and display the properties of interest to ensure that they were correctly obtained from the model object.
The model initialization code in the window class file looks like this:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { string connection = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\MyDB.mdb;Persist Security Info=True"; DataStoreModel sourceData = new DataStoreModel(connection); DataStoreViewModel sourceDataModel = new DataStoreViewModel(sourceData); this.MyVisualControl.DataContext = sourceDataModel; }
We especially note the last line - the assignment of a DataContext will thus allow “linking” the properties of the control with the properties of the model using binding expressions.
Run the application, but instead of the expected connection string, we see an empty one.
Let's use the
SNOOP utility and make sure that the DataContext of the storage object is not assigned:

It seems that this is due to the fact that:
The DataStore object is NOT in the visual tree, and therefore the context of the parent is NOT set to it .
Destination DataContext
Thus, we need to determine when a DataContext is assigned in a visual control, and set this value to an internal DataStore object.
The
FrameworkContentElement class contains a
DataContextChanged event.
Let's use this and, having written a simple code, we will receive the correct result.
public class SomeVisualControl : Control {
Unfortunately, there is one BUT ...
If you are using WPF, then you can use this approach. The thing is that the current version of Silverlight does not contain a
DataContextChanged event.
And since we are writing a common code for WPF and SL controls, it was necessary to write a universal solution.
So how do you know when the DataContext of the visual control changes?
You can use the following approach ...
Frankly, the idea is not new. I just tried to generalize it so that it could be used in different classes and for classes with a hierarchy of nested non-visual objects.
The essence of the idea is that the DependencyProperty is created and the binding is made, where the created property is associated with the DataContext property of the control in which you want to know about the context change. However, when registering a
DependencyProperty, you must specify a
PropertyChangedCallback . This callback function will be called when the value of the DataContext property changes. And it is here that you can assign the context to all the necessary objects, in our case, the InnerDataStore object.
Looking ahead, I’ll say that we will define an interface that will report that the DataContext property has changed and should be assigned to nested objects.
public interface IDataContextOwner { object DataContext { get; } void UpdateInnerDataContext(object dataContext); }
We implement the class described above that contains the binding on the DataContext and the PropertyChangedCallback method.
In this case, we will pass an object to the class constructor that implements the IDataContextOwner interface (in our case, this will be SomeVisualControl) and call the UpdateInnerDataContext interface method to tell our visual control that it is time to update the context of its internal storage.
public class DataContextBinder : DependencyObject { IDataContextOwner owner; public DataContextBinder(IDataContextOwner owner) { if (owner == null) throw new ArgumentNullException("owner"); this.owner = owner; InitializeBinding(); } protected virtual void InitializeBinding() { Binding binding = new Binding("DataContext"); binding.Source = owner; binding.Mode = BindingMode.OneWay; BindingOperations.SetBinding(this, DataContextProperty, binding); } public object DataContext { get { return (object)GetValue(DataContextProperty); } set { SetValue(DataContextProperty, value); } } public static readonly DependencyProperty DataContextProperty = DependencyProperty.Register("DataContext", typeof(object), typeof(DataContextBinder), new PropertyMetadata(null, new PropertyChangedCallback(OnDataContextChanged))); public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DataContextBinder)d).OnDataContextChanged(e.OldValue, e.NewValue); } private void OnDataContextChanged(object oldValue, object newValue) { owner.UpdateInnerDataContext(newValue); } }
Now we will write a fairly simple implementation of the IDataContextOwner interface in our View (SomeVisualControl):
public class SomeVisualControl : Control, IDataContextOwner {
The last thing you need to do is create an instance of the DataContextBinder inside the View.
This can be done directly in the class constructor:
public SomeVisualControl() { this.dataContextBinder = new DataContextBinder(this); }
Run the application and make sure that the context is assigned to the internal object and the string from the model is correctly installed in the DataStore object. Now the application window displays data from a user-defined model.

findings
This implementation is not tied to a specific class and can be applied where the need has arisen to assign a DataContext to an object that cannot receive it using “standard” means.
At the same time, when there is a need to pass the context deep into the hierarchy of non-visual objects, you simply create an object of the DataContextBinder class and implement the IDataContextOwner interface.
This frees you from the cumbersome writing of dependency properties and the binding definition between them in each of the classes. DataContextBinder encapsulates functionality in itself and at some point notifies the owner about the need to set a new context value on nested objects.
Sample source code is available here:
WPF and
Silverlight