<转>RichTextBox的扩展(插入图片到RichTextBox)

 当你在网上搜索插入图片到RichTextBox的方案时,大多数的解决方法是通过拷贝图片到剪贴板,然后粘贴图片到目标RichTextBox中最后清空剪贴板内容。程序如下:
public void InsertImage()
{
...
string lstrFile = fileDialog.FileName;
Bitmap myBitmap = new Bitmap(lstrFile);
// 拷贝位图到剪贴板
Clipboard.SetDataObject(myBitmap);
// 获取对象格式
DataFormats.Format myFormat = DataFormats.GetFormat (DataFormats.Bitmap);
// 验证并粘贴位图
If (NoteBox.CanPaste(myFormat)) {
NoteBox.Paste(myFormat);
}
else {
MessageBox.Show("The data format that you attempted site" +
" is not supportedby this control.");
}
...
}


   这并不是一个好的解决方案,因为它在没有提示用户的情况下修改了剪贴板的内容。而其他的方案则是硬编码大量的图形十六进制数据到程序中,但这样做显的很不灵活。对于在运行期插入纯文本到RichTextBox中来说,这些都不是好的解决方案。本文提供的方案能解决以上方案的这些问题。
    本方案主要实现以下功能:
   1. 允许在运行期以编程的方式插入或追加内容到RichTextBox中。
   2. 允许对插入或追加的纯文本进行字体,颜色和高显亮颜色进行修改。
   3. 在不通过剪贴板情况下以编程的方式插入图片
   RichTextBox的内容即可以是纯文本格式也可以是富文本格式。在下文中所有富文本格式都简称为RTF。

提示: 
    转换纯文本到RTF是关于追加字符串到创建RTF代码的一个过程。这个过程比较简单,但是需要读者熟悉RTF文档结构和控制字。本文不是一个关于RTF的指南,这里只讨论插入纯文本的议题。因此更多的有关RTF的内容读者需要去阅读源码和RTF指南1.6版。

背景
    在进入主题前,明白什么是RTF文档和图元文件是必要的。

RTF文档
   
    RTF是一种结构化文件格式,它通过使用控制字和符号来创建一个应用于不同环境下的文件。当它被阅读时RTF阅读器会转换RTF内容,控制字和符号到格式化的文本。这样就可以很简单以HTML格式显示给用户。在本文中RTF的阅读器是指RichTextBox。
    RTF指南是一个有着250个页面的文档,所以不可能将它全部包含到本文中来。本文只介绍在插入图片操作时用到的控制字。完整的RTF文档请参考RTF指南1.6版。

图元文件
   
    在.Net框架中,Image类继承了图元文件类,但是图元文件不是光栅化的图片对象。光栅化的图片对象是由一组矩阵格式的像素点组成的,如位图。图元文件是一个包含了几何方位和绘制命令的向量图像。图元文件可以使用.Net框架的函数转换到位图,但是位图不能在.Net框架中转换到图元文件。因此位图可以被嵌入到图元文件中。
    .Net框架提供了两种格式图元文件: windows 图元文件格式(以下简称WMF)和增强图元文件格式 (以下简称EMF)。这些图元文件区别主要是各自不同的绘制命令;EMF比WMF拥有更多的绘制命令。根据微软的文档显示,WMF已不提倡继续使用只是为了向后兼容才保留的,但是本方案还是使用WMF来实现。完整的关于元数据文档可到微软的网站查看。

插入和追加纯文本

    插入RTF文本到一个RichTextBox是通过赋值一个字符串给RTF文档到RichTextBox.Rtf属性或是RichTextBox.SelectedRtf属性来实现的。如果在文本插入的时候已有文本被选中,那么那些文本将被替换。如果没有文本被选中,那么文本将被插入到光标位置。追加文本的代码如下:

/// 追加文本根据给出的字体,颜色,移动光标到尾部并插入
public void AppendTextAsRtf(string _text, Font _font,
RtfColor _textColor, RtfColor _backColor)
{
// 移动光标到文本的尾部
this.Select(this.TextLength, 0);
InsertTextAsRtf(_text, _font, _textColor, _backColor);
}

这里对AppendTextAsRtf有三个重载的方案:
/// 根据当前的字体追加文本并高显亮
public void AppendTextAsRtf(string _text)
{
AppendTextAsRtf(_text, this.Font);
}

/// 根据指定的字体追加文本
public void AppendTextAsRtf(string _text, Font _font)
{
AppendTextAsRtf(_text, _font, textColor);
}

/// 根据指定的字体和颜色追加文本,并高显亮
public void AppendTextAsRtf(string _text, Font _font, RtfColor _textColor)
{
AppendTextAsRtf(_text, _font, _textColor, highlightColor);
}

插入文本
   在插入文本到RichTextBox时,文本必须是RTF格式。根据RTF指南说明RTF文档必须包含一个文档头和文档区域用于验证。文档头包含了文档中用到的语言和字体颜色列表。文档区域则是存储被格式的内容的地方。通过调用InsertTextAsRtf函数,RTF文档头被构建同时纯文本被格式化并加入到文档区域中。
public void InsertTextAsRtf(string _text, Font _font,
RtfColor _textColor, RtfColor _backColor)
{
StringBuilder _rtf = new StringBuilder();

// 追加文档头
_rtf.Append(RTF_HEADER);

// 创建字体表
_rtf.Append(GetFontTable(_font));

// 创建颜色表
_rtf.Append(GetColorTable(_textColor, _backColor));

// 创建文档区域
_rtf.Append(GetDocumentArea(_text, _font));

this.SelectedRtf = _rtf.ToString();
}

这里也有三个重载版本的InsertTextAsRtf函数:
public void InsertTextAsRtf(string _text)
{
InsertTextAsRtf(_text, this.Font);
}

public void InsertTextAsRtf(string _text, Font _font)
{
InsertTextAsRtf(_text, _font, textColor);
}

public void InsertTextAsRtf(string _text, Font _font, RtfColor _textColor)
{
InsertTextAsRtf(_text, _font, _textColor, highlightColor);
}

插入图片
    当一个图片被粘贴到RichTextBox(或记事本,或word软件),图片先被嵌入到WMF然后被贴入文档中。InsertImage方法完成同样的工作,但是不通过剪贴板。根据RTF指南1.6版说明,插入的图片如果是bitmap,JPEG,GIF,PNG和增强 图元文件格式的可以直接插入而不用先嵌入到windows图元文件。但是,当那只是对于微软的word软件来说,对于写字板和RichTextBox,如果图片没有先嵌入到Windows图元文件中将被忽略。



图元文件
     Windows图元文件在windows1.0的时候就被支持。它们在功能上受到限制并且也是为了向后兼容的目的才被windows窗体支持。.Net框架不支持直接创建windows图元文件,但是能够读取它们。.Net支持创建增强图元文件,因此增强 图元文件能通过非托管代码转换到Windows图元文件。GDI+包含了这个转换功能函数叫做EmfToWmfBits()。函数的形式如下:

[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中。设备环境是一个结构,它包含了控件要显示文本和图形到具体的设备上的信息。图元文件必须被关联到设备环境中才能获取分辨率信息。每个图形对象都能提供一个句柄指向本身的设备环境。RichTextBox的设备环境通过GetGraphics方法来获取,然后调用GetHdc方法来获取最终的图形对象。

...
// 存储图元文件的内存流
_stream = new MemoryStream();

// 从RichTextBox的图形对象
using(_graphics = this.CreateGraphics())
{
// 从图形对象来获取设备环境
_hdc = _graphics.GetHdc();

// 从设备环境中创建一个增强 图元文件
_图元文件 = new 图元文件(_stream, _hdc);

// 释放设备环境
_graphics.ReleaseHdc(_hdc);
}
...

一个图形环境从图元文件被创建,然后图像将绘制到这个环境上。
...
// 从增强图元文件中获取图形对象
using(_graphics = Graphics.FromImage(_metaFile))
{
// 绘制图像到增强文件上
_graphics.DrawImage(_image, new Rectangle(0, 0, _image.Width, _image.Height));
}
...
    通过带有一个null缓存参数的方法调用EmfToWmfBits函数将返回要存储Windows图元文件的缓存大小。这个将用于创建一个足够大的内存来存储Windows图元文件。

...
// 获取字节数
uint _bufferSize = GdipEmfToWmfBits(_hEmf, 0, null, MM_ANISOTROPIC,
EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);

// 创建一个字节数组来存储文件
byte[] _buffer = new byte[_bufferSize];
...
   调用EmfToWmfBits来将图元文件拷贝到一个缓存中并返回已拷贝的字节数。

...
// 获取文件
uint _convertedSize = GdipEmfToWmfBits(_hEmf, _bufferSize, _buffer,
MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
...
   创建一个关于图像的十六进制数组并准备插入到RichTextBox中。

// 追加位数到RTF字符串
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.01mm。
\picwgoal[N]和\pichgoal[N] -定义目标图像的大小
[BYTES] - 图像的十六进制描述
在ExRichTextBox显示时,水平和垂直方位上的比率值必须先被计算出来。这些值可以由ExRichTextBox的默认构造函数从图形对象和存储的如xDpi和yDpi中获取(在大多数系统这些值是96Dpi,但是为什么呢?)。
图元文件的面积可以使用下面的公式来换算。
1英寸 = 2.54厘米
1英寸 = 25.4豪米
[N] = 目标图元文件的1/20
= 图像宽度的英寸值*n/20屏幕比率
= (图像宽度的像素值/图形环境垂直方向的比率)*1440
= (图像宽度的像素值/图形对象的DpiX)*1440

...
// 计算图像的目标宽度
int picwgoal = (int)Math.Round((_image.Width / xDpi) * TWIPS_PER_INCH);

// 计算图像的目标高度
int pichgoal = (int)Math.Round((_image.Height / yDpi) * TWIPS_PER_INCH);
...
    在图像的RTF描述被创建后,图像插入到RTF文档中就像插入纯文本那样简单。如果文本被选中,那么插入时就被替换,同样如果没被选中那么图像就插入到光标位置。

使用代码

    使用ExRichTextBox比较简单只要像ExRichTextBox工程那样包含已编译的.dll到你的工程并添加它到.Net工具条中就可以。这里有两个公有的属性用于设置:TextColor,设置插入文本的颜色如果在插入时没有指定颜色的话;HighlightColor,设置插入文本的背景颜色如果插入时没指定的话。默认情况下它们被设置为黑和白。在下载的工程中包含了两个例子:第一个模拟聊天窗口,用户通过点击表情符号或键入":)"来插入表情图像。它也展示了如何去插入纯文本到RTF。具体如下:

..
// 当一个表情图像被点击,插入一个图像到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)
{
// 添加伪装信息
rtbox_MessageHistory.AppendTextAsRtf("Local User Said\n\n",
new Font(this.Font, FontStyle.Underline | FontStyle.Bold),
RtfColor.Blue, RtfColor.Yellow);

// 将表情符号转换成图像
int _index;
if ((_index = rtbox_SendMessage.Find(":)")) > -1) {
rtbox_SendMessage.Select(_index, ":)".Length);
rtbox_SendMessage.InsertImage(new Bitmap(typeof(IMWindow),
"Emoticons.Beer.png"));
}

// 添加一个消息到历史记录中
rtbox_MessageHistory.AppendRtf(rtbox_SendMessage.Rtf);

// 添加一个新行
rtbox_MessageHistory.AppendTextAsRtf("\n");

// 焦点历史记录
rtbox_MessageHistory.Focus();

// 查看最新的
rtbox_MessageHistory.Select(rtbox_MessageHistory.TextLength, 0);
rtbox_MessageHistory.ScrollToCaret();

// 返回焦点消息框
rtbox_SendMessage.Focus();

// 添加RTF代码到窗口
frm_RtfCodes.AppendText(rtbox_SendMessage.Rtf);

// 清除一个发送消息框内容
rtbox_SendMessage.Text = String.Empty;
}
...
    第二个例子包含了如何使用ExRichTextBox处理大图片。用户可以通过菜单插入bitmap, JPEG, GIF,Icon,PNG和TIFF格式的图像或者是纯文本。

posted on 2007-11-01 16:17  西门潇洒  阅读(13239)  评论(9编辑  收藏  举报