RichTextBox заменить строку смайликом/изображением

В RichtTextBox я хочу автоматически заменить строки смайликов (например, :D) изображением смайликов. У меня это работает до сих пор, за исключением того, что когда я пишу строку смайликов между существующими словами/строками, изображение вставляется в конец строки.

Например: hello (inserting :D here) this is a message
приводит к: hello this is a message ☺ ‹‹ изображение

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

Это то, что я уже получил:

public class Emoticon
{
    public Emoticon(string key, Bitmap bitmap)
    {
        Key = key;
        Bitmap = bitmap;
        BitmapImage = bitmap.ToBitmapImage();
    }

    public string Key { get; }
    public Bitmap Bitmap { get; }
    public BitmapImage BitmapImage { get; }
}

public class EmoticonRichTextBox : RichTextBox
{
    private readonly List<Emoticon> _emoticons;

    public EmoticonRichTextBox()
    {
        _emoticons = new List<Emoticon>
        {
            new Emoticon(":D", Properties.Resources.grinning_face)
        };
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        base.OnTextChanged(e);
        Dispatcher.InvokeAsync(Look);
    }

    private void Look()
    {
        const string keyword = ":D";

        var text = new TextRange(Document.ContentStart, Document.ContentEnd);
        var current = text.Start.GetInsertionPosition(LogicalDirection.Forward);

        while (current != null)
        {
            var textInRun = current.GetTextInRun(LogicalDirection.Forward);
            if (!string.IsNullOrWhiteSpace(textInRun))
            {
                var index = textInRun.IndexOf(keyword, StringComparison.Ordinal);
                if (index != -1)
                {
                    var selectionStart = current.GetPositionAtOffset(index, LogicalDirection.Forward);
                    if (selectionStart == null)
                        continue;

                    var selectionEnd = selectionStart.GetPositionAtOffset(keyword.Length, LogicalDirection.Forward);
                    var selection = new TextRange(selectionStart, selectionEnd) { Text = string.Empty };

                    var emoticon = _emoticons.FirstOrDefault(x => x.Key.Equals(keyword));
                    if (emoticon == null)
                        continue;

                    var image = new System.Windows.Controls.Image
                    {
                        Source = emoticon.BitmapImage,
                        Height = 18,
                        Width = 18,
                        Margin = new Thickness(0, 3, 0, 0)
                    };

                    // inserts at the end of the line
                    selection.Start?.Paragraph?.Inlines.Add(image);

                    // doesn't work
                    CaretPosition = CaretPosition.GetPositionAtOffset(1, LogicalDirection.Forward);
                }
            }

            current = current.GetNextContextPosition(LogicalDirection.Forward);
        }
    }
}

public static class BitmapExtensions
{
    public static BitmapImage ToBitmapImage(this Bitmap bitmap)
    {
        using (var stream = new MemoryStream())
        {
            bitmap.Save(stream, ImageFormat.Png);
            stream.Position = 0;

            var image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.DecodePixelHeight = 18;
            image.DecodePixelWidth = 18;
            image.StreamSource = stream;
            image.EndInit();
            image.Freeze();

            return image;
        }
    }
}

person Community    schedule 04.08.2017    source источник
comment
Просто чтобы уточнить, если вы работаете с этим текстом текстового поля: This is :D a message, вы получите это: This is a message ☺?   -  person Ian H.    schedule 04.08.2017
comment
@ ЯнХ. правильно, это проблема   -  person    schedule 04.08.2017
comment
@rmbq Это о WinForms.   -  person Yusuf Tarık Günaydın    schedule 04.08.2017
comment
@rmbq Это не дубликат, потому что другой вопрос касается RichTextBox в WinForms, и есть огромные различия.   -  person julianstark999    schedule 04.08.2017


Ответы (2)


Неисправная линия selection.Start?.Paragraph?.Inlines.Add(image);. Вы добавляете изображение в конец абзаца. Вы должны использовать один из методов InsertBefore или InsertAfter.

Но чтобы использовать эти методы, вы должны перебрать встроенные строки и найти подходящую строку для вставки до или после. Это не так сложно. Вы можете определить встроенный, сравнив свойства selectionStart и selectionEnd со свойствами ElementStart и ElementEnd встроенного.

Еще одна сложная возможность заключается в том, что позиция, которую вы хотите вставить, может находиться внутри строки. Затем вы должны разделить эту строку и создать три других:

  • Один, содержащий элементы перед позицией вставки
  • Один, содержащий изображение
  • Один, содержащий элементы после позиции вставки.

Затем вы можете удалить строку и вставить новые три строки в нужное место.

RichTextBox Wpf не имеет самого красивого API. Иногда с ним может быть трудно работать. Существует еще один элемент управления, который называется AvalonEdit. Его гораздо проще использовать, чем RichTextBox. Вы можете рассмотреть это.

person Yusuf Tarık Günaydın    schedule 04.08.2017

Как предложил @Yusuf Tarık Günaydın, я искал элемент управления AvalonEdit, который довольно легко справился с этой задачей.

С помощью этого примера мне просто нужно было создать VisualLineElementGenerator, который ищет смайлик сопоставляет и вставляет изображения.

public static class BitmapExtensions
{
    public static BitmapImage ToBitmapImage(this Bitmap bitmap)
    {
        using (var stream = new MemoryStream())
        {
            bitmap.Save(stream, ImageFormat.Png);
            stream.Position = 0;

            var image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.DecodePixelHeight = 18;
            image.DecodePixelWidth = 18;
            image.StreamSource = stream;
            image.EndInit();
            image.Freeze();

            return image;
        }
    }
}

public class Emoticon
{
    public Emoticon(string key, Bitmap bitmap)
    {
        Key = key;
        Bitmap = bitmap;
        BitmapImage = bitmap.ToBitmapImage();
    }

    public string Key { get; }
    public Bitmap Bitmap { get; }
    public BitmapImage BitmapImage { get; }
}

public class EmoticonTextBox : TextEditor
{
    public EmoticonTextBox()
    {
        HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
        VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;

        TextArea.TextView.ElementGenerators.Add(new ImageElementGenerator());
    }
}

public class ImageElementGenerator : VisualLineElementGenerator
{
    // To use this class:
    // textEditor.TextArea.TextView.ElementGenerators.Add(new ImageElementGenerator());

    private static readonly Regex ImageRegex = new Regex(@":D", RegexOptions.IgnoreCase);

    private readonly List<Emoticon> _emoticons;

    public ImageElementGenerator()
    {
        _emoticons = new List<Emoticon>
        {
            new Emoticon(":D", Properties.Resources.grinning_face)
        };
    }

    private Match FindMatch(int startOffset)
    {
        var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
        var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);

        return ImageRegex.Match(relevantText);
    }

    public override int GetFirstInterestedOffset(int startOffset)
    {
        var match = FindMatch(startOffset);
        return match.Success ? startOffset + match.Index : -1;
    }

    public override VisualLineElement ConstructElement(int offset)
    {
        var match = FindMatch(offset);
        if (!match.Success || match.Index != 0)
            return null;

        var key = match.Groups[0].Value;
        var emoticon = _emoticons.FirstOrDefault(x => x.Key.Equals(key));

        var bitmap = emoticon?.BitmapImage;
        if (bitmap == null)
            return null;

        var image = new System.Windows.Controls.Image
        {
            Source = bitmap,
            Width = bitmap.PixelWidth,
            Height = bitmap.PixelHeight
        };

        return new InlineObjectElement(match.Length, image);
    }
}
person Community    schedule 04.08.2017