Oto próba rozwiązania niektórych problemów z innymi rozwiązaniami:
- Korzystanie z menu kontekstowego kliknięcia prawym przyciskiem myszy dla wycinania / kopiowania / przeszłości zaznacza cały tekst, nawet jeśli nie zaznaczyłeś go wszystkich.
- Po powrocie z menu kontekstowego prawym przyciskiem myszy cały tekst jest zawsze zaznaczony.
- Po powrocie do aplikacji za pomocą Alt+ Tabcały tekst jest zawsze zaznaczony.
- Podczas próby zaznaczenia tylko części tekstu za pierwszym kliknięciem wszystko jest zawsze zaznaczone (w przeciwieństwie do paska adresu Google Chrome na przykład).
Kod, który napisałem, jest konfigurowalny. Można wybrać na jakie działania zaznaczyć cały zachowanie powinno nastąpić poprzez ustawienie trzech tylko do odczytu pola: SelectOnKeybourdFocus
, SelectOnMouseLeftClick
, SelectOnMouseRightClick
.
Minusem tego rozwiązania jest to, że jest ono bardziej złożone, a stan statyczny jest przechowywany. Wygląda to na brzydką walkę z domyślnym zachowaniem TextBox
kontroli. Nadal działa, a cały kod jest ukryty w klasie kontenera 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;
}
}
Aby dołączyć właściwość Attached do a TextBox
, wystarczy dodać przestrzeń nazw xml ( xmlns
) właściwości Attached, a następnie użyć jej w następujący sposób:
<TextBox attachedprop:TextBoxExtensions.SelectOnFocus="True"/>
Kilka uwag na temat tego rozwiązania:
- Aby zastąpić domyślne zachowanie zdarzenia opuszczenia myszy i umożliwić zaznaczenie tylko części tekstu przy pierwszym kliknięciu, cały tekst jest zaznaczany przy zdarzeniu w górę.
- Musiałem sobie poradzić z faktem, że
TextBox
pamięta swój wybór po tym, jak stracił koncentrację. Właściwie zastąpiłem to zachowanie.
- Musiałem pamiętać, czy naciśnięcie przycisku myszy to pierwsza akcja na
TextBox
( FirstActionIsMouseDown
polu statycznym).
- Musiałem zapamiętać menu kontekstowe otwarte prawym przyciskiem myszy (
ContextMenu
pole statyczne).
Jedynym efektem ubocznym, który znalazłem, jest to, kiedy SelectOnMouseRightClick
jest prawdziwe. Czasami menu kontekstowe kliknięcia prawym przyciskiem miga, gdy jest otwarte, a kliknięcie prawym przyciskiem myszy pustego miejsca TextBox
nie powoduje „zaznaczenia wszystkiego”.