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();
    }
}