Ecco un tentativo di risolvere alcuni dei problemi con altre soluzioni:
- L'uso del menu contestuale del tasto destro per tagliare / copiare / incollare seleziona tutto il testo anche se non è stato selezionato tutto.
- Quando si ritorna dal menu contestuale del tasto destro, tutto il testo è sempre selezionato.
- Quando si ritorna all'applicazione con Alt+ Tab, tutto il testo è sempre selezionato.
- Quando si tenta di selezionare solo una parte del testo al primo clic, tutto viene sempre selezionato (diversamente dalla barra degli indirizzi di Google Chrome, ad esempio).
Il codice che ho scritto è configurabile. Si può scegliere su quali azioni il comportamento seleziona tutto dovrebbe avvenire impostando tre campi di sola lettura: SelectOnKeybourdFocus
, SelectOnMouseLeftClick
, SelectOnMouseRightClick
.
L'aspetto negativo di questa soluzione è che è più complesso e lo stato statico è memorizzato. Sembra una brutta lotta con il comportamento predefinito del TextBox
controllo. Tuttavia, funziona e tutto il codice è nascosto nella classe del contenitore Proprietà associate.
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;
}
}
Per collegare la proprietà allegata a a TextBox
, tutto ciò che devi fare è aggiungere lo spazio dei nomi xml ( xmlns
) della proprietà allegata e quindi usarlo in questo modo:
<TextBox attachedprop:TextBoxExtensions.SelectOnFocus="True"/>
Alcune note su questa soluzione:
- Per sovrascrivere il comportamento predefinito di un evento del mouse verso il basso e abilitare la selezione solo una parte del testo al primo clic, tutto il testo viene selezionato sull'evento del mouse verso l'alto.
- Ho dovuto affrontare il fatto che
TextBox
ricorda la sua selezione dopo aver perso la concentrazione. In realtà ho ignorato questo comportamento.
- Ho dovuto ricordare se un pulsante del mouse verso il basso è la prima azione sul
TextBox
( FirstActionIsMouseDown
campo statico).
- Ho dovuto ricordare il menu contestuale aperto con un clic destro (
ContextMenu
campo statico).
L'unico effetto collaterale che ho riscontrato è quando SelectOnMouseRightClick
è vero. A volte il menu contestuale del tasto destro lampeggia quando viene aperto e facendo clic con il tasto destro del mouse su uno spazio vuoto TextBox
non fa "selezionare tutto".