RichTextBox will replace the line with a smiley / image

In RichTTextBox, I want to automatically replace emoticon strings (for example :D ) with a smiley image. I still work, except that when I write a smiley line between existing words / lines, the image is inserted at the end of the line.

For example: hello (inserting :D here) this is a message
Results: hello this is a message ☺ <Image

Another (tiny) problem is that the carriage position is set before the image after insertion.

This is what I have already received:

 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; } } } 
+5
source share
2 answers

Invalid selection.Start?.Paragraph?.Inlines.Add(image); . You add an image at the end of the paragraph. You must use one of the InsertBefore or InsertAfter .

But to use these methods, you must iterate over the Inline strings and find the correct inline text to insert before or after. It is not that difficult. You can define inline by comparing selectionStart and selectionEnd with the ElementStart and ElementEnd properties.

Another difficult possibility is that the position you want to insert may be inside the built-in. Then you should break this line and create three others:

  • One item containing items before the insertion position
  • One containing image
  • One containing elements after the insertion position.

Then you can remove the inline and insert the new three lines in the correct position.

Wpf RichTextBox does not have the most beautiful API. Sometimes it’s hard to work with him. There is another AvalonEdit control. It is much easier to use than RichTextBox. You can consider this.

+2
source

As @Yusuf Tarık Günaydın suggested, I was looking for AvalonEdit , which made the trick pretty easy.

With this example, I just need to create a VisualLineElementGenerator that looks for emoticons and inserts images.

 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); } } 
+1
source

Source: https://habr.com/ru/post/1270567/


All Articles