WPF RichTextBox的有条件滚动?

4
我在我的应用程序中有一个 RichTextBox,在某些事件中会获得新的内容。
当添加新内容时,我想要滚动到底部,但前提是滚动条在添加内容前已经在底部。
我该如何做到这一点?
更具体地说,让我困扰的部分是确定滚动位置。
如果有影响的话,RichTextBox 使用默认样式和模板,几个画笔被更改或设置为 null,垂直滚动条可见性为自动,并且它是只读的。

你能比较VerticalOffset和ViewportHeight吗? - paparazzo
4个回答

4
如果您希望一个文本框在滚动条被拖到底部时自动滚动到新添加的文本,请将以下类添加到您的项目中。
public class RichTextBoxThing : DependencyObject
{
    public static bool GetIsAutoScroll(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsAutoScrollProperty);
    }

    public static void SetIsAutoScroll(DependencyObject obj, bool value)
    {
        obj.SetValue(IsAutoScrollProperty, value);
    }

    public static readonly DependencyProperty IsAutoScrollProperty =
        DependencyProperty.RegisterAttached("IsAutoScroll", typeof(bool), typeof(RichTextBoxThing), new PropertyMetadata(false, new PropertyChangedCallback((s, e) =>
            {
                RichTextBox richTextBox = s as RichTextBox;
                if (richTextBox != null)
                {
                    if ((bool)e.NewValue)
                        richTextBox.TextChanged += richTextBox_TextChanged;
                    else if ((bool)e.OldValue)
                        richTextBox.TextChanged -= richTextBox_TextChanged;

                }
            })));

    static void richTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        RichTextBox richTextBox = sender as RichTextBox;
        if ((richTextBox.VerticalOffset + richTextBox.ViewportHeight) == richTextBox.ExtentHeight || richTextBox.ExtentHeight < richTextBox.ViewportHeight)
            richTextBox.ScrollToEnd();
    }
}

然后在任何您想要自动滚动行为的富文本框中,添加IsAutoSroll属性即可。

<RichTextBox ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" local:RichTextBoxThing.IsAutoScroll="True"/> 

与其他答案相同的问题。当滚动条刚出现时无法正常工作。 - Vercas
谢谢!现在它的工作完全符合我的预期。我喜欢这个解决方案,因为它还给了我一个使用附加属性的示例,并向我展示了一些稍后可以使用的控件属性。以前我真的不知道该寻找什么。 - Vercas

3

更简单的滚动条件是VerticalOffset + ViewportHeight >= ExtentHeight

示例:

bool shouldScroll = rtbx.VerticalOffset + rtbx.ViewportHeight >= 
                    rtbx.ExtentHeight;

// changes to RichTextBox 
// ...

if(shouldScroll) rtbx.ScrollToEnd();

也适用于“滚动条刚出现”的情况。


这个很好用,但是由于你在比较可能相等的双精度数时,应该在比较中引入一个“epsilon”。基本上我做了 rtbx.VerticalOffset + rtbx.ViewportHeight >= rtbx.ExtentHeight - 1.0; 这样就可以正常工作了。虽然如此,我还是给你点赞,因为这节省了我一些时间! - Benlitz

1

你可以基本上执行以下操作: 获取滚动条并订阅ValueMaximumMinimum(它们都是依赖属性)的更改。这样,你就可以通过将Value设置为Maximum来在代码后台控制位置。

现在,如何访问滚动条呢?有几种方法。如果你确定你的RichTextBox的控件模板是什么,你可以使用GetTemplateChild(name)来获取它(你可以通过检查例如Blend中的模板来获取名称)。如果不确定,最好创建自己的模板(同样,Blend会给你一个很好的模板作为起点),并将其应用到你感兴趣的RichTextBox上。


0

尝试使用这个扩展方法:

public static class RichTextBoxExtensions
{
    public static void ScrollIfNeeded(this RichTextBox textBox)
    {
        var offset = textBox.VerticalOffset + textBox.ViewportHeight;
        if (Math.Abs(offset - textBox.ExtentHeight) > double.Epsilon) return;
        textBox.ScrollToEnd();
    }
}

使用方法如下:

textBox.AppendText(// Very long text here);
textBox.ScrollIfNeeded();

编辑: 另一种方法是在滚动条出现时滚动到底部:

public static class RichTextBoxExtensions
{
    public static void ScrollIfNeeded(this RichTextBox textBox)
    {
        var offset = textBox.VerticalOffset + textBox.ViewportHeight;
        if (Math.Abs(offset - textBox.ExtentHeight) <= double.Epsilon)
        {
            textBox.ScrollToEnd();
        }
        else
        {
            var contentIsLargerThatViewport = textBox.ExtentHeight > textBox.ViewportHeight;
            if (Math.Abs(textBox.VerticalOffset - 0) < double.Epsilon && contentIsLargerThatViewport)
            {
                textBox.ScrollToEnd();
            }
        }
    }
}

在滚动条不可见之前它是无法工作的。(即在内容大于可用空间之前) - Vercas
不行,因为在滚动条可见之前,VerticalOffset 为0。你说过只有当已经在底部时才想要它自动滚动到底部... - khellang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接