WPF has made it possible to create beautiful and compelling user interfaces in .NET - but there are a few   times when the built-in user experience behavior of some of the controls isn't quite what you'd expect.
One of these areas happens to be the focus behavior of text boxes.
The Problem
A common behavior in most Windows applications that users are accustomed to(often without realizing it),   is that upon focus entering a textbox, the text is automatically selected. This makes it possible to click or tab in to a textbox and immediately start typing - and content will be automatically replaced. This behavior is part of standard Windows text   boxes ... but not the WPF TextBox   control.
The approach to resolving this that may occur to you, is to handle focus events on all  TextBox control instances and call the convenient  SelectAll() method to highlight all content. Unfortunately, it's not   quite that simple. Beyond the fact that wiring every TextBox control   in your application gets old fast, there are a number of subtle edge cases where this approach fails.
We're going to look at how we can restore this behavior to text box controls in your WPF applications with   as little pain as possible - and make it work they way users would expect - in   all cases.
The Approach
The first issue we're going to tackle is how to respond to focus behavior on all text boxes throughout an   application. It may occur to you to create a global style for text box controls and use an    EventSetter to subscribe to    Control.GetFocus. Unfortunately, styles have a number of limitations that make them less than ideal for this purpose. You can't apply multiple styles to a single control in WPF - which means that text   box styles throughout your application would need to replicate this behavior. The best you can do is create a common style that all other TextBox styles are based on. This is a fragile and awkward approach at best - and at worst it becomes problematic if one   of the derived styles needs to apply its own processing on GetFocus.
Fortunately there's a better way: the  EventManager   class. EventManager provides us with a way to globally subscribe to   events for a particular type of control, rather than on an instance-by-instance basis. As it turns out, a convenient place to put this code is in your Application class'  OnStartup() method:
protected override void  OnStartup(StartupEventArgs  e)
{
     EventManager.RegisterClassHandler(typeof(TextBox),  UIElement.GotFocusEvent,  
new RoutedEventHandler(SelectAllText), true);
new RoutedEventHandler(SelectAllText), true);
    base.OnStartup(e);
}
  
  
private static void  SelectAllText(object  sender,  RoutedEventArgs  e)
{
     var  textBox = e.OriginalSource  as  TextBox;
     if  (textBox !=  null)
        textBox.SelectAll();
}
So we're done now, right? Well ... not quite. A little experimentation will show that while using the keyboard   to set focus works well, using the mouse doesn't work as expected: when clicked, the selection initially appears, and then promptly vanishes. The reason for this is that the  TextBox control internally responds to the  MouseDown event, which fires after  GotFocus. The text box relies on that event to place the insertion   caret at the location in the text box where the user clicked. Since all of this happens  GotFocus, the selection we set is lost. We're going to fix that.
The Ultimate Solution
The solution presented here is a little different from that above - basically, we're going to divide focus   processing into two separation mechanisms - one that deals with focus changes from the keyboard, and another that responds to focus established by clicking the mouse.
Dealing with keyboard focus is easy - WPF has a  UIElement.GotKeyboardFocus   event that we can respond to instead of the more generalized Control.GotFocus.   The mouse is a little bit trickier. First, we don't want to break the default click handling that  TextBox implements - so we're going to instead use the  PreviewMouseLeftButtonDown   event to intercept clicks before the control responds to them.   Preview events, also known as tunneling events, are routed events where the direction of the route travels from the application root towards the element that raised the event and is reported as the   source in event data. We can use the preview mouse event to selectively handle the mouse click only in those cases when the focus is not yet transferred to the text box.
Now that we've established the approach, let's look at the code:
public partial class  App  :  Application
{
     protected override void  OnStartup(StartupEventArgs  e)
    {
         EventManager.RegisterClassHandler(typeof(TextBox),  UIElement.PreviewMouseLeftButtonDownEvent,
            new  MouseButtonEventHandler(SelectivelyHandleMouseButton),  true);
         EventManager.RegisterClassHandler(typeof(TextBox),  UIElement.GotKeyboardFocusEvent,
           new  RoutedEventHandler(SelectAllText),  true);
         base.OnStartup(e);
    }
     private static void  SelectivelyHandleMouseButton(object  sender,  MouseButtonEventArgs  e)
    {
         var  textbox = (sender  as  TextBox);
         if  (textbox !=  null  && !textbox.IsKeyboardFocusWithin)
        {
             if( e.OriginalSource.GetType().Name ==  "TextBoxView"  )
            {
                e.Handled =  true;
                textbox.Focus();
            }
        }
    }
     private static void  SelectAllText(object  sender,  RoutedEventArgs  e)
    {
         var  textBox = e.OriginalSource  as  TextBox;
         if  (textBox !=  null)
            textBox.SelectAll();
    }  
}
So let's break it down. In the  OnStartup() method we globally register the  GotKeyBoardFocus and  PreviewMouseLeftButtonDown events to the handlers below.
The  SelectAllText() method is the same as before. The  SelectivelyHandleMouseButton() method provides the code that corrects   the focus behavior on clicks. First, it checks that we're actually dealing with a  TextBox control by casting the  sender parameter to the appropriate type. If keyboard focus is not   yet in that text box (which we check using the IsKeyboardFocusWithin   property), it then marks the event as handled, and calls textbox.Focus()   to transfer focus (which will trigger the GotKeyboardFocus event for   us).
There's only one last bit of code here that deserves an explanation, the line:
   if(   e.OriginalSource.GetType().Name == "TextBoxView"  )
The last edge case we need to handle is when the user clicks in the "chrome" portion of a  TextBox - things like the border or scroll bars. It would be really   awkward to select the text each time the user clicked the scrollbar. To fix this, we need to test that the mouse click originated in the "textual" portion of the control - which happens to be implemented by the  TextBoxView class. WPF is helps us out by notifying us of the original   source of the event via the RoutedEventArgs.OriginalSource   property. Unfortunately, this class is not public outside of WPF infrastructure code - so we have to test for it by it's name. This is by far the least attractive aspect of the code shown above ... but sadly, it's the only reliable solution I've found so far.
Conclusion
So let's look at what we've learned so far.
-   The built-in WPF  TextBox control doesn't automatically select text when it receives focus.  
-   We can globally respond to events from any WPF control using the  EventManager.RegisterGlobalHandler() method.  
-   We can use the  OnStartup() method of the  Application class to centrally register our handlers when the application loads.  
-   Focus behavior in WPF varies slightly depending on whether the control receives focus via the mouse or keyboard.  
-   We can use the  PreviewXXX family of events to intercept clicks and supply our own event handling before the  TextBox controls responds.  
-   We can use the  RoutedEventArgs.OriginalSource property to avoid highlighting a selection when the original   click events occurs within a non-content area of the TextBox control.  
Put this all together, and we have an elegant, centralized solution to selecting the text inside the TextBox   control when it receives focus.
 
 
No comments:
Post a Comment