根据TextWrapping属性获取TextBlock的行?

问题描述 投票:6回答:3

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

Text的(WidthHeightTextWrappingFontSizeFontWeightFontFamilyTextBlock)属性是动态的(由用户在运行时输入)。

每次用户更改以前的属性之一时,ContentTextBlock属性在运行时更改。 (一切都好,直到这里)

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

换句话说,我需要在一个单独的字符串中的每一行或我需要一个字符串与Scape序列\n

有什么想法吗?

c# wpf textblock textwrapping
3个回答
6
投票

如果没有公开的方式,我会感到惊讶(尽管人们不知道,尤其是WPF)。 确实看起来像TextPointer class是我们的朋友,所以这里有一个基于TextBlock.ContentStartTextPointer.GetLineStartPositionTextPointer.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,无论这意味着什么。


4
投票

使用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>

2
投票

如果它不是问题,你可以在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控件显示(只是为了立即反馈)。

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

© www.soinside.com 2019 - 2024. All rights reserved.