Binding to a singleton the elegant way using a custom DataSourceProvider
How to turn a static property of any type into a StaticResource
There are several reasons why you might want to use the singleton pattern in WPF. You often need an object that is globally accessible and you only need one instance of that object. A
static
class might do the job, but if you want to bind to the object and use it as a StaticResource
, you need an instance.
There are several ways to bind to a singleton. The simplest solution is to use the x:Static
syntax:
<TextBlock Text="{Binding SomeProperty, Source={x:Static local:MySingleton.Instance}}" />
This works fine, but there are 2 disadvantages. First, the syntax is too long and it looks ugly. Second, the reference to MySingleton.Instance
appears in every single binding expression and if you decide to change your class later (or just rename it...), you will have a lot of work to do.
The solution is to store your singleton instance as a StaticResource
. This will add one level of indirection, so if your class changes for some reason, you will only have to change the way the StaticResource
is created without breaking all the bindings.
Now I'm getting to the point of this article. How can you use a singleton as a StaticResource
? Singleton does not have any public
constructor, so you can't do it the easy way. A common solution is to use ObjectDataProvider
:
<Application.Resources>
<ObjectDataProvider x:Key="MyData" ObjectType="{x:Type local:MySingleton}" MethodName="GetInstance" />
</Application.Resources>
This works really fine. Binding to our singleton is now as simple as this:
<TextBlock Text="{Binding SomeProperty, Source={StaticResource MyData}}" />
It's fine, but it's still not perfect. The problem is that ObjectDataProvider
is only able to access public
methods, not properties, so we had to change the static public Instance
property to a static public GetInstance()
method. It's not a big problem, but I liked the property-based singleton better, because it's easier and more readable... What if there is an easy solution to use a property-based singleton instance as a StaticResource
? Yes!
The ObjectDataProvider
class is derived from DataSourceProvider
. Let's derive a custom class from DataSourceProvider
to enable the functionality we need. It's as simple as this:
/// <summary>
/// Represents a DataSourceProvider that gets the data from a static property
/// </summary>
public class PropertyDataProvider : DataSourceProvider {
private Type _objectType;
private string _propertyName;
/// <summary>
/// Gets or sets the object type used to get data
/// </summary>
public Type ObjectType {
get { return _objectType; }
set {
if (value == _objectType) return;
_objectType = value;
OnPropertyChanged(new PropertyChangedEventArgs("ObjectType"));
if (!base.IsRefreshDeferred) base.Refresh();
}
}
/// <summary>
/// Gets or sets the name of a static property of ObjectType type
/// </summary>
public string PropertyName {
get { return _propertyName; }
set {
if (value == _propertyName) return;
_propertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("PropertyName"));
if (!base.IsRefreshDeferred) base.Refresh();
}
}
protected override void BeginQuery() {
Exception error = null;
object result = null;
if (_objectType == null) {
error = new InvalidOperationException("ObjectType is not set.");
} else if (String.IsNullOrEmpty(_propertyName)) {
error = new InvalidOperationException("PropertyName is not set.");
} else {
PropertyInfo prop = _objectType.GetProperty(_propertyName, BindingFlags.Static | BindingFlags.Public);
if (prop == null) {
error = new MissingMemberException(_objectType.FullName, _propertyName);
} else {
try {
result = prop.GetValue(null, null);
} catch (MethodAccessException e) {
error = e;
} catch (TargetInvocationException e) {
error = e;
}
}
}
base.OnQueryFinished(result, error, null, null);
}
}
The class only has 2 properties and it overrides the BeginQuery()
method. This method is a bit messy because it needs to handle exceptions in a specific way... It would look like the following without the exception-handling trash:
protected override void BeginQuery() {
object result = null;
PropertyInfo prop = _objectType.GetProperty(_propertyName, BindingFlags.Static | BindingFlags.Public);
result = prop.GetValue(null, null);
base.OnQueryFinished(result, null, null, null);
}
(Note: The code above is more readable, but it's NOT correct!)
You can now create a StaticResource
from a singleton the following way:
<Application.Resources>
<local:PropertyDataProvider x:Key="MyData" PropertyName="Instance" ObjectType="{x:Type local:MySingleton}" />
</Application.Resources>
To sum this up, the class I introduced in this article might seem pretty useless, but if you want to bind to a singleton and you prefer the Instance
property syntax over the GetInstance()
method syntax, now you know there is a way to do it... It's just a syntactic sugar, but it's so easy to use that it's worth the effort.