#1,071 – How TextBox Reacts to Gaining Keyboard Focus

If you have an application with several TextBox controls, you’ll notice that the TextBox that currently has focus “lights up” by drawing a light blue border around the edge of the TextBox.

1071-001

The TextBox draws the blue border by setting up a trigger in its control template.  You can see the body of the control template by right-clicking the TextBox in Visual Studio from the design surface and selecting Edit Template, followed by Edit a Copy.

1071-002

When you do this, you’ll be asked to give the new copy a name (e.g. TextBoxStyle1).  You’ll then get the full body of the template in the XAML document.

Looking at this template, you’ll see a Trigger on the IsKeyboardFocused property that sets the value of the BorderBrush on a Border element.  It sets it to a static resource, which is defined earlier in the template (to a light blue color).

1071-002 1071-003

#1,070 – Event Sequence for Keyboard Focus Events

Events raised by user interface elements that are related to keyboard focus are:

  • PreviewLostKeyboardFocus  (tunneling)
  • PreviewGotKeyboardFocus  (tunneling)
  • LostKeyboardFocus  (bubbling)
  • GotKeyboardFocus  (bubbling)

When keyboard focus changes from one control to another, the events fired by the controls are in the order listed above.

Suppose that we have two TextBox controls in a StackPanel, which is contained within a Window.  If the first TextBox has keyboard focus and the user causes the second TextBox to receive focus, the sequence of events is as follows:

  • Window fires PreviewLostKeyboardFocus
  • StackPanel fires PreviewLostKeyboardFocus
  • TextBox #1 fires PreviewLostKeyboardFocus
  • Window fires PreviewGotKeyboardFocus
  • StackPanel fires PreviewGotKeyboardFocus
  • TextBox #2 fires PreviewGotKeyboardFocus
  • TextBox #1 fires LostKeyboardFocus
  • StackPanel fires LostKeyboardFocus
  • Window fires LostKeyboardFocus
  • TextBox #2 fires GotKeyboardFocus
  • StackPanel fires GotKeyboardFocus
  • Window fires GotKeyboardFocus

1070-001

#1,069 – Main Window Initially Has Keyboard Focus

When you first start a WPF application which contains elements that can get keyboard focus (e.g. TextBox), it’s the main Window that has the keyboard focus when the application starts.

We can see this by using code that reports the current keyboard focus within a Label at the bottom of the window.

1069-001

If we attach an event handler to the main Window for the TextInput event and use the handler to log information about the event, we can start typing after the window comes up and see that the Window is getting TextInput events based on what we type.  Nothing is rendered to the screen, but our logging indicates that TextInput events are being fired, with the main window as their source.

        private void Window_TextInput(object sender, TextCompositionEventArgs e)
        {
            Trace.WriteLine(string.Format("Window_TextInput: [{0}], source={1}", e.Text, e.Source.ToString()));
        }

1069-002

#1,068 – Most Controls Can Get Keyboard Focus

In WPF, controls that a user interacts with are typically able to get keyboard focus.  (Focusable property is true).  This includes controls that supported text-based input, like the TextBox.  But it also includes controls that the user typically interacts with using the mouse, rather than the keyboard.

Controls that cannot get keyboard focus are ones like the Label that the user does not interact with.

For example, a user will typically use only the mouse when interacting with a Button or a CheckBox, but both of these controls can get keyboard focus.  This is because the user can also interact with these controls using the keyboard.  (E.g. Enter key to “click” a Button, or Spacebar to toggle a CheckBox).

The example below uses code to detect which control has focus.  Note that as we tab through the controls, the TextBox controls get focus, as well as the Button and the CheckBox.

1068-001

1068-002

1068-003

#1,067 – Experimenting with Keyboard Focus

A control has keyboard focus if it can accept input from the keyboard.  The sample code below is a working app that lets us experiment with keyboard focus by setting a Label to indicate which control has focus.

XAML includes some controls and defines a GotKeyboardFocus for the top-level Window.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Keyboard Focus" Width="320" Height="190"
        GotKeyboardFocus="Window_GotKeyboardFocus">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <StackPanel Background="AliceBlue" Margin="5">
                <TextBox Name="txtLeft" Margin="5" Width="80"/>
                <Button Name="btnClickMe" Content="Click Me" Margin="5"/>
            </StackPanel>

            <StackPanel Background="PaleGoldenrod" Margin="5">
                <Label Content="I'm a Label"/>
                <TextBox Name="txtRight" Margin="5" Width="80"/>
                <CheckBox Name="chkCheckMe" Margin="5" Content="Check Me"/>
            </StackPanel>
        </StackPanel>

        <Label Grid.Row="1" Margin="5" Content="{Binding WhoHasFocus}"/>
    </Grid>
</Window>

Code-behind uses this code to set property indicating who has focus.

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
//using System.Windows.Media;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        // INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void RaisePropertyChanged(string propName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }

        private string whoHasFocus;
        public string WhoHasFocus
        {
            get { return whoHasFocus; }
            set {
                if (value != whoHasFocus)
                {
                    whoHasFocus = value;
                    RaisePropertyChanged("WhoHasFocus");
                }
            }
        }

        private void Window_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            CheckKeyboardFocus();
        }

        private void CheckKeyboardFocus()
        {
            IInputElement elem = Keyboard.FocusedElement;

            if (elem == null)
                WhoHasFocus = "NO FOCUS";
            else
            {
                FrameworkElement felem = elem as FrameworkElement;
                if (felem != null)
                {
                    string identifier = ((felem.Name != null) && (felem.Name.Length > 0)) ?
                        felem.Name :
                        felem.GetType().ToString();
                    WhoHasFocus = string.Format("FrameworkElement [{0}]", identifier);
                }
                else
                {
                    // Maybe a FrameworkContentElement has focus
                    FrameworkContentElement fcelem = elem as FrameworkContentElement;
                    if (fcelem != null)
                    {
                        string identifier = ((fcelem.Name != null) && (fcelem.Name.Length > 0)) ?
                            fcelem.Name :
                            fcelem.GetType().ToString();
                        WhoHasFocus = string.Format("FrameworkContentElement [{0}]", identifier);
                    }
                    else
                    {
                        WhoHasFocus = string.Format("Element of type [{0}] has focus", elem.GetType().ToString());
                    }
                }
            }
        }
    }
}

1067-001

#948 – Complete Example of Limiting TextBox Input

If you want to limit text allowed as input to a TextBox, a full strategy for checking text being input should include handling the PreviewKeyDown and PreviewTextInput events, as well as implementing a pasting handler.  Below is a full example that limits text input to alphabetic characters only.

        <TextBox Name="txtMyText" Margin="5" Height="100"
                 TextWrapping="Wrap"
                 AcceptsReturn="True"
                 VerticalScrollBarVisibility="Auto"
                 PreviewTextInput="TextBox_PreviewTextInput"
                 PreviewKeyDown="TextBox_PreviewKeyDown"/>

 

        public MainWindow()
        {
            this.InitializeComponent();
            DataObject.AddPastingHandler(txtMyText, PasteHandler);
        }

        private bool IsAlphabetic(string s)
        {
            Regex r = new Regex(@"^[a-zA-Z]+$");

            return r.IsMatch(s);
        }

        private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            // Prohibit non-alphabetic
            if (!IsAlphabetic(e.Text))
                e.Handled = true;
        }

        private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            // Prohibit space
            if (e.Key == Key.Space)
                e.Handled = true;
        }

        private void PasteHandler(object sender, DataObjectPastingEventArgs e)
        {
            TextBox tb = sender as TextBox;
            bool textOK = false;

            if (e.DataObject.GetDataPresent(typeof(string)))
            {
                // Allow pasting only alphabetic
                string pasteText = e.DataObject.GetData(typeof(string)) as string;
                if (IsAlphabetic(pasteText))
                    textOK = true;
            }

            if (!textOK)
                e.CancelCommand();
        }

#945 – A Strategy for Limiting Allowed Text in a TextBox

You can limit the text that a user enters into a TextBox by handling the PreviewTextInput event and setting the TextCompositionEventArgs.Handled property to true for characters that you do not want to allow as input.

The PreviewTextInput event will not give you access to every possible keystroke that you might want to use in limiting input.  It’s not fired, for example, when the user presses the spacebar.

You often will also want to handle the PreviewKeyDown event to block keystrokes that don’t trigger PreviewTextInput.

Finally, you may want to intercept Paste events on a TextBox, in order to filter out text that you don’t want a user to paste into the TextBox.

A full strategy for limiting user-entered text might then include:

  • Handling PreviewTextInput and blocking undesirable text
  • Handling PreviewKeyDown and blocking undesirable keystrokes
  • Handling paste operations and blocking undesirable text