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

Bind to the Inverse of a Boolean Property Value

0.00/5 (No votes)
22 Oct 2009 1  
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. :)

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 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