Aquí hay un intento de resolver algunos de los problemas con otras soluciones:
- El uso del menú contextual del botón derecho para cortar / copiar / pegar selecciona todo el texto, incluso si no lo seleccionó todo.
- Al regresar del menú contextual del botón derecho, todo el texto siempre está seleccionado.
- Al volver a la aplicación con Alt+Tab , todo el texto siempre está seleccionado.
- Al intentar seleccionar solo una parte del texto en el primer clic, todo está siempre seleccionado (a diferencia de la barra de direcciones de cromos de Google, por ejemplo).
El código que escribí es configurable. Se puede elegir en las acciones que el seleccionar todo comportamiento debe ocurrir mediante el establecimiento de tres campos de sólo lectura: SelectOnKeybourdFocus
, SelectOnMouseLeftClick
,SelectOnMouseRightClick
.
La desventaja de esta solución es que es más compleja y se almacena el estado estático. Parece una lucha fea con el comportamiento predeterminado del TextBox
control. Aún así, funciona y todo el código está oculto en la clase de contenedor Propiedad adjunta.
public static class TextBoxExtensions
{
// Configuration fields to choose on what actions the select all behavior should occur.
static readonly bool SelectOnKeybourdFocus = true;
static readonly bool SelectOnMouseLeftClick = true;
static readonly bool SelectOnMouseRightClick = true;
// Remembers a right click context menu that is opened
static ContextMenu ContextMenu = null;
// Remembers if the first action on the TextBox is mouse down
static bool FirstActionIsMouseDown = false;
public static readonly DependencyProperty SelectOnFocusProperty =
DependencyProperty.RegisterAttached("SelectOnFocus", typeof(bool), typeof(TextBoxExtensions), new PropertyMetadata(false, new PropertyChangedCallback(OnSelectOnFocusChanged)));
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetSelectOnFocus(DependencyObject obj)
{
return (bool)obj.GetValue(SelectOnFocusProperty);
}
public static void SetSelectOnFocus(DependencyObject obj, bool value)
{
obj.SetValue(SelectOnFocusProperty, value);
}
private static void OnSelectOnFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is TextBox textBox)) return;
if (GetSelectOnFocus(textBox))
{
// Register events
textBox.PreviewMouseDown += TextBox_PreviewMouseDown;
textBox.PreviewMouseUp += TextBox_PreviewMouseUp;
textBox.GotKeyboardFocus += TextBox_GotKeyboardFocus;
textBox.LostKeyboardFocus += TextBox_LostKeyboardFocus;
}
else
{
// Unregister events
textBox.PreviewMouseDown -= TextBox_PreviewMouseDown;
textBox.PreviewMouseUp -= TextBox_PreviewMouseUp;
textBox.GotKeyboardFocus -= TextBox_GotKeyboardFocus;
textBox.LostKeyboardFocus -= TextBox_LostKeyboardFocus;
}
}
private static void TextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (!(sender is TextBox textBox)) return;
// If mouse clicked and focus was not in text box, remember this is the first click.
// This will enable to prevent select all when the text box gets the keyboard focus
// right after the mouse down event.
if (!textBox.IsKeyboardFocusWithin)
{
FirstActionIsMouseDown = true;
}
}
private static void TextBox_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (!(sender is TextBox textBox)) return;
// Select all only if:
// 1) SelectOnMouseLeftClick/SelectOnMouseRightClick is true and left/right button was clicked
// 3) This is the first click
// 4) No text is selected
if (((SelectOnMouseLeftClick && e.ChangedButton == MouseButton.Left) ||
(SelectOnMouseRightClick && e.ChangedButton == MouseButton.Right)) &&
FirstActionIsMouseDown &&
string.IsNullOrEmpty(textBox.SelectedText))
{
textBox.SelectAll();
}
// It is not the first click
FirstActionIsMouseDown = false;
}
private static void TextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (!(sender is TextBox textBox)) return;
// Select all only if:
// 1) SelectOnKeybourdFocus is true
// 2) Focus was not previously out of the application (e.OldFocus != null)
// 3) The mouse was pressed down for the first after on the text box
// 4) Focus was not previously in the context menu
if (SelectOnKeybourdFocus &&
e.OldFocus != null &&
!FirstActionIsMouseDown &&
!IsObjectInObjectTree(e.OldFocus as DependencyObject, ContextMenu))
{
textBox.SelectAll();
}
// Forget ContextMenu
ContextMenu = null;
}
private static void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (!(sender is TextBox textBox)) return;
// Remember ContextMenu (if opened)
ContextMenu = e.NewFocus as ContextMenu;
// Forget selection when focus is lost if:
// 1) Focus is still in the application
// 2) The context menu was not opened
if (e.NewFocus != null
&& ContextMenu == null)
{
textBox.SelectionLength = 0;
}
}
// Helper function to look if a DependencyObject is contained in the visual tree of another object
private static bool IsObjectInObjectTree(DependencyObject searchInObject, DependencyObject compireToObject)
{
while (searchInObject != null && searchInObject != compireToObject)
{
searchInObject = VisualTreeHelper.GetParent(searchInObject);
}
return searchInObject != null;
}
}
Para adjuntar la propiedad adjunta a un TextBox
, todo lo que necesita hacer es agregar el espacio de nombres xml ( xmlns
) de la propiedad adjunta y luego usarlo así:
<TextBox attachedprop:TextBoxExtensions.SelectOnFocus="True"/>
Algunas notas sobre esta solución:
- Para anular el comportamiento predeterminado de un evento de mouse down y permitir seleccionar solo una parte del texto con el primer clic, todo el texto se selecciona en el evento de mouse up.
- Tuve que lidiar con el hecho de que el
TextBox
recuerda su selección después de que pierde el foco. De hecho, he anulado este comportamiento.
- Tenía que recordar si un botón del mouse hacia abajo es la primera acción en el
TextBox
(FirstActionIsMouseDown
campo estático).
- Tenía que recordar el menú contextual abierto con un clic derecho (
ContextMenu
campo estático).
El único efecto secundario que encontré es cuando SelectOnMouseRightClick
es cierto. A veces, el menú contextual del botón derecho parpadea cuando se abre y al hacer clic derecho en un espacio en blanco TextBox
no se selecciona "seleccionar todo".