#1,073 – Attached Events

When events are routed in WPF, elements up (bubbling) or down (tunneling) the logical tree are given the chance to add a handler for a particular event.

For example, if you have a Button within a StackPanel and the user clicks on the Button, the Button will raise a Click event, but also bubble the event up to the StackPanel so that it can also raise the Click event.

Click is defined as a RoutedEventHandler in the ButtonBase class.  There is also a public static ClickEvent object of type RoutedEvent defined in ButtonBase.  So we think of ButtonBase as “owning” the Click event.  This makes sense, since buttons are generally the elements that will raise Click events.

The StackPanel does not define a Click event, but can define a handler for a routed event object that it does not own.  This is known as an attached event.

#1,072 – Adding Custom Triggers Related to Keyboard Focus

The default control template for the TextBox control changes the color of the Border around the control when the control gets focus.  It does this by using a trigger hooked to the IsKeyboardFocused property.

You can add your own triggers related to keyboard focus by defining a custom property trigger.  The XAML fragment below defines a new Style element that changes the Background of the control when IsKeyboardFocused is true.

    <Window.Resources>
        <Style x:Key="HoneydewFocus" TargetType="TextBox">
            <Style.Triggers>
                <Trigger Property="IsKeyboardFocused" Value="true">
                    <Setter Property="Background" Value="Honeydew"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <StackPanel>
        <TextBox Margin="5" Width="80"
                 Style="{StaticResource HoneydewFocus}"/>

        <TextBox Margin="5" Width="80"
                 Style="{StaticResource HoneydewFocus}"/>

        <Button Content="Click Me" HorizontalAlignment="Center"
                Padding="12,5" Margin="5" />
    </StackPanel>

As we tab between the two TextBox controls, the one with keyboard focus will have a Honeydew-colored background, as well as the default blue border.

1072-001

#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

See You at TechEd

I’ll Be at TechEd North America in Houston next week (12-15 May, 2014).  If you’re a fan of the 2,000 Things blogs and will be in Houston, look me up.  (E.g. DM @spsexton on Twitter).  We can go grab a beer and sing the praises of C#, WPF and all things .NET.

#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

#1,066 – Elements Must Be Visible and Enabled to Fire Events

Events fired from user interface elements in WPF are typically routed events, firing events from elements up or down the logical tree after the source element has fired the event.

A user interface element must be both visible (Visibility = Visible) and enabled (IsEnabled = true) in order to fire an event.  If an element is not visible or is disabled, the topmost element beneath the element may fire the event instead.

Below, if we set IsEnabled on the Label to false, its MouseDown event will not fire and the StackPanel will become the source of the event instead.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Events" Width="208" Height="183"
        MouseDown="Window_MouseDown">

    <StackPanel MouseDown="StackPanel_MouseDown"
                Background="AliceBlue" Margin="10">
        <Label Content="Label" Background="Orange" Margin="10"
               MouseDown="Label_MouseDown"
               IsEnabled="False"/>
    </StackPanel>
</Window>

When we click on the Label, the StackPanel fires the MouseDown event, followed by the parent Window.

1066-002

1066-001

#1,065 – ViewBox Child Must Have Explicit Size

Whatever content you set as the content wrapped by a ViewBox, that content needs to be able to determine its own size.  The ViewBox needs to know what size to make the content at a scale of 1.0.

If the ViewBox wraps a panel containing elements that can size based on their own content, like buttons, everything works fine.

1065

However, if the ViewBox wraps a Canvas, you need to give the Canvas an explicit size so that the ViewBox knows what size to start with.  If you don’t do this, the ViewBox won’t be able to scale the content within the Canvas.

1065-001