很懷念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 是預留的一點點空白)
那麼求寬度時,是不是“同理可證”了(呵呵,如果是在上高中,我可真要 這麼寫了,但程序是嚴謹的,忽悠不過去的~)
不行!
因為,上面代碼中的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 AutoSizeTextBoxDemo
{
internal class AutoSizeRichTextBox : RichTextBox
{
public AutoSizeRichTextBox()
{
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));
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));
}
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();
}
}
}