Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Implement Reactive UI Element in WPF

0.00/5 (No votes)
12 Jul 2012 1  
How to implement Reactive UI element in WPF with MVVM design pattern.

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:

  1. Enable or disable UI elements dynamically when the network connection is available or unavailable, respectively.
  2. Show or hide UI elements dynamically when a network connection is available or unavailable, respectively.
  3. Enforce UI elements access control dynamically based on privileges. (See Implement UI Element Authorization in WPF)
  4. Enable or disable Submit button when form validation passes or fails, respectively.
  5. Change UI Form's background color based on Day mode or Night mode.
  6. 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:

  1. Create a class and inherit from the abstract class MarkupExtension.
  2. Override the ProvideValue function.
  3. Get a reference of the IProvideValueTarget service provider from the IServiceProvider parameter of the ProvideValue function.
  4. Maintain a reference to the TargetObject (DependencyObject) and TargetProperty (DependencyProperty) through the IProvideValueTarget interface.
  5. Setup an event handler and subscribe to the notification event.
  6. 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)
            {
                //  4) Maintains a reference to the TargetObject and TargetProperty
                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.");
            }
        }
        //  5) Subscribe to the event notification
        ConnectionManager.Instance.ConnectionStatusChanged += OnConnectionStateChanged;

        //  The initial value when the UI element is displayed
        return ConnectionManager.Instance.State == ConnectionState.Online;
    }

    private void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e)
    {
        //  6) Update the DependencyProperty value in event handler
        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)
            {
                //  4) Maintains a reference to the TargetObject and TargetProperty
                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.");
            }
        }
        //  5) Subscribe to the event notification
        ConnectionManager.Instance.ConnectionStatusChanged += OnConnectionStateChanged;

        //  The initial value when the UI element is displayed
        return (ConnectionManager.Instance.State == 
                  ConnectionState.Online) ? Visibility.Visible : Visibility.Collapsed;
    }

    private void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e)
    {
        //  6) Update the DependencyProperty value in event handler
        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here