CellEditingTemplate 上 DataGrid 中自定义文本框控件上的 SelectAll 文本
SelectAll text on Custom TextBox control in a DataGrid on CellEditingTemplate
问题说明了一切:我可以使用事件 select DataGrid 自定义文本框中的所有文本,但是当最初创建文本框时它不起作用(即当单元格进入编辑模式并且创建文本框)。
如果我在文本框创建后单击它,文本将完全 selected,但在显示文本框后它应该已经 selected。这是行不通的。我尝试在代码中设置焦点,或在 XAML 中使用 FocusManager 但没有帮助。
这是代码(减去依赖属性):
<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>
和代码(未显示依赖属性):
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();
}
}
}
}
好的,最终使用行为解决了我的问题,同时在 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>
和 CustomControl 事件声明:
/// <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
}
最终行为基于用户 Rekshino 的回答 ():
/// <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();
}
}
问题说明了一切:我可以使用事件 select DataGrid 自定义文本框中的所有文本,但是当最初创建文本框时它不起作用(即当单元格进入编辑模式并且创建文本框)。
如果我在文本框创建后单击它,文本将完全 selected,但在显示文本框后它应该已经 selected。这是行不通的。我尝试在代码中设置焦点,或在 XAML 中使用 FocusManager 但没有帮助。
这是代码(减去依赖属性):
<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>
和代码(未显示依赖属性):
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();
}
}
}
}
好的,最终使用行为解决了我的问题,同时在 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>
和 CustomControl 事件声明:
/// <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
}
最终行为基于用户 Rekshino 的回答 (
/// <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();
}
}