我有一个TextBox,可以通过将其Text
属性绑定到viewmodel属性来以编程方式更改其文本。例如,这可能是由于按键(例如↑或↓)而发生的,但是也可能在没有任何用户输入的情况下发生。发生这种情况时,似乎文本框中的所有现有选择都将被删除。我想要的行为是:如果一个文本框具有焦点,并且在程序更改之前选择了所有文本(或者如果该文本为空),则我希望在更改之后选择所有文本。但是,在用户键入引起的更改后,不应 选择文本,因为这将意味着用户将一次又一次地替换一个字符。
我还没有找到实现此目的的方法。有可能吗?
具体来说:我已经设置了一个全局事件处理程序,以便在TextBox聚焦时选择所有文本,以便允许用户在需要时更轻松地编辑TextBox中的现有文本:
EventManager.RegisterClassHandler(
typeof(TextBox),
UIElement.GotFocusEvent,
new RoutedEventHandler((s, _) => (s as TextBox)?.SelectAll()));
但是,在我的一种视图中,从TextBox A中跳出会触发异步操作,该操作会更改TextBox B中的文本(按跳位顺序排列)。这很快发生,但是TextBox B在文本更改发生之前就获得了焦点,因此没有选择文本。我希望选择TextBox B中到达的文本,以便用户可以根据需要更轻松地对其进行更改。
答案 0 :(得分:2)
我更喜欢在Behavior
中实现这种功能,可以在XAML中添加它;这需要System.Windows.Interactivity.WPF NuGet Package。
我还没有进行充分的测试,因为我不确定如何复制您的“异步操作”,但是对于我尝试过的“常规”程序化值更改,它似乎很有用。
如果您真的不希望它的Behavior
方面,那么从中提取事件处理逻辑以用于您喜欢的任何方法应该很简单。
以下是它的简短动图:
public class KeepSelectionBehavior : Behavior<TextBox>
{
private bool _wasAllTextSelected = false;
private int inputKeysDown = 0;
protected override void OnAttached()
{
base.OnAttached();
CheckSelection();
AssociatedObject.TextChanged += TextBox_TextChanged;
AssociatedObject.SelectionChanged += TextBox_SelectionChanged;
AssociatedObject.PreviewKeyDown += TextBox_PreviewKeyDown;
AssociatedObject.KeyUp += TextBox_KeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_TextChanged;
AssociatedObject.SelectionChanged -= TextBox_SelectionChanged;
AssociatedObject.PreviewKeyDown -= TextBox_PreviewKeyDown;
AssociatedObject.KeyUp -= TextBox_KeyUp;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (_wasAllTextSelected && inputKeysDown == 0)
{
AssociatedObject.SelectAll();
}
CheckSelection();
}
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
CheckSelection();
}
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (IsInputKey(e.Key))
{
inputKeysDown++;
}
}
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
if (IsInputKey(e.Key))
{
inputKeysDown--;
}
}
private bool IsInputKey(Key key)
{
return
key == Key.Space ||
key == Key.Delete ||
key == Key.Back ||
(key >= Key.D0 && key <= Key.Z) ||
(key >= Key.Multiply && key <= Key.Divide) ||
(key >= Key.Oem1 && key <= Key.OemBackslash);
}
private void CheckSelection()
{
_wasAllTextSelected = AssociatedObject.SelectionLength == AssociatedObject.Text.Length;
}
}
您可以像这样使用它:
<Window
x:Class="ScriptyBot.Client.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<StackPanel>
<TextBox Name="TextBox1" Margin="20">
<i:Interaction.Behaviors>
<behaviors:KeepSelectionBehavior />
</i:Interaction.Behaviors>
</TextBox>
</StackPanel>
</Window>
我正在用一个简单的DispatchTimer
对其进行测试,该文本每秒更新一次文本:
public partial class TestWindow : Window
{
private DispatcherTimer timer;
public TestWindow()
{
InitializeComponent();
timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (sender, e) => { TextBox1.Text = DateTime.Now.ToString(); };
timer.Start();
}
}
默认情况下,必须在XAML中将Behavior
手动应用于每个控件,这可能会很烦人。如果您改为将此基础类用于Behavior
,则可以使用Style
添加它。这也适用于隐式样式,因此您可以在app.xaml
中设置一次,而不是为每个控件手动设置。
public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
where TComponent : System.Windows.DependencyObject
where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior>, new()
{
public static readonly DependencyProperty IsEnabledForStyleProperty =
DependencyProperty.RegisterAttached(name: "IsEnabledForStyle",
propertyType: typeof(bool),
ownerType: typeof(AttachableForStyleBehavior<TComponent, TBehavior>),
defaultMetadata: new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged));
public bool IsEnabledForStyle
{
get => (bool)GetValue(IsEnabledForStyleProperty);
set => SetValue(IsEnabledForStyleProperty, value);
}
private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement uiElement)
{
var behaviors = Interaction.GetBehaviors(uiElement);
var existingBehavior = behaviors.FirstOrDefault(b => b.GetType() == typeof(TBehavior)) as TBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behaviors.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behaviors.Add(new TBehavior());
}
}
}
}
Behavior
类的声明更改如下:
public class KeepSelectionBehavior : AttachableForStyleBehavior<TextBox, KeepSelectionBehavior>
并像这样应用(它甚至可以绑定到bool
并动态地打开和关闭!):
<Style TargetType="TextBox">
<Setter Property="KeepSelectionBehavior.IsEnabledForStyle" Value="True" />
</Style>
我个人还是喜欢使用基于Style
的方法,即使将其添加到单个一次性控件中也是如此。它大大减少了键入,而且我不必记住如何为xmlns
或Interactions
命名空间定义Behaviors
。
答案 1 :(得分:0)
我希望选择
TextBox
B中到达的文本,以便用户可以根据需要更轻松地对其进行更改。
然后处理TextChanged
事件。每当Text
属性更改时,都会引发此事件。 Yoy可能希望添加一个延迟,以便用户可以在每次击键时都不会选择文本的情况下进行键入:
private DateTime _last;
private void txt2_TextChanged(object sender, TextChangedEventArgs e)
{
if (DateTime.Now.Subtract(_last) > TimeSpan.FromSeconds(3))
{
TextBox tb = (TextBox)sender;
if (Keyboard.FocusedElement == tb)
tb.SelectAll();
}
_last = DateTime.Now;
}