将 .PNG 图像嵌入 RichEdit 文档






4.47/5 (7投票s)
尽管大多数 Windows 程序员已经使用过 RichEdit 控件,但许多人并未意识到它的所有功能。其中一项功能就是图像嵌入。
引言
RichEdit
是 Microsoft 提供的一个富文本格式 (RTF) 处理控件,它拥有长达 25 年的、尽管有些混乱的历史。有随操作系统附带的版本,也有随 Microsoft Office 分发的版本。随操作系统分发的版本是免费的,因为每个人都拥有一个操作系统。因此,程序员可以使用一个无需额外付费即可生成格式化和对齐文本的控件,并且用户也肯定拥有它——因此它很受欢迎。
背景
我不会在这里过多地赘述 RichEdit
控件的历史,无论它多么引人入胜,但我会在文章底部列出自 Windows NT 和 Office 2002 以来的所有版本。我也不会过多地赘述它许多功能的困难之处和文档的缺乏——确实很难掌握它的复杂性。
本文是受 Murray Sargent 2012 年关于 RichEdit 8.0 图像支持的博客(1)启发而写的后续文章。这篇博客文章已经很旧了,但值得关注,因为我在互联网上搜索后,未能找到任何关于如何实现它的例子。
直到 RichEdit
8.0(为了与博客保持一致,我们继续称其为 8.0),我们才能通过一个复杂的 OLE 机制将 .BMP 图像插入 RTF 文本中。虽然复杂,并且也没有得到很好的文档记录,但它确实有效。然而,.BMP 图像不支持压缩,也没有透明的 Alpha 通道,而 RichEdit
8.0 支持 .PNG、.JPG 和 .GIF。
此外,我们将要使用的方法比 .BMP 图像的 OLE 方法要简单得多。所以,你有一个理由继续阅读下去。
Using the Code
我决定卷起袖子做一个演示——用汇编语言 (MASM) (2)!我知道现在大多数人在 M(ASM) 方面都有些困难,但我会解释所有主要的步骤,以便于你(如果你愿意的话)将其转换为任何其他编程语言。
要构建源代码,无需更改,并且尽量减少麻烦和干扰,你需要 MASM32 SDK,可以从 http://www.masm32.com/download.htm 免费下载。
在 Murray Sargent 的博客中提到的两种方法中,我选择了 EM_INSERTIMAGE
消息方法。
那么,我们开始吧!
1. 创建一个对话框
演示从一个控制台应用程序创建对话框开始(在隐藏与演示无关且分散注意力的控制台窗口之后)。
main PROC
INVOKE GetConsoleWindow
INVOKE ShowWindow, eax, 0
INVOKE GetModuleHandle, NULL
mov hInst, eax
Dialog 0, \
"Courier New",8, \
WS_OVERLAPPED or WS_SYSMENU or DS_CENTER, \
0, \
0,0,200,200, \
1024
CallModalDialog hInst, 0, DlgProc, NULL
(...)
main ENDP
Dialog
和 CallModalDialog
是 MASM32 SDK 中的宏。Dialog
在运行时构建模板,CallModalDialog
从该模板创建模态对话框并调用 DialogBoxIndirectParam
。
2. 填充对话框
在对话框过程初始化中,我们调用函数来创建我们的 RichEdit
和两个按钮,并处理其他一些细节。
DlgProc PROC hwndDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rc:RECT
LOCAL tempWidth : DWORD
LOCAL tempHeight : DWORD
SWITCH uMsg
CASE WM_INITDIALOG
; Caption and icon for the Dialog bOx
INVOKE SetWindowText, hwndDlg, TEXT_(".PNGs images in RichEdit")
INVOKE LoadIcon,hInst,TEXT_("MAINICON")
INVOKE SendMessage,hwndDlg,WM_SETICON,ICON_BIG,eax
; Here we make a call to create the RichEdit
INVOKE CreateRichEdit, hwndDlg, 10, 10, 200, 200, hInst
mov hEdit, eax
; Background color - all black
INVOKE SendMessage, hEdit, EM_SETBKGNDCOLOR, 0, 0
; Foreground text
mov cf.cbSize, SIZEOF(CHARFORMAT)
mov cf.dwMask, CFM_COLOR
mov cf.dwEffects, 0
mov cf.yHeight, 200
mov cf.yOffset, 0
RGB 0,255,255
mov cf.crTextColor, eax
mov cf.bCharSet, ANSI_CHARSET
mov cf.bPitchAndFamily, DEFAULT_PITCH or FF_DONTCARE
INVOKE SendMessage,hEdit,EM_SETCHARFORMAT,SCF_ALL, ADDR cf
;Finally, we create 2 buttons, each will embed
;a different image into the RTF when clicked.
INVOKE CreateButton1, hwndDlg, 10, 240, 90, 30, hInst
mov hButton1, eax
INVOKE CreateButton2, hwndDlg, 100, 240, 200, 30, hInst
mov hButton2, eax
3. 创建我们的 Rich Edit
这需要调用 LoadLibrary
函数来加载 Msftedit.dll,然后,我们调用 CreateWindowsEx
函数创建我们的 RichEdit
控件,指定 "RICHEDIT50W
"(称为 MSFTEDIT_CLASS
)作为窗口类。
CreateRichEdit PROC hwndOwner:HWND, x:SDWORD, y:SDWORD, w:SDWORD, h:SDWORD, hinst:HINSTANCE
INVOKE LoadLibrary, TEXT_("Msftedit.dll")
.IF eax==0
; Unlikely to fail, but anyway.
ret
.ENDIF
INVOKE CreateWindowEx, 0, TEXT_("RICHEDIT50W"),
TEXT_("You need Windows 8 or above to test.",13,10,
"Type whatever and/or click the buttons:",13,10,13,10),\
WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_TABSTOP
OR ES_LEFT OR ES_MULTILINE OR ES_WANTRETURN,\
x, y, w, h,\
hwndOwner, NULL, hinst, NULL
ret
CreateRichEdit ENDP
4. 点击按钮
当按下其中一个按钮时,焦点会设置到 Rich Edit,并且会调用我们的 insertEmoticon
函数。
这个函数是演示的核心,它执行以下操作:
- 选择图像,这是一个类型为
RT_PNG
的自定义资源。 - 创建一个实现
IStream 接口
的stream
对象。 - 将图像存储到
stream
对象中。 - 发送带有
EM_INSERTIMAGE
消息的消息。这会将选区替换为一个显示图像的 blob,如果没有选区,则在插入点后插入 blob。 - 就这样,但我们还使用了一些额外的指令来将插入点移动到图像之后。
insertEmoticon PROC png : PTR
LOCAL rcRes : HRSRC
LOCAL hResData : HGLOBAL
LOCAL _pIStream : PTR
LOCAL sizeOfRes : DWORD
LOCAL rip : RICHEDIT_IMAGE_PARAMETERS
LOCAL NewCharRange : CHARRANGE
LOCAL dwStart : DWORD
LOCAL dwEnd : DWORD
INVOKE FindResource, 0, png, TEXT_("RT_PNG")
.IF eax
mov rcRes, eax
INVOKE LoadResource, 0, rcRes
mov hResData, eax
INVOKE SizeofResource, 0, rcRes
mov sizeOfRes, eax
INVOKE CreateStreamOnHGlobal, 0, TRUE, aDDr _pIStream
; IStream::Write
coinvoke _pIStream, IStream, Write, hResData, sizeOfRes, 0
INVOKE RtlZeroMemory, aDDr rip, sizeof rip
mov rip.xWidth, 350 ; unit is 0.01mm
mov rip.yHeight, 350
TA_BASELINE equ 24
mov rip._type, TA_BASELINE
mov eax, TEXT_("Nice Emoticon")
mov rip.pwszAlternateText, eax
mov eax, _pIStream
mov rip.pIStream, eax
EM_INSERTIMAGE equ WM_USER + 314;
INVOKE SendMessage,hEdit,EM_INSERTIMAGE ,0, aDDr rip
; Advance caret
INVOKE SendMessage, hEdit, EM_SETSEL, 0, -1
INVOKE SendMessage, hEdit, EM_GETSEL, aDDr dwStart, aDDr dwEnd
inc dwEnd
INVOKE SendMessage, hEdit, EM_SETSEL, dwEnd, dwEnd
INVOKE SendMessage, hEdit, EM_REPLACESEL, TRUE, 0
; IStream::Release
coinvoke _pIStream, IStream, Release
.ENDIF
ret
insertEmoticon ENDP
RichEdit 控件列表
这是一个表格,列出了自 Windows NT 和 Office 2002(也称为 Office XP 或 Office 10)以来所有 RichEdit
控件的版本(不包括因更新而产生的次要/内部版本号更改)。
InsertImage 方法
TOM2ITextRange2::InsertImage() 方法相对比 EM_INSERTIMAGE 方法复杂一些,特别是对于那些对 COM 技术(谁不是呢?)有些困难的人。然而,它功能更强大,并且与其他 TOM(文本对象模型)框架部分无缝集成。
我制作了一个简单的演示,将文本加载到 RichEdit 控件中(从 Windows 8.0 或更高版本开始)。
然后你按下左下角的按钮,空格会被替换为预期的 .PNG 图像。
源代码和构建好的演示都已附加。
参考文献
更新历史
- 2019 年 4 月 4 日 - 初始发布
- 2019 年 4 月 6 日
- 自 Windows NT/Office 2002 以来的所有
RichEdit
控件版本的表格 - 重新格式化的代码
- 改进了图像插入后前进插入点的代码
- 自 Windows NT/Office 2002 以来的所有
- 2019 年 4 月 11 日 - 添加了 Murray Sargeant 博客中提到的另一种方法的演示——TOM2ITextRange2::InsertImage() 方法。这个演示是用 C++ 编写的,可能更容易理解。
注释
(1): 尽管 Murray Sargent 这么说,但它在 Windows 8.0 的 Richedit 7.5 上也有效。
(2): 在 4 月 11 日的更新中,我为博客中提到的另一种方法制作了一个 C++ 演示。