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

本文关键字:垂直居中 RichTextBox FlowDocument 光标 保持 | 更新日期: 2023-09-27 18:26:09

我在弄清楚这一点时遇到了一些困难,希望有人能帮忙。

我有一个带有RichTextBox的WPF项目。

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

例如,在编辑时向上或向下推,而不是光标向上,我希望文本向下。这将导致光标保持静止的印象。

非常感谢。

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

不确定这是否是你想要的,但这里有一个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;
            }
        }
    }

所以我正在沿着这些路线尝试一些东西,但还没有成功,只是忙于其他事情:

TextPointer start = flowDocument.ContentStart;
TextPointer caretPosition = RichTextBox1.CaretPosition;
var offset = start.GetOffsetToPosition(caretPosition);
RichTextBox1.ScrollToVerticalOffset(offset);