在运行时将纯文本和图像插入 RichTextBox






4.90/5 (116投票s)
2003年7月15日
10分钟阅读

1261603

19501
本文描述了如何在运行时以编程方式将文本和图像插入 RichTextBox。
引言
如果您曾搜索过一种简单方法来将图像插入 RichTextBox
,那么您很可能遇到过以下解决方案的变体,该解决方案将图像复制到剪贴板,将其粘贴到目标 RichTextBox
中,然后清除剪贴板内容。
public void InsertImage() {
...
string lstrFile = fileDialog.FileName;
Bitmap myBitmap = new Bitmap(lstrFile);
// Copy the bitmap to the clipboard.
Clipboard.SetDataObject(myBitmap);
// Get the format for the object type.
DataFormats.Format myFormat = DataFormats.GetFormat (DataFormats.Bitmap);
// After verifying that the data can be pasted, paste
if(NoteBox.CanPaste(myFormat)) {
NoteBox.Paste(myFormat);
}
else {
MessageBox.Show("The data format that you attempted site" +
" is not supportedby this control.");
}
...
}
这不是一个好的解决方案,因为它在未告知用户的情况下修改了剪贴板,这会带来真正的不便。其他解决方案将图像的 HEX 表示形式的数千行硬编码到程序中,但这不是很灵活(或实用)。也没有标准的方法可以在运行时将纯文本插入 RichTextBox
。本文提供了解决这些问题的方案。
该解决方案必须
- 允许在运行时以编程方式将纯文本插入或附加到
RichTextBox
的内容中。 - 允许在将纯文本插入或附加到
RichTextBox
内容时指定字体、文本颜色和高亮颜色(文本背景色)。 - 允许以编程方式插入图像,而无需使用剪贴板。
RichTextBox
的内容可以是纯文本格式或富文本格式。此后,富文本格式简称为 RTF。
注意:将纯文本转换为 RTF 实际上是通过附加字符串来创建 RTF 代码。这非常简单,但需要熟悉 RTF 文档结构和控制字。为了避免将本文变成 RTF 教程,将简要讨论插入纯文本的方法,但要获得完整的解释,读者应查看源代码并阅读 RTF 规范 v1.6。
背景
在深入探讨解决方案之前,有必要介绍一下 RTF 文档和图元文件。
RTF 文档
RTF 是一种结构化的文件格式,它使用控制字和符号来创建可在不同操作环境中使用的文件。当被读取时,RTF 控制字和符号由 RTF 阅读器处理,该阅读器将 RTF 转换为格式化文本。这类似于浏览器向用户显示 HTML 的方式。在这种情况下,RTF 阅读器是 RichTextBox
。
RTF 规范是一个 250 多页的文档,因此试图在本文中对其进行总结将是对其作者的严重不公。本文中将解释的唯一 RTF 控制字是那些在插入图像时使用的控制字。有关 RTF 的完整介绍,请阅读 RTF 规范 v1.6。
元文件
在 .NET 框架中,Metafile
类派生自 Image
类,但图元文件不是像可以转换为 Bitmap
对象的栅格图像。栅格图像由称为位图的矩形像素阵列组成。图元文件是矢量图像,它以绘图命令的形式包含图像的几何表示。Metafile
可以使用 .NET 框架转换为 Bitmap
,但 Bitmap
不能仅使用 .NET 转换为 Metafile
。但是,位图可以嵌入到图元文件中。
.NET 框架支持两种类型的图元文件:Windows 图元文件格式 (WMF) 和增强型图元文件格式 (EMF)。这些图元文件在它们支持的绘图命令方面有所不同;增强型图元文件支持比 Windows 图元文件更多的绘图命令。根据 Microsoft 的文档,WMF 格式不应使用,并且仅用于向后兼容性,但是此解决方案使用了 WMF。有关图元文件的完整文档,请单击此处。
插入和附加纯文本
RTF 的插入到 RichTextBox
中是通过将 RTF 文档的字符串表示形式分配给 RichTextBox.Rtf
属性或 RichTextBox.SelectedRtf
属性来完成的。当使用后者进行插入时,如果在插入时有文本被选中,则该文本将被替换。如果没有文本被选中,则文本将插入到插入符号的位置。
附加文本
纯文本通过将插入符号移动到 RichTextBox
中 RTF 文本的末尾并执行插入操作来附加到 RichTextBox
的内容中。
/// Appends the text using the given font, text,
/// and highlight colors. Simply
/// moves the caret to the end of the RichTextBox's text
/// and makes a call to insert.
public void AppendTextAsRtf(string _text, Font _font,
RtfColor _textColor, RtfColor _backColor) {
// Move carret to the end of the text
this.Select(this.TextLength, 0);
InsertTextAsRtf(_text, _font, _textColor, _backColor);
}
AppendTextAsRtf
还有其他三个重载,它们最终都会调用上面的重载。
/// Appends the text using the current font, text, and highlight colors.
public void AppendTextAsRtf(string _text) {
AppendTextAsRtf(_text, this.Font);
}
/// Appends the text using the given font, and
/// current text and highlight colors.
public void AppendTextAsRtf(string _text, Font _font) {
AppendTextAsRtf(_text, _font, textColor);
}
/// Appends the text using the given font and text color, and the current
/// highlight color.
public void AppendTextAsRtf(string _text, Font _font, RtfColor _textColor) {
AppendTextAsRtf(_text, _font, _textColor, highlightColor);
}
插入文本
将文本插入 RichTextBox
时,文本必须是富文本格式的文档。RTF 文档由必须符合 RTF 规范的“标题”和“文档”区域组成。标题除其他内容外,还包含所使用的语言以及文档中使用的字体和颜色表。文档区域是文档实际内容存储和格式化的位置。调用 InsertTextAsRtf
方法时,会构造一个 RTF 标题,并将纯文本添加并格式化到文档区域中。
public void InsertTextAsRtf(string _text, Font _font,
RtfColor _textColor, RtfColor _backColor) {
StringBuilder _rtf = new StringBuilder();
// Append the RTF header
_rtf.Append(RTF_HEADER);
// Create the font table from the font passed in and append it to the
// RTF string
_rtf.Append(GetFontTable(_font));
// Create the color table from the colors passed in and append it to the
// RTF string
_rtf.Append(GetColorTable(_textColor, _backColor));
// Create the document area from the text to be added as RTF and append
// it to the RTF string.
_rtf.Append(GetDocumentArea(_text, _font));
this.SelectedRtf = _rtf.ToString();
}
InsertTextAsRtf
还有其他三个重载,如下所示。有关此过程的深入了解,请查看源代码并参阅 RTF 规范 v1.6。
/// Inserts the text using the current font, text, and highlight colors.
public void InsertTextAsRtf(string _text) {
InsertTextAsRtf(_text, this.Font);
}
/// Inserts the text using the given font, and current text and highlight
/// colors.
public void InsertTextAsRtf(string _text, Font _font) {
InsertTextAsRtf(_text, _font, textColor);
}
/// Inserts the text using the given font and text color, and the current
/// highlight color.
public void InsertTextAsRtf(string _text, Font _font,
RtfColor _textColor) {
InsertTextAsRtf(_text, _font, _textColor, highlightColor);
}
插入图像
当图像粘贴到 RichTextBox
(或 WordPad 或 Microsoft Word)中时,图像会嵌入到 Windows 元文件 (WMF) 中,并且元文件会放置在文档中。InsertImage
方法执行相同的操作,但不使用剪贴板。根据 RTF 规范 v1.6,可以直接将位图、JPEG、GIF、PNG 和增强型元文件插入到 RTF 文档中,而无需先将其嵌入到 Windows 元文件中。但是,虽然这适用于 Microsoft Word,但如果图像未嵌入到 Windows 元文件中,WordPad 和 RichTextBox
就会简单地忽略它们。
元文件
Windows 图元文件是最初在 Windows 1.0 (1985) 上支持的图元文件格式。它们功能有限,并且仅为了向后兼容性而受 Windows 窗体支持。.NET 不直接支持 Windows 图元文件的创建,但可以读取它们。但是,支持增强型图元文件的创建,并且可以使用非托管代码将增强型图元文件转换为 Windows 图元文件。GDI+(gdiplus.dll)包含一个名为 EmfToWmfBits()
的函数,它将增强型图元文件转换为 Windows 图元文件。此函数如下所示。
[DllImportAttribute("gdiplus.dll")]
private static extern uint GdipEmfToWmfBits (IntPtr _hEmf,
uint _bufferSize, byte[] _buffer,
int _mappingMode, EmfToWmfBitsFlags _flags);
_hEmf
是正在转换的增强型图元文件的句柄。_bufferSize
是用于存储已转换的 Windows 图元文件的缓冲区大小。_buffer
是用于存储已转换的 Windows 图元文件的字节数组。_mappingMode
指图像的映射模式。映射模式定义了用于转换图像的方向和单位,由 Windows API 提供。在此解决方案中,MM_ANISOTROPIC
用作映射模式。它允许图像的两个轴独立更改。_flags
指示转换图元文件的选项。
图像通过将图像绘制到从元文件创建的图形上下文来嵌入到增强型元文件中。然后将增强型元文件转换为 Windows 元文件。增强型元文件是使用下面的构造函数重载创建的。
Metafile(Stream stream, IntPtr referencedHdc);
这使用设备上下文 referencedHdc
创建一个新的增强型图元文件,并将其存储在 stream
中。设备上下文是一个结构,其中包含控制特定设备上文本和图形显示的信息。图元文件需要与设备上下文关联才能获取分辨率信息。每个 Graphics
对象都可以提供其设备上下文的句柄。RichTextBox
的设备上下文是通过调用其 GetGraphics
方法,然后调用所得 Graphics
对象的 GetHdc
方法来获取的。
...
// Memory stream where Metafile will be stored
_stream = new MemoryStream();
// Get a graphics context from the RichTextBox
using(_graphics = this.CreateGraphics()) {
// Get the device context from the graphics context
_hdc = _graphics.GetHdc();
// Create a new Enhanced Metafile from the device context
_metaFile = new Metafile(_stream, _hdc);
// Release the device context
_graphics.ReleaseHdc(_hdc);
}
...
现在从元文件创建一个 Graphics
上下文,并将图像绘制(嵌入)到文件中。
...
// Get a graphics context from the Enhanced Metafile
using(_graphics = Graphics.FromImage(_metaFile)) {
// Draw the image on the Enhanced Metafile
_graphics.DrawImage(_image, new Rectangle(0, 0,
_image.Width, _image.Height));
}
...
以 null
缓冲区参数调用 EmfToWmfBits
将返回存储 Windows 元文件所需的缓冲区大小。这用于创建一个足够大的数组来容纳 Windows 元文件。
...
// Get number of bytes
uint _bufferSize = GdipEmfToWmfBits(_hEmf, 0, null,
MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
// Create an array to hold the file
byte[] _buffer = new byte[_bufferSize];
...
调用 EmfToWmfBits
并带有一个实例化的缓冲区,会将元文件复制到缓冲区中,并返回复制的字节数。
...
// Get the file
uint _convertedSize = GdipEmfToWmfBits(_hEmf, _bufferSize,
_buffer, MM_ANISOTROPIC,
EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
...
从数组创建图像的十六进制表示形式,现在可以将其插入 RichTextBox
中。
...
// Append the bits to the RTF string
for(int i = 0; i < _buffer.Length; ++i) {
_rtf.Append(String.Format("{0:X2}", _buffer[i]));
}
return _rtf.ToString();
...
RTF 图片目标
在 RTF 文档中定义图片或图像使用的最小控制字是 "{\pict\wmetafile8\picw[N]\pich[N]\picwgoal[N]\pichgoal[N] [BYTES]}",其中...
\pict | - 起始图片或图像标签 |
\wmetafile[N] | - 表示图像类型为 Windows 图元文件。[N] = 8 指定图元文件的轴可以独立调整大小。 |
\picw[N] 和 \pich[N] | - 定义图像的大小,其中 [N] 的单位是百分之一毫米 (0.01)mm。 |
\picwgoal[N] 和 \pichgoal[N] | - 定义图像的目标大小,其中 [N] 的单位是缇。 |
[BYTES] | - 图像的十六进制表示。 |
进行上述计算需要 ExRichTextBox
显示的水平和垂直分辨率。这些值在 ExRichTextBox
的默认构造函数中从 Graphics
对象获取,并分别存储为 xDpi
和 yDpi
。(在大多数系统上,这两个值都是 96 Dpi,但为什么假设呢?)
使用以下转换单位和公式计算元文件的尺寸(以 0.01 毫米为单位)。(下面的示例解释了如何找到当前宽度,但通过分别用高度和垂直分辨率替换宽度和水平分辨率,可以找到高度,使用相同的公式。)
1 英寸 = 2.54 厘米
1 英寸 = 25.4 毫米
1 英寸 = 2540 (0.01)毫米
[N] | = 元文件的当前宽度(以百分之一毫米 (0.01mm) 为单位) |
= 图像宽度(英寸)* 每英寸的 (0.01mm) 数量 | |
= (图像宽度(像素)/图形上下文的水平分辨率)* 2540 | |
= (图像宽度(像素)/Graphics.DpiX)* 2540 |
...
// Calculate the current width of the image in (0.01)mm
int picw = (int)Math.Round((_image.Width / xDpi) * HMM_PER_INCH);
// Calculate the current height of the image in (0.01)mm
int pich = (int)Math.Round((_image.Height / yDpi) * HMM_PER_INCH);
...
缇是屏幕无关单位,用于确保屏幕应用程序中屏幕元素的位置和比例在所有显示系统上都相同。元文件以缇为单位的目标尺寸使用以下转换单位和公式计算。(下面的示例解释了如何找到目标宽度,但通过分别用高度和垂直分辨率替换宽度和水平分辨率,可以找到高度,使用相同的公式。)
1 缇 = 1/20 磅
1 磅 = 1/72 英寸
1 缇 = 1/1440 英寸
[N] | = 元文件以缇为单位的目标宽度 |
= 图像宽度(英寸)* 每英寸的缇数 | |
= (图像宽度(像素)/图形上下文的水平分辨率)* 1440 | |
= (图像宽度(像素)/Graphics.DpiX)* 1440 |
...
// Calculate the target width of the image in twips
int picwgoal = (int)Math.Round((_image.Width / xDpi) * TWIPS_PER_INCH);
// Calculate the target height of the image in twips
int pichgoal = (int)Math.Round((_image.Height / yDpi) * TWIPS_PER_INCH);
...
创建图像的 RTF 表示后,图像将插入到 RTF 文档中,其方式与插入文本类似。如果选中任何文本,图像将替换选中的文本。如果没有选中任何文本,图像将插入到插入符号的位置。
使用代码
要使用 ExRichTextBox
,只需将 ExRichTextBox 项目作为引用包含在您的项目中,或者编译 .dll 并将其添加到您的 VS.NET 工具箱中。有两个可以设置的公共属性:TextColor
是在插入时未指定文本颜色时插入文本将具有的颜色;HighlightColor
是在插入时未指定高亮颜色时插入文本的背景颜色。默认情况下,这些属性分别设置为黑色和白色。项目下载中包含两个使用控件的示例。第一个模拟聊天窗口。用户可以单击表情符号或键入“:)”来插入笑脸(示例只查找第一个出现的“:)”)。它还说明了如何将纯文本作为 RTF 插入。相关方法如下所示。
...
// When an emoticon is clicked, insert its image into to RTF
private void cmenu_Emoticons_Click(object _sender,
EventArgs _args) {
rtbox_SendMessage.InsertImage(_item.Image);
}
...
private void btn_SendMessage_Click(object sender,
System.EventArgs e) {
// Add fake message owner using insert
rtbox_MessageHistory.AppendTextAsRtf("Local User Said\n\n",
new Font(this.Font, FontStyle.Underline | FontStyle.Bold),
RtfColor.Blue, RtfColor.Yellow);
// Just to show it's possible, if the text contains a smiley face [:)]
// insert the smiley image instead. This is not
// a practical way to do this.
int _index;
if ((_index = rtbox_SendMessage.Find(":)")) > -1) {
rtbox_SendMessage.Select(_index, ":)".Length);
rtbox_SendMessage.InsertImage(
new Bitmap(typeof(IMWindow), "Emoticons.Beer.png"));
}
// Add the message to the history
rtbox_MessageHistory.AppendRtf(rtbox_SendMessage.Rtf);
// Add a newline below the added line, just to add spacing
rtbox_MessageHistory.AppendTextAsRtf("\n");
// History gets the focus
rtbox_MessageHistory.Focus();
// Scroll to bottom so newly added text is seen.
rtbox_MessageHistory.Select(rtbox_MessageHistory.TextLength, 0);
rtbox_MessageHistory.ScrollToCaret();
// Return focus to message text box
rtbox_SendMessage.Focus();
// Add the Rtf Codes to the RtfCode Window
frm_RtfCodes.AppendText(rtbox_SendMessage.Rtf);
// Clear the SendMessage box.
rtbox_SendMessage.Text = String.Empty;
}
...
包含的第二个示例是检查 ExRichTextBox
如何处理大图像的方法。用户可以插入位图、JPEG、GIF、图标、PNG 和 TIFF,或者从菜单中插入纯文本。
关注点
ExRichTextBox
是插入小图像的良好解决方案,但将 24 位、432 X 567 JPEG 插入第二个示例应用程序需要整整 2.65 秒。这是因为图像被复制到一个字节数组中,然后复制到一个字符串中,然后插入。应该有一种方法可以在较低级别插入图像的字节表示形式,跳过字符串转换。然而,作者目前对 Win32 API 不太熟悉,因此这将在不久的将来得到改进。