万星星@豌豆荚 欢迎加入我们
一个吃软饭的男人!!!!!我只想写程序####
微博:http://weibo.com/wanlianwen
posts - 172,  comments - 1253,  trackbacks - 0

研究背景

自职业生涯起到现在,我参与过三个im类产品,其中我主要负责的是界面库开发。众所周知,im软件中有一个非常重要的控件用于消息展示,实现方式无外乎基于richedit(目前这一类为主要方式,代表:QQ)、基于webbrowser(代表:GTalk),可能会有基于WebKit的,我没有刻意去搜集。很多时候对于相对简单的情况,比如在游戏中,完全可以自己绘制。

 

我曾经模仿过GTalk的实现方式,由于当时自己经验欠缺以及与公司写页面的人沟通上的问题,效果不是那么满意,仅仅够用,后来自己离开也没再继续做这方面研究。机制上这种方式是可行的,native端事情不多。

 

后来的工作中,主要是基于richedit在做,都是安排其他人负责。在开发过程中,遇到种种问题,用过各种不优雅的“伎俩”,由于欠缺OLE知识,做的人很痛苦,找不到乐趣。微软官方有一个例子,然而只披露了使用技巧的冰山一角;codeproject也有少的可怜的几个例子,经不住商业化应用;互联网搜索的一些文章大多都是简单的插入图片等,聊胜于无。

 

《基于Chrome开源提取的界面开发框架》系列文章获得了不少支持,在提取过程中我自己也成长很多。抽取出来的引擎要想用于商业化开发,我个人觉得欠缺的主要是富文本渲染这一块,这使我开始研究richedit。断断续续,期间各种事情,几经放弃。后来一个网友在这个问题上又找到我,临时的帮助他解决问题之后,不禁感叹,为什么互联网上找不到一个优雅的解决方案甚至是深入的介绍?

 

网友megax的文章http://www.cppblog.com/megax/archive/2012/03/22/168601.html中关于制作编辑器方面知识提及的http://www.catch22.net/tuts/neatpad让我着实佩服,老外对待研究的态度真的很严谨,分享的开发性和持续性方面远远胜于我们。系列文章中的绝大多数概念知识我都接触过,然而很多没有深究,经验远远不如文章主人。

 

Richedit的研究的大部分知识都在OLE方面。现在计算机的发展,技术的百花齐放,使得Windows平台不再那么大行其道,Windows技术也不再那么不可一世,Mfc越来越被抛弃,Windows程序员诚惶诚恐,新生代早早把自己定位在更炫更酷更激情的技术平台。在Windows Native开发没有彻底失宠前,我打算把自己死啃得来的OLE知识发挥“余热”,对richedit这个东西应用于im领域的问题解决一下,希望对其他人有帮助,也希望没有重复造轮子。



研究目标

RicheditWindows底层的组件,甚至在2004年泄漏的Win2K代码中都没有,它是独立于edit组件的,而edit位于ntuser中,亦相当底层,虽在泄漏代码中出现,然而抽取出来的可能性不大。从某种角度来讲,Reactos 就是抄袭的这份代码,明眼人可以从其死灰复燃的更新列表中发觉。很奇怪的是Reactos 的代码中有richedit,我也移植过,只是后来发现功能实在太弱,无可用性,遂放弃,至于Wine 是不是抄袭这份代码,我无从得知,也没精力再去跟踪。

 

Richedit 的接口相当稳定,我在Win8中试验过完全兼容,我想它应该会持续很久,所以值得去好好研究一把。

 

《基于Chrome开源提取的界面开发框架》的view框架,如果能有一个rtf格式的渲染利器,配以ole的展示,我想足以成为互联网商业开发的UI解决方案。

 

Richedit 就机制上来讲跟WebKit一样,或者应该反过来说。Richedit窗口本身是对ITextServices的封装,实现ITextHost接口与ITextServices交互提供平台支持。ITextServices的支持分两大类:基于文本的ITextDocument和基于oleIRichEditOle。作为ole容器,提供的功能主要通过实现几个接口完成的,包括:IOleClientSiteIAdviseSinkIOleInPlaceSite,缺省的Richedit 貌似不支持定位激活,想要达到激活效果必须支持最后一个接口。与剪贴板和拖拽数据打交道需要支持统一数据传输接口IDataObject

 

研究主要参考对象为QQ,目标为:支持粘贴格式、动画ole控件、定制ole菜单、拖拽、文本操纵、窗口/无窗口的统一支持等,我会在下一篇列出详细的大纲。



已做工作

OLE标准提供了大量工业化标准接口以及繁杂的交互规范,事实上除了微软(以office系列产品为典范),鲜有软件全盘实施。可以说MFC的大部分工作都是用在实现OLE,所以其臃肿大抵源于此,不得不臃肿,早些年我接触wtl后得出,MFC - WTL == OLE,或许还有一些打印等方面设施,但我不觉得这些是主要特性。以我现在眼光来看待,把MFC拆分开,其集合类、打印框架、COM支持、OLE支持、文档/视图/框架模板(尽管现在用的极少,大多在行业软件领域)、进程/线程/模块状态管理等,都还不错,呃,貌似我快把它说全了,不好意思,我的老毛病又犯了,其实在心底,我还有那么一点不舍,即便我很多年没有用它开发商业软件了。我从MFC学到的东西太多太多,以至于我不肯说它的坏话。

 

再一次,我选择了从MFC中抽取代码。我尝试过直接使用MFC,静态链接以便偷偷的不告诉别人,以免“破坏形象”。但我不觉得我能做到,原因就是整个MFC耦合的比较紧密,我不想或者无法使用整个框架来对外提供服务,所以我不得不制造小轮子。我也试过直接实现,但是工作量还真不小,我怕自己等不及,故再次祭出看家本领,顺藤摸瓜,牵出一个支持OLE的最小内核,经过1-2周我能腾出的时间,终于做到了,于是我写下了开篇,后面我需要把整个思路刻画出来。下面是一个示例,插入浏览器,支持定位激活:


 

这个例子不具备任何说明性,只是用来测试最小的OLE内核框架是否可以工作。

 

测试的接口如下:


 

这一篇到此为止!依然是闲话多,干货少。我会努力的!

posted on 2012-05-20 20:01 万连文 阅读(4723) 评论(15)  编辑 收藏 引用 所属分类: richedit

FeedBack:
# re: richedit研究开篇01
2012-05-20 22:36 | unkown
动画ole控件,关键点在于定时刷新时,使用IOleInPlaceSite::GetWindowContext 获取到动画的范围,然后InvalidateRect  回复  更多评论
  
# re: richedit研究开篇01
2012-05-20 22:50 | 万连文
@unkown

感谢提示,动画的效率需要考虑如何设计时钟队列,刷新也需要考虑当前屏幕的最小优化,好像据说直接InvalidateRect效率不高,有待进一步确认,不过技术细节差不多都闹清楚了。  回复  更多评论
  
# re: richedit研究开篇01
2012-05-21 09:24 | unkown
呵呵,要用IOleInPlaceSite::GetWindowContext 获取到动画的rectPos范围,然后InvalidateRect的这种方法,是因为直接调用FireViewChange,会引起Richedit重新排列刷新GIF当前所在的行,有滚动的问题尤其明显。  回复  更多评论
  
# re: richedit研究开篇01
2012-05-21 15:13 | 饭中淹
我用RICHEDIT的WINDOWLESS模式,在codeproject上找到的代码。
最后事情归结为实现一个RTF的生成器,下载了最新的RTF文档之后,我彻底萎了。
  回复  更多评论
  
# re: richedit研究开篇01[未登录]
2012-05-29 07:31 | 路人甲
win2k 泄漏代码里是有 richedit 控件源码的. 下面是我整理的一份. 供参考.
http://winutilities.svn.sourceforge.net/svnroot/winutilities/richedit
  回复  更多评论
  
# re: richedit研究开篇01
2012-05-29 08:33 | 万连文
@路人甲

参见:http://www.cnblogs.com/wlwel/archive/2012/05/20/2510761.html
发现你的代码跟这个朋友给我的一样,能解释为是同一人吗?  回复  更多评论
  
# re: richedit研究开篇01[未登录]
2012-05-29 18:36 | 路人甲
@万连文
不是的。我都忘了是取自 Nt4 还是 win2k 了。 工程里有旧版 WordPad 的完整源码,微软的示例程序, 全面展示了 richedit 的用法。 Enjoy it!!!
  回复  更多评论
  
# re: richedit研究开篇01
2012-05-29 19:31 | 万连文
@路人甲
非常感谢你,这样看来Nt4 里面的richedit 还是比较独立的,再次谢谢你!  回复  更多评论
  
# re: richedit研究开篇01
2012-06-03 14:05 | weolar
嘿嘿,万兄我又来了。我还尝试过用ie2来做richedit:
http://bbs.pediy.com/showthread.php?t=137616  回复  更多评论
  
# re: richedit研究开篇01
2012-06-03 16:11 | 万连文
@weolar
Greate! 只可惜我没有帐户,可以发我邮箱一份吗?如果还能有闲暇的蛋疼的时间我一定会回头再看看,是否还有能让自己提升一把的亮点。  回复  更多评论
  
# re: richedit研究开篇01
2012-06-03 16:14 | 万连文
@weolar
呃,我已经注册,24小时之后可以下载。我记忆中,之前是见过,也想下载,那时候好像还需要邀请码。发完上面的信息后,我抱着试一试的心情,点击了注册,居然不需要,难道以前是错觉?  回复  更多评论
  
# re: richedit研究开篇01
2012-07-02 14:29 | M77
如果博主只是要RichEdit的功能,而且又不跨平台,在Windows下直接用RichEdit就可以了,我记得chrome UI可以添加原生控件的。  回复  更多评论
  
# re: richedit研究开篇01
2012-07-02 17:11 | 万连文
@M77
原生控件不是Windowless,效果有差距。  回复  更多评论
  
# re: richedit研究开篇01
2013-06-24 17:08 | zhenhua
我遇到一个问题,在richedit中插入网页,只能显示黑色的背景,请问这是什么原因!  回复  更多评论
  
# re: richedit研究开篇01[未登录]
2013-06-25 10:52 | rich
请问这样插入网页为什么不行

IStorage* lpStorage = NULL;//存储接口
IOleObject* lpOleObject = NULL;//OLE对象
LPLOCKBYTES lpLockBytes = NULL;//LOCKBYTE
IOleClientSite* lpOleClientSite = NULL;
CComPtr<IWebBrowser2> lpPolyCtl = NULL; //控件
CLSID clsid;
REOBJECT reobject;
HRESULT hr;
IRichEditOle *lpRichEditOle =m_RichEdit.GetIRichEditOle();
if(lpRichEditOle == NULL)
return ;
//创建PolyCtl对象并获取接口
hr = ::CoCreateInstance(CLSID_WebBrowser,NULL,CLSCTX_INPROC,IID_IWebBrowser2,(LPVOID*)&lpPolyCtl);
//hr = ::CoCreateInstance(CLSID_InternetExplorer,NULL,CLSCTX_LOCAL_SERVER,IID_IWebBrowser2,(LPVOID*)&lpPolyCtl);
if( lpPolyCtl == NULL )
{
return ;
}

// USES_CONVERSION;
BOOL bRet = TRUE;

try{
hr = lpPolyCtl->QueryInterface(IID_IOleObject,(void **)&lpOleObject);//获得数据对象接口
if( hr != S_OK )
AfxThrowOleException(hr);
hr = lpOleObject->GetUserClassID(&clsid);
if ( hr != S_OK)
AfxThrowOleException(hr);

hr = ::CreateILockBytesOnHGlobal(NULL, TRUE, &lpLockBytes);//创建LOCKBYTE对象
if (hr != S_OK)
AfxThrowOleException(hr);
ASSERT(lpLockBytes != NULL);

hr = ::StgCreateDocfileOnILockBytes(lpLockBytes,//创建复合文档
STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_READWRITE, 0, &lpStorage);
if (hr != S_OK)
{
VERIFY(lpLockBytes->Release() == 0);
lpLockBytes = NULL;
AfxThrowOleException(hr);
}

lpRichEditOle->GetClientSite(&lpOleClientSite);

ZeroMemory(&reobject, sizeof(REOBJECT));//初始化一个对象
reobject.cbStruct = sizeof(REOBJECT);
reobject.clsid = clsid;
reobject.cp = REO_CP_SELECTION;
reobject.dvaspect = DVASPECT_CONTENT;
reobject.dwFlags = REO_BELOWBASELINE;
reobject.poleobj = lpOleObject;
reobject.polesite = lpOleClientSite;
reobject.pstg = lpStorage;


lpOleObject->SetClientSite(lpOleClientSite);//

hr = lpRichEditOle->InsertObject( &reobject );
if (hr != S_OK)
AfxThrowOleException(hr);
OleSetContainedObject(lpOleObject,TRUE);

hr = lpPolyCtl->Navigate(L"www.baidu.com",NULL,NULL,NULL,NULL);
if (hr != S_OK)
AfxThrowOleException(hr);
::SendMessage(m_RichEdit.GetSafeHwnd(), EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
lpOleObject->DoVerb(OLEIVERB_UIACTIVATE, NULL, lpOleClientSite, 0,
m_RichEdit.m_hWnd, NULL);
lpOleObject->DoVerb(OLEIVERB_SHOW, NULL, lpOleClientSite, 0, m_RichEdit.m_hWnd,
NULL);
m_RichEdit.RedrawWindow();
}

catch( COleException* e )
{
TRACE(_T("OleException code:%d"),e->m_sc);
e->Delete();
bRet = FALSE;
}

// release the interface
//if( lpPolyCtl != NULL ) lpPolyCtl->Release();
if( lpOleObject != NULL ) lpOleObject->Release();
if( lpOleClientSite != NULL ) lpOleClientSite->Release();
if( lpStorage != NULL ) lpStorage->Release();

return ;  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


简历下载
联系我

<2011年9月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

常用链接

留言簿(66)

随笔分类

随笔档案

相册

搜索

  •  

最新评论

阅读排行榜

评论排行榜