Click here to Skip to main content
Click here to Skip to main content

Bind to the Inverse of a Boolean Property Value

, 22 Oct 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Ever wish you could bind to "Disabled" instead of "Enabled"?

Introduction

Oftentimes, user interface code gets cluttered by event handlers that manipulate state, e.g. when button A is clicked, disable button B, etc.

Data binding is a great way to simplify user interface logic. This article assumes a familiarity with .NET data binding with WinForms. (If you are unfamiliar with this concept, information is readily available on CodeProject or through MSDN -- it is an elegant technique, one that I use for many purposes).

Background

Anyone familiar with WinForms development has undoubtedly seen code to manage Enabled state of various UI elements.

The traditional way consists of careful event handlers and property initialization.

  public Form1()
  {
     InitializeComponent();
     
     button1.Enabled = true;
     button2.Enabled = false;
     panel1.Enabled = false;
  }
  
  private void button1_Click(object sender, EventArgs e)
  {
     button1.Enabled = false;
     button2.Enabled = true;
     panel1.Enabled = true;
  }
  
  private void button2_Click(object sender, EventArgs e)
  {
     button1.Enabled = true;
     button2.Enabled = false;
     panel1.Enabled = false;
  }

This can be simplified with a binding, e.g.

  public Form1()
  {
     InitializeComponent();
     
     button1.Enabled = true;
     button2.Enabled = false;
     panel1.DataBindings.Add(new Binding("Enabled", button2, "Enabled"));
  }
  
  private void button1_Click(object sender, EventArgs e)
  {
     button1.Enabled = false;
     button2.Enabled = true;
  }
  
  private void button2_Click(object sender, EventArgs e)
  {
     button1.Enabled = true;
     button2.Enabled = false;
  }

Unfortunately, since button 1 and 2 are inversely related, i.e. when button 1 is enabled, button 2 is disabled, we can't add a traditional binding. This happens more often than you may consider, e.g. connect and disconnect buttons behave exactly this way. The InvertedBinding construct defined in this article provides a seamless solution to this problem.

Using the Code

The attached project is a VS 2008 solution targeting .NET 2.0 Framework, but the relevant code is included here. Simply snag the InvertedBinding class and you're good to go.

Usage Example

  public Form1()
  {
     InitializeComponent();
     
     button2.DataBindings.Add(InvertedBinding.Create(button1, "Enabled"));
     panel1.DataBindings.Add(new Binding("Enabled", button2, "Enabled"));
  }
  
  private void button1_Click(object sender, EventArgs e)
  {
     button1.Enabled = false;
  }
  
  private void button2_Click(object sender, EventArgs e)
  {
     button1.Enabled = true;
  }

Now, the entire UI enabled state is driven off of button1.Enabled. There is also no need to set initial values (in the Designer or in the code).

Implementation
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;

namespace InverseBinding
{
   public class InvertedBinding : INotifyPropertyChanged, IDisposable
   {
      #region Private Fields
      private readonly object _dataSource;
      private readonly EventInfo _changedEvent;
      private readonly MethodInfo _getAccessor;
      private readonly MethodInfo _setAccessor;
      #endregion

      #region Constructors
      protected InvertedBinding
		(object dataSource, string dataSourceBoundPropertyName)
      {
         if ((dataSource == null) || (dataSourceBoundPropertyName == null))
         {
            throw new ArgumentNullException();
         }

         _dataSource = dataSource;

         EventInfo propertyChangedEvent = 
            _dataSource.GetType().GetEvent(string.Format("{0}Changed", 
					dataSourceBoundPropertyName));
         if ((propertyChangedEvent != null) && (typeof(EventHandler).
		IsAssignableFrom(propertyChangedEvent.EventHandlerType)))
         {
            _changedEvent = propertyChangedEvent;
            _changedEvent.AddEventHandler(_dataSource, 
		new EventHandler(OnDataSourcePropertyChanged));
         }

         PropertyInfo dataSourceBoundProperty = 
            _dataSource.GetType().GetProperty(dataSourceBoundPropertyName);
         if (dataSourceBoundProperty == null)
         {
            throw new MissingMemberException(string.Format(
                         	"Could not find property '{0}.{1}'",
                         	_dataSource.GetType().FullName, 
			dataSourceBoundPropertyName));
         }

         _getAccessor = dataSourceBoundProperty.GetGetMethod();
         if (_getAccessor == null)
         {
            throw new MissingMethodException(string.Format(
                     "No get accessor for '{0}'", dataSourceBoundProperty.Name));
         }
         if (!typeof(bool).IsAssignableFrom(_getAccessor.ReturnType))
         {
            throw new ArgumentException(
               string.Format(
                  "Class only works on boolean properties, 
		'{0}' is not of type bool", 
		dataSourceBoundProperty.Name));
         }

         _setAccessor = dataSourceBoundProperty.GetSetMethod();
      }
      #endregion

      public static Binding Create(object dataSource, string propertyName)
      {
         return new Binding(propertyName, 
		new InvertedBinding(dataSource, propertyName), "InvertedProperty");
      }

      public bool InvertedProperty
      {
         get
         {
            return !GetDataBoundValue();
         }
         set
         {
            if (_setAccessor == null)
            {
               // nothing to do since no one will get notified.
               return;
            }

            bool curVal = InvertedProperty;
            
            // a little bit of trickery here, we only want to change 
            // the value if IS the same
            // rather than the conventional if it's different
            if (curVal == value)
            {
               _setAccessor.Invoke(_dataSource, new object[] { !value });
               if (PropertyChanged != null)
               {
                  PropertyChanged(this, 
			new PropertyChangedEventArgs("InvertedProperty"));
               }
            }
         }
      }

      #region INotifyPropertyChanged Members
      public event PropertyChangedEventHandler PropertyChanged;
      #endregion

      #region IDisposable Members
      public void Dispose()
      {
         if (_changedEvent != null)
         {
            _changedEvent.RemoveEventHandler
		(_dataSource, new EventHandler(OnDataSourcePropertyChanged));
         }
      }
      #endregion

      private void OnDataSourcePropertyChanged(object sender, EventArgs e)
      {
         // refresh our property (which may trigger our PropertyChanged event)
         InvertedProperty = !GetDataBoundValue();
      }

      private bool GetDataBoundValue()
      {
         return (bool) _getAccessor.Invoke(_dataSource, null);
      }
   }
}

Points of Interest

Implementing INotifyPropertyChanged is important in order for the framework (e.g. Button or Panel) to update their state when the bound property changes.

The code shows many examples of reflection, including dynamic event discovery and registration. This sort of thing does not excite me anymore since it is second nature at this point, but a beginner may find it interesting. Smile | :)

It is possible to adapt this code to target .NET 3.0 which would allow for getting rid of magic strings using lambda.

History

  1. Initial revision
  2. Minor updates (tailored exception messages, added XML documentation)

License

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

Share

About the Author

vtchris
Software Developer SparkDev
United States United States
I am a software engineer living and working in Seattle, WA.
 
I was an early adopter of .NET and have been writing C# code on a full-time basis since 2003. Almost all my experience is doing new dev in a wide variety of applications from navigation systems to security products to telemedicine solutions.

Comments and Discussions

 
Generalsimpler solution PinmemberMr.PoorEnglish22-Oct-09 13:46 
GeneralRe: simpler solution Pinmembervtpdawg22-Oct-09 14:16 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150326.1 | Last Updated 22 Oct 2009
Article Copyright 2009 by vtchris
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid