WPF TextBox移动光标并更改焦点

时间:2012-12-21 18:43:17

标签: c# .net wpf mvvm textbox

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

我有一个文本框,并且绑定到OnTextChanged事件是以下方法。

这里的目的是给文本框焦点,将光标移动到TextBox的末尾,并将焦点返回到实际聚焦的任何内容(通常是按钮)。问题是,在将焦点发送回最初聚焦的元素之前,似乎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()));

3 个答案:

答案 0 :(得分:1)

你走在正确的轨道上,问题是,对于你的.Focus()电话,你需要将电话延迟到Dispatcher的稍后时间。
不要使用发送DispatcherPriority值(这是最高的),请尝试使用Dispatcher将焦点设置为稍后的DispatcherPriority,例如输入

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

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

答案 1 :(得分:0)

经过许多天,我终于能够开始工作了。它需要Dispatcher检查文本框是否同时具有焦点和键盘焦点以及大量循环。

以下是供参考的代码。有一些评论,但如果有人点击此页面寻找答案,你将不得不自己阅读。提醒一下,这是关于文本更改的。

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();
        }
    }

答案 2 :(得分:0)

最后,我发现&#34;对&#34;这个问题的解决方案(底部的完整解决方案):

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-namespace。快乐的编码!