65.9K
CodeProject 正在变化。 阅读更多。
Home

TRichTextBox –一个通用的 RichTextBox,可以显示动画图像等

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (13投票s)

2010年12月7日

CPOL

5分钟阅读

viewsIcon

56367

downloadIcon

2808

本文介绍了如何将Windows控件对象插入RichTextBox中并用它们来承载图像。

摘要

RichTextBox中显示图像是一个常见的需求,但可用的解决方案有限。通过剪贴板粘贴或嵌入RTF内容仅支持静态图像。本文介绍了如何将Windows控件对象插入RichTextBox并用它们来承载图像。这是一个直接但灵活且可用的解决方案。随附的演示项目展示了一个简单的结果示例。

demo3.PNG

引言

在EE(Experts Exchange)上,我经常遇到关于如何将图像插入RichTextBox的问题。特别是对于开发即时通讯(IM)项目的开发者来说,除了处理聊天文本消息外,表情图标是不可避免的元素。一张哭泣的图片比“我哭了”、“我想哭”这样的苍白文字更能传达情感。然而,RichTextBox基本上是程序员唯一的选择,除非你编写自己的阅读器或浏览器。使用Web浏览器控件实际上也是一个不错的选择。我们可能会在后续文章中讨论它(但不是这篇)。

有两种“标准”的方法可以在RichTextBox中显示静态图像。CodeProject文章《在运行时向RichTextBox插入纯文本和图像》详细描述了两种实现方法:

  • 将图像复制到剪贴板,然后将数据粘贴到目标RichTextBox
  • 读取RTF规范,并通过RTF标签包装的图元文件插入图像数据。

操作剪贴板内容确实令人厌烦,而且不是良好的编程实践。例如,我习惯在打字时将文字复制作为备份,并在发布到论坛之前一定会再次复制整篇文章。万一发生什么问题,剪贴板可以作为应急备份工具。

实际上,在粘贴图像后恢复原始剪贴板数据是很容易的,负责任的开发者应该这样做。

public void InsertImage()
{
  ...
  string lstrFile = fileDialog.FileName;
  Bitmap myBitmap = new Bitmap(lstrFile);

  //keep the clipboard data before set image in it.
  object orgData = Clipboard.GetDataObject

  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.");
  }
//Restore the original data.
Clipboard.SetDataObject(orgData )
  ...
}

上述两种解决方案的根本问题是它们禁用了GIF图像的动画。动画GIF文件包含多个图像帧。“复制和粘贴”只读取图像的第一帧,所以动画就停止了。而“RTF标签”将图像数据转换为一个巨大的字符串,并且RTF规范不足以支持从字符串中获取下一个活动帧。

插入Windows控件

Picture box(图片框)可以显示动画图像。而用户控件可以包含其他控件。所以将控件插入RichTextBox以达到任何可能目的,这并不是什么不寻常的事情。在它可以工作之前,有两个问题需要解决:

  • 首先,内部控件在父控件中保持静止,而且……
  • 其次,输入文本会跑到控件后面,而不是围绕它换行。

我们需要使内部控件在RichTextBox滚动时与文本一起移动,并且我们需要为内部控件留出足够的空间以防止重叠——并覆盖附近的文本,如下图所示。

demo1.PNG

为了解决上述问题,文本的起始点可以用作参考点来决定控件相对于文本的位置。当内容从PosA移动到PosB时,PictureBox也必须移动到新的位置,这很容易计算。

thePictureBox.newPosition = PosB - PosA + thePictureBox.oldPosition = PosB - Delta

demo2.PNG

RichTextBox.GetPositionFromCharIndex方法允许我们检索文本起始位置(PosA),其字符索引为零。我们需要存储此值,并在VScroll事件中使用相同的方法检索文本的新起始位置(PosB)。然后我们可以设置内部控件的新位置。

调用GetPositionFromCharIndex的开销有点大。考虑到在一个聊天会话中,可能会向RichTextBox插入几十甚至上百张图片,因此频繁调用该函数会导致滚动非常缓慢。

获取新位置的替代调用是Win32 API GetScrollPos()。在大多数情况下,从GetScrollPos()获得的值几乎与从GetPositionFromCharIndex获得的值的负值完全相同……只有几点差异,肉眼看不出来。然而,进一步的测试表明,如果用户单击滚动条并拖动,GetScrollPos()返回的值不会改变。只有在用户释放鼠标按钮后才能检索到新的位置值。因此,图片在滚动时不会移动,只有在滚动停止后它们才会飞到新的位置。我们必须发送消息来通知位置变化。所以我们必须添加更多的编码逻辑。最后,在VScroll中获取位置的调用是:

private void TRichTextBox_VScroll(object sender, EventArgs e)
{
    Point pt = new Point();
    SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref pt);

    foreach (MetaInfo one in ControlList)
    {
        one.TheControl.Location = 
           new Point(one.TheControl.Location.X, -pt.Y - one.DeltaY);
    }
}

使用代码

所有推导过程都是为了让该技术易于使用。首先,在Form1.Designer.cs中将你的RichTextBox派生自TRichTextBox

private Trestan.TRichTextBox richTextBox1;
this.richTextBox1 = new Trestan.TRichTextBox();

其次,无论何时你需要使用PictureBox来显示图像,只需调用richTextBox1.AddControl将其添加到列表中。

PictureBox thePic = new PictureBox();
                    thePic.Image = theImage;
                    thePic.Size = theImage.Size;
                    this.richTextBox1.AddControl(thePic);

就是这样!TRichTextBox会为你处理所有剩余的事情。

附加/注释

  • GDI+似乎比GDI函数更精细。某些GDI函数可以加载的图像,在GDI+中可能会导致崩溃。因此,在将图像设置为PictureBox之前,会进行一个简单的健全性检查,其中图像的多个帧会逐一读取,如果在任何过程中捕获到异常,该图像将被丢弃。
  • 一个自我管理函数RemoveSome(),它将使用文本长度和接收到的图像数量作为阈值,自动截断早期接收到的消息。
  • 最有趣的是,你不限于只将PictureBox控件添加到TRichTextBox中;你可以将几乎任何类型的控件插入其中。它们都会随着文本一起滚动,成为内容的一个整体组成部分。如下图所示,每个消息行都附带一个按钮。因此,TRichTextBox更有潜力成为P2P论坛的客户端读取器,而无需中心化网站。

最终结果

demo3.PNG

© . All rights reserved.