#pragma once
#include <richedit.h>
#include <textserv.h>
#include <RichOle.h>
#include "CaretWindow.h"
#include "richeditolemgr.h"
#pragma comment(lib, "Riched20.lib")
class RichEditBase;
// RichEdit及其Callback的實現,可以參考MFC CRichEditView
// RichEidt的ole view的實現,可以參考ATL插入一個控件對象的實現代碼
//
//
// Q1. 無窗口RE是怎么創建的?
// a. 調用CreateTextSevices()
// b. ??
//
// Q2. 無窗口RE是怎么繪制到窗口上面的?
// a. 拿到一個內存DC?
// b. 調用itextservices->txdraw方法,隨便提供一個clientrect,這樣就能在該窗口區域上顯示一個白色背景了
// c.
//
// Q3. 無窗口RE是怎么決定自己在窗口上的范圍的?
// a. Draw的時候給出的clientrect就是顯示的范圍
// b. 然后在其它時候判斷當前鼠標是否位於窗口內或者返回TxGetClientRect
//
// Q4. 怎么輸入文字?
// a. 將WM_CHAR消息傳遞給service text即可。需要注意的是模態對話框是收不到WM_CHAR消息的。
//
// Q5. 輸入文字后是怎么刷新顯示的?
// a. 內部將調用TxInvalidateRect接口,在該接口中,我們自己去實現為::InvalidateRect
//
// Q6. 怎么設置它的樣式?例如多行編輯功能?
// a. TxGetPropertyBits 返回的
//
// Q7. 當有選中區域時,發現光標的位置顯示不太准確,怎么才能在有選中區域時不顯示光標?
// a. 其實這個時候TxCreateCaret(HBITMAP hbmp 這個hbmp是不為空的,使用它創建的caret就不會顯示。why?
//
// Q8. 為什么我用CCaretWindow實現光標時,不能做到輸入文字時將系統光標隱藏?
//
//
// Q9. 光標是怎么銷毀的?
// a. 向text service傳遞kill focus消息,而且還得傳遞set focus消息,否則再次得到焦點時光標不顯示。
//
// Q10.為什么我向textservice發送SETFOCUS消息后,卻不會響應TxCreateCaret消息
// a. 因為沒有調用OnTxInPlaceActivate
//
// Q11.在控件位置改變后,怎么去同步光標的位置?
// a. 向txt service發送一個通知:m_spTextServices->OnTxPropertyBitsChange(TXTBIT_CLIENTRECTCHANGE, TRUE);
//
// Q12.為什么拖拽時,刷新會出現異常?先添加后刪除,但是分開刷新的
//
// Q13.如何顯示滾動條?
// 在TxGetScrollBars中,返回你的m_dwStyle樣式。因此需要在m_dwStyle中指定
// WM_HSCROLL,WM_VSCROLL...
//
// Q14.OLE的生命周期.
// 因為richedit的undo功能,對象會一直保存在內存中,直到richedit關閉。
//
// 1. WM_KEYDOWN delete鍵刪除對象時,IRichEditOleCallback::DeleteObject。這里不能調用Release
// > UIDLL.dll!IRichEditOleCallbackImpl<WindowlessRichEdit>::DeleteObject(IOleObject * lpoleobj=0x0011fc90) 行251 C++
// riched20.dll!CObjectMgr::ReplaceRange() + 0x9a 字節
// riched20.dll!CRchTxtPtr::ReplaceRange() + 0x18dde 字節
// riched20.dll!CTxtRange::ReplaceRange() + 0x80 字節
// riched20.dll!CTxtSelection::ReplaceRange() + 0x1f9 字節
// riched20.dll!CTxtSelection::Backspace() + 0xc0 字節
// riched20.dll!CTxtEdit::OnTxKeyDown() + 0x5d4 字節
// riched20.dll!CTxtEdit::TxSendMessage() + 0x21f3 字節
// UIDLL.dll!WindowlessRichEdit::OnPostHandleMsg(HWND__ * hWnd=0x00220428, unsigned int Msg=256, unsigned int wParam=8, long lParam=917505) 行196 + 0x39 字節 C++
//
// 2. DeleteObject之后,接着會響應IOleObject::Close。 這里不能調用Release
// 將一個OLE對象用delete鍵給刪除掉時,會調用。然后將對象用ctrl+z再還原,然后再刪除時,又會調用一次
//
// > UIDLL.dll!CGifOleObject::Close(unsigned long dwSaveOption=0) 行50 C++
// riched20.dll!COleObject::Close() + 0x2d 字節
// riched20.dll!CReplaceObjectAE::OnCommit() + 0xa 字節
// riched20.dll!CommitAEList() + 0x21 字節
// riched20.dll!CGenUndoBuilder::Done() + 0xf276 字節
// riched20.dll!CTxtEdit::TxSendMessage() + 0x11b 字節
// UIDLL.dll!WindowlessRichEdit::OnPostHandleMsg(HWND__ * hWnd=0x001504fc, unsigned int Msg=256, unsigned int wParam=8, long lParam=917505) 行196 + 0x39 字節 C++
//
// 3. 銷毀. 在ole對象已不在undo范圍時,richedit才會將該對象release掉,此時CGifOleObject被釋放
// 堆棧如下:
// > UIDLL.dll!CGifOleObject::~CGifOleObject() 行13 C++
// UIDLL.dll!CGifOleObject::`scalar deleting destructor'() + 0x2b 字節 C++
// UIDLL.dll!CGifOleObject::Release() 行66 + 0x2b 字節 C++
// riched20.dll!SafeReleaseAndNULL() + 0x17 字節
// riched20.dll!COleObject::DisconnectObject() + 0x95 字節
// riched20.dll!COleObject::CleanupState() + 0x33 字節
// riched20.dll!COleObject::MakeZombie() + 0xa 字節
// riched20.dll!CReplaceObjectAE::Destroy() + 0x1a 字節
// riched20.dll!DestroyAEList() + 0x1d 字節
// riched20.dll!CUndoStack::ClearAll() + 0x2c 字節
// riched20.dll!CGenUndoBuilder::Done() + 0x1c56d 字節
// riched20.dll!CTxtEdit::TxSendMessage() + 0x11b 字節
// UIDLL.dll!WindowlessRichEdit::OnChar(unsigned int uMsg=258, unsigned int wParam=97, long lParam=1966081) 行297 + 0x39 字節 C++
//
// Q15. Rich Edit 剪貼板操作
// http://zjqzy03080312.blog.163.com/blog/static/1857428072012124112714348/
//
// 應用程序可以粘貼剪貼板中內容到一個Rich Edit控件中,采用最佳可用剪貼板格式或者指定的剪貼板格式。
// 你也可以決定是否一個Rich Edit控件可以粘貼某種剪貼板格式。
//
// 對於一個Edit控件而言,你可以使用WM_COPY或者WM_CUT消息來拷貝或者剪切當前選中內容。同樣的,你可
// 以使用WM_PASTE消息將這些剪貼板內容粘貼到一個Rich Edit控件中。控件將粘貼它所識別的第一個可用格式,
// 這大概是最具描述性的格式。
//
// 你可以使用EM_PASTESPECIAL消息來粘貼指定的剪貼板格式。這個消息對具有"特殊粘貼"命令的應用程序很有
// 用,該命令可以讓用戶選擇剪貼板格式。你可以使用EM_CANPASTE消息來決定控件是否識別某種指定的格式。
//
// 你也可以使用EM_CANPASTE消息來決定Rich Edit控件是否識別所有可用的剪貼板格式。該消息在處理WM_INITMENUPOPUP
// 消息時很有用。應用程序可以啟用或者屏蔽"粘貼"命令,取決於控件是否可以粘貼任一個可用類型。
//
// Rich Edit控件注冊兩種剪貼板格式:"富文本格式(RTF)"和一種叫做"RichEdit 文本與對象"的格式。應用程序
// 可以使用RegisterClipboardFormat函數來注冊這些格式,其取值為CF_RTF與CF_RETEXTOBJ
//
class UIAPI ITextHostImpl : public ITextHost
{
public:
ITextHostImpl();
~ITextHostImpl(){};
// ITextHost Interface
virtual HDC TxGetDC();
virtual INT TxReleaseDC(HDC hdc);
// virtual BOOL TxShowScrollBar(INT fnBar, BOOL fShow);
// virtual BOOL TxEnableScrollBar (INT fuSBFlags, INT fuArrowflags);
// virtual BOOL TxSetScrollRange(INT fnBar,LONG nMinPos,INT nMaxPos,BOOL fRedraw);
// virtual BOOL TxSetScrollPos (INT fnBar, INT nPos, BOOL fRedraw);
// virtual void TxInvalidateRect(LPCRECT prc, BOOL fMode);
virtual void TxViewChange(BOOL fUpdate);
virtual BOOL TxCreateCaret(HBITMAP hbmp, INT xWidth, INT yHeight);
virtual BOOL TxShowCaret(BOOL fShow);
virtual BOOL TxSetCaretPos(INT x, INT y);
virtual BOOL TxSetTimer(UINT idTimer, UINT uTimeout);
virtual void TxKillTimer(UINT idTimer);
virtual void TxScrollWindowEx (INT dx,INT dy,LPCRECT lprcScroll,LPCRECT lprcClip,HRGN hrgnUpdate,LPRECT lprcUpdate,UINT fuScroll);
virtual void TxSetCapture(BOOL fCapture);
virtual void TxSetFocus();
virtual void TxSetCursor(HCURSOR hcur, BOOL fText);
virtual BOOL TxScreenToClient (LPPOINT lppt);
virtual BOOL TxClientToScreen (LPPOINT lppt);
virtual HRESULT TxActivate( LONG * plOldState );
virtual HRESULT TxDeactivate( LONG lNewState );
// virtual HRESULT TxGetClientRect(LPRECT prc);
// virtual HRESULT TxGetViewInset(LPRECT prc);
virtual HRESULT TxGetCharFormat(const CHARFORMATW **ppCF );
virtual HRESULT TxGetParaFormat(const PARAFORMAT **ppPF);
virtual COLORREF TxGetSysColor(int nIndex);
// virtual HRESULT TxGetBackStyle(TXTBACKSTYLE *pstyle);
virtual HRESULT TxGetMaxLength(DWORD *plength);
virtual HRESULT TxGetScrollBars(DWORD *pdwScrollBar);
virtual HRESULT TxGetPasswordChar(TCHAR *pch);
virtual HRESULT TxGetAcceleratorPos(LONG *pcp);
virtual HRESULT TxGetExtent(LPSIZEL lpExtent);
virtual HRESULT OnTxCharFormatChange (const CHARFORMATW * pcf);
virtual HRESULT OnTxParaFormatChange (const PARAFORMAT * ppf);
virtual HRESULT TxGetPropertyBits(DWORD dwMask, DWORD *pdwBits);
virtual HRESULT TxNotify(DWORD iNotify, void *pv);
// Far East Methods for getting the Input Context
//#ifdef WIN95_IME
virtual HIMC TxImmGetContext();
virtual void TxImmReleaseContext( HIMC himc );
//#endif
virtual HRESULT TxGetSelectionBarWidth (LONG *lSelBarWidth);
// 外部設置方法 (部分參考microsoft windowlessRE工程)
bool IsPasswordMode() { return m_fPassword; }
void SetPasswordMode(bool b);
WCHAR SetPasswordChar(WCHAR chPasswordChar);
LONG SetAccelPos(LONG l_accelpos);
bool SetFont(LOGFONT* plf);
void InitDefaultParaFormat();
bool IsWordWrap() { return m_fWordWrap; }
void SetWordWrap(bool fWordWrap);
bool IsReadOnly() { return (m_dwStyle & ES_READONLY) != 0; }
void SetReadOnly(bool fReadOnly);
DWORD GetMaxLength() { return m_dwMaxLength; }
void SetMaxLegnth(DWORD dw);
LONG GetSelBarWidth();
LONG SetSelBarWidth(LONG l_SelBarWidth);
bool GetRichTextFlag() { return m_fRich; }
void SetRichTextFlag(bool b);
void RevokeDragDrop(void);
void RegisterDragDrop(void);
bool SetText(const TCHAR* szText);
IRichEditOle* GetRichEditOle() { return m_spOle; }
protected:
// unknown attribute
// SIZE m_sizeExtent; // text service 用來實現縮放的參數。Each HIMETRIC unit corresponds to 0.01 millimeter.
int m_nxPerInch;
int m_nyPerInch;
LONG m_laccelpos; // Accelerator position
// 已知屬性
DWORD m_dwStyle; // 編輯框樣式
WCHAR m_chPasswordChar; // Password character, TODO: 該接口未測試過
DWORD m_dwMaxLength; // 最大輸入內容長度
LONG m_lSelBarWidth; // 類似於VS的左側(顯示行數字),專門用於點擊選中一行的東東
unsigned m_fWordWrap:1; // Whether control should word wrap
unsigned m_fRich:1; // Whether control is rich text
unsigned m_fRegisteredForDrop:1; // whether host has registered for drop
unsigned m_fPassword:1; //
CHARFORMAT2W m_cf; // Default character format
PARAFORMAT m_pf; // Default paragraph format
// 其它資源、數據
CComPtr<ITextServices> m_spTextServices;
CComPtr<IRichEditOle> m_spOle;
HWND m_hParentWnd; // 所在的窗口句柄
CCaret m_caret; // 光標(集成了系統光標和分層窗口模擬光標兩套)
};
interface ITextEditControl
{
virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) = 0;
};
// 條件判斷
#define PRE_HANDLE_MSG() \
{ \
SetMsgHandled(TRUE); \
lResult = OnPreHandleMsg(hWnd,uMsg,wParam,lParam); \
if(IsMsgHandled()) \
return FALSE; \
}
// 默認處理
#define POST_HANDLE_MSG() \
{ \
SetMsgHandled(TRUE); \
lResult = OnPostHandleMsg(hWnd,uMsg,wParam,lParam); \
if(IsMsgHandled()) \
return TRUE; \
}
class UIAPI WindowlessRichEdit : public ITextHostImpl,
public ITextEditControl
, public IRichEditOleCallback
{
public:
WindowlessRichEdit(RichEditBase*);
~WindowlessRichEdit(void);
public:
//////////////////////////////////////////////////////////////////////////
// 處理從父窗口中轉發過來的消息
BEGIN_MSG_MAP_EX(WindowlessRichEdit)
PRE_HANDLE_MSG()
MSG_WM_SETCURSOR(OnSetCursor)
MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged)
MESSAGE_HANDLER_EX(WM_KEYDOWN, OnDefaultHandle)
MESSAGE_HANDLER_EX(WM_CHAR, OnChar)
MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST,WM_MOUSELAST, OnDefaultHandle)
MESSAGE_HANDLER_EX(WM_SETFOCUS, OnDefaultHandle)
MESSAGE_HANDLER_EX(WM_VSCROLL, OnDefaultHandle)
MESSAGE_HANDLER_EX(WM_HSCROLL, OnDefaultHandle)
// POST_HANDLE_MSG()
END_MSG_MAP()
protected:
LRESULT OnPreHandleMsg( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
LRESULT OnPostHandleMsg( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
BOOL OnSetCursor(HWND wnd, UINT nHitTest, UINT message);
LRESULT OnDefaultHandle(UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnKillFocus(HWND wndFocus);
void OnObjectPosChanged(UINT fwSide, LPRECT pRect);
LRESULT OnChar(UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnWindowPosChanged(LPWINDOWPOS);
protected:
// *** IRichEditOleCallback methods ***
virtual HRESULT __stdcall GetNewStorage(LPSTORAGE FAR * lplpstg);
virtual HRESULT __stdcall GetInPlaceContext(LPOLEINPLACEFRAME FAR * lplpFrame, LPOLEINPLACEUIWINDOW FAR * lplpDoc, LPOLEINPLACEFRAMEINFO lpFrameInfo) ;
virtual HRESULT __stdcall ShowContainerUI(BOOL fShow) ;
virtual HRESULT __stdcall QueryInsertObject(LPCLSID lpclsid, LPSTORAGE lpstg, LONG cp) ;
virtual HRESULT __stdcall DeleteObject(LPOLEOBJECT lpoleobj) ;
virtual HRESULT __stdcall QueryAcceptData(LPDATAOBJECT lpdataobj, CLIPFORMAT FAR * lpcfFormat, DWORD reco, BOOL fReally, HGLOBAL hMetaPict) ;
virtual HRESULT __stdcall ContextSensitiveHelp(BOOL fEnterMode) ;
virtual HRESULT __stdcall GetClipboardData(CHARRANGE FAR * lpchrg, DWORD reco, LPDATAOBJECT FAR * lplpdataobj) ;
virtual HRESULT __stdcall GetDragDropEffect(BOOL fDrag, DWORD grfKeyState, LPDWORD pdwEffect) ;
virtual HRESULT __stdcall GetContextMenu(WORD seltype, LPOLEOBJECT lpoleobj, CHARRANGE FAR * lpchrg, HMENU FAR * lphmenu) ;
public:
// Helper
WORD GetSelectionType() const
{
LRESULT lr = 0;
m_spTextServices->TxSendMessage(EM_SELECTIONTYPE, 0, 0L, &lr);
return (WORD)lr;
}
bool GetSelectionOleObject(RichEditOleObjectItem** ppItem);
// Function
bool Create(HWND hWndParent);
void Draw(HDC);
bool HitTest(POINT pt);
//
bool InsertOleObject(RichEditOleObjectItem* pItem);
bool InsertGif(const TCHAR* szGifPath);
// Call this function to paste the OLE item in dataobj into this rich edit document/view.
void DoPaste(LPDATAOBJECT pDataObject, CLIPFORMAT cf, HMETAFILEPICT hMetaPict);
protected:
// Creation IDataObject from IDataObject (used for drag-drop, paste)
IOleObject* CreateOleObjectFromData(LPDATAOBJECT pDataObject, bool bOleCreateFromDataOrOleCreateStaticFromData = true, OLERENDER render = OLERENDER_DRAW, CLIPFORMAT cfFormat = 0, LPFORMATETC lpFormatEtc = NULL);
int GetObjectTypeByOleObject(LPOLEOBJECT pOleObject);
public:
// IUnknown Interface
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void **ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef(void);
virtual ULONG STDMETHODCALLTYPE Release(void);
// ITextHost Interface
// 需要根據控件屬性進行定制的接口放在這里實現,其它接口接口放在ITextHostImpl中實現
virtual BOOL TxShowScrollBar(INT fnBar, BOOL fShow);
virtual BOOL TxEnableScrollBar (INT fuSBFlags, INT fuArrowflags);
virtual BOOL TxSetScrollRange(INT fnBar,LONG nMinPos,INT nMaxPos,BOOL fRedraw);
virtual BOOL TxSetScrollPos (INT fnBar, INT nPos, BOOL fRedraw);
virtual HRESULT TxGetClientRect(LPRECT prc);
virtual void TxInvalidateRect(LPCRECT prc, BOOL fMode);
virtual HRESULT TxGetBackStyle(TXTBACKSTYLE *pstyle);
virtual HRESULT TxGetViewInset(LPRECT prc);
protected:
void ClearOleObjects();
protected:
RichEditBase* m_pRichEditBase;
RichEditOleObjectManager m_olemgr;
protected:
// 非windowless richedit要調用的初始化函數
static LPCTSTR GetLibraryName();
void InitRichEidtDll();
void ReleaseRichEidtDll();
static HMODULE s_RichEditDll;
static LONG s_refDll;
public:
//RichEdit formats
static UINT s_cfRichTextFormat; // CLIPFORMAT
static UINT s_cfRichTextAndObjects; // CLIPFORMAT
};
//#include <atlctl.h>