以编程方式更改文本后保留TextBox选择

时间:2018-09-04 20:00:44

标签: wpf

我有一个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中到达的文本,以便用户可以根据需要更轻松地对其进行更改。

2 个答案:

答案 0 :(得分:2)

我更喜欢在Behavior中实现这种功能,可以在XAML中添加它;这需要System.Windows.Interactivity.WPF NuGet Package

我还没有进行充分的测试,因为我不确定如何复制您的“异步操作”,但是对于我尝试过的“常规”程序化值更改,它似乎很有用。

如果您真的不希望它的Behavior方面,那么从中提取事件处理逻辑以用于您喜欢的任何方法应该很简单。

以下是它的简短动图:

Code in Action Animation

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的方法,即使将其添加到单个一次性控件中也是如此。它大大减少了键入,而且我不必记住如何为xmlnsInteractions命名空间定义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;
}
相关问题