RichTextBox 用表情符号/图像替换字符串
RichTextBox replace string with emoticon / image
在 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;
}
}
}
故障线路为selection.Start?.Paragraph?.Inlines.Add(image);
。您将图像附加到段落的末尾。您应该使用 InsertBefore
或 InsertAfter
方法之一。
但是要使用这些方法,您应该遍历内联并找到合适的内联插入之前或之后。这并不难。您可以通过比较 selectionStart
和 selectionEnd
与内联的 ElementStart
和 ElementEnd
属性来确定内联。
另一个棘手的可能性是您要插入的位置可能位于行内。然后你应该拆分那个内联并创建另外三个:
- 一个包含插入位置之前的元素
- 一个包含图像
- 一个包含插入位置之后的元素。
然后,您可以删除内联并在适当的位置插入新的三个内联。
Wpf的RichTextBox没有最漂亮的API。有时很难与之合作。还有另一个控件叫做 AvalonEdit。它比 RichTextBox 更容易使用。你可以考虑一下。
作为 @Yusuf Tarık Günaydın suggested, I looked for the AvalonEdit 控制,相当容易做到这一点。
在这个 example 的帮助下,我只需要创建一个 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);
}
}
在 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;
}
}
}
故障线路为selection.Start?.Paragraph?.Inlines.Add(image);
。您将图像附加到段落的末尾。您应该使用 InsertBefore
或 InsertAfter
方法之一。
但是要使用这些方法,您应该遍历内联并找到合适的内联插入之前或之后。这并不难。您可以通过比较 selectionStart
和 selectionEnd
与内联的 ElementStart
和 ElementEnd
属性来确定内联。
另一个棘手的可能性是您要插入的位置可能位于行内。然后你应该拆分那个内联并创建另外三个:
- 一个包含插入位置之前的元素
- 一个包含图像
- 一个包含插入位置之后的元素。
然后,您可以删除内联并在适当的位置插入新的三个内联。
Wpf的RichTextBox没有最漂亮的API。有时很难与之合作。还有另一个控件叫做 AvalonEdit。它比 RichTextBox 更容易使用。你可以考虑一下。
作为 @Yusuf Tarık Günaydın suggested, I looked for the AvalonEdit 控制,相当容易做到这一点。
在这个 example 的帮助下,我只需要创建一个 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);
}
}