Click here to Skip to main content
15,877,754 members
Articles / Desktop Programming / Windows Forms

Bind to the Inverse of a Boolean Property Value

Rate me:
Please Sign up or sign in to vote.
4.33/5 (4 votes)
22 Oct 2009CPOL2 min read 32.7K   127   13   3
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.

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

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

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


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

 
QuestionSimpler reusable solution Pin
JJ Borter30-Apr-21 0:24
JJ Borter30-Apr-21 0:24 
Generalsimpler solution Pin
Mr.PoorEnglish22-Oct-09 12:46
Mr.PoorEnglish22-Oct-09 12:46 
GeneralRe: simpler solution Pin
vtchris-peterson22-Oct-09 13:16
vtchris-peterson22-Oct-09 13:16 
Good idea. I would still want to encapsulate in a separate class to keep the client as simple as possible -- could certainly still be accomplished with your solution.

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.