Saturday, August 29, 2015

Small but powerful UI highlight decorations in WinForms with closures

I've been working so much recently with languages for which closures is the norm - Node (which, if you haven't heard yet, is a useful JavaScript platform that obviates the need for a browser), Go, Ruby - that it's easy to think of this as the natural way to program.

In this post, and a follow-up to come in a couple of days, I'll talk about use of closures in languages for which it's not so natural. Next time, I'll talk about C++'s support, which is only very recent (in consideration of its age) with the advent of C++ 11 / 14; I'll also show how they can be synthesised with some locality (but a lot of messing around) in "old" C++ (C++'98 and before).

First, C#, which has great support in recent forms of the language, but didn't start out that way.

A while back I was helping out a buddy with a WinForms scientific application, which involved a large and sophisticated amount of user input, spread over numerous forms (separated by a two-level nesting of tabs). The challenge was that inputs in many parts of each form depended on the presence and on the values of input in other parts of each form, and there were also such dependencies between parts of different forms.

Dealing with invalid user input follows the established troika of detect, report, respond (just as it does for dealing with failure). For a UI program in the activity of eliciting user input, detection and response are pretty run of the mill:
  • Detection, is simply determining whether the given input is (in)valid, such as negative numbers or alpha/punct in a numeric field. This may be done when a control is being populated, when focus leaves the input control, or when a button is pressed (whether to dismiss a dialogue or to fill out a further portion of the form). Doing so on button pressing is the easiest, and that was the case here;
  • Response, for user input, is, of course, in the purview of the user. The key to eliciting a good  response (and resulting in good user experience) is good reporting.
From experience - think MFC programming a long time ago - I've found it useful to report invalid input in forms to users by two simultaneous actions:
  • set the focus to the control containing the "offending" values (which may include just missing values); and
  • change the control background to an eye-catching colour, and then set it back when focus leaves the control (presumably with a correct value, set there by the now, ahem, focused user).
Back in the days of Windows UI programming in C/C++, whether straight to the Win32 API, using MFC, ATL, or whatever, setting up such functionality involved a lot of coding and the involvement of multiple components. (Dare one remind gentle readers about control reflection ... ???)

In WinForms, it's straightforward enough to go to a control - just call ctrl.Focus() - and to set the background colour - just assign to ctrl.BackColor - but how do we get it to change back when the focus leaves the control without involving the rest of the program?

Here's where C#"s closures come to the rescue. Rather than any more waffle, let me just present the solution (coded for TextBox) as it appears in the utility code:

public static void FocusAndHighlight(TextBox tbx, Color highlightColour)
{
  // Have to declare these two as variables, since they
  // will be "closed over" by the lamdba (aka closure)

  Color         originalClr = tbx.BackColor;
  EventHandler  handler     = null;

  // Note that handler has to be initialised and then
  // separately assigned

  handler = (object sender, EventArgs e) =>
  {
    tbx.BackColor = originalClr;

    tbx.Leave -= handler;
  };

  tbx.Leave += handler;
  tbx.BackColor = highlightColour;
  tbx.Focus();
}

I trust the logic here is pretty straightforward:

  • remember the original background colour of the control;
  • create a handler, using lambda syntax, that returns the background colour to its original and attach it to the control's Leave event;
  • set the background colour to the required highlight;
  • set focus to the control.
The key to making this work is to declare two local variables - originalClr, which stores the original colour, and handler, which allows the handler to be removed from the control's Leave events, returning it to its original state entirely when the focus is left. 

Declaring the variables is essential, and this is something you must remember when working with closures in C# (and in many other languages): closures closes around variables, not values.

With this component, getting the user's attention and taking them to where they need to be is a single call:

if(0 == tbx.Text.Length)
{
  ControlUtil.FocusAndHighlight(tbx, Color.Yellow);
}



No comments:

Post a Comment