你的位置:首页 > ASP.net教程

[ASP.net教程]不再需要ImageOle或DynamicGifCtl,.NET实现IM编辑控件


  多年前写过一篇文章《C# 实现IM聊天信息输入显示控件(1)-显示GIF动画图片》,主要是使用ActiveX控件实现RichTextBox插入gif动画图片,包括使用QQ的ImageOle和飞信使用的DynamicGifCtl,这2种方式都需要先注册ActiveX。后续发现QQ新版本没有再使用ImageOle,最近刚好有这方面的需求,于是通过万能的谷歌,找到了相关的资料,不敢独享,于是就有了这篇文章。

一、致谢

  感谢大神万大侠,没有他的系列文章和介绍,我也不可能写下这篇文章。我只是在他提供的控件基础上进行简单的封装,以便用于.NET。万大侠的系列文章地址:致力于richedit应用于IM解决方案。

二、im_richedit简介

  万大侠的im_richedit提供了2个抽象类和一个函数供实现一个IMRichTextBox,它们分别是:

  1、IMRichEditDelegate类

class IMRichEditDelegate { public: virtual void EraseBackground(HDC dc, const RECT& rect) = 0; virtual void PostRenderRichObject(ULONG richobject_id,                  HDC dc, const RECT& rect) = 0;};

  2、IMRichEdit类

class IMRichEdit { public: virtual void DeleteThis() = 0; virtual int GetCharSize() const = 0; virtual void SetCharSize(int size) = 0; virtual BSTR GetCharFace() const = 0; // 注意, 返回的BSTR需要释放!!! virtual void SetCharFace(const wchar_t* face_name) = 0; virtual bool GetCharBold() const = 0; virtual void SetCharBold(bool bold) = 0; virtual bool GetCharItalic() const = 0; virtual void SetCharItalic(bool italic) = 0; virtual COLORREF GetCharColor() const = 0; virtual void SetCharColor(COLORREF color) = 0; virtual int GetSelectionCharSize() const = 0; virtual void SetSelectionCharSize(int size) = 0; virtual BSTR GetSelectionCharFace() const = 0; virtual void SetSelectionCharFace(const wchar_t* face_name) = 0; virtual bool GetSelectionCharBold() const = 0; virtual void SetSelectionCharBold(bool bold) = 0; virtual bool GetSelectionCharItalic() const = 0; virtual void SetSelectionCharItalic(bool italic) = 0; virtual COLORREF GetSelectionCharColor() const = 0; virtual void SetSelectionCharColor(COLORREF color) = 0; virtual int SaveSelectionCharFormat() = 0; virtual bool RestoreSelectionCharFormat(int save_state) = 0; virtual void SelectAll() = 0; virtual void Cut() = 0; virtual void Copy() = 0; virtual void Paste() = 0; virtual void ResetContent() = 0; virtual void SetCaretToEnd() = 0; virtual void ScrollToCaret() = 0; virtual void InsertText(const wchar_t* text) = 0; virtual bool InsertLink(const wchar_t* text) = 0; virtual void InsertBreak() = 0; virtual ULONG InsertRichObject(IMRichObjectType type) = 0; virtual ULONG GetRichObjectId(IOleObject* ole_object) const = 0; virtual bool GetRichObjectType(ULONG richobject_id,                 IMRichObjectType* type) const = 0; // picture_filepath缓冲区大小为MAX_PATH. virtual bool GetRichObjectPicture(ULONG richobject_id,                   wchar_t* picture_filepath) const = 0; virtual bool SetRichObjectPicture(ULONG richobject_id,                   const wchar_t* picture_filepath) = 0; // Tag含义: //  IMRichObjectCustomPicture: 自定义 //  IMRichObjectSystemPicture: 系统编号 //  IMRichObjectFancyCharacter: 字符值 virtual bool GetRichObjectTag(ULONG richobject_id, int* tag) const = 0; virtual bool SetRichObjectTag(ULONG richobject_id, int tag) = 0; virtual bool GetRichObjectFrameCount(ULONG richobject_id,                    UINT* frame_count) const = 0; virtual bool GetRichObjectCurremtFrame(ULONG richobject_id,                     UINT* current_frame) const = 0;};

  3、CreateIMRichEdit函数

IM_RICHEDIT_EXPORT im_richedit::IMRichEdit* CreateIMRichEdit(  IRichEditOle* richedit_ole, im_richedit::IMRichEditDelegate* delegate);

三、.NET IMRichTextBox实现

  主要参考万大侠提供的示例,使用C++/CLI对im_richedit进行封装。

1、IMRichEditDelegate抽象类实现

  IMRichEditDelegateImpl.h

#pragma once#include "im_richedit/im_richedit_sdk.h"namespace Starts2000{	namespace Forms	{		namespace Control		{			class IMRichEditDelegateImpl : public im_richedit::IMRichEditDelegate			{			public:				IMRichEditDelegateImpl();				void EraseBackground(HDC dc, const RECT& rect);				void PostRenderRichObject(ULONG richobject_id, HDC dc, const RECT& rect);			};		}	}}

  IMRichEditDelegateImpl.cpp

#include "IMRichEditDelegateImpl.h"namespace Starts2000{	namespace Forms	{		namespace Control		{			using namespace System::Drawing;			IMRichEditDelegateImpl::IMRichEditDelegateImpl()			{			}			void IMRichEditDelegateImpl::EraseBackground(HDC dc, const RECT& rect)			{				COLORREF old_color = ::SetBkColor(dc, GetSysColor(COLOR_WINDOW));				if (old_color != CLR_INVALID)				{					::ExtTextOut(dc, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);					::SetBkColor(dc, old_color);				}			}			void IMRichEditDelegateImpl::PostRenderRichObject(ULONG richobjectId,				HDC dc, const RECT& rect)			{			}		}	}}

2、定义IIMRichTextBox接口,并使用万大侠提供的IMRichEdit抽象类进行实现。

  IIMRichTextBox.h

#pragma once#include "im_richedit/im_richedit_sdk.h"namespace Starts2000{	namespace Forms	{		namespace Control		{			using namespace System;			using namespace System::Drawing;			using namespace System::Runtime::InteropServices;			public enum struct IMRichObjectType			{				CustomPicture = im_richedit::IMRichObjectCustomPicture,				SystemPicture = im_richedit::IMRichObjectSystemPicture,				FancyCharacter = im_richedit::IMRichObjectFancyCharacter			};			public interface class IIMRichTextBox			{				Int32 GetCharSize() = 0;				void SetCharSize(Int32 size) = 0;				String^ GetCharFace() = 0; // 注意, 返回的BSTR需要释放!!!				void SetCharFace(String^ faceNname) = 0;				Boolean GetCharBold() = 0;				void SetCharBold(Boolean bold) = 0;				Boolean GetCharItalic() = 0;				void SetCharItalic(Boolean italic) = 0;				Color GetCharColor() = 0;				void SetCharColor(Color color) = 0;				Int32 GetSelectionCharSize() = 0;				void SetSelectionCharSize(Int32 size) = 0;				String^ GetSelectionCharFace() = 0;				void SetSelectionCharFace(String^ faceName) = 0;				Boolean GetSelectionCharBold() = 0;				void SetSelectionCharBold(Boolean bold) = 0;				Boolean GetSelectionCharItalic() = 0;				void SetSelectionCharItalic(Boolean italic) = 0;				Color GetSelectionCharColor() = 0;				void SetSelectionCharColor(Color color) = 0;				Int32 SaveSelectionCharFormat() = 0;				Boolean RestoreSelectionCharFormat(int saveState) = 0;				void SelectAll() = 0;				void Cut() = 0;				void Copy() = 0;				void Paste() = 0;				void ResetContent() = 0;				void SetCaretToEnd() = 0;				void ScrollToCaret() = 0;				void InsertText(String^ text) = 0;				Boolean InsertLink(String^ text) = 0;				void InsertBreak() = 0;				UInt32 InsertRichObject(IMRichObjectType type) = 0;				UInt32 GetRichObjectId(IntPtr oleObjectPtr) = 0;				Boolean GetRichObjectType(UInt32 richobjectId, [Out] IMRichObjectType %type) = 0;				// picture_filepath缓冲区大小为MAX_PATH.				Boolean GetRichObjectPicture(UInt32 richobjectId, String^ pictureFilePath) = 0;				Boolean SetRichObjectPicture(UInt32 richobjectId, String^ pictureFilePath) = 0;				// Tag含义:				//  IMRichObjectCustomPicture: 自定义				//  IMRichObjectSystemPicture: 系统编号				//  IMRichObjectFancyCharacter: 字符值				Boolean GetRichObjectTag(UInt32 richobjectId, [Out] IMRichObjectType %tag) = 0;				Boolean SetRichObjectTag(UInt32 richobjectId, IMRichObjectType tag) = 0;				Boolean GetRichObjectFrameCount(UInt32 richobjectId, [Out] Int32 %frameCount) = 0;				Boolean GetRichObjectCurremtFrame(UInt32 richobjectId, [Out] Int32 %currentFrame) = 0;				void InsertImage(String^ fileName) = 0;			};		}	}}

  IMRichTextBoxWrapper.h

#pragma once#include <msclr\marshal.h># include <vcclr.h>#include "IIMRichTextBox.h"namespace Starts2000{	namespace Forms	{		namespace Control		{			using msclr::interop::marshal_as;			ref class IMRichTextBoxWrapper : public IIMRichTextBox			{			private:				im_richedit::IMRichEdit* _imRichEdit;			public:				IMRichTextBoxWrapper(im_richedit::IMRichEdit* imRichEdit)				{					_imRichEdit = imRichEdit;				}				virtual Int32 GetCharSize() sealed				{					return _imRichEdit->GetCharSize();				};				virtual void SetCharSize(Int32 size) sealed				{					return _imRichEdit->SetCharSize(size);				};				virtual String^ GetCharFace() sealed				{					BSTR bstr = _imRichEdit->GetCharFace();					String^ str = marshal_as<String^>(bstr);					delete bstr;					return str;				};// 注意, 返回的BSTR需要释放!!!				virtual void SetCharFace(String^ faceName) sealed				{					pin_ptr<const WCHAR> pFaceName = PtrToStringChars(faceName);					_imRichEdit->SetCharFace(pFaceName);				};				virtual Boolean GetCharBold() sealed				{					return _imRichEdit->GetCharBold();				};				virtual void SetCharBold(Boolean bold) sealed				{					_imRichEdit->SetCharBold(bold);				};				virtual Boolean GetCharItalic() sealed				{					return _imRichEdit->GetCharItalic();				};				virtual void SetCharItalic(Boolean italic) sealed				{					_imRichEdit->SetCharItalic(italic);				};				virtual Color GetCharColor() sealed				{					COLORREF colorRef = _imRichEdit->GetCharColor();					return ColorTranslator::FromWin32(colorRef);				};				virtual void SetCharColor(Color color) sealed				{					_imRichEdit->SetCharColor(ColorTranslator::ToWin32(color));				};				virtual Int32 GetSelectionCharSize() sealed				{					return _imRichEdit->GetSelectionCharSize();				};				virtual void SetSelectionCharSize(Int32 size) sealed				{					_imRichEdit->SetSelectionCharSize(size);				};				virtual String^ GetSelectionCharFace() sealed				{					BSTR bstr = _imRichEdit->GetSelectionCharFace();					String^ str = marshal_as<String^>(bstr);					delete bstr;					return str;				};				virtual void SetSelectionCharFace(String^ faceName) sealed				{					pin_ptr<const WCHAR> pFaceName = PtrToStringChars(faceName);					_imRichEdit->SetSelectionCharFace(pFaceName);				};				virtual Boolean GetSelectionCharBold() sealed				{					return _imRichEdit->GetSelectionCharBold();				};				virtual void SetSelectionCharBold(Boolean bold) sealed				{					_imRichEdit->SetSelectionCharBold(bold);				};				virtual Boolean GetSelectionCharItalic() sealed				{					return _imRichEdit->GetSelectionCharItalic();				};				virtual void SetSelectionCharItalic(Boolean italic) sealed				{					_imRichEdit->SetSelectionCharItalic(italic);				};				virtual Color GetSelectionCharColor() sealed				{					COLORREF colorRef = _imRichEdit->GetSelectionCharColor();					return ColorTranslator::FromWin32(colorRef);				};				virtual void SetSelectionCharColor(Color color) sealed				{					_imRichEdit->SetSelectionCharColor(ColorTranslator::ToWin32(color));				};				virtual Int32 SaveSelectionCharFormat() sealed				{					return _imRichEdit->SaveSelectionCharFormat();				};				virtual Boolean RestoreSelectionCharFormat(int saveState) sealed				{					return _imRichEdit->RestoreSelectionCharFormat(saveState);				};				virtual void SelectAll() sealed				{					_imRichEdit->SelectAll();				};				virtual void Cut() sealed				{					_imRichEdit->Cut();				};				virtual void Copy() sealed				{					_imRichEdit->Copy();				};				virtual void Paste() sealed				{					_imRichEdit->Paste();				};				virtual void ResetContent() sealed				{					_imRichEdit->ResetContent();				};				virtual void SetCaretToEnd() sealed				{					_imRichEdit->SetCaretToEnd();				};				virtual void ScrollToCaret() sealed				{					_imRichEdit->ScrollToCaret();				};				virtual void InsertText(String^ text) sealed				{					pin_ptr<const WCHAR> pText = PtrToStringChars(text);					_imRichEdit->InsertText(pText);				};				virtual Boolean InsertLink(String^ text) sealed				{					pin_ptr<const WCHAR> pText = PtrToStringChars(text);					return _imRichEdit->InsertLink(pText);				};				virtual void InsertBreak() sealed				{					_imRichEdit->InsertBreak();				};				virtual UInt32 InsertRichObject(IMRichObjectType type) sealed				{					return _imRichEdit->InsertRichObject(						static_cast<im_richedit::IMRichObjectType>(type));				};				virtual UInt32 GetRichObjectId(IntPtr oleObjectPtr) sealed				{					return _imRichEdit->GetRichObjectId(						reinterpret_cast<IOleObject *>(oleObjectPtr.ToPointer()));				};				virtual Boolean GetRichObjectType(					UInt32 richobjectId, [Out] IMRichObjectType %type) sealed				{					im_richedit::IMRichObjectType objType;					bool rel = _imRichEdit->GetRichObjectType(richobjectId, &objType);					type = static_cast<IMRichObjectType>(objType);					return rel;				};				// picture_filepath缓冲区大小为MAX_PATH.				virtual Boolean GetRichObjectPicture(					UInt32 richobjectId, String^ pictureFilePath) sealed				{					wchar_t *pFilePath = new wchar_t[MAX_PATH];					bool rel = _imRichEdit->GetRichObjectPicture(richobjectId, pFilePath);					pictureFilePath = marshal_as<String^>(pFilePath);					return rel;				};				virtual Boolean SetRichObjectPicture(					UInt32 richobjectId, String^ pictureFilePath) sealed				{					pin_ptr<const WCHAR> pFileName = PtrToStringChars(pictureFilePath);					return _imRichEdit->SetRichObjectPicture(richobjectId, pFileName);				};				// Tag含义:				//  IMRichObjectCustomPicture: 自定义				//  IMRichObjectSystemPicture: 系统编号				//  IMRichObjectFancyCharacter: 字符值				virtual Boolean GetRichObjectTag(					UInt32 richobjectId, [Out] IMRichObjectType %tag) sealed				{					int iTag;					bool rel = _imRichEdit->GetRichObjectTag(richobjectId, &iTag);					tag = static_cast<IMRichObjectType>(iTag);					return rel;				};				virtual Boolean SetRichObjectTag(UInt32 richobjectId, IMRichObjectType tag) sealed				{					return _imRichEdit->SetRichObjectTag(						richobjectId, static_cast<im_richedit::IMRichObjectType>(tag));				};				virtual Boolean GetRichObjectFrameCount(					UInt32 richobjectId, [Out] Int32 %frameCount) sealed				{					UINT uiFrameCount;					bool rel = _imRichEdit->GetRichObjectFrameCount(richobjectId, &uiFrameCount);					frameCount = uiFrameCount;					return rel;				};				virtual Boolean GetRichObjectCurremtFrame(					UInt32 richobjectId, [Out] Int32 %currentFrame) sealed				{					UINT uiCurrentFrame;					bool rel = _imRichEdit->GetRichObjectCurremtFrame(richobjectId, &uiCurrentFrame);					currentFrame = uiCurrentFrame;					return rel;				};				virtual void InsertImage(String^ fileName) sealed				{					ULONG id = _imRichEdit->InsertRichObject(im_richedit::IMRichObjectSystemPicture);					pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);					_imRichEdit->SetRichObjectPicture(id, pFileName);				};			};		}	}}

3、通过继承.NET的RichTextBox,实现IMRichTextBox

  IMRichTextBox主要通过IIMRichTextBox接口定义的IMRichTextBoxWrapper属性来使用万大侠封装的im_richedi的功能。

  IMRichTextBox.h

#pragma once#include "im_richedit/im_richedit_sdk.h"#include "AutoNative.h"#include "IMRichEditDelegateImpl.h"#include "IMRichTextBoxWrapper.h"#pragma comment(lib, "im_richedit/im_richedit.lib")namespace Starts2000{	namespace Forms	{		namespace Control		{			using namespace System;			using namespace System::Diagnostics;			using namespace System::Windows::Forms;			using namespace System::Security::Permissions;			public ref class IMRichTextBox : public RichTextBox			{			public:				IMRichTextBox();				property IIMRichTextBox^ IMRichTextBoxWrapper				{					IIMRichTextBox^ get()					{						return _imRichTextBoxWrapper;					}				}			protected:				[SecurityPermission(SecurityAction::LinkDemand, Flags = SecurityPermissionFlag::UnmanagedCode)]				void WndProc(System::Windows::Forms::Message %msg) override;			private:				IMRichEditDelegateImpl* _imRichEditDelegate;				im_richedit::IMRichEdit* _imRichEdit;				IIMRichTextBox^ _imRichTextBoxWrapper;			};		}	}}

  IMRichTextBox.cpp

#include "IMRichTextBox.h"namespace Starts2000{	namespace Forms	{		namespace Control		{			IMRichTextBox::IMRichTextBox() : RichTextBox()			{				RichTextBox::HideSelection = false;			}			void IMRichTextBox::WndProc(Message %msg)			{				if (msg.Msg > 2)				{					__super::WndProc(msg);					return;				}				HWND richEditHwnd = NULL;				LPRICHEDITOLE lpRichEditOle = NULL;				switch (msg.Msg)				{				case WM_CREATE:					__super::WndProc(msg);					richEditHwnd = reinterpret_cast<HWND>(Handle.ToPointer());					::SendMessage(richEditHwnd, EM_GETOLEINTERFACE, 0, reinterpret_cast<LPARAM>(&lpRichEditOle));#ifdef _DEBUG					Debug::Assert(lpRichEditOle != NULL);#endif					_imRichEditDelegate = new IMRichEditDelegateImpl();					_imRichEdit = ::CreateIMRichEdit(lpRichEditOle, _imRichEditDelegate);					_imRichTextBoxWrapper = gcnew Starts2000::Forms::Control::IMRichTextBoxWrapper(_imRichEdit);					break;				case WM_DESTROY:					if (_imRichEdit)					{						_imRichEdit->DeleteThis();						_imRichEdit = NULL;					}					if (_imRichEditDelegate != NULL)					{						delete _imRichEditDelegate;						_imRichEditDelegate = NULL;					}					__super::WndProc(msg);					break;				default:					__super::WndProc(msg);					break;				}			}		}	}}

四、示例及效果

  IMRichTextBox不能通过工具箱直接拖到窗体设计器上,只能手动添加代码。    

using System;using System.Drawing;using System.IO;using System.Windows.Forms;using Starts2000.Forms.Control;namespace Starts2000.RichEditDemo{  public partial class FormMain : Form  {    IMRichTextBox _imRichTextBox;    public FormMain()    {      InitializeComponent();      _imRichTextBox = new IMRichTextBox();      _imRichTextBox.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;      _imRichTextBox.Location = new Point(3, 3);      _imRichTextBox.Width = ClientSize.Width - 6;      _imRichTextBox.Height = ClientSize.Height - 40;      Controls.Add(_imRichTextBox);    }    private void btnInsertImage_Click(object sender, EventArgs e)    {      OpenFileDialog dialog = new OpenFileDialog();      dialog.DefaultExt = "gif";      dialog.Filter = "图片文件|*.jpg;*.gif;*.bmp";      dialog.Multiselect = true;      if (dialog.ShowDialog() == DialogResult.OK)      {        foreach (var imgFile in dialog.FileNames)        {          _imRichTextBox.IMRichTextBoxWrapper.InsertImage(imgFile);        }      }      _imRichTextBox.IMRichTextBoxWrapper.ScrollToCaret();    }    private void btnInserText_Click(object sender, EventArgs e)    {      var wrapper = _imRichTextBox.IMRichTextBoxWrapper;      var path = Application.StartupPath;      wrapper.SaveSelectionCharFormat();      wrapper.SetSelectionCharColor(Color.FromArgb(0, 102, 0));      wrapper.InsertText("Starts2000 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));      wrapper.RestoreSelectionCharFormat(-1);      wrapper.InsertBreak();      wrapper.InsertImage(Path.Combine(path, @"Emotion\2.gif"));      wrapper.SaveSelectionCharFormat();      wrapper.SetSelectionCharColor(Color.Red);      wrapper.InsertText("Hello, IMRichTextBox!");      wrapper.RestoreSelectionCharFormat(-1);      wrapper.InsertImage(Path.Combine(path, @"Emotion\18.gif"));      wrapper.InsertLink("博客园");      wrapper.InsertBreak();      wrapper.ScrollToCaret();    }  }}

  效果:

五、总结

  1、C++/CLI在封装现有C++项目供.NET使用还是非常给力的。

  2、万大侠的im_richedit还提供了WindowLess的richedit的封装,由于我没有使用,所以没有进行封装,如果有需要,大家可自行封装。

  3、项目使用VS2013进行开发、编译和调试,不保证其他版本VS下能正常编译,项目源码下载:IMRichTextBox。