转载

不再需要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();   }  } } 

效果:

不再需要ImageOle或DynamicGifCtl,.NET实现IM编辑控件

五、总结

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

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

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

正文到此结束
Loading...