Aqui está uma tentativa de resolver alguns dos problemas com outras soluções:
- O uso do botão direito do mouse no menu de contexto para recortar / copiar / colar seleciona todo o texto, mesmo que você não tenha selecionado tudo.
- Ao retornar do menu de contexto do botão direito, todo o texto é sempre selecionado.
- Ao retornar ao aplicativo com Alt+ Tab, todo o texto é sempre selecionado.
- Ao tentar selecionar apenas parte do texto no primeiro clique, tudo sempre é selecionado (ao contrário da barra de endereço do Google Chrome, por exemplo).
O código que escrevi é configurável. Você pode escolher em quais ações o seleccionar todo comportamento deve ocorrer, definindo três campos somente leitura: SelectOnKeybourdFocus
, SelectOnMouseLeftClick
, SelectOnMouseRightClick
.
A desvantagem desta solução é que ela é mais complexa e o estado estático é armazenado. Parece uma luta feia com o comportamento padrão do TextBox
controle. Ainda assim, ele funciona e todo o código está oculto na classe de contêiner Attached Property.
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 anexar a propriedade anexada a a TextBox
, tudo o que você precisa fazer é adicionar o namespace xml ( xmlns
) da propriedade anexada e usá-lo da seguinte maneira:
<TextBox attachedprop:TextBoxExtensions.SelectOnFocus="True"/>
Algumas notas sobre esta solução:
- Para substituir o comportamento padrão de um evento de mouse para baixo e ativar a seleção de apenas parte do texto no primeiro clique, todo o texto é selecionado no evento de mouse para cima.
- Eu tive que lidar com o fato de que o se
TextBox
lembra de sua seleção depois que perde o foco. Na verdade, eu substituí esse comportamento.
- Eu tinha que lembrar se um botão do mouse pressionado é a primeira ação no
TextBox
( FirstActionIsMouseDown
campo estático).
- Eu tive que lembrar o menu de contexto aberto com um clique direito (
ContextMenu
campo estático).
O único efeito colateral que encontrei é quando SelectOnMouseRightClick
é verdade. Às vezes, o menu de contexto do botão direito pisca quando é aberto e clicar com o botão direito do mouse em um espaço em branco no campo TextBox
"não selecionar tudo".