不再需要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。