Click here to Skip to main content
15,881,789 members
Articles / Desktop Programming / WPF
Article

Implement Reactive UI Element in WPF

Rate me:
Please Sign up or sign in to vote.
4.92/5 (9 votes)
12 Jul 2012CPOL4 min read 34.5K   487   30   2
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

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

C#
[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.

XML
<Button IsEnabled="{r:ConnectionAwareToEnabled}">Browse</Button>

or

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Singapore Singapore
Elvin Cheng is currently living in Woodlands, Singapore. He has been developing applications with the .NET Framework, using C# and ASP.NET since October 2002. Elvin specializes in building Real-time monitoring and tracking information system for Semi-conductor manufacturing industry. During his spare time, he enjoys reading books, watching movie and gym.

Comments and Discussions

 
GeneralWhy would you not just bind to a global aware static property Pin
Sacha Barber12-Jul-12 6:26
Sacha Barber12-Jul-12 6:26 
GeneralRe: Why would you not just bind to a global aware static property Pin
Elvin Cheng12-Jul-12 15:02
Elvin Cheng12-Jul-12 15:02 

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.