保持FlowDocument光标在RichTextBox中垂直居中

7

我遇到了一点困难,希望有人能帮忙。

我有一个WPF项目,其中包含一个RichTextBox。

在编辑文本时,我希望文档中的光标始终垂直居中。

例如,当我向上或向下推动时,在编辑时,光标不会向上移动,而是希望文本向下移动。这应该会给人留下光标保持静止的印象。

非常感谢。


你该怎么做呢?你的应用程序和文本框是固定的。如果你需要改变文本的位置,你需要在开头添加空格,而在检索时只需修剪文本即可。 - om471987
肯定是可能的,我认为我几乎有一个可行的解决方案,但在发布任何东西之前还需要进行一些微调,也想看到更好的方法。 - shenku
你能给我展示一个样例吗?我会尝试找出方法。 - om471987
2个回答

3

不确定这是否符合你的意愿,但这是一个证明中心光标位置的RichTextBox概念验证(当用户点击文本框时)。

正如Omkar所说,如果文档已经滚动到开头或结尾,您需要添加空格以便允许文本滚动。

<RichTextBox HorizontalAlignment="Left" Height="311" VerticalAlignment="Top" Width="509" PreviewKeyDown="HandleKeyDownEvent">
            <FlowDocument>
                <Paragraph Margin="0">
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla turpis sem, tincidunt id vestibulum venenatis, fermentum eget orci. Donec mollis neque ac leo tincidunt tempus. Pellentesque mollis, nunc sit amet fermentum rutrum, lectus augue ultrices nibh, at lacinia est est ut justo. Cras non quam eu enim vulputate porttitor eu sit amet lectus. Suspendisse potenti. Maecenas metus nunc, dapibus id dapibus rhoncus, semper quis leo. Pellentesque eget risus magna, dignissim aliquam diam. Morbi.
                </Paragraph>
            </FlowDocument>
        </RichTextBox>

在代码后台:
private void HandleKeyDownEvent(object sender, KeyEventArgs e)
        {
            RichTextBox rtb = sender as RichTextBox;
            if (rtb != null)
            {
                //text to scroll up relative to caret
                if (e.Key == Key.Down)
                {
                    Block paragraph;

                    //get the whitespace paragraph at end of documnent
                    paragraph = 
                            rtb.Document.Blocks
                                .Where(x => x.Name == "lastParagraph")
                                .FirstOrDefault();

                    // if there is no white space paragraph create it
                    if (paragraph == null)
                    {
                        paragraph = new Paragraph { Name = "lastParagraph", Margin = new Thickness(0) };

                        //add to the end of the document
                        rtb.Document.Blocks.InsertAfter(rtb.Document.Blocks.LastBlock, paragraph);
                    }

                    // if viewport larger than document, add whitespace content to fill view port
                    if (rtb.ExtentHeight < rtb.ViewportHeight)
                    {
                        Thickness margin = new Thickness() { Top = rtb.ViewportHeight - rtb.ExtentHeight };
                                margin.Bottom = rtb.ViewportHeight - rtb.ExtentHeight;
                                paragraph.Margin = margin;

                    }

                    // if the document has been scrolled to the end or doesn't fill the view port
                    if (rtb.VerticalOffset + rtb.ViewportHeight == rtb.ExtentHeight)
                    {
                        // and a line to the white paragraph
                        paragraph.ContentEnd.InsertLineBreak();   
                    }

                    //move the text up relative to caret
                    rtb.LineDown();
                }
                // text is to scroll download relative to caret
                if (e.Key == Key.Up)
                {
                    // get whitespace at start of document
                    Block paragraph;
                    paragraph =
                            rtb.Document.Blocks
                                .Where(x => x.Name == "firstParagraph")
                                .FirstOrDefault();

                    //if whitespace paragraph is null append a new one
                    if (paragraph == null)
                    {
                        paragraph = new Paragraph { Name = "firstParagraph", Margin = new Thickness(0) };
                        rtb.Document.Blocks.InsertBefore(rtb.Document.Blocks.FirstBlock, paragraph);
                    }

                    // up document is at top add white space 
                    if (rtb.VerticalOffset == 0.0)
                    {
                        paragraph.ContentStart.InsertLineBreak();
                    }

                    //move text one line down relative to caret
                    rtb.LineUp();
                }
            }
        }

编辑:这种方法似乎有效。通过使用一行顶部到下一行顶部的差异来确定行高,避免了换行对偏移量的影响。

    <RichTextBox
        PreviewKeyDown="PreviewKeyDownHandler">
        <FlowDocument>
             <!-- Place content here -->
        </FlowDocument>
   </RichTextBox>

在代码后端:

    private void PreviewKeyDownHandler(object sender, KeyEventArgs e)
    {            
        RichTextBox rtb = sender as RichTextBox;
        if (rtb != null)
        {
            if (e.Key == Key.Down)
            {
                // if there is another line below current
                if (rtb.CaretPosition.GetLineStartPosition(0) != rtb.CaretPosition.GetLineStartPosition(1))
                {
                    // find the FlowDocumentView through reflection
                    FrameworkElement flowDocumentView = GetFlowDocument(rtb);

                    // get the content bounds of the current line 
                    Rect currentLineBounds = rtb.CaretPosition.GetCharacterRect(LogicalDirection.Forward);

                    // move the caret down to next line
                    EditingCommands.MoveDownByLine.Execute(null, rtb);

                    // get the content bounds of the new line
                    Rect nextLineBounds = rtb.CaretPosition.GetCharacterRect(LogicalDirection.Forward);

                    // get the offset the document
                    double currentDocumentOffset = flowDocumentView.Margin.Top;

                    // add the height of the previous line to the offset 
                    // the character rect of a line doesn't include the baseline offset so the actual height of line has to be determined
                    // from the difference in the offset between the tops of the character rects of the consecutive lines
                    flowDocumentView.Margin = new Thickness { Top = currentDocumentOffset + currentLineBounds.Top - nextLineBounds.Top };
                }

                // prevent default behavior
                e.Handled = true;
            }
            if (e.Key == Key.Up)
            {
                if (rtb.CaretPosition.GetLineStartPosition(0) != rtb.CaretPosition.GetLineStartPosition(-1))
                {
                    FrameworkElement flowDocumentView = GetFlowDocument(rtb);

                    Rect currentLineBounds = rtb.CaretPosition.GetCharacterRect(LogicalDirection.Forward);

                    EditingCommands.MoveUpByLine.Execute(null, rtb);

                    Rect nextLineBounds = rtb.CaretPosition.GetCharacterRect(LogicalDirection.Forward);

                    double currentDocumentOffset = flowDocumentView.Margin.Top;

                    flowDocumentView.Margin = new Thickness { Top = currentDocumentOffset + currentLineBounds.Top - nextLineBounds.Top };
                }

                e.Handled = true;
            }
        }
    }

    protected FrameworkElement GetFlowDocument(RichTextBox textBox)
    {
        FrameworkElement flowDocumentVisual =
          GetChildByTypeName(textBox, "FlowDocumentView") as FrameworkElement;

        return flowDocumentVisual;
    }

    protected DependencyObject GetChildByTypeName(DependencyObject dependencyObject, string name)
    {
        if (dependencyObject.GetType().Name == name)
        {
            return dependencyObject;
        }
        else
        {
            if (VisualTreeHelper.GetChildrenCount(dependencyObject) > 0)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(dependencyObject);

                for (int idx = 0; idx < childCount; idx++)
                {
                    var dp = GetChildByTypeName(VisualTreeHelper.GetChild(dependencyObject, idx), name);
                    if (dp != null)
                        return dp;
                }

                return null;
            }
            else
            {
                return null;
            }
        }
    }

嗨,按下事件没有针对你正在检查的 Key.Down 键被触发。 即使我进入代码似乎也没有影响。 - shenku
@shenku:是的,RichTextBox会吞噬KeyDown事件,这就是为什么我使用PreviewKeyDown事件的原因,它是一个隧道事件,意味着在执行RichTextBox的KeyDown处理程序之前引发,不会被RTB吞噬。请检查RichTextBox的XAML声明。 - Luke Forder
快了,这个可以工作,但随着时间的推移,当你滚动时它会慢慢地向上移动,而不是保持在原地。似乎会跳到换行符上。:S - shenku
@shenku:嗯...看来元素(段落等)的边距不被计算为文本的一部分,因此在插入符上方累积的边距的更改会改变其相对偏移量。将所有元素的边距设置为0可以解决问题,但这并不是一个好的解决方案,我会看看是否可以找到另一种方法。 - Luke Forder
@shenku:我找到了一种解决换行问题的方法。 - Luke Forder

0

所以我正在尝试类似这样的东西,但还没有让它工作,只是忙于其他事情:

TextPointer start = flowDocument.ContentStart;
TextPointer caretPosition = RichTextBox1.CaretPosition;

var offset = start.GetOffsetToPosition(caretPosition);
RichTextBox1.ScrollToVerticalOffset(offset);

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