• [WPF]根据内容自动设置大小的RichTextBox


                                                [WPF]根据文本内容自动设置大小的RichTextBox

                                                                周银辉 

    很怀念windows forms当中的AutoSize属性啊,但可惜的是WPF并没有实现这个属性, 这多少让人有些郁闷。
    那就自个写吧,相对比较容易的是TextBox之类的仅仅显示平文本的控件,你可以根据你的文本,字体等等属性构造一个FormattedText

    实例,这个实例有Width/Height属性(我还是很怀念Font.MeasureString方法),最让人纠结的是RichTextBox控件,哎,又是它。

    思路很简单,监视文本变化,文本变化时调整控件大小:

           
            
    protected override void OnTextChanged(TextChangedEventArgs e)
            {
                
    base.OnTextChanged(e);

                AdjustSizeByConent();
            }

            
    public void AdjustSizeByConent()
            {       
                
    //myHeight = ... 取得正确的高度
                Height = myHeight;    

                
    //myWidth = ... 取得正确的宽度
                Width = myWidth;
            }

    如何获取正确的高度呢,有一个非常捡便宜的方法,分别对Document.ContentStart和Document.ContentEnd调用TextPointer.GetCharacterRect()方法,我们可以获得文档开始处和结束处的内容边框,如下图所示:
     

     注意到两个红色边框了吗,用第二个边框的bottom减去第一个边框的top,就可以得到内容的高度,所以:      

                Rect rectStart = Document.ContentStart.GetCharacterRect(LogicalDirection.Forward);
                Rect rectEnd 
    = Document.ContentEnd.GetCharacterRect(LogicalDirection.Forward);

                var height 
    = rectEnd.Bottom - rectStart.Top;
                var remainH 
    = rectEnd.Height/2.0;

                Height 
    = Math.Min(MaxHeight, Math.Max(MinHeight, height + remainH));

    (代码中的remainH 是预留的一点点空白)[updated: 完整代码中抛弃了这种做法,而使用了将height设置为NaN]

    那么求宽度时,是不是“同理可证”了(呵呵,如果是在上高中,我可真要这么写了,但程序是严谨的,忽悠不过去的~)

    不行!
    因为,上面代码中的rectStart和rectEnd宽度始终返回的是0(而高度却返回的是正确的值),不知道为啥。

    这导致获取宽度是非常麻烦,下面是一种解决方案,将控件中的文本抽取出来,构造成一个比较复杂的FormattedText,然后由它来求宽度:
     代码

                var formattedText = GetFormattedText(Document);
    // ReSharper disable ConvertToConstant.Local
                var remainW = 20;
    // ReSharper restore ConvertToConstant.Local

                Width 
    = Math.Min(MaxWidth, Math.Max(MinWidth, formattedText.WidthIncludingTrailingWhitespace + remainW));

    OK,有人会问了,既然可以通过FormattedText获取宽度,那为啥不能通过它同理可证求高度呢?
    不可以的,不信你在RichTextBox中敲几次回车试试,一个回车导致一个段落, richTextBox段落之间是有距离的,默认很大(大得有点不协调),FormattedText是不会计算段落间隔的,所以FormattedText的高度比实际高度要小,够纠结吧。

    好了,完整的代码在这里(注意哦,我这里只处理的文本,那我向其中插入图片呢...恩,不work)

    AutoSizeRichTextBox
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media;

    namespace WpfApplication2
    {
        
    internal class AutoSizeRichTextBox : RichTextBox
        {
            
    public AutoSizeRichTextBox()
            {
                Height 
    = Double.NaN;//set to nan to enable auto-height
                Loaded += ((sender, args) => AdjustSizeByConent());
            }

            
    protected override void OnTextChanged(TextChangedEventArgs e)
            {
                
    base.OnTextChanged(e);

                AdjustSizeByConent();
            }



            
    public void AdjustSizeByConent()
            {
                var formattedText 
    = GetFormattedText(Document);
                
    // ReSharper disable ConvertToConstant.Local
                var remainW = 20;
                
    // ReSharper restore ConvertToConstant.Local

                Width 
    = Math.Min(MaxWidth, Math.Max(MinWidth, formattedText.WidthIncludingTrailingWhitespace + remainW));

            }

            
    private static FormattedText GetFormattedText(FlowDocument doc)
            {
                var output 
    = new FormattedText(
                    GetText(doc),
                    CultureInfo.CurrentCulture,
                    doc.FlowDirection,
                    
    new Typeface(doc.FontFamily, doc.FontStyle, doc.FontWeight, doc.FontStretch),
                    doc.FontSize,
                    doc.Foreground);

                
    int offset = 0;

                
    foreach (TextElement textElement in GetRunsAndParagraphs(doc))
                {
                    var run 
    = textElement as Run;

                    
    if (run != null)
                    {
                        
    int count = run.Text.Length;

                        output.SetFontFamily(run.FontFamily, offset, count);
                        output.SetFontSize(run.FontSize, offset, count);
                        output.SetFontStretch(run.FontStretch, offset, count);
                        output.SetFontStyle(run.FontStyle, offset, count);
                        output.SetFontWeight(run.FontWeight, offset, count);
                        output.SetForegroundBrush(run.Foreground, offset, count);
                        output.SetTextDecorations(run.TextDecorations, offset, count);

                        offset 
    += count;
                    }
                    
    else
                    {
                        offset 
    += Environment.NewLine.Length;
                    }
                }




                
    return output;
            }

            
    private static IEnumerable<TextElement> GetRunsAndParagraphs(FlowDocument doc)
            {
                
    for (TextPointer position = doc.ContentStart;
                    position 
    != null && position.CompareTo(doc.ContentEnd) <= 0;
                    position 
    = position.GetNextContextPosition(LogicalDirection.Forward))
                {
                    
    if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd)
                    {
                        var run 
    = position.Parent as Run;

                        
    if (run != null)
                        {
                            
    yield return run;
                        }
                        
    else
                        {
                            var para 
    = position.Parent as Paragraph;

                            
    if (para != null)
                            {
                                
    yield return para;
                            }
                            
    else
                            {
                                var lineBreak 
    = position.Parent as LineBreak;

                                
    if (lineBreak != null)
                                {
                                    
    yield return lineBreak;
                                }
                            }
                        }
                    }
                }
            }

            
    private static string GetText(FlowDocument doc)
            {
                var sb 
    = new StringBuilder();

                
    foreach (TextElement text in GetRunsAndParagraphs(doc))
                {
                    var run 
    = text as Run;
                    sb.Append(run 
    == null ? Environment.NewLine : run.Text);
                }

                
    return sb.ToString();
            }


        }
    }

    [Update 2010-07-14] 

    后来发现,如果文本框被旋转了的话(RenderTransform, RotateTransform.Angle=xxx),当文本框高度改变的时候,文本框在视觉上会有位移(当然Canvas.GetLeft, Canvas.GetTop等值是保持不变的),为了纠正该位移,你可以对文本框(或其他)尝试如下函数:

            private static void AdjustOffsetAfterSizeAdjustedByContent(FrameworkElement element, Size oldSize)
            {
                element.UpdateLayout();

                
    double angle = 0.0;

                var transformOrigin 
    = element.RenderTransformOrigin;
                var rotateTransform 
    = element.GetRenderTransform<RotateTransform>();

                
    if (rotateTransform != null)
                {
                    angle 
    = rotateTransform.Angle * Math.PI / 180;
                }

                var delta 
    = new Point(element.ActualWidth - oldSize.Width, element.ActualHeight - oldSize.Height);
                var x 
    = Canvas.GetLeft(element);
                var y 
    = Canvas.GetTop(element);
                var dx 
    = delta.Y * transformOrigin.Y * Math.Sin(-angle);
                var dy 
    = delta.Y * transformOrigin.Y * (1 - Math.Cos(-angle));
                x 
    += dx;
                y 
    -= dy;

                Canvas.SetLeft(element, x);
                Canvas.SetTop(element, y);
            }


            
    public static T GetRenderTransform<T>(this UIElement element) where T : Transform
            {
                
    if (element.RenderTransform.Value.IsIdentity)
                {
                    element.RenderTransform 
    = CreateSimpleTransformGroup();
                }

                
    if (element.RenderTransform is T)
                {
                    
    return (T)element.RenderTransform;
                }

                
    if (element.RenderTransform is TransformGroup)
                {
                    var group 
    = (TransformGroup)element.RenderTransform;

                    
    foreach (var t in group.Children)
                    {
                        
    if (t is T)
                        {
                            
    return (T)t;
                        }
                    }
                }

                
    throw new NotSupportedException("Can not get instance of " + typeof(T).Name + " from " + element + "'s RenderTransform : " + element.RenderTransform);
            }
     

  • 相关阅读:
    实验六 继承定义与使用
    第四周java实验
    解决 GitHub 提交次数过多 .git 文件过大的问题
    添加开机启动项目
    bash启用 z(同理git bash)
    WIndows to go安装win10系统到移动硬盘
    Make for Windows
    zotero引用3GPP标准暂不完善——使用BibTeX
    Spyder中内嵌的IPython Console自动续行而不运行的问题
    texstudio.org打不开——下载最新版TeXstudio
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1762633.html
Copyright © 2020-2023  润新知