WPF Binding Beyond the Limitation of Name Scopes

There are many situations that a property should be bind to something outside of its NameScope. There are many situations that a property should be bind to a DynamicResource. Many UI patterns like Composite UI Applications need a mechanism to support binding across modules. In this paper, I propose a mechanism to address those issues.

Introduction

One of the great UI patterns that partitions a complicated application to a collection of loose-coupled, simple, independent modules is Composite UI Application pattern. Although, it had been provided before the WPF framework, but it really suits the WPF. It involves many smaller patterns like dependency injection, Model-View-Controllers, Enterprise Logging pattern, Composite Events and Composite Commands and etc. The main idea behind it is dividing a shell window into regions and allows modules to penetrate their Views into the regions. Communication among modules has been done using the pattern infrastructure and the modules do not dependent on each other. Currently, I face a situation in a Composite UI Application that I need a loosely binding across modules. Although, it was possible to pass message across modules using Composite Commands and Composite events, but having loosely binding makes the code very simple. After some investigation, I realized that there are similar binding problems in other areas. For example, binding the content of a tooltip to its parent; since the tooltip belongs neither to the logical tree nor to the visual tree, it can’t be bind to parent data context. On the other hand it has a different NameScope too. A similar problem occurs when you want to bind an item to a resource that dynamically has been added to the project. The solution behind all of these cases is the ability to do binding beyond the NameScope limitation. A NameScope is a class that implements the INameScope interface. WPF uses the NameScopes to enforce the uniqueness of names. All of the controls inside a NameScope must have unique names. The following classes in the WPF have implemented the INameScope: Window, Page, UserControl, Style, Template, NameScope and ResourceDictionary. In the WPF, one can’t do binding among objects in different nameScopes. In this paper, Using attached properties and custom MarkupExtensions, I provide a mechanism that objects can be bind to each other with or without being declared in the same NameScope. The capabilities of the framework can be summarized as follows:

  • The ability to bind two controls that don’t belong to the same NameScope.
  • The ability to create many transparent NameScopes. Transparent NameScopes can be existed alongside the real NameScopes and objects can be attached to them from anywhere in a project. An object can be attached to many transparent namescopes too without any conflict. It gives the developers much higher freedom in binding.
  • The ability to bind to dynamic resources.

Currently, there are similar solutions, such as the great elementSpy of Josh Smith, but their solutions uses reflection and needs fully trust permissions. The one proposed here doesn’t need fully trusted permission and also it targets wider applications.

Using the Code

There are the following concepts in the framework.

1. TransparentResourceDictionary: TransparentResourceDictionary is a virtual NameScope that can be defined using attached properties. TransparentResources contain transparent keys plus the owner of them. If one wants to bind to an object beyond the NameScope limitation, that object should be attached to a transparent resource.

2. TransparentKey: Each object inside a TransparentResourceDictionary should have a TransparentKey in order to be identified.

3. TransparentBinding: In order to bind to an object that is attached to a TransparentResourceDictionary, you should use TransparentBinding. TransparentBinding provides properties to specify TransparentResourceDictionary plus TransparentKey.  Suppose you want to bind the Content property of a Label to Text property of a TextBlock in another NameScope. First, you should define a TransparentResourceDictionary using attached properties like this.

<StackPanel

icp:TransparentNameScope.TransparentNameScope="leftWindow">

Second, you should attach the TextBlock to the TransparentResourceDictionary using attached properties.

<TextBox

icp:TransparentNameScope.TransparentKey="leftWindow,leftTextBox">

Third, you should use the TransparentBinding to bind Content property of the Label to Text Property of the TextBox.

<Label Content="{Binding Path={icp:TransparentPath Path=Text}, Source={icp:TransparentResourceBinding Key=leftTextBox, NameScope=leftWindow}}"></Label>

Structure of the Code

Using class diagrams is the best way to illustrate the architecture of a package. Here is the class diagram of the framework.

TransparentResourceDictionary Class

This class acts as a Dictionary that can contain TransparentResourceItems. Here is the code of the class.

internal class TransparentResourceDictionary

{

public TransparentResourceDictionary(string name)

{

Name = name;

transparentResources.Add(this);

}

internal string Name;

internal static List<TransparentResourceDictionary> transparentResources = new List<TransparentResourceDictionary>();

internal List<TransparentResourceItem> Items = new List<TransparentResourceItem>();

}

As you can see in the above code, the constructor of the class adds each created instance to a static list called transparentResources. Using this trick, the framework can have access to all of the created TransparentResourceDictionaries. The class has a name property which is used to distinguish the TransparentResourceDictionaries from each other. Items inside the dictionary have been kept in its Items property. These items will be wrapped by TransparentResoureItem.

TransparentResourceItem class

Whenever, an item should be added to a TransparentResourceDictionary, an instance of the TransparentResourceItem will be created. The Source property of the created item is the real object that should be added to the dictionary and its key property is used to identify items inside a TransparentResourceDictionary. The transparentResourceItem class announces the changes of its Source property, since the target bindings needs to know the changes of their Source properties. Here is the code of the class.

public class TransparentResourceItem : DependencyObject, INotifyPropertyChanged

{

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

protected void notifyPropertyChanged(string name)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(name));

}

}

#endregion

public string Key;

public object Source

{

get

{

return source;

}

set

{

if (source != value)

{

source = value;

notifyPropertyChanged("Source");

}

}

}

private object source;

}

TransparentNameScope class

This class provides attached properties to allow developers create TransparentResourceDictionaries and attach items to them. The class has the following properties.

TransparentNameScope: This property is used to define TransparentResources.

TransparentKey: This property is used to assign a key to an object in a TransparentResourceDictionary. The value assigned to an object should contain the name of TransparentResourceDictionary plus the key in a comma sperate string like the following:

<TextBox

icp:TransparentNameScope.TransparentKey="leftWindow,leftTextBox">

Here is the code of the class:

public class TransparentNameScope

{

public static string GetTransparentKey(DependencyObject obj)

{

return (string)obj.GetValue(TransparentKeyProperty);

}

public static void SetTransparentKey(DependencyObject obj, string value)

{

obj.SetValue(TransparentKeyProperty, value);

}

public static readonly DependencyProperty TransparentKeyProperty =

DependencyProperty.RegisterAttached("TransparentKey", typeof(string), typeof(TransparentNameScope), new UIPropertyMetadata("", onTransparentKeyChanged));

private static void onTransparentKeyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)

{

string[] newValue = e.NewValue.ToString().Split(',');

if (newValue.Length < 2)

{

throw new Exception("ResourceName should contain both NameScope and ResourceKey");

}

string nameScope = newValue[0];

string key = newValue[1];

TransparentResourceDictionary globalResource = TransparentResourceDictionary.transparentResources.FirstOrDefault(c => c.Name == nameScope);

if (globalResource == null)

{

return;

}

TransparentResourceItem registerItem = globalResource.Items.FirstOrDefault(c => c.Key == key);

if (registerItem == null)

{

registerItem = new TransparentResourceItem();

registerItem.Key = key;

globalResource.Items.Add(registerItem);

}

registerItem.Source = obj;

}

public static string GetTransparentNameScope(DependencyObject obj)

{

return (string)obj.GetValue(TransparentNameScopeProperty);

}

public static void SetTransparentNameScope(DependencyObject obj, string value)

{

obj.SetValue(TransparentNameScopeProperty, value);

}

// Using a DependencyProperty as the backing store for TransparentNameScope. This enables animation, styling, binding, etc...

public static readonly DependencyProperty TransparentNameScopeProperty =

DependencyProperty.RegisterAttached("TransparentNameScope", typeof(string), typeof(TransparentNameScope), new UIPropertyMetadata("", onTransparentNameScopeChanged));

private static void onTransparentNameScopeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)

{

string newValue = e.NewValue.ToString();

if (newValue == String.Empty)

{

TransparentResourceDictionary globalResource = TransparentResourceDictionary.transparentResources.FirstOrDefault(c => c.Name == e.OldValue.ToString());

if (globalResource != null)

{

TransparentResourceDictionary.transparentResources.Remove(globalResource);

}

}

else

{

TransparentResourceDictionary g = new TransparentResourceDictionary(newValue);

}

}

}

TransparentResourceBinding and TransparentPath Classes

Custom MarkupExtenstion is one of the amazing features of the WPF that provides a great deal of flexibility. Binding, StaticResource and DynamicResource are examples of predefined MarkupExtensions. In fact, they inherit from MarkupExtension. One can create his own MarkupExtensions too by inheriting from the MarkupExtension class. Here is the signature of the class.

public abstract class MarkupExtension

{

protected MarkupExtension();

public abstract object ProvideValue(IServiceProvider serviceProvider);

}

<span style="font-size:11pt;font-family:'Calibri"';font-weight:normal;">As can be seen, the class has one abstract method that should be implemented by its descendant. The method gets an IServiceProvider from the runtime that can provide context, and the method returns a value based on it. Both of the TransparentResourceBinding and TransparentPath inherit from MarkupExtension class. TransparentResourceBinding provides a mechanism to bind the Source property of a binding to a TransparenResourceItem. Here is the code of TransparentResourceBinding.

public class TransparentResourceBinding : MarkupExtension

{

public string Key

{

get

{

return key;

}

set

{

key = value;

}

}

private string key;

public string NameScope

{

get

{

return nameScope;

}

set

{

nameScope = value;

}

}

private string nameScope = "";

public override object ProvideValue(IServiceProvider serviceProvider)

{

IProvideValueTarget service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

if (service == null || service.TargetObject == null || service.TargetProperty == null)

{

return null;

}

TransparentResourceDictionary globalResource = TransparentResourceDictionary.transparentResources.FirstOrDefault(c => c.Name == nameScope);

TransparentResourceItem registerItem = globalResource.Items.FirstOrDefault(c => c.Key == key);

if (registerItem == null)

{

registerItem = new TransparentResourceItem();

registerItem.Key = key;

globalResource.Items.Add(registerItem);

}

if (globalResource == null)

{

return null;

}

if (service.TargetObject is DependencyObject)

{

DependencyProperty property = service.TargetProperty as DependencyProperty;

DependencyObject target = service.TargetObject as DependencyObject;

if (property == null || target == null)

{

return null;

}

return registerItem.Source;

}

else if (service.TargetObject is Binding)

{

Binding originalBinding = service.TargetObject as Binding;

originalBinding.Source = registerItem;

return registerItem;

}

return null;

}

}

The TransparentPath has been used to specify the Path of binding instead of directly specifying the path. Actually, the TransparentPath does nothing except adding the “Source.” to the beginning of the Path. Here is the code of TransparentPath.

public class TransparentPath : MarkupExtension

{

public string Path

{

get

{

return path;

}

set

{

path = value;

}

}

private string path;

public override object ProvideValue(IServiceProvider serviceProvider)

{

IProvideValueTarget service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

if (service == null || service.TargetObject == null || service.TargetProperty == null)

{

return null;

}

if (service.TargetObject is Binding)

{

Binding originalBinding = service.TargetObject as Binding;

if (Path.Length > 0)

{

return new PropertyPath(String.Format("Source.{0}", path));

}

else

{

return new PropertyPath("Source");

}

}

return null;

}

}

The Scenario Behind Attaching an Object to a TransparentResourceDictionary.

An old sentence says: One image is better than millions of words. Instead of describing what happened when an object is attached to a TransparentResourceDictionary, I provide a flowchart that shows the basic steps. The input of the flowchart is a string that contains TransparentResourceDictionary plus TransparentResourceKey.

The Scenario Behind Binding a Property to a ResourceKey.

The input of the flowchart is a TransparentResourceName, TransparentResourceKey and a Path. All of them are strings

The code can be downloaded here.

By Siyamand Ayubi   Popularity  (3695 Views)