Click here to Skip to main content
15,883,883 members
Articles / Desktop Programming / WPF
Tip/Trick

Binding to a singleton the elegant way using a custom DataSourceProvider

Rate me:
Please Sign up or sign in to vote.
4.83/5 (4 votes)
18 Sep 2010CPOL2 min read 23K   2   1
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:

XAML
<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:

XAML
<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:

XAML
<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:

C#
/// <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:

C#
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:

XAML
<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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
Czech Republic Czech Republic
That's all folks!

Comments and Discussions

 
QuestionThis seems great but Pin
Khrol22-Jun-15 11:05
Khrol22-Jun-15 11:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.