WPF TextFormatter中第二行的缩进 [英] Indentation of second line in WPF TextFormatter

查看:99
本文介绍了WPF TextFormatter中第二行的缩进的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用TextFormatter制作WPF文本编辑器。我需要在每个段落中缩进第二行。



第二行中的缩进宽度应类似于第一行中第一个单词的宽度,包括第一个单词之后的空白。像这样:

 缩进第二行缩进Inde 
缩进第二行缩进
第二行第二行的缩进l
在inde
的第二行缩进

第二件事:段落的最后一行应该在中间。



如何实现这一目标?



谢谢!

解决方案

这远非易事。我建议您使用WPF的



我没有做过的事情:




  • 这不支持编辑,它不是文本框。对于这么小的赏金来说,这是太多工作了:-)

  • 支持多个段落。我只是缩进了示例中的第二行。您需要解析文本以提取段落。

  • 应该添加DPI意识支持(对于.NET Framework 4.6.2或更高版本)。这是在 TextFormatting示例中完成的,您基本上需要携带 PixelsPerDip 值。

  • 在某些情况下会发生什么情况(只有两行,等等)

  • 在自定义控件上公开常用属性(FontFamily等)


I'm making a WPF text-editor using TextFormatter. I need to indent the second line in each paragraph.

The indentation width in the second line should be like the width of the first word on the first line, including the white space after the first word. Something like that:

Indent of second line in Indentation Inde
       second line in Indentation Indenta
of second line in Indentation of second l
ine in Indentation of second line in Inde
       ntation of second line in

Second thing: The last line in the paragraph should be in the center.

how to make this happen?

Thanks in advance!!

解决方案

This is far from being easy. I suggest you use WPF's Advanced Text Formatting.

There is an offical (relatively poor, but it's the only one) sample: TextFormatting.

So, I have created a small sample app with a textbox and a special custom control that renders the text from the textbox simultaneously, the way you want (well, almost, see remarks at the end).

<Window x:Class="WpfApp3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="550" Width="725">
    <StackPanel Margin="10">
        <TextBox  Name="TbSource" AcceptsReturn="True" TextWrapping="Wrap" BorderThickness="1"
                 VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"></TextBox>
        <Border BorderThickness="1" BorderBrush="#ABADB3" Margin="0" Padding="0">
            <local:MyTextControl Margin="5" Text="{Binding ElementName=TbSource, Path=Text}" />
        </Border>
    </StackPanel>
</Window>

I have chosen to write a custom control, but you could also build a geometry (like in the official 'TextFormatting' sample).

[ContentProperty(nameof(Text))]
public class MyTextControl : FrameworkElement
{
    // I have only declared Text as a dependency property, but fonts, etc should be here
    public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyTextControl),
        new FrameworkPropertyMetadata(string.Empty,
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

    private List<TextLine> _lines = new List<TextLine>();
    private TextFormatter _formatter = TextFormatter.Create();

    public string Text { get => ((string)GetValue(TextProperty)); set { SetValue(TextProperty, value); } }

    protected override Size MeasureOverride(Size availableSize)
    {
        // dispose old stuff
        _lines.ForEach(l => l.Dispose());

        _lines.Clear();
        double height = 0;
        double width = 0;
        var ts = new MyTextSource(Text);
        var index = 0;
        double maxWidth = availableSize.Width;
        if (double.IsInfinity(maxWidth))
        {
            // it means width was not fixed by any constraint above this.
            // we pick an arbitrary value, we could use visual parent, etc.
            maxWidth = 100;
        }

        double firstWordWidth = 0; // will be computed with the 1st line

        while (index < Text.Length)
        {
            // we indent the second line
            var props = new MyTextParagraphProperties(new MyTextRunProperties(), _lines.Count == 1 ? firstWordWidth : 0);
            var line = _formatter.FormatLine(ts, index, maxWidth, props, null);
            if (_lines.Count == 0)
            {
                // get first word and whitespace real width (so we can support justification / whitespaces widening, kerning)
                firstWordWidth = line.GetDistanceFromCharacterHit(new CharacterHit(ts.FirstWordAndSpaces.Length, 0));
            }

            index += line.Length;
            _lines.Add(line);

            height += line.TextHeight;
            width = Math.Max(width, line.WidthIncludingTrailingWhitespace);
        }
        return new Size(width, height);
    }

    protected override void OnRender(DrawingContext dc)
    {
        double height = 0;
        for (int i = 0; i < _lines.Count; i++)
        {
            if (i == _lines.Count - 1)
            {
                // last line centered (using pixels, not characters)
                _lines[i].Draw(dc, new Point((RenderSize.Width - _lines[i].Width) / 2, height), InvertAxes.None);
            }
            else
            {
                _lines[i].Draw(dc, new Point(0, height), InvertAxes.None);
            }
            height += _lines[i].TextHeight;
        }
    }
}

// this is a simple text source, it just gives back one set of characters for the whole string
public class MyTextSource : TextSource
{
    public MyTextSource(string text)
    {
        Text = text;
    }

    public string Text { get; }

    public string FirstWordAndSpaces
    {
        get
        {
            if (Text == null)
                return null;

            int pos = Text.IndexOf(' ');
            if (pos < 0)
                return Text;

            while (pos < Text.Length && Text[pos] == ' ')
            {
                pos++;
            }

            return Text.Substring(0, pos);
        }
    }

    public override TextRun GetTextRun(int index)
    {
        if (Text == null || index >= Text.Length)
            return new TextEndOfParagraph(1);

        return new TextCharacters(
           Text,
           index,
           Text.Length - index,
           new MyTextRunProperties());
    }

    public override TextSpan<CultureSpecificCharacterBufferRange> GetPrecedingText(int indexLimit) => throw new NotImplementedException();
    public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int index) => throw new NotImplementedException();
}

public class MyTextParagraphProperties : TextParagraphProperties
{
    public MyTextParagraphProperties(TextRunProperties defaultTextRunProperties, double indent)
    {
        DefaultTextRunProperties = defaultTextRunProperties;
        Indent = indent;
    }

    // TODO: some of these should be DependencyProperties on the control
    public override FlowDirection FlowDirection => FlowDirection.LeftToRight;
    public override TextAlignment TextAlignment => TextAlignment.Justify;
    public override double LineHeight => 0;
    public override bool FirstLineInParagraph => true;
    public override TextRunProperties DefaultTextRunProperties { get; }
    public override TextWrapping TextWrapping => TextWrapping.Wrap;
    public override TextMarkerProperties TextMarkerProperties => null;
    public override double Indent { get; }
}

public class MyTextRunProperties : TextRunProperties
{
    // TODO: some of these should be DependencyProperties on the control
    public override Typeface Typeface => new Typeface("Segoe UI");
    public override double FontRenderingEmSize => 20;
    public override Brush ForegroundBrush => Brushes.Black;
    public override Brush BackgroundBrush => Brushes.White;
    public override double FontHintingEmSize => FontRenderingEmSize;
    public override TextDecorationCollection TextDecorations => new TextDecorationCollection();
    public override CultureInfo CultureInfo => CultureInfo.CurrentCulture;
    public override TextEffectCollection TextEffects => new TextEffectCollection();
}

This is the result:

Things I have not done:

  • This does not support edit, it's not a textbox. This is too much work for such a small bounty :-)
  • Support multiple paragraphs. I've just indented the second line in my sample. You would need to parse the text to extract "paragraphs" whatever you think that is.
  • DPI awareness support should be added (for .NET Framework 4.6.2 or above). This is done in the 'TextFormatting' sample, you basically need to carry the PixelsPerDip value all around.
  • What happens in some edge cases (only two lines, etc.)
  • Expose usual properties (FontFamily, etc...) on the custom control

这篇关于WPF TextFormatter中第二行的缩进的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆