Code Focused

Windows Forms: Don't Skimp on the Caps Lock Warning

If you've ever worked in an enterprise environment and fielded calls from employees unable to get into their accounts due to a depressed Caps Lock key, you know how essential the following tweak to your code can be.

When you activate the password-entry features on a Windows Forms TextBox control, you get a Caps Lock warning bubble for free (see Figure 1). The popup message provides a gentle reminder that it might be prudent to check the keyboard state before entering that secure password. And that gentleness is a big part of the problem. This isn't the era of ho-hum black-and-white Flash Gordon serials. We have the Marvel Cinematic Universe! If people are already finding those multi-hero fight scenes mundane, you better believe that you need more power in your Caps Lock warnings.

Well, perhaps it's not that bad. But there’s also the issue that if Caps Lock is already activated when the password field first appears, the message bubble doesn't show up. For these and various other reasons, I sometimes add a dedicated warning message right on the form. (See Figure 2.)

[Click on image for larger view.] Figure 1. The Standard Caps Lock Warning
[Click on image for larger view.] Figure 2. A Better Caps Lock Warning

It's a fairly straightforward coding process. First, add your password TextBox control to a form, and next to it put a Panel control, laden with warning signs and with its Visible property set to False. Then add the event handlers that manage the visibility of the warning message, as shown in Listing 1.

Listing 1: Managing Warning Message Visibility
private void PasswordField_Enter(object sender, EventArgs e)
{
  // ----- Monitor caps lock on arrival.
  PanelWarning.Visible = Control.IsKeyLocked(Keys.CapsLock);
}

private void PasswordField_Leave(object sender, EventArgs e)
{
  // ----- Monitor caps lock on departure.
  PanelWarning.Visible = false;
}

private void PasswordField_KeyDown(object sender, KeyEventArgs e)
{
  // ----- Monitor caps lock on activity.
  PanelWarning.Visible = Control.IsKeyLocked(Keys.CapsLock);
}

The problem is that the .NET-supplied message bubble still shows up, obscuring that lovely handcrafted warning panel. Under the hood, Windows Forms is deciding to show that bubble by monitoring control-level Windows messages, akin to .NET events. For tool-tip bubbles, a message known as EM_SHOWBALLOONTIP triggers the standard Caps Lock presentation. If you disable the monitoring of that message in the password textbox, the interruption goes away.

To build a tip-less text entry control, add a new class to your project named QuietTextBox and adjust it to use the standard TextBox class as its base class:

// ----- Assumes: using System.Windows.Forms;
public class QuietTextBox : TextBox
{
}

Internal messages within Windows are identified by 32-bit pre-defined integers, and the balloon-tip message has been assigned the hexadecimal value of 0x1503. Add that definition to the class:

private const int EM_SHOWBALLOONTIP = 0x1503;

Every form and control class in Windows Forms applications has a protected method named WndProc that handles all of the event-driven activity exposed by that form or control. To disable the balloon-tip message, override that method, watch for the specific message and add code that causes it to have no impact:

protected override void WndProc(ref Message m)
{
  // ----- Prevent the system-level Caps Lock message from showing up.
  if (m.Msg == EM_SHOWBALLOONTIP)
  {
    m.Result = (IntPtr)0;
    return;
  }
  base.WndProc(ref m);
}

That's all you need to hide the popup message. Build the project to make it available within the Windows Forms designer. There are a few ways to integrate this new control into a form, but I've found it easiest to add a regular password TextBox control to a form and then modify its definition within the form's Designer.cs file. Open that file and locate the two lines that define the password TextBox control:

// ----- Within the InitializeComponent method:
this.PasswordField = new System.Windows.Forms.TextBox();

// ----- And later, near the bottom of the file:
private System.Windows.Forms.TextBox PasswordField;

Modify these two lines, replacing the reference to the built-in TextBox control with the new QuietTextBox class:

// ----- Within the InitializeComponent method:
this.PasswordField = new QuietTextBox();

// ----- And later, near the bottom of the file:
private QuietTextBox PasswordField;

Implementing a similar Caps Lock warning in a Windows Presentation Foundation (WPF) application is even easier, because the PasswordBox control in that system doesn't present a bubble message automatically when Caps Lock is activated. As in a Windows Forms application, you add your password entry field and the warning-related elements to the XAML window surface:

<!-- NOTE: Most attributes omitted for brevity. -->
<PasswordBox x:Name="PasswordField" TabIndex="1"
  GotFocus="PasswordField_GotFocus"
  LostFocus="PasswordField_LostFocus"
  KeyDown="PasswordField_KeyDown" ... />
<Border x:Name="PanelWarning" Visibility="Hidden" ... >
  <Canvas ...>
    <Label Content="Warning!" ... />
    <Label Content="Caps Lock Is On" ... />
  </Canvas>
</Border>

Then add the event handlers, which will have slightly different event names and content from those in Windows Forms, as shown in Listing 2.

Listing 2: Adding Event Handlers
private void PasswordField_GotFocus(object sender, RoutedEventArgs e)
{
  // ----- Monitor caps lock.
  PanelWarning.Visibility =
    ((Keyboard.GetKeyStates(Key.CapsLock) & KeyStates.Toggled) > 0)
    ? Visibility.Visible : Visibility.Hidden;
}

private void PasswordField_LostFocus(object sender, RoutedEventArgs e)
{
  // ----- Monitor caps lock.
  PanelWarning.Visibility = Visibility.Hidden;
}

private void PasswordField_KeyDown(object sender, KeyEventArgs e)
{
  // ----- Monitor caps lock.
  PanelWarning.Visibility =
    ((Keyboard.GetKeyStates(Key.CapsLock) & KeyStates.Toggled) > 0)
    ? Visibility.Visible : Visibility.Hidden;
}

Caps Lock warnings might not seem like a big deal. But if you’ve ever worked in an enterprise environment and fielded calls from employees unable to get into their accounts due to a depressed (physically, not emotionally) Caps Lock key, then you know how essential such small updates to your code can be.

About the Author

Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube