如何在WPF TextBox中自动选择全部文本?

264

如果我从GotFocus事件处理程序中调用SelectAll,使用鼠标操作时它不起作用-选择会在鼠标释放时消失。

编辑:人们喜欢Donnelle的答案,我试图解释为什么我不像已被接受的答案那样喜欢它。

  • 它更复杂,而已被接受的答案以更简单的方式完成了相同的事情。
  • 已接受答案的可用性更好。当您在文本中间单击时,释放鼠标后文本会取消选择,使您能够立即开始编辑,如果您仍想全选,只需再次按下按钮,这次它不会在释放时取消选择。按照Donelle的方法,如果我在文本中间单击,我必须第二次单击才能进行编辑。如果我单击文本内部而不是外部,则这很可能意味着我要开始编辑而不是覆盖所有内容。

如果你将要有多个表单,她的答案会比第一个更简单。两种选项的可用性都是无关紧要的,因为你可以改变它们中的任何一个的工作方式。 - thepaulpage
1
@Sergey:您可能想要更改此问题的已接受答案,因为自那以后有更好的答案。我不会建议我的,但您可以 ;) - Grokys
问题有Silverlight标签,但是Silverlight并没有大多数事件/任何类型的预览事件。那么应该使用哪种解决方案来处理Silverlight呢? - Valentin Kuzub
链接“为什么WPF中的焦点如此棘手?”已损坏。 - Maxence
1
如下所述,在 https://dev59.com/SHRB5IYBdhLWcg3wUVxd#2553297 的评论中提到,http://madprops.org/blog/wpf-textbox-selectall-on-focus/ 是一个简单的解决方案,并且保留了原始的鼠标行为。我将事件注册放在构造函数中,因为应用程序中只有一个 WPF 控件。 - CAD bloke
http://stackoverflow.com/questions/31291770 - amit jha
35个回答

223

我们这样做是为了第一次点击时全选,再次点击将光标移到指定位置(我们的应用程序设计用于使用带笔的平板电脑)。

你可能会觉得这很有用。

public class ClickSelectTextBox : TextBox
{
    public ClickSelectTextBox()
    {
        AddHandler(PreviewMouseLeftButtonDownEvent, 
          new MouseButtonEventHandler(SelectivelyIgnoreMouseButton), true);
        AddHandler(GotKeyboardFocusEvent, 
          new RoutedEventHandler(SelectAllText), true);
        AddHandler(MouseDoubleClickEvent, 
          new RoutedEventHandler(SelectAllText), true);
    }

    private static void SelectivelyIgnoreMouseButton(object sender, 
                                                     MouseButtonEventArgs e)
    {
        // Find the TextBox
        DependencyObject parent = e.OriginalSource as UIElement;
        while (parent != null && !(parent is TextBox))
            parent = VisualTreeHelper.GetParent(parent);

        if (parent != null)
        {
            var textBox = (TextBox)parent;
            if (!textBox.IsKeyboardFocusWithin)
            {
                // If the text box is not yet focussed, give it the focus and
                // stop further processing of this click event.
                textBox.Focus();
                e.Handled = true;
            }
        }
    }

    private static void SelectAllText(object sender, RoutedEventArgs e)
    {
        var textBox = e.OriginalSource as TextBox;
        if (textBox != null)
            textBox.SelectAll();
    }
}

6
我在这里看到了一个几乎相同的答案http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/564b5731-af8a-49bf-b297-6d179615819f/,它也有效,但是它没有使用e.OriginalSource,也没有遍历视觉树。这样做有什么优势吗? - Marco Luglio
1
工作得很好,但如果仍允许使用鼠标拖选文本,那将更加完美。 Google Chrome地址栏是理想系统的完美示例:如果用户单击并释放而不拖动,则会突出显示整个文本。但是,如果用户单击并拖动,则拖动通常选择文本而不是全部选择。全选仅在鼠标释放时发生。我会进行调整,看看能否改进这个设计。 - devios1
2
这种解决方案的另一个缺点是,当您使用TextBox的“剪切/复制/粘贴”菜单时,选择任何菜单项时整个文本都会被选中。 - user128300
1
我发现在 textBox.IsFocusedSelectAllText 方法中增加一个测试可以改善它。当 GetKeyboardFocus 是由于通过 alt-tab 切换到程序时,您不希望选择全部内容。 - Scott Stafford
我只希望有更好的方法来修改正常工作,而不必在子类中继承和覆盖。 - C. Tewalt
显示剩余2条评论

181

Donnelle的答案效果最好,但是必须派生一个新类来使用它有点麻烦。

相反,我在App.xaml.cs中为应用程序中的所有TextBox注册处理程序。这使我能够在标准TextBox控件中使用Donnelle的答案。

在App.xaml.cs中添加以下方法:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e) 
    {
        // Select the text in a TextBox when it receives focus.
        EventManager.RegisterClassHandler(typeof(TextBox), TextBox.PreviewMouseLeftButtonDownEvent,
            new MouseButtonEventHandler(SelectivelyIgnoreMouseButton));
        EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotKeyboardFocusEvent, 
            new RoutedEventHandler(SelectAllText));
        EventManager.RegisterClassHandler(typeof(TextBox), TextBox.MouseDoubleClickEvent,
            new RoutedEventHandler(SelectAllText));
        base.OnStartup(e); 
    }

    void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
    {
        // Find the TextBox
        DependencyObject parent = e.OriginalSource as UIElement;
        while (parent != null && !(parent is TextBox))
            parent = VisualTreeHelper.GetParent(parent);

        if (parent != null)
        {
            var textBox = (TextBox)parent;
            if (!textBox.IsKeyboardFocusWithin)
            {
                // If the text box is not yet focused, give it the focus and
                // stop further processing of this click event.
                textBox.Focus();
                e.Handled = true;
            }
        }
    }

    void SelectAllText(object sender, RoutedEventArgs e)
    {
        var textBox = e.OriginalSource as TextBox;
        if (textBox != null)
            textBox.SelectAll();
    }
}

4
这是一个相当不错的解决方案,而且很久以前Matt Hamilton在这里描述过:http://madprops.org/blog/wpf-textbox-selectall-on-focus/。 - Ashley Davis
互联网上最佳答案。 - Sean
2
太棒了!非常感谢。我希望我能给这个点赞255次。 - Steven C. Britton
5
"Focused" 这个拼写在美国更常见;然而,在英国和加拿大有时也使用 "Focussed" 这个拼写,特别是在澳大利亚和新西兰。 - Donnelle
这是最好的答案! - Etienne Charland
显示剩余2条评论

101

我选择了Donnelle的答案的一部分(跳过了双击),因为我认为这更自然。然而,像Grokys一样,我不喜欢需要创建派生类的需求。但是我也不喜欢Grokys的OnStartup方法。而且我需要它是“通常但不总是”的基础。

我已经将其实现为附加的DependencyProperty,所以我可以在xaml中设置local:SelectTextOnFocus.Active = "True"。我发现这种方式最令人愉悦。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

public class SelectTextOnFocus : DependencyObject
{
    public static readonly DependencyProperty ActiveProperty = DependencyProperty.RegisterAttached(
        "Active",
        typeof(bool),
        typeof(SelectTextOnFocus),
        new PropertyMetadata(false, ActivePropertyChanged));

    private static void ActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox)
        {
            TextBox textBox = d as TextBox;
            if ((e.NewValue as bool?).GetValueOrDefault(false))
            {
                textBox.GotKeyboardFocus += OnKeyboardFocusSelectText;
                textBox.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
            }
            else
            {
                textBox.GotKeyboardFocus -= OnKeyboardFocusSelectText;
                textBox.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDown;
            }
        }
    }

    private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DependencyObject dependencyObject = GetParentFromVisualTree(e.OriginalSource);

        if (dependencyObject == null)
        {
            return;
        }

        var textBox = (TextBox)dependencyObject;
        if (!textBox.IsKeyboardFocusWithin)
        {
            textBox.Focus();
            e.Handled = true;
        }
    }

    private static DependencyObject GetParentFromVisualTree(object source)
    {
        DependencyObject parent = source as UIElement;
        while (parent != null && !(parent is TextBox))
        {
            parent = VisualTreeHelper.GetParent(parent);
        }

        return parent;
    }

    private static void OnKeyboardFocusSelectText(object sender, KeyboardFocusChangedEventArgs e)
    {
        TextBox textBox = e.OriginalSource as TextBox;
        if (textBox != null)
        {
            textBox.SelectAll();
        }
    }

    [AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
    [AttachedPropertyBrowsableForType(typeof(TextBox))]
    public static bool GetActive(DependencyObject @object)
    {
        return (bool) @object.GetValue(ActiveProperty);
    }

    public static void SetActive(DependencyObject @object, bool value)
    {
        @object.SetValue(ActiveProperty, value);
    }
}

为了实现我的“通常但不总是”功能,我在(全局)TextBoxStyle中将此附加属性设置为True。这样,“选择文本”始终处于“开启”状态,但我可以在每个文本框上禁用它。


9
+1 这比全局设置好多了,而且它更符合 WPF 的方式,比从 TextBox 派生更好。 - stijn
3
我同意stijn的观点,把你的代码藏在app.cs文件中不太好,因为可怜的开发人员需要弄清楚为什么SelectAllOnFocus事件会发生。 :-) 我只需将此代码放入我的TextBoxBehaviors类中,然后更新我的基本TextBox样式。 运作得很好。谢谢。 - Lee Campbell
2
@tronda:只需使用TextBox的TargetType在资源中添加样式即可。我建议您查看http://wpftutorial.net/Styles.html。 - Nils
3
给最佳答案再加1分。我发现的唯一问题是即使我使用右键(我经常通过上下文菜单编辑文本),文本也总是被选中——解决方案对于这种情况不起作用,因为它总是选择所有文本,即使我只想通过上下文菜单剪切1个单词。你们知道如何解决这个问题吗? - user3313608
2
我喜欢这个答案,但是为什么你要扩展DependencyObject?我把它移除了,它仍然可以正常工作。 - Fred
显示剩余4条评论

89

不知道为什么在 GotFocus 事件中会失去选择。

但是一种解决方法是在 GotKeyboardFocusGotMouseCapture 事件中进行选择。这样它总是有效的。

-- 编辑 --

在此添加一个示例,以向人们展示如何解决其中提到的一些缺点:

private void TextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    // Fixes issue when clicking cut/copy/paste in context menu
    if (textBox.SelectionLength == 0) 
        textBox.SelectAll();
}

private void TextBox_LostMouseCapture(object sender, MouseEventArgs e)
{
    // If user highlights some text, don't override it
    if (textBox.SelectionLength == 0) 
        textBox.SelectAll();

    // further clicks will not select all
    textBox.LostMouseCapture -= TextBox_LostMouseCapture; 
}

private void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    // once we've left the TextBox, return the select all behavior
    textBox.LostMouseCapture += TextBox_LostMouseCapture;
}

12
不行,当鼠标在现有文本中单击时,只要松开鼠标按钮,选择就会丢失。 - Sergey Aldoukhov
4
虽然在第二次单击后,它再次选择了全部文本...不确定这是否是WPF设计师有意为之的行为,但可用性并不差。与单个GotFocus处理程序的另一个区别是,在文本框中单击空白处会同时选择所有文本。 - Sergey Aldoukhov
3
这也是我的第一种解决方案。但我发现,当用户无法使用鼠标选择文本时,他们会感到非常烦恼,因为每次单击都会选择整个文本... - Nils
1
这种解决方案的另一个缺点是,当您使用TextBox的“剪切/复制/粘贴”菜单时,选择任何菜单项时整个文本都会被选中。 - user128300
有没有解决方案? - Dominic Jonas
显示剩余2条评论

43

以下是实现答案解决方案的混合行为,供您参考:

一个用于附加到单个文本框的混合行为:

public class SelectAllTextOnFocusBehavior : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
        AssociatedObject.GotMouseCapture += AssociatedObjectGotMouseCapture;
        AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectPreviewMouseLeftButtonDown;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
        AssociatedObject.GotMouseCapture -= AssociatedObjectGotMouseCapture;
        AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectPreviewMouseLeftButtonDown;
    }

    private void AssociatedObjectGotKeyboardFocus(object sender,
        System.Windows.Input.KeyboardFocusChangedEventArgs e)
    {
        AssociatedObject.SelectAll();
    }

    private void AssociatedObjectGotMouseCapture(object sender,
        System.Windows.Input.MouseEventArgs e)
    {
        AssociatedObject.SelectAll();   
    }

    private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if(!AssociatedObject.IsKeyboardFocusWithin)
        {
            AssociatedObject.Focus();
            e.Handled = true;
        }
    }
}

另外还有一个用于附加到包含多个文本框的容器根部的方法:

public class SelectAllTextOnFocusMultiBehavior : Behavior<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.GotKeyboardFocus += HandleKeyboardFocus;
        AssociatedObject.GotMouseCapture += HandleMouseCapture;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.GotKeyboardFocus -= HandleKeyboardFocus;
        AssociatedObject.GotMouseCapture -= HandleMouseCapture;
    }

    private static void HandleKeyboardFocus(object sender,
        System.Windows.Input.KeyboardFocusChangedEventArgs e)
    {
        var txt = e.NewFocus as TextBox;
        if (txt != null)
            txt.SelectAll();
    }

    private static void HandleMouseCapture(object sender,
        System.Windows.Input.MouseEventArgs e)
    {
        var txt = e.OriginalSource as TextBox;
        if (txt != null)
            txt.SelectAll();
    }
}

这无疑是最好、最干净的解决方案。非常感谢分享。 - Golvellius
看起来很不错,但出现了一些问题,它破坏了选项卡控件... 你有什么想法吗? - Marc
我想使用您的解决方案。但是我真的很迷茫... 也许您有示例吗? - Juan Pablo Gomez
当您在文本框中单击某个位置并具有焦点时(想象一下您要将插入符号移动到另一个位置),它会再次选择所有内容,而不是移动插入符号。这是意外的。通过将GotMouseCapture替换为常见的MouseDoubleClick来解决此问题。感谢MSDN提供的后续解决方案。 - norekhov
1
当文本框通过FocusManager.FocusedElement接收初始焦点时,似乎无法正常工作。有任何想法为什么? - szx
SelectAllTextOnFocusBehavior 工作得很好,但是 SelectAllTextOnFocusMultiBehavior 存在与问题中相同的问题。我尝试通过添加 PreviewMouseLeftButtonDown 来修复它,但是它似乎没有被触发... 只有当你点击文本后面而不是文本中时,它才会被正确地选择。 - KCT

33

我使用了一个Attached Behavior来解决这个问题,而不是像Sergey的回答中那样使用Expression Behavior。这意味着我不需要在Blend SDK中依赖于System.Windows.Interactivity:

public class TextBoxBehavior
{
    public static bool GetSelectAllTextOnFocus(TextBox textBox)
    {
        return (bool)textBox.GetValue(SelectAllTextOnFocusProperty);
    }

    public static void SetSelectAllTextOnFocus(TextBox textBox, bool value)
    {
        textBox.SetValue(SelectAllTextOnFocusProperty, value);
    }

    public static readonly DependencyProperty SelectAllTextOnFocusProperty =
        DependencyProperty.RegisterAttached(
            "SelectAllTextOnFocus",
            typeof (bool),
            typeof (TextBoxBehavior),
            new UIPropertyMetadata(false, OnSelectAllTextOnFocusChanged));

    private static void OnSelectAllTextOnFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBox = d as TextBox;
        if (textBox == null) return;

        if (e.NewValue is bool == false) return;

        if ((bool) e.NewValue)
        {
            textBox.GotFocus += SelectAll;
            textBox.PreviewMouseDown += IgnoreMouseButton;
        }
        else
        {
            textBox.GotFocus -= SelectAll;
            textBox.PreviewMouseDown -= IgnoreMouseButton;
        }
    }

    private static void SelectAll(object sender, RoutedEventArgs e)
    {
        var textBox = e.OriginalSource as TextBox;
        if (textBox == null) return;
        textBox.SelectAll();
    }

    private static void IgnoreMouseButton(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        var textBox = sender as TextBox;
        if (textBox == null || (!textBox.IsReadOnly && textBox.IsKeyboardFocusWithin)) return;

        e.Handled = true;
        textBox.Focus();
    }
}

然后您可以这样在XAML中使用它:

<TextBox Text="Some Text" behaviors:TextBoxBehavior.SelectAllTextOnFocus="True"/>

我在这里写了一篇关于它的博客(链接)


我喜欢这种方法,但是Get/Set方法不应该以“Property”结尾;在添加Xaml部分后,我不得不将其删除才能使代码编译。 - Patrick Quirk
非常好,正如预期的那样工作。我喜欢这个因为它在做MVVM时帮助我保持视图关注点分离。 - Killnine
1
博客文章链接对我无效,这个链接可以使用 http://www.dutton.me.uk/2013-07-25/how-to-select-all-wpf-textbox-text-on-focus-using-an-attached-behavior/ - Steve
谢谢@Steve,我已经在答案中更新了URI。 - Dutts

17

这里有一个非常好的、非常简单的解决方案,来自于MSDN

<TextBox
    MouseDoubleClick="SelectAddress"
    GotKeyboardFocus="SelectAddress"
    PreviewMouseLeftButtonDown="SelectivelyIgnoreMouseButton" />

这是背后的代码:

private void SelectAddress(object sender, RoutedEventArgs e)
{
    TextBox tb = (sender as TextBox);
    if (tb != null)
    {
        tb.SelectAll();
    }
}

private void SelectivelyIgnoreMouseButton(object sender,
    MouseButtonEventArgs e)
{
    TextBox tb = (sender as TextBox);
    if (tb != null)
    {
        if (!tb.IsKeyboardFocusWithin)
        {
            e.Handled = true;
            tb.Focus();
        }
    }
}

1
基本上,这与该线程中最受欢迎的解决方案相同。但由于它早了两年,现在我知道@Donnelle是从哪里借来的 ;) - Sergey Aldoukhov
这个解决方案似乎是最简单的,而且对我很有效。我想在进入文本框时默认选择特定的文本子集。 - Jack B Nimble

12

我认为这个方案很有效:

private void ValueText_GotFocus(object sender, RoutedEventArgs e)
{
    TextBox tb = (TextBox)e.OriginalSource;
    tb.Dispatcher.BeginInvoke(
        new Action(delegate
            {
                tb.SelectAll();
            }), System.Windows.Threading.DispatcherPriority.Input);
}

如果您希望将其实现为扩展方法:

public static void SelectAllText(this System.Windows.Controls.TextBox tb)
{
    tb.Dispatcher.BeginInvoke(
        new Action(delegate
        {
            tb.SelectAll();
        }), System.Windows.Threading.DispatcherPriority.Input);
}

在你的GotFocus事件中:

private void ValueText_GotFocus(object sender, RoutedEventArgs e)
{
    TextBox tb = (TextBox)e.OriginalSource;
    tb.SelectAllText();
}

我发现上面的解决方案是因为几个月前我正在寻找一种设置给定 UIElement 焦点的方法。我在某个地方发现了下面的代码(特此致谢),它运行良好。尽管它与 OP 的问题没有直接关联,但我还是发布了它,因为它展示了使用 Dispatcher 与一个 UIElement 进行操作的相同模式。

// Sets focus to uiElement
public static void DelayedFocus(this UIElement uiElement)
{
    uiElement.Dispatcher.BeginInvoke(
    new Action(delegate
    {
        uiElement.Focusable = true;
        uiElement.Focus();
        Keyboard.Focus(uiElement);
    }),
    DispatcherPriority.Render);
}

我猜这是最简单的实现方法。创建扩展方法后,您只需调用myTextBox.SelectAllText()即可。为什么这个答案没有得到更多的分数?为什么其他解决方案要好得多? - Tono Nam
2
我会避免使用这种方法,因为它依赖于异步调用在文本框的MouseUp处理程序之后运行。我不会相信这是100%确定性的,并且可能导致不一致的行为。即使发生的可能性很小,我也更愿意选择上面可靠的方法。 - Rob H

7
在 App.xaml 文件中:
<Application.Resources>
    <Style TargetType="TextBox">
        <EventSetter Event="GotKeyboardFocus" Handler="TextBox_GotKeyboardFocus"/>
    </Style>
</Application.Resources>

在App.xaml.cs文件中:

private void TextBox_GotKeyboardFocus(Object sender, KeyboardFocusChangedEventArgs e)
{
    ((TextBox)sender).SelectAll();
}

使用这段代码,您可以访问应用程序中的所有文本框。

6
我发现这里提供的答案都没有模仿标准的Windows文本框。例如,试着点击文本框最后一个字符和右侧之间的空白处。这里的大多数解决方案都会选择整个内容,这使得向文本框附加文本变得非常困难。
我在这里提供的答案在这方面表现更好。它是一种行为(因此需要Blend SDK中的System.Windows.Interactivity程序集)。它也可以使用附加属性进行重写。
public sealed class SelectAllTextOnFocusBehavior : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObject_PreviewMouseLeftButtonDown;
    }

    void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // Find the textbox
        DependencyObject parent = e.OriginalSource as UIElement;
        while (parent != null && !(parent is TextBox))
            parent = VisualTreeHelper.GetParent(parent);

        var textBox = parent as TextBox;
        Debug.Assert(textBox != null);

        if (textBox.IsFocused) return;

        textBox.SelectAll();
        Keyboard.Focus(textBox);
        e.Handled = true;
    }
}

这是基于我在这里找到的代码。


1
虽然这是一个很好的答案,但我认为当用户单击空白处时,他的意图(在业务应用程序中)最可能是要覆盖整个值,因此选择全部是正确的方法。 - Sergey Aldoukhov
1
Sergey:第一次点击将选择整个值,第二次点击将把光标放在该值的右侧。在其他提供的解决方案中,第二次点击将保持整个值被选中,这使得附加到该值非常困难。 - Kristof Verbiest
这个代码怎么使用?我把它添加到了App.xaml.cs中,但似乎对我的应用程序中的TextBox没有影响。 - PIntag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接