65.9K
CodeProject 正在变化。 阅读更多。
Home

将 .PNG 图像嵌入 RichEdit 文档

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (7投票s)

2019年4月4日

CPOL

5分钟阅读

viewsIcon

16500

downloadIcon

1057

尽管大多数 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

DialogCallModalDialog 是 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 函数。

这个函数是演示的核心,它执行以下操作:

  1. 选择图像,这是一个类型为 RT_PNG 的自定义资源。
  2. 创建一个实现 IStream 接口stream 对象。
  3. 将图像存储到 stream 对象中。
  4. 发送带有 EM_INSERTIMAGE 消息的消息。这会将选区替换为一个显示图像的 blob,如果没有选区,则在插入点后插入 blob。
  5. 就这样,但我们还使用了一些额外的指令来将插入点移动到图像之后。
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 日
    1. 自 Windows NT/Office 2002 以来的所有 RichEdit 控件版本的表格
    2. 重新格式化的代码
    3. 改进了图像插入后前进插入点的代码
  • 2019 年 4 月 11 日 - 添加了 Murray Sargeant 博客中提到的另一种方法的演示——TOM2ITextRange2::InsertImage() 方法。这个演示是用 C++ 编写的,可能更容易理解。
注释

(1): 尽管 Murray Sargent 这么说,但它在 Windows 8.0 的 Richedit 7.5 上也有效。

(2): 在 4 月 11 日的更新中,我为博客中提到的另一种方法制作了一个 C++ 演示。

© . All rights reserved.