• 不再需要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 <msclrmarshal.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, @"Emotion2.gif"));
                wrapper.SaveSelectionCharFormat();
                wrapper.SetSelectionCharColor(Color.Red);
                wrapper.InsertText("Hello, IMRichTextBox!");
                wrapper.RestoreSelectionCharFormat(-1);
                wrapper.InsertImage(Path.Combine(path, @"Emotion18.gif"));
                wrapper.InsertLink("博客园");
                wrapper.InsertBreak();
                wrapper.ScrollToCaret();
            }
        }
    }
    

      效果:

    五、总结

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

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

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

  • 相关阅读:
    ubuntu下eclipse连接mysql
    关于oracle 11g导出数据时 报 ORA 1455错误的处理
    SQL语句改动表名和字段名
    C++组合通信
    退出应用工具类
    ListView间隔设置颜色
    Android闪光灯操作
    Android设置对话框去除黑边
    android设置组件透明度
    git在windows命令行下使用
  • 原文地址:https://www.cnblogs.com/Starts_2000/p/4815915.html
Copyright © 2020-2023  润新知