The question says it all: I can use Events to select all the text in a DataGrid Custom TextBox, BUT it does not work when the TextBox is initially created (i.e. When the cell enters edit mode and create the TextBox).
IF I click in the TextBox after it is created the text is fully selected, but it should already be selected after the TextBox is displayed. This does not work. I tried setting Focus in Code, or using FocusManager in XAML but not helps.
Here is the Code (less the Dependency Properties):
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ccont = "clr-namespace:App.Controls">
<!-- Default style for the Validation Buttons -->
<Style TargetType="{x:Type ccont:vokDataGridEdit}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0"
ContextMenuService.Placement = "Right"
ContextMenuService.PlacementTarget = "{Binding Path=., RelativeSource={RelativeSource Self}}"
x:Name = "TextBox">
<TextBox.ContextMenu>
<ContextMenu x:Name="Menu">
<ContextMenu.Template>
<ControlTemplate>
<Border CornerRadius = "5"
Background = "LightGray"
BorderThickness = "1"
BorderBrush = "Gray"
Padding = "2">
<StackPanel Orientation="Vertical">
<!-- Title -->
<TextBlock Text="Test" x:Name = "Test" />
<!-- TODO: List of matches -->
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0" />
</StackPanel>
</Border>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And Code (Dependency Properties not shown):
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace App.Controls
{
/// <summary>
/// DataGrid Edit control (see: https://www.c-sharpcorner.com/article/wpf-routed-events/ for RoutedEvents)
/// </summary>
public class vokDataGridEdit : Control
{
static vokDataGridEdit()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Demo purpose only, check for previous instances and remove the handler first
if (this.GetTemplateChild("TextBox") is TextBox button)
{
button.PreviewMouseLeftButtonDown += this.SelectContentPreparation;
button.GotKeyboardFocus += this.SelectContent;
button.MouseDoubleClick += this.SelectContent;
//button.GotFocus += this.SelectContent;
}
}
/// <summary>
/// Prepare the Control to ensure it has focus before subsequent event fire
/// </summary>
private void SelectContentPreparation(object sender, MouseButtonEventArgs e)
{
if (sender is TextBox tb)
{
if (!tb.IsKeyboardFocusWithin)
{
e.Handled = true;
tb.Focus();
}
}
}
private void SelectContent(object sender, RoutedEventArgs e)
{
if (sender is TextBox tb)
{
e.Handled = true;
tb.SelectAll();
}
}
}
}
Ok finaly using a Behavior solved my issue in conjunction with using Events on the CustomControl. I still have no clues why it did not work using Events though...
CustomControl XAML:
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b = "http://schemas.microsoft.com/xaml/behaviors"
xmlns:ccont = "clr-namespace:App.Controls"
xmlns:res = "clr-namespace:App.Controls.Resources"
xmlns:valid = "clr-namespace:App.Controls.Validation"
xmlns:conv = "clr-namespace:Common.MVVM.Converter;assembly=Common.MVVM"
xmlns:behav = "clr-namespace:Common.MVVM.Behavior;assembly=Common.MVVM">
<!-- Default style for the Validation Buttons -->
<Style TargetType="{x:Type ccont:vokDataGridEdit}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">
<Grid>
<!-- !!! The Value edited !!! -->
<TextBox BorderThickness = "0"
x:Name = "textBox">
<!-- Create a binding proxy to serve binding properties to data validation Binding Wrapper
see: https://social.technet.microsoft.com/wiki/contents/articles/31422.wpf-passing-a-data-bound-value-to-a-validation-rule.aspx -->
<TextBox.Resources>
<valid:BindingProxy x:Key="proxy" Context="{Binding HintsInternal, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"/>
</TextBox.Resources>
<!-- Bind with data validation -->
<TextBox.Text>
<Binding RelativeSource = "{RelativeSource AncestorType=ccont:vokDataGridEdit}"
Path = "Text"
Mode = "TwoWay"
UpdateSourceTrigger = "PropertyChanged"
ValidatesOnExceptions = "True">
<Binding.ValidationRules>
<valid:vokDataGridEditValidation>
<valid:vokDataGridEditValidation.Bindings>
<valid:vokDataGridEditValidationBindings InvalidEntries="{Binding Context, Source={StaticResource proxy}}" />
</valid:vokDataGridEditValidation.Bindings>
</valid:vokDataGridEditValidation>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<!-- Select all text on focus -->
<b:Interaction.Behaviors>
<behav:TextBoxSelectAllBehavior />
</b:Interaction.Behaviors>
</TextBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And CustomControl event declarations:
/// <summary>
/// DataGrid Edit control (see: https://www.c-sharpcorner.com/article/wpf-routed-events/ for RoutedEvents)
/// </summary>
public class vokDataGridEdit : Control
{
#region Initialization
static vokDataGridEdit()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
}
/// <summary>
/// Set event handlers when applying the template
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// OnLoaded set focus to the TextBox (lambda and own Event need not Garbage Collection) and ensure it has focus for new text input
if (this.GetTemplateChild("textBox") is TextBox textBox)
{
this.Loaded += (sender, e) => { textBox.Focus(); };
this.PreviewTextInput += (sender, e) => { textBox.Focus(); };
}
}
#endregion
// Skipping Dependency Properties
}
And Finaly the behavior based on an Answer by user Rekshino here (https://stackoverflow.com/a/60844159/12886393):
/// <summary>
/// Behavior for control to select all text in TextBox on GotFocus
/// </summary>
public class TextBoxSelectAllBehavior : Behavior<TextBox>
{
/// <summary>
/// Flag marking if Selection is to be performed on MouseUp
/// </summary>
private bool _doSelectAll = false;
/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.GotFocus += this.AssociatedObject_GotFocus;
this.AssociatedObject.PreviewMouseUp += this.AssociatedObject_MouseUp;
this.AssociatedObject.PreviewMouseDown += this.AssociatedObject_MouseDown;
}
/// <summary>
/// Select all via dispatcher if action set for MouseUp
/// </summary>
private void AssociatedObject_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (this._doSelectAll)
{
this.AssociatedObject.Dispatcher.BeginInvoke((Action)(() => { this.AssociatedObject.SelectAll(); }));
}
this._doSelectAll = false;
}
/// <summary>
/// Triggers SelectAll on mouse up if focus was not set
/// </summary>
private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this._doSelectAll = !this.AssociatedObject.IsFocused;
}
/// <summary>
/// Selects all
/// </summary>
private void AssociatedObject_GotFocus(object sender, System.Windows.RoutedEventArgs e)
{
this.AssociatedObject.SelectAll();
}
/// <summary>
/// Clean-up the behavior
/// </summary>
protected override void OnDetaching()
{
this.AssociatedObject.GotFocus -= this.AssociatedObject_GotFocus;
this.AssociatedObject.PreviewMouseUp -= this.AssociatedObject_MouseUp;
this.AssociatedObject.PreviewMouseDown -= this.AssociatedObject_MouseDown;
base.OnDetaching();
}
}