Introduction
In WPF, there are times when it is desirable to bind to static values instead of adding to the ViewModel
associated with the DataContext
. Commonly, the static values that want to bind to are the values in the Settings file. There is no problem in binding to these values; the problem is when the values change; project Settings do not support the INotifyPropertyChanged
interface and so the support for responding to a PropertyChanged
event. Thus if there are changes to the properties, these changes are not propagated to the View
.
Background
In the past I have added these static variables to the ViewModel
so that I can bind to properties, but these really should not be part of the ViewModel
since they have nothing to do with the underlying Model
. In the particular case that got me investigating the idea, I was looking into how to better implement a design for code I was having a hard time maintaining. I had a group of values that I was storing in the Settings
file associated with saved directory paths and some enumerations associated with how directory paths were recovered, or selecting default directories. What made it confusing were all the fallback options for bad values for different directory paths. To improve the maintainability, I did not really remove this information from the ViewModel
, but since I was already implementing a change, I decided to investigate removing this information from the ViewModel
. I also wanted to put the binding commands in the same class as the bindable properties.
Implementation
The example that I have created for this paper is very simple. It consists of a TextBox
containing the path, and a button to let the user browse to a new folder, which will then appear in the TextBox
.
The first thing that was obvious was that I would need an Adapter
class to interface the Settings to the View. The first problem was that I would need to bind to a static class, otherwise I needed to have the class associated with the DataContext
for the window, which fails to accomplish the objective. The problem with a static class is that it cannot implement INotifyPropertyChanged
. The solution is to have a static property that returns the instance of the class implementing the INotifyPropertyChanged
, which is a singleton. I used the same class for both the static and instance code. The following is the code that creates and returns an instance using a static property:
public class DirectoryManager : INotifyPropertyChanged
{
private static DirectoryManager _instance = new DirectoryManager();
public static DirectoryManager Instance { get { return _instance; } }
private DirectoryManager() { }
public event PropertyChangedEventHandler PropertyChanged;
}
Notice that I also make the default instance constructor for the class private
to ensure that this will be a singleton, and of course the class inherits from INotifyPropertyChanged
and has the required PropertyChanged
event defined.
The public property to display the directory path is a normal property used for binding with additional code to get and save the string from the Settings file:
public string DirectoryPath
{
get { return Properties.Settings.Default.DirectoryPath; }
set
{
if (Properties.Settings.Default.DirectoryPath != value)
{
Properties.Settings.Default.DirectoryPath = value;
Properties.Settings.Default.Save();
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("DirectoryPath"));
}
}
}
This is all very straightforward (normally I do not directly handle the PropertyChanged
event in the property, but for examples, I prefer to). The hard part is wiring up the support for the Browse button.
Unfortunately, it is not possible to use the ICommand
interface for the button; it seems that Microsoft did not provide support for Source bindings (which provide the ability to bind static variables) with Command bindings. Therefore, I have to use RoutedUICommand
instead. This is definitely more complex than normal Command binding, which I do not like (I would have liked to have seen a simpler implementation for binding to events than the ICommand
), but it works, and it is not bad to have a simple example to demonstrate how to implement a RoutedUICommand
. Within the DirectoryManager
class, the following must be included to create the RoutedUICommand
:
public static RoutedUICommand BrowseCommand
{
get { return _browseCommand; }
}
public static void BrowseCommand_Executed(object sender,
ExecutedRoutedEventArgs e)
{
var path = DirectoryManager.Browse(_instance.DirectoryPath,
"Select Directory");
if (path != null)
_instance.DirectoryPath = path;
}
public static void BrowseCommand_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
static DirectoryManager()
{
_browseCommand = new RoutedUICommand("Browse for Directory",
"BrowseCommand", typeof(DirectoryManager));
}
As can be seen, we need a property to return the BrowseCommand
, and in the class constructor, we need to instantiate the command.
Also, we need to define two methods to implement the Execute
and CanExecute
methods for the command. The names for these methods are not important since there is another piece of code that associates the methods with the command:
public static void BindCommands(System.Windows.Window window)
{
window.CommandBindings.Add(new CommandBinding(BrowseCommand,
BrowseCommand_Executed, BrowseCommand_CanExecute));
}
There is also supposed to be a way to create the binding using XAML, but that does not seem to work, so we have to do the binding in code.
To be able to use the command in the window, we need to call this method in the Window
’s code-behind:
namespace BindableSettingsExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DirectoryManager.BindCommands(this);
}
}
}
Now it is possible to create XAML that provides Binding to the Settings file, and also command binding for the Browse button.
<Window x:Class="BindableSettingsExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bindableSettings="clr-namespace:BindableSettingsExample"
Title="Bindable Settings Example" Height="300" Width="525">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="75"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Margin="0,0,2,-5"
HorizontalAlignment="Left"
Content="Directory Path:"/>
<TextBox Grid.Column="0" Grid.Row="1" Name="txtDirectoryPath"
Height="22" Margin="6,0,0,5"
Text="{Binding DirectoryPath,Source={x:Static
bindableSettings:DirectoryManager.Instance}, Mode=TwoWay}"
IsReadOnly="True"/>
<Button Grid.Column="1" Grid.Row="1" Content="_Browse" Height="22"
Padding="5,2,5,2" Margin="5,0,7,5"
Command="bindableSettings:DirectoryManager.BrowseCommand"/>
</Grid>
</Window>
As can be seen, the binding for the text for the TextBox
is just:
{Binding DirectoryPath,Source={x:Static
bindableSettings:DirectoryManager.Instance}, Mode=TwoWay}
The binding for the command for the button is just:
bindableSettings:DirectoryManager.BrowseCommand
Conclusion
This implementation was used to bind to the Settings file, but the design is actually much more flexible. The design can be used with data that is not saved in the Settings file, or even only session dependent. It is also possible to change the static instance to different classes that are application state dependent. This can be done without mucking up the ViewModel
for the DataContext
with information that is not associated with the Model.