根据TextWrapping属性获取TextBlock的行

本文关键字:TextBlock 的行 获取 属性 TextWrapping 根据 | 更新日期: 2023-09-27 17:50:20

我有一个TextBlock在WPF应用程序。

其中TextBlock的(Text, Width, Height, TextWrapping, FontSize, FontWeight, FontFamily)属性是动态的(由用户在运行时输入)

每次用户更改前面的一个属性时,TextBlockContent属性都会在运行时更改。(在此之前一切正常)

现在,我需要根据之前指定的属性获得TextBlock的行。
这意味着我需要TextWrapping算法将产生的行。

换句话说,我需要每一行在一个单独的字符串中,或者我需要一个带有Scape Sequence 'n的字符串。

有什么办法吗?

根据TextWrapping属性获取TextBlock的行

如果没有这样做的公共方式,我会感到惊讶(尽管谁也不知道,特别是使用WPF)。
确实看起来像TextPointer类是我们的朋友,所以这是一个基于TextBlock的解决方案。ContentStart TextPointer。GetLineStartPosition和TextPointer。GetOffsetToPosition:

public static class TextUtils
{
    public static IEnumerable<string> GetLines(this TextBlock source)
    {
        var text = source.Text;
        int offset = 0;
        TextPointer lineStart = source.ContentStart.GetPositionAtOffset(1, LogicalDirection.Forward);
        do
        {
            TextPointer lineEnd = lineStart != null ? lineStart.GetLineStartPosition(1) : null;
            int length = lineEnd != null ? lineStart.GetOffsetToPosition(lineEnd) : text.Length - offset;
            yield return text.Substring(offset, length);
            offset += length;
            lineStart = lineEnd;
        }
        while (lineStart != null);
    }
}

这里没有太多需要解释的
得到该行的起始位置,减去前一行的起始位置,得到行文本的长度,就得到了。
唯一棘手的(或不明显的)部分是需要将ContentStart偏移一个,因为根据设计 The TextPointer returned by this property always has its LogicalDirection set to Backward. ,所以我们需要获得相同(!?)位置的指针,但是使用 LogicalDirection set to Forward ,无论这意味着什么。

使用FormattedText类,可以首先创建格式化的文本并评估其大小,因此您可以在第一步中知道它所占用的空间。如果太长,则由您决定是否将其分割为单独的行。

然后在第二步中,它可以被绘制。

DrawingContext对象上的任何事情都可能在以下方法中发生:

protected override void OnRender(System.Windows.Media.DrawingContext dc)

下面是CustomControl解决方案:

[ContentProperty("Text")]
public class TextBlockLineSplitter : FrameworkElement
{
    public FontWeight FontWeight
    {
        get { return (FontWeight)GetValue(FontWeightProperty); }
        set { SetValue(FontWeightProperty, value); }
    }
    public static readonly DependencyProperty FontWeightProperty =
        DependencyProperty.Register("FontWeight", typeof(FontWeight), typeof(TextBlockLineSplitter), new PropertyMetadata(FontWeight.FromOpenTypeWeight(400)));
    public double FontSize
    {
        get { return (double)GetValue(FontSizeProperty); }
        set { SetValue(FontSizeProperty, value); }
    }
    public static readonly DependencyProperty FontSizeProperty =
        DependencyProperty.Register("FontSize", typeof(double), typeof(TextBlockLineSplitter), new PropertyMetadata(10.0));
    public String FontFamily
    {
        get { return (String)GetValue(FontFamilyProperty); }
        set { SetValue(FontFamilyProperty, value); }
    }
    public static readonly DependencyProperty FontFamilyProperty =
        DependencyProperty.Register("FontFamily", typeof(String), typeof(TextBlockLineSplitter), new PropertyMetadata("Arial"));
    public String Text
    {
        get { return (String)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(String), typeof(TextBlockLineSplitter), new PropertyMetadata(null));
    public double Interline
    {
        get { return (double)GetValue(InterlineProperty); }
        set { SetValue(InterlineProperty, value); }
    }
    public static readonly DependencyProperty InterlineProperty =
        DependencyProperty.Register("Interline", typeof(double), typeof(TextBlockLineSplitter), new PropertyMetadata(3.0));
    public List<String> Lines
    {
        get { return (List<String>)GetValue(LinesProperty); }
        set { SetValue(LinesProperty, value); }
    }
    public static readonly DependencyProperty LinesProperty =
        DependencyProperty.Register("Lines", typeof(List<String>), typeof(TextBlockLineSplitter), new PropertyMetadata(new List<String>()));
    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        Lines.Clear();
        if (!String.IsNullOrWhiteSpace(Text))
        {
            string remainingText = Text;
            string textToDisplay = Text;
            double availableWidth = ActualWidth;
            Point drawingPoint = new Point();
            // put clip for preventing writing out the textblock
            drawingContext.PushClip(new RectangleGeometry(new Rect(new Point(0, 0), new Point(ActualWidth, ActualHeight))));
            FormattedText formattedText = null;
            // have an initial guess :
            formattedText = new FormattedText(textToDisplay,
                Thread.CurrentThread.CurrentUICulture,
                FlowDirection.LeftToRight,
                new Typeface(FontFamily),
                FontSize,
                Brushes.Black);
            double estimatedNumberOfCharInLines = textToDisplay.Length * availableWidth / formattedText.Width;
            while (!String.IsNullOrEmpty(remainingText))
            {
                // Add 15%
                double currentEstimatedNumberOfCharInLines = Math.Min(remainingText.Length, estimatedNumberOfCharInLines * 1.15);
                do
                {
                    textToDisplay = remainingText.Substring(0, (int)(currentEstimatedNumberOfCharInLines));
                    formattedText = new FormattedText(textToDisplay,
                        Thread.CurrentThread.CurrentUICulture,
                        FlowDirection.LeftToRight,
                        new Typeface(FontFamily),
                        FontSize,
                        Brushes.Black);
                    currentEstimatedNumberOfCharInLines -= 1;
                } while (formattedText.Width > availableWidth);
                Lines.Add(textToDisplay);
                System.Diagnostics.Debug.WriteLine(textToDisplay);
                System.Diagnostics.Debug.WriteLine(remainingText.Length);
                drawingContext.DrawText(formattedText, drawingPoint);
                if (remainingText.Length > textToDisplay.Length)
                    remainingText = remainingText.Substring(textToDisplay.Length);
                else
                    remainingText = String.Empty;
                drawingPoint.Y += formattedText.Height + Interline;
            }
            foreach (var line in Lines)
            {
                System.Diagnostics.Debug.WriteLine(line);
            }
        }
    }
}

该控件的用法(这里的边框显示有效的剪辑):

<Border BorderThickness="1" BorderBrush="Red" Height="200" VerticalAlignment="Top">
    <local:TextBlockLineSplitter>Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do. Once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, &quot;and what is the use of a book,&quot; thought Alice, ...</local:TextBlockLineSplitter>
</Border>

如果它不是一个问题,你可以使用反射的TextBlock控件(它当然知道字符串是如何包装的)。如果你不使用MVVM,我想它很适合你。

首先我创建了一个最小的窗口来测试我的解决方案:

<Window x:Class="WpfApplication1.MainWindow" Name="win"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="600" Width="600">
    <StackPanel>
        <TextBlock Name="txt"  Text="Lorem ipsum dolor sit amet, consectetur adipisci elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua." Margin="20" 
                   TextWrapping="Wrap" />
        <Button Click="OnCalculateClick" Content="Calculate ROWS" Margin="5" />
        <TextBox Name="Result" Height="100" />
    </StackPanel>
</Window>

现在让我们看看代码背后最重要的部分:

private void OnCalculateClick(object sender, EventArgs args)
{
    int start = 0;
    int length = 0;
    List<string> tokens = new List<string>();
    foreach (object lineMetrics in GetLineMetrics(txt))
    {
        length = GetLength(lineMetrics);
        tokens.Add(txt.Text.Substring(start, length));
        start += length;
    }
    Result.Text = String.Join(Environment.NewLine, tokens);
}
private int GetLength(object lineMetrics)
{
    PropertyInfo propertyInfo = lineMetrics.GetType().GetProperty("Length", BindingFlags.Instance
        | BindingFlags.NonPublic);
    return (int)propertyInfo.GetValue(lineMetrics, null);
}
private IEnumerable GetLineMetrics(TextBlock textBlock)
{
    ArrayList metrics = new ArrayList();
    FieldInfo fieldInfo = typeof(TextBlock).GetField("_firstLine", BindingFlags.Instance
        | BindingFlags.NonPublic);
    metrics.Add(fieldInfo.GetValue(textBlock));
    fieldInfo = typeof(TextBlock).GetField("_subsequentLines", BindingFlags.Instance
        | BindingFlags.NonPublic);
    object nextLines = fieldInfo.GetValue(textBlock);
    if (nextLines != null)
    {
        metrics.AddRange((ICollection)nextLines);
    }
    return metrics;
}

GetLineMetrics方法检索LineMetrics的集合(一个内部对象,所以我不能直接使用它)。该对象有一个名为"Length"的属性,其中包含您需要的信息。所以GetLength方法只是读取这个属性的值。

行存储在名为tokens的列表中,并通过使用TextBox控件显示(只是为了获得即时反馈)。

我希望我的样品可以帮助你完成你的任务。