Set keyboard focus when the user begins editing a wpf datagrid templatecolumn ?

The problem:
When I begin editing a grid cell, I'd expect the keyboard focus to be set the edit control for the cell. In case of a textbox, I'd also like the existing contents to be selected by default ; so that i can begin typing and set a new value quickly.


This works for the default data grid column types... except for a DataGridTemplateColumn ; a column whose display & edit behavior can be customized. You can use a templateselector to dynamically choose the templates to be used for displaying and editing values.

However if you attempt to tab into a TemplateColumn cell and hit F2 to begin editing, you'd find that you can't start typing in it. You need to click in it once or press Tab once more to do that. Compare the behavior of a TextColumn and TemplateColumn showing the same bound value as shown in the code below. The TextColumn behaves as expected. The edit control receives keyboard focus and all the text in it is selected.

Highly unintuitive.


The fix for the keyboard focus thing turned out to be setting FocusManager.FocusedElement attached property on the desired control in your Data Template for editing. Check out the sample below with/without the FocusedElement property setter :



...
<DataTemplate x:Key="DefaultTitleTemplate">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate x:Key="EditTitleTemplate">

<TextBox x:Name="Fox"

FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource HighlightTextBoxStyle}"
>
</TextBox>
</DataTemplate>
</Grid.Resources>
<DockPanel>
<TextBox DockPanel.Dock="Top" x:Name="Test" Text="{Binding Path=(FocusManager.FocusedElement).Name, ElementName=MyWindow}"
Style="{StaticResource HighlightTextBoxStyle}"/>
<toolkit:DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTemplateColumn Header="Templated Title"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}">

</toolkit:DataGridTemplateColumn>
<toolkit:DataGridTextColumn Header="Title" >
<toolkit:DataGridTextColumn.Binding>
<Binding Path="Title">
<Binding.ValidationRules>
<local:CannotBeginWithX/>
</Binding.ValidationRules>
</Binding>
</toolkit:DataGridTextColumn.Binding>
</toolkit:DataGridTextColumn>
...



Still struggling to find the
fix to auto-select the text in the textbox in the expanded edit cell template.

Update: Got this to work by subscribing to the PreparingCellForEdit event. The editor is passed in as an event argument. For a templatedcolumn, the grid is unable to guess your editor control type. So you need to cast it to your particular editor type and call Focus()+SelectAll() on it. I subscribed to the event in the ctor and wasn't able to get the editor.. You need to wait till the Loaded event to subscribe. Then it worked.

3 comments:

  1. This works when you have only one templatecolumn. with multiple templatecolumns it doesn't work.

    ReplyDelete
  2. Worked like a charm. Thanks a lot!

    ReplyDelete
  3. To select the text in a textbox into the celleditingtemplate, you can use a generic attachedbehaviour. See below the attached property that can be used to do that:

    #region SelectTextOnFocusProperty
    public static readonly DependencyProperty SelectTextOnFocusProperty =
    DependencyProperty.RegisterAttached("SelectTextOnFocus", typeof(Boolean),
    typeof(AttachedBehaviors), new UIPropertyMetadata(false, OnSelectTextOnFocusPropertyChanged));
    public static bool GetSelectTextOnFocus(UIElement obj)
    {
    return (bool)obj.GetValue(SelectTextOnFocusProperty);
    }
    public static void SetSelectTextOnFocus(UIElement obj, bool value)
    {
    obj.SetValue(SelectTextOnFocusProperty, value);
    }
    private static void OnSelectTextOnFocusPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
    UIElement ct = depObj as UIElement;
    if (e.NewValue is bool == false)
    return;
    if ((bool)e.NewValue)
    {
    ct.GotFocus += OnGotFocusHandler;
    ct.LostFocus += OnLostFocusHandler;
    }
    else
    {
    ct.KeyDown -= OnKeyDownHandler;
    }
    }
    private static void OnGotFocusHandler(Object sender, RoutedEventArgs e)
    {
    try
    {
    TextBoxBase tb = e.OriginalSource as TextBoxBase;
    if (tb != null)
    {
    tb.SelectAll();
    return;
    }
    PasswordBox pw = e.OriginalSource as PasswordBox;
    if (pw != null)
    {
    pw.SelectAll();
    return;
    }
    }
    catch
    {
    //Util.ShowEventHandlerError(ex);
    }

    }
    private static void OnLostFocusHandler(Object sender, RoutedEventArgs e)
    {
    try
    {
    TextBoxBase tb = e.OriginalSource as TextBoxBase;
    if (tb != null)
    {
    TextBox tx = tb as TextBox;
    if (tx != null)
    {
    tx.Select(0, 0);
    return;
    }
    RichTextBox rtx = tb as RichTextBox;
    if (rtx != null)
    {
    TextPointer tp1 = rtx.Document.ContentStart.GetPositionAtOffset(0); ;
    rtx.Selection.Select(tp1, tp1);
    return;
    }
    }
    PasswordBox pw = e.OriginalSource as PasswordBox;
    if (pw != null)
    {
    pw.Password = pw.Password;
    return;
    }

    }
    catch
    {
    //Util.ShowEventHandlerError(ex);
    }

    }
    #endregion

    ReplyDelete