背景
在WPF开发时,要实现网页上的富文本编辑器的效果,在文本框中既可以输入文字,也可以插入图片,需要用到RichTextBox控件。但是要把RichTextBox中的内容转成Html存储,或者将Html展示在RichTextBox中,网上没有现成的工具。
本文简单地介绍了通过C#实现了WPF中文字+图片的富文本转换功能。
实现思路
概述
首先构建一个包含RichTextBox的简单界面,输入文字/插入图片后,在控件的TextChanged Hook中断点调试,以理解程序运行时,RichTextBox暂存文字和图片的结构,其实与web页面有诸多相似之处。
RichTextBox中的Document属性就是用于存放DOM的,Document.Blocks内的Block为Paragraph时,就是用于存储文字和图片的。Paragraph可以理解为html中的p标签,内部的Inlines则是具体的文字/图片容器。存放文字的Run/Span和存放图片的InlineUIContianer都继承自Inline。
RichTextBox转Html
将RichTextBox实时转成Html很简单,在TextChanged Hook中监听RichTextBox中内容的改变,并调用RichTextHandler.FlowDocumentToHtml即可。
实现思路:遍历RichTextBox.Document.Blocks.Inlines,识别其中的文字和图片元素,拼接Html。
Html转RichTextBox
将Html转成RichTextBox比上一步更为复杂,我们可以在RichTextBox的Loaded Hook中,调用RichTextHandler.HtmlToFlowDocument实现。
实现思路(假设输入的Html为上一步转出的标准Html):
通过正则,匹配Html中的p标签
提取p标签中的内容,判断是否包含图片
通过正则,提取图片前的文字/图片url/图片后的文字,分别按序创建对应的容器,按照概述中的结构,完成Document.Blocks的装配,即可实现,详见实现代码步骤。
其他诸如新增按钮插入图片,ctrl V复制文本等,均可以采用此思路进行转换。
实现代码
public class RichTextBoxHandler
{
public static string FlowDocumentToHtml(FlowDocument fd)
{
try
{
string html = "";
foreach (Block block in fd.Blocks)
{
if (block is Paragraph)
{
html += "<p>";
Paragraph paragraph = (Paragraph)block;
foreach (Inline inline in paragraph.Inlines)
{
if (inline is Run)
{
html += HttpUtility.HtmlEncode(new TextRange(inline.ContentStart, inline.ContentEnd).Text);
}
if (inline is Span)
{
html += HttpUtility.HtmlEncode(new TextRange(inline.ContentStart, inline.ContentEnd).Text);
}
if (inline is InlineUIContainer)
{
InlineUIContainer uiContainer = (InlineUIContainer)inline;
if (uiContainer.Child is Image)
{
Image image = (Image)uiContainer.Child;
string source = image.ToolTip.ToString();
string src = '/' + source.Replace(ClientDomain.nowExeDir, "").Replace("\\", @"/");
html += $"<img src='{src}' />";
}
}
}
html += "</p>";
}
}
return html;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
public static void HtmlToFlowDocument(string html, RichTextBox rtb)
{
try
{
if (rtb == null) return;
rtb.Document.Blocks.Clear();
if (html == null) return;
Regex pReg = new Regex(@"<p[^>]*?>[\w\W]*?<\/p>", RegexOptions.IgnoreCase);
MatchCollection pCollection = pReg.Matches(html);
if (pCollection.Count > 0)
{
foreach (Match mt in pCollection)
{
Regex nopReg = new Regex(@"</?[p|P][^>]*>", RegexOptions.IgnoreCase);
string text = nopReg.Replace(mt.Groups[0].Value, "");//根据内容循序减掉
if (text.IndexOf("<img src=") > -1)
{
Regex imgReg = new Regex(@"<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
MatchCollection imgMatchs = imgReg.Matches(text);//匹配内容中含有图片资源的集合
if (imgMatchs.Count > 0)
{
int index = 0;
foreach (Match imgmt in imgMatchs)
{//一段中可能存在多个图片,循环截取
//本段总内容
Paragraph p = new Paragraph();
#region 图片前文本
if (text.IndexOf(imgmt.Groups[0].Value) > 0)
{
string pretext = text.Substring(0, text.IndexOf(imgmt.Groups[0].Value));
Run run = new Run();
run.Text = HttpUtility.HtmlDecode(pretext);
p.Inlines.Add(run);
text = text.Substring(text.IndexOf(imgmt.Groups[0].Value));
}
#endregion
#region 图片
string imgUrl = imgmt.Groups[1].Value;
string localsource = ClientDomain.nowExeDir + imgUrl.Replace(@"/", "\\");
if (File.Exists(localsource))
{
Image img = new Image();
img.ToolTip = localsource;
BitmapImage bImg = new BitmapImage();
img.IsEnabled = true;
bImg.BeginInit();
bImg.CacheOption = BitmapCacheOption.OnLoad;
//注意:图片通过这种方式刷入缓存,可以有效解决删除本地图片的占用问题
using (Stream ms = new MemoryStream(File.ReadAllBytes(localsource)))
{
bImg.StreamSource = ms;
bImg.EndInit();
bImg.Freeze();
}
img.Source = bImg;
if (bImg.Height > 200 || bImg.Width > 200)
{
img.Height = bImg.Height * 0.3;
img.Width = bImg.Width * 0.3;
}
img.Stretch = Stretch.Uniform; //图片缩放模式
InlineUIContainer uiContainer = new InlineUIContainer();
uiContainer.Child = img;
p.Inlines.Add(uiContainer);
}
text = text.Substring(imgmt.Groups[0].Value.Length);
#endregion
#region 图片后文本
if (imgMatchs.Count == (index + 1))
{
if (!string.IsNullOrEmpty(text))
{
Run run = new Run();
run.Text = HttpUtility.HtmlDecode(text);
p.Inlines.Add(run);
}
}
#endregion
rtb.Document.Blocks.Add(p);
index++;
}
}
}
else
{
Run run = new Run();
run.Text = HttpUtility.HtmlDecode(text);
Paragraph p = new Paragraph();
p.Inlines.Add(run);
rtb.Document.Blocks.Add(p);
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
其他功能的实现
本文仅对RichTextBox和Html互转进行了简单的阐述,其他功能暂不做赘述,如果遇到问题,欢迎与博主一起探讨和指正!
评论