【问题标题】:WPF TextBox Move Cursor and Change FocusWPF TextBox 移动光标和更改焦点
【发布时间】:2015-07-07 15:18:44
【问题描述】:

这是一个奇怪的问题,我什至不知道要搜索什么,但相信我,我知道。

我有一个文本框并绑定到它的OnTextChanged 事件是下面的方法。

这里的目的是给文本框焦点,将光标移动到文本框的末尾,然后将焦点返回到实际焦点所在的位置(通常是按钮)。问题是在我将焦点发送回最初聚焦的元素之前,似乎 TextBox 没有“重绘”(因为没有更好的词?),因此光标位置不会在屏幕上更新(尽管所有属性都认为它有) .

目前,我已经粗暴地破解了这个,基本上将前一个焦点项目的重新聚焦延迟了 10 毫秒,并在不同的线程中运行它,以便 UI 有时间更新。现在,这显然是一个任意的时间量,并且在我的机器上运行良好,但是在旧机器上运行这个应用程序的人可能会遇到问题。

有没有合适的方法来做到这一点?我想不通。

private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
    if (sender == null) return;
    var box = sender as TextBox;

    if (!box.IsFocused)
    {

        var oldFocus = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this));
        box.Select(box.Text.Length, 0);
        Keyboard.Focus(box); // or box.Focus(); both have the same results

        var thread = new Thread(new ThreadStart(delegate
                                                    {
                                                        Thread.Sleep(10);
                                                        Dispatcher.Invoke(new Action(() => oldFocus.Focus()));
                                                    }));
        thread.Start();
    }
}

编辑

我的一个新想法是在 UI 完成更新后运行 oldFocus.Focus() 方法,所以我尝试了以下方法,但得到了相同的结果:(

var oldFocus = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this));

Dispatcher.Invoke(DispatcherPriority.Send, new Action(delegate
 {
   box.Select(box.Text.Length, 0);
   box.Focus();
 }));

Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => oldFocus.Focus()));

【问题讨论】:

  • 我猜这是一个 WPF 应用程序,所以我更改了标签。您通常应该告诉我们您正在编写什么样的应用程序,这样我们就不必猜错了。
  • 我很抱歉。以后不会忘记的。
  • 你试过 oldFocus.Invalidate() 来强制重绘吗?
  • “光标”是指插入符号吗?
  • @JasonTyler 我试过 box.InvalidateVisual();没有变化。似乎我无法弄清楚如何强制 UI 重绘框。不确定这是否是正确的术语。

标签: c# .net wpf mvvm textbox


【解决方案1】:

您在正确的轨道上,问题是您的 .Focus() 呼叫要坚持,您需要在调度程序中将呼叫延迟到稍后的时间。
不要使用 SendDispatcherPriority 值(最高),而是尝试使用 Dispatcher 将焦点设置在稍后的 DispatcherPriority,例如 Input。 p>

Dispatcher.BeginInvoke(DispatcherPriority.Input,
new Action(delegate() { 
    oldFocus.Focus();         // Set Logical Focus
    Keyboard.Focus(oldFocus); // Set Keyboard Focus
 }));

如您所见,我也在设置键盘焦点。
WPF 可以有多个焦点范围,并且多个元素可以有逻辑焦点 (IsFocused = true)。但是,只有 一个 元素可以拥有键盘焦点并接收键盘输入。

【讨论】:

  • 感谢您的回复。不过,我相信我已经在编辑中这样做了。我调用最高级别委托(发送)上的文本框的选择和焦点,然后调用最低级别委托(空闲)返回焦点。结果也好不到哪里去。
【解决方案2】:

经过很多天,我终于能够让它工作了。它要求调度程序检查文本框是否同时具有焦点和键盘焦点以及大量循环。

这是供参考的代码。其中有一些 cmets,但如果有人点击此页面寻找答案,您必须自己通读。提醒一下,这是关于文本更改的。

protected void TextBox_ShowEndOfLine(object sender, TextChangedEventArgs e)
    {
        if (sender == null) return;
        var box = sender as TextBox;

        if (!box.IsFocused && box.IsVisible)
        {
            IInputElement oldFocus = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this));
            box.Focus();
            box.Select(box.Text.Length, 0);
            box.Focus();

            // We wait for keyboard focus and regular focus before returning focus to the button
            var thread = new Thread((ThreadStart)delegate
                                        {
                                            // wait till focused
                                            while (true)
                                            {
                                                var focused = (bool)Dispatcher.Invoke(new Func<bool>(() => box.IsKeyboardFocusWithin && box.IsFocused && box.IsInputMethodEnabled), DispatcherPriority.Send);
                                                if (!focused)
                                                    Thread.Sleep(1);
                                                else
                                                    break;
                                            }

                                            // Focus the old element
                                            Dispatcher.Invoke(new Action(() => oldFocus.Focus()), DispatcherPriority.SystemIdle);
                                        });
            thread.Start();
        }
        else if (!box.IsVisible)
        {
            // If the textbox is not visible, the cursor will not be moved to the end. Wait till it's visible.
            var thread = new Thread((ThreadStart)delegate
                                        {
                                            while (true)
                                            {
                                                Thread.Sleep(10);
                                                if (box.IsVisible)
                                                {
                                                    Dispatcher.Invoke(new Action(delegate
                                                                                     {
                                                                                         box.Focus();
                                                                                         box.Select(box.Text.Length, 0);
                                                                                         box.Focus();

                                                                                     }), DispatcherPriority.ApplicationIdle);
                                                    return;
                                                }
                                            }
                                        });
            thread.Start();
        }
    }

【讨论】:

    【解决方案3】:

    最后,我找到了这个问题的“正确”解决方案(底部的完整解决方案):

    if (!tb.IsFocused)
    {
        tb.Dispatcher.BeginInvoke(new Action(() => 
            tb.ScrollToHorizontalOffset(1000.0)), DispatcherPriority.Input);
    }
    

    实际上,您不想关注文本框 - 需要此 hack,因为如果 TextBox 没有焦点,TextBox.CaretIndex、TextBox.Select() 等将不会做任何事情。使用其中一种 Scroll 方法可以在不集中注意力的情况下工作。我不知道double offset 到底应该是什么(使用1000.0 的过高值对我有用)。该值的行为类似于像素,因此请确保它对于您的场景足够大。

    接下来,您不希望在用户使用键盘输入编辑值时触发此行为。作为奖励,我结合了垂直和水平滚动,其中多行 TextBox 垂直滚动,而单行 TextBox 水平滚动。最后,您可能希望将此事物作为附加属性/行为重用。希望您喜欢这个解决方案:

        /// <summary>The attached dependency property.</summary>
        public static readonly DependencyProperty AutoScrollToEndProperty =
            DependencyProperty.RegisterAttached("AutoScrollToEnd", typeof(bool), typeof(TextBoxBehavior),
                new UIPropertyMetadata(false, AutoScrollToEndPropertyChanged));
    
        /// <summary>Gets the value.</summary>
        /// <param name="obj">The object.</param>
        /// <returns>The value.</returns>
        public static bool GetAutoScrollToEnd(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToEndProperty);
        }
    
        /// <summary>Enables automatic scrolling behavior, unless the <c>TextBox</c> has focus.</summary>
        /// <param name="obj">The object.</param>
        /// <param name="value">The value.</param>
        public static void SetAutoScrollToEnd(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToEndProperty, value);
        }
    
        private static void AutoScrollToEndPropertyChanged(DependencyObject dependencyObject,
            DependencyPropertyChangedEventArgs e)
        {
            var textBox = dependencyObject as TextBox;
            var newValue = (bool)e.NewValue;
            if (textBox == null || (bool)e.OldValue == newValue)
            {
                return;
            }
            if (newValue)
            {
                textBox.TextChanged += AutoScrollToEnd_TextChanged;
            }
            else
            {
                textBox.TextChanged -= AutoScrollToEnd_TextChanged;
            }
        }
    
        private static void AutoScrollToEnd_TextChanged(object sender, TextChangedEventArgs args)
        {
            var tb = (TextBox)sender;
            if (tb.IsFocused)
            {
                return;
            }
            if (tb.LineCount > 1) // scroll to bottom
            {
                tb.ScrollToEnd();
            }
            else // scroll horizontally (what about FlowDirection ??)
            {
                tb.Dispatcher.BeginInvoke(new Action(() => tb.ScrollToHorizontalOffset(1000.0)), DispatcherPriority.Input);
            }
        }
    

    XAML 用法:

            <TextBox b:TextBoxBehavior.AutoScrollToEnd="True"
                     Text="{Binding Filename}"/>
    

    其中xmlns:b 是对应的 clr 命名空间。编码愉快!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-02
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多