2

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

我有一个文本框,绑定到它的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()));
4

3 回答 3

1

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

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

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

于 2012-12-22T12:09:57.117 回答
0

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

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

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();
        }
    }
于 2012-12-27T23:37:31.907 回答
0

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

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 命名空间在哪里。快乐编码!

于 2015-07-07T07:18:44.693 回答