Я не ожидаю найти лучший ответ здесь. У меня была задача создать такой специальный TextBox для мессенджера, и я выкопал Интернет для какой-то помощи. Все, что я нашел, был RichTextBox, проект Avalon, который нацелен на особые потребности редактирования кода, и очень сложен, и исходный код TextBox, который также очень сложен. Мы закончили с неприятным кодом с обходными путями и взломами.

аю текстовый редактор WPF, используя TextFormatter. Мне нужно сделать отступ во второй строке каждого абзаца.

Ширина отступа во второй строке должна соответствовать ширине первого слова в первой строке, включая пробел после первого слова. Что-то вроде того:

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

Второе: последняя строка в абзаце должна быть в центре.

как это сделать?

Заранее спасибо!!

 Oceans20 сент. 2017 г., 16:15
Хорошо, чтобы прояснить некоторые последние детали .. делает контрольAccepstReturn или жеAcceptsTab? И если это разрешено, каково тогда желаемое поведение? Что должно произойти, если текст состоит только из 1 строки (означает ли это, что применяется последнее правило)? Что, если текст имеет длину всего 2 строки, означает ли это, что вторая строка должна быть с отступом или по центру?
 Oceans18 сент. 2017 г., 15:54
Как определяется длина первой строки? Это фиксированный размер или он основан на первомEnvironment.NewLine ? Когда необходимо применять форматирование? Это во время записи или нажатия кнопки или потери фокуса?
 google dev18 сент. 2017 г., 18:45
Ширина линии определяется шириной элемента управления, содержащего текст, форматирование должно применяться, пока пользователь печатает текст.
 Simon Mourier17 сент. 2017 г., 07:58
Вы хотите, чтобы текст был редактируемым или только для рендеринга?
 google dev17 сент. 2017 г., 08:34
Я хочу, чтобы текст был редактируемым, но любая помощь приветствуется.

Ответы на вопрос(1)

Решение Вопроса

Расширенное форматирование текста.

Существует официальный (относительно скудный, но единственный) пример:TextFormatting.

Итак, я создал небольшой пример приложения с текстовым полем и специальным настраиваемым элементом управления, который одновременно отображает текст из текстового поля так, как вы хотите (ну, почти, смотрите примечания в конце).

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

Я решил написать собственный элемент управления, но вы также можете построить геометрию (как в официальном образце TextFormatting).

[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();
}

Это результат:

Вещи, которые я не сделал:

Это не поддерживает редактирование, это не текстовое поле. Это слишком много работы для такой маленькой награды :-)Поддержка нескольких абзацев. Я только что отступил вторую строку в моем образце. Вам нужно будет проанализировать текст, чтобы извлечь «параграфы», что бы вы ни думали.Должна быть добавлена ​​поддержка осведомленности о DPI (для .NET Framework 4.6.2 или выше). Это делается в примере TextFormatting, вам нужноPixelsPerDip ценить все вокруг.Что происходит в некоторых крайних случаях (только две строки и т. Д.)Предоставьте обычные свойства (FontFamily и т. Д.) В пользовательском элементе управления.
 Simon Mourier21 сент. 2017 г., 22:11
@codexer - ты даунвотер?
 Çöđěxěŕ21 сент. 2017 г., 22:18
Нет, когда я делаю это, потому что автору не удалось исправить свой пост после попытки попросить его обновить его. Я также оставлю отзыв.
 Çöđěxěŕ21 сент. 2017 г., 20:18
This is too much work for such a small bounty :-)Тогда зачем отвечать?
 Maciek Świszczowski22 сент. 2017 г., 11:11
Я не ожидаю найти лучший ответ здесь. У меня была задача создать такой специальный TextBox для мессенджера, и я выкопал Интернет для какой-то помощи. Все, что я нашел, был RichTextBox, проект Avalon, который нацелен на особые потребности редактирования кода, и очень сложен, и исходный код TextBox, который также очень сложен. Мы закончили с неприятным кодом с обходными путями и взломами.
 Maciek Świszczowski22 сент. 2017 г., 11:06
Спасибо за ссылку @SimonMourier

Ваш ответ на вопрос