Introduction
This article continues with the idea stemming from the article named "Implement UI Element
Authorization in WPF". Though they are not related, understanding the concept presented in that articles may become useful if you want to improve the previous solution.
The idea behind the Reactive UI element is very simple. Take a network connection as an example, distributed WPF and Silverlight application, relying heavily on network
connections (Intranet or Internet) to access the resources on the server. This application may restrict
a user from performing certain operations whenever there is no network
connection (offline), but still allow those operations that do no depend on the network connection and/or resources from the server. In
a programming context,
the application wants to enable UI elements when the network connection is available, but disable them when the network connection is lost.
In a traditional implementation, the application would setup an event handler and listen to the online/offline event from a component (self-contained network detection logic)
and perform the intended operation (Enable/Disable) to all the affected UI elements when the connection state changes. It is tedious and error-prone whenever there
is a need to add or remove the participated UI elements in this process. Or worst,
the developer has to duplicate this piece of code in different Forms. In Reactive UI element,
one just need to encapsulate this piece of code in a self-contained behavior (Reactive UI Behavior) and attach/bind it to the participating UI element's property. Simple and
reusable!
With Reactive UI Behavior, an application can implement the following features with ease:
- Enable or disable UI elements dynamically when the network connection is available or unavailable, respectively.
- Show or hide UI elements dynamically when a network connection is available or unavailable, respectively.
- Enforce UI elements access control dynamically based on privileges. (See Implement
UI Element Authorization in WPF)
- Enable or disable Submit button when form validation passes or fails, respectively.
- Change UI Form's background color based on Day mode or Night mode.
- Your imagination goes on here...
Background
MarkupExtension
is the building block of Reactive UI Behavior where it enables us to embed
business logic and declaratively associate or bind it to UI elements in XAML. The process of creating a Reactive UI Behavior is as simple as following the steps outlined below:
- Create a class and inherit from the abstract class
MarkupExtension
.
- Override the
ProvideValue
function.
- Get a reference of the
IProvideValueTarget
service provider from the IServiceProvider
parameter of the ProvideValue
function.
- Maintain a reference to the
TargetObject
(DependencyObject
) and
TargetProperty
(DependencyProperty
) through the IProvideValueTarget
interface.
- Setup an event handler and subscribe to the notification event.
- Update the referenced
TargetProperty
(DependencyProperty) with
the newly evaluated value in the event handler upon callback.
In this article, I have created two Reactive UI behaviors in the demo project. Their implementations are shown below:
ConnectionAwareToEnabled
- can be bound to a UI element which has the
IsEnabled
property.
ConnectionAwareToVisibility
- can be bound to a UI element which has the
Visibility
property.
ConnectionAwareToEnabled
[MarkupExtensionReturnType(typeof(bool))]
public class ConnectionAwareToEnabled : MarkupExtension
{
private DependencyObject TargetObject { get; set; }
private DependencyProperty TargetProperty { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provider != null)
{
3) Gets a reference of IProvideValueTarget provider from the IServiceProvider parameter
if (provider.TargetObject is DependencyObject && provider.TargetProperty is DependencyProperty)
{
TargetObject = (DependencyObject) provider.TargetObject;
TargetProperty = (DependencyProperty) provider.TargetProperty;
}
else
{
throw new InvalidOperationException("The binding target is not a " +
"DependencyObject or its property is not a DependencyProperty.");
}
}
ConnectionManager.Instance.ConnectionStatusChanged += OnConnectionStateChanged;
return ConnectionManager.Instance.State == ConnectionState.Online;
}
private void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e)
{
TargetObject.Dispatcher.BeginInvoke(new Action(() => TargetObject.SetValue(
TargetProperty, e.CurrentState == ConnectionState.Online)));
}
}
ConnectionAwareToVisibility
[MarkupExtensionReturnType(typeof(Visibility))]
public class ConnectionAwareToVisibility : MarkupExtension
{
private DependencyObject TargetObject { get; set; }
private DependencyProperty TargetProperty { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provider != null)
{
3) Gets a reference of IProvideValueTarget provider from the IServiceProvider parameter
if (provider.TargetObject is DependencyObject &&
provider.TargetProperty is DependencyProperty)
{
TargetObject = (DependencyObject) provider.TargetObject;
TargetProperty = (DependencyProperty) provider.TargetProperty;
}
else
{
throw new InvalidOperationException("The binding target is not " +
"a DependencyObject or its property is not a DependencyProperty.");
}
}
ConnectionManager.Instance.ConnectionStatusChanged += OnConnectionStateChanged;
return (ConnectionManager.Instance.State ==
ConnectionState.Online) ? Visibility.Visible : Visibility.Collapsed;
}
private void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e)
{
TargetObject.Dispatcher.BeginInvoke(new Action(() => TargetObject.SetValue(TargetProperty,
(ConnectionManager.Instance.State == ConnectionState.Online) ?
Visibility.Visible : Visibility.Collapsed)));
}
}
Astute readers may have spotted that the above implementation make use of the referenced
TargetObject
's Dispatcher
to update
the UI element's property, which avoids the cross-thread marshalling issue of callback from
the background thread.
Using the code
Using the Reactive UI behavior in XAML is similar to normal data binding, except that it saves you from typing the Binding
keyword. The code snippet below shows how to apply them in XAML markup. 'r' is the namespace reference of the Reactive UI behavior.
<Button IsEnabled="{r:ConnectionAwareToEnabled}">Browse</Button>
or
<Button Visibility="{r:ConnectionAwareToVisibility}">Browse</Button>
The demo project included in this article demonstrates that UI Elements like Button are Enabled or Visible when the network connection is available,
but will be Disabled or Hide when unavailable. To test it out, you can disable or enable the Network Device (Control Panel -> Network
and Internet -> Network Connections) in the Control Panel. Be patient to wait for at least 5 seconds!
Points of Interest
Last but no least, experienced readers or developers may have realized that the code listing above does have memory leak issues. The memory
leaks are due to the ConnectionManager
having a strong reference to the event listener (Reactive UI Behavior) and thus preventing
the garbage collector from collecting it. Rest assured, the complete code included in the demo project has
a proper implementation which adheres
to the Weak Event Patterns with the use
of WeakEventManager
and the IWeakEventListener
interface
to avoid memory leak issues. Be sure to check out the following code files included in the demo project:
- ConnectionAwareToEnabledExtension.cs
- ConnectionAwareToVisibilityExtension.cs
- ConnectionStateEventManager.cs
Last, but not least, you can download the sample, which is available from the link at the top of this article.
History
- 12 July, 2012: Initial version.