可视编辑:定位激活和定位载体
定态编辑,作为对OLE1的一个延伸,使读者能在留在载体应用程序的上下文中时编辑一个对象,而不是迫使读者在一个单独的服务器窗口中编辑一个嵌入对象。
但是这个词对复合文档来说太特定了,因为诸如视频对象(video objects)之类的特定对象,更适合于被播放(played)而不是被编辑(edited)。所以,当然不得更改这个术语了,这样就变成了“定位激活(in-place activation)”。
合并用户接口以使最终用户能操作该对象进程,被称为定位激活。
当一个对象处于定位活动状态或只是活动状态时,会发生许多事件,其中每一个均可由载体或对象来处理,根据OLE2设计规范中阐明的交互可操作性规则。
在十五章中,如果对实现一个定位对象感兴趣的话,可以直接跳到第十六章,但是这里的有关载体材料的内容会对自己有所帮助。
前面章节已经做到的:
经过前面章节的实现----当一个Cosmo对象嵌入到文档中时,可以在里面进行绘制,当我想改变里面绘制的图形,我只需在其上进行双击来激活它。这样该对象打开并进入一个全平面的Cosmo窗口,就可以在那里面进行修改。这些都发生在载体调用该对象的IOleObject::DoVerb函数中。
为了支持嵌入,载体(比如office)实现了IOleClientSite和IAdviseSink接口,而对象实现了IOleObject、IDataObject和IPersistStorage接口。为了支持面向嵌入对象的定位激活(现在到目前为止,链接对象可以进行定位激活了),这些接口的存在告知周围:该载体和对象具有定位能力,也就是它们理解支持这些接口的约定条件。
DoVerb----一个小傻瓜
当我打开一个文档(里面含有一个嵌入对象Cosmo时),文档象往常那样一直运行着,直到它向嵌入的Cosmo对象发出看起来无关紧要的IOleObject::DoVerb(OLEIVERB_PRIMARY)调用。
现在加入说Cosmo具有定位的能力(在下一章中它就会有这种能力的)。知道了Cosmo具有这个新的能力后,它开始执行IOleObject::DoVerb时将采取一些稍稍不同的行动,与显示自己的窗口来打开面向编辑的对象所不同的是,Cosmo调用载体的IOleClientSite::QueryInterface,申请IOleInPlaceSite。
在对象确定了载体是否具有定位能力之后,它还必须询问载体是否可以立即通过调用IOleInPlaceSite::CanInPlaceActivate定位激活该对象。例如,如果该对象以一个不同于DVASPECT_CONTENT的画面被展示出来,那么一个载体将会拒绝激活定位嵌入对象。如果该载体拒绝定位激活,那么该特定对象总是可以象往常那样打开进入一个单独的窗口。
定位激活
在对象确定它可以进行定位激活之后,它通过调用IOleInPlaceSite::OnInPlaceActivate把自己的意图告诉载体。然后通过调用IOleInPlaceSite::GetWindowContext,它得到指向其它两个载体接口----IOleInPlaceUIWindow(面向文档的)和IOleInPlaceFrame的指针,以及其他某些必要的信息。
为了能够有效地进行定位激活,需要解决四个首要的用户接口问题:
a、载体的标题条并不指示我们正在编辑一个对象
b、相对于装载来说,很少有信息表明对象本身正处于“定位激活”状态
c、菜单仅持有载体函数,其中一些在一个对象处于定位激活时是不能用的
d、工具条仅仅显示出诸如该菜单之类的载体函数
后面的章节就是着手解决这些问题的
标题条和活动对象加影线
之前一直未提及的IOleInPlaceActiveObject接口,这个接口使载体能将一些重要的事件通知给对象,但该IOleInPlaceActiveObject接口是在对象不必处于活动状态而仅仅是处于运行状态(并且是隐含的)时候被使用的。IOleInPlaceActiveObject接口指针总是被载体的IOleInPlaceFrame和IOleInPlaceUIWindow的SetActiveObject成员来接收的,这个函数的另外一个参数是标题条的字符串的指针。
加影线,只需要在围绕着那个编辑窗口的外围绘制一个影线边界,还可以再这个影线边界中包含变换尺寸(resizing)句柄。
下面要解决的另外的用户接口问题是菜单栏和工具条
关于这个就算了,以后遇到的时候再回来看P736
操作一个活动对象
还记得一般是在定位激活的对象外面点击一下鼠标,会使对象从激活状态编程装载状态么。就是因为用户在对象外的文档窗口中扣击鼠标,将通过调用IOleObject::InPlaceDeactivate来指使载体释放该对象的,此时,对象恢复在激活进程中所做的一切,包括移去所有的对象用户接口。。(这是载体对操作一个活动对象的鼠标事件的处理)
键盘事件分两种情况处理
第一种是dll服务器的:当一个对象被定位激活时,载体必须修改其信息环路,在做任何其自己的转换(translation)操作之前调用IOleInPlaceActiveObject::TranslateAccelerator,不这么调用的话,dll服务器就没有机会来处理加速键了,因为他们没有信息环路来调用他们自己的。
第二种exe服务器:当服务器是exe时,他的信息环路实际上就是处于控制之中的,而且必须被修改来在检查加速键之后在调用TranslateMessage和DispatchMessage之前调用OleTranslateAccelerator。如果发现按下的加速键是在载体的加速键中,那么OleTranslateAccelerator将调用IOleInPlaceFrame::TranslateAccelerator
在定位状态下,对象还可能允许自己变换尺寸,此时它可以调用IOleInPlaceSite::OnPosRectChange以希望得到的矩形的尺寸。作为响应,载体调用IOleInPlaceObject::SetObjectRects来通知对象实际可变换多少尺寸但并不改变载体现场自身的尺寸。
如果对象太小,那么用户可以把对象打开进入完全的服务器窗口,这通过以OLEIVERB_OPEN来实现,导致该对象总是在另一窗口内激活而决不会定位激活。一般,对象将要为此提供某种方法,用菜单项或在影线边界上的一个双击。载体还可以用诸如Ctrl+Enter那样的键击来产生此调用。
释放
载体通过调用IOleInPlaceObject::InPlaceDeactivate把释放操作通知给对象。
Active和UI Active以及由里及表(inside-out)的对象
这个也不是太重要
逐步实现定位载体
产生定位载体支持的步骤
a、准备载体应用程序以处理创建共享菜单、切换工具条以及当某对象处于定位活动态时处理加速键
b、实现股价IOleInPlaceFrame、IOleUIWindow和IOleInPlaceSite接口
c、激活和释放对象
d、贡献属于应用程序的那一半共享菜单
e、协商工具空间,并在某对象要求空间时处理窗口的重定位(repositioning)
f、在应用程序启动之前给对象一个处理加速键的机会,并确保所有的在一个定位对话过程中不用的加速键被禁用
g、处理在载体中发生事件所有的遗留问题,例如,文档之间的切换,变换框架或文档窗口的尺寸,滚动一个文档,及清洗定位载体接口的非正常成员。这些都是使之能发挥作用所需要的。还有很多任意可选的对载体的补充
实现骨架定位载体接口
对于所以定位接口来说GetWindow函数返回任何与特定对象有关的窗口,这是对定位激活是完全面向对象用户接口的这种观点的一种有力的支持,每个定位接口都有一个相关的窗口。例如,IOleInPlaceFrame::GetWindow,返回载体的框架窗口,而IOleInPlaceUIWindow::GetWindow返回载体的文档窗口。
现实实现载体一些操作的步骤
a、将每个接口加入到它相应的载体对象中并修改(或在框架情况下可能是加入)该接口,连同它所有的基本接口,以便QueryInterface可以知道它
b、实现各个接口的普通的GetWindow函数
c、在框架和文档接口中实现SetActiveObject来保存IOleInPlaceActiveObject指针并改变载体和标题条
d、在IOleInPlaceSite中实现CanInPlaceActivate,OnInPlaceActivate,OnInPlaceDeactivate和GetWindowContext
e、从其他各个事物中返回ResultFromScode(E_NOTIMPL)
P748以后都是一些对上面的实现
为什么想拥有一个以上活动对象----答案就在于所谓的由里及表的对象(即是那种只要是课件的但就是活动对象),最终用户首先在操作该对象前得双击鼠标,如同对典型的对象(outside-in objects)必须做的那样。
当由里及表对象通过单击和双击被选中时,它们表现得有些不同。例如,一个对象实际上从未有过一个“被装载”状态,因为“被装载”意味着是定位活动而不是UI活动。然而,处于定位活动中就意味着可以以单击鼠标来操作该对象。可以有任何数目的对象处以定位活动态但唯有当前被选中的对象是UI活动的。
IOleInPlaceSite这个接口的成员很关键
其中的CanInPlaceActivate、OnInPlaceActivate、OnInPlaceDeactivate、GetWindowContext这四个函数,前面三个都很简短GetWindowContext是一个很棘手的成分,要求更多的代码,因为要涉及更多的参数。该函数必须将指向载体的IOleInPlaceFrame和IOleInPlaceUIWindow接口的指针保存到两个外部参数中(ppIIPFrame和ppIIPUIWindow),指定对象的初始位置和裁剪矩形(在prcPos和prcClip),并以加速键信息填充一个OLEINPLACEFRAMEINFO结构
对多数应用程序来说,CanInPlaceActivate是不会太复杂的。在Patron(书中的例子)中,仅当对象不是嵌入的或不是用FVASPECT_CONTENT显示的时候才禁止定位激活(返回S_FALSE,而不是一个错误码)。
OnInPlaceActivate、OnInPlaceDeactivate告诉载体让其自身进入或退出一个定位状态。
激活和释放对象
激活对象总是通过以对象特定的动词或那些类似于OLEIVERB_SHOW的动词来调用IOleObject::DoVerb来发生的。然而,现在来看下面的这两个问题:
a、OLEIVERB_OPEN将绝不会定位激活一个对象。这是一个包子打开一个独享进入一个服务器窗口的动词
b、其次,还有另一个就是DoVerb的参数:lpMSG,一个指向MSG结构的指针,这个参数的存在是用于对象有点类似于一个按钮载体是依据WM_LBUTTONDOWN和WM_LBUTTONONUP信息调用DoVerb(OLEIVERB_PRIMARY)的情况下。
其实服务器还在运行
应牢记一个重要的事实:释放一个定位对象后,服务器并没有被卸载,也就是说,OLE以打开引用计数保持它处于运行之中,尽管载体本身没有指向该对象的指针。这样做是为了使再次激活对象变得非常迅速,因为节省装入服务器的时间
菜单----包含了和服务器菜单的合并
这些都在P757中描述,如果出现什么问题,回头再来查看
工具条----包含了和服务器菜单的合并和处理
这些都在P760中描述,如果出现什么问题,回头再来查看
经验:“驯化”ShowObject
在一个定位载体中要小心对待IOleClientSite::ShowObject的实现。如果对象不是完全可见的,且对象的左上角并不在文档窗口的左上象限内,那么Patron(书中所给的例子)以这个函数滚动该对象。如果已经看到一个对IOleInPlaceSite::OnInPlaceActivate的调用,那么只要对象有任一部分是可见的就不要滚动文档。仅在整个对象均在视窗之外时才应进行滚动,因为在那种情况下使用者才确实需要看到这个对象。
重画的优化----在P765,似乎对我不是太重要
提供定位加速键和焦点----在P768,似乎对我不是太重要
调用IOleInPlaceActiveObject::On[Frame | Document] Window Activate
如果在文档中有一个定位对象,那么必须调用OnDocWindowActivate,不管应用程序正在变成活动的还是变成非活动的,其中给该函数的参数是一个指示应用程序当前正处于哪种状态的标示
提供最基本的Undo支持
即使载体没有一个Undo命令(如在Patron的情况下),它仍然要这样做:首先,通过调用IOleInPlaceObject::InPlaceDeactivate来实现IOleInPlaceSite::DeactivateAndUndo;其次,无论何时,当对象被释放时,以OLEIVERB_DISCARDUNDOSTATE调用IOleObject::DoVerb。
提供一个Open加速键
如果想要,读者可以为定位激活加入一个Ctrl+Enter加速键,将生成一个对IOleObject::DoVerb(OLEIVERB_OPEN)的调用以将一个对象从定位激活状态转换到像非定位嵌入对象那样的打开状态。
实现一个状态行IOleInPlaceFrame::SetStatusText
这个状态栏需要实现么?好像不是太需要把,直接跳过了,如果到时候需要用到显示对象的状态在再回来P778的实现IOleInPlaceFrame::SetStatusText那里看看
显示或隐藏无模式弹出式窗口
在定位对话中,载体和对象在不同时刻和地点使用其他的EnableModeless调用来显示或隐藏浮动的弹出式窗口。例如,假定对象创建了一个浮动的工具调色板来代替工具条。
支持由里及表(参考第5点,我怎么感觉这里说的是内嵌对象啊 =_=!)
一个载体对由里及表(内嵌)对象的基本支持包括5个步骤
a、当创建或装载对象时,载体调用IOleObject::GetMiscStatus并保存返回值。两个相关标志是OLEMISC_INSIDEOUT和
OLEMISC_ACTIVATEWHENVISIBLE。Patron在CTenant::FObjectInitialize的最后,执行了此步骤和第2个步骤
b、如果一个对象是OLEMISIC_ACTIVATEWHENVISIBLE,则载体即刻调用IOleObject::DoVerb(OLEVERB-INPLACEACTIVATE)来使对象活动出来,但这里并不是UI活动
c、当选择的对象改变时,如果先前的选择是由里及表(内嵌)的,那么载体调用IOleInPlaceObject::UIDeactivate,而如果新选中的对象同样也是insite out的,则在该对象上调用IOleObject::DoVerb(OLEIVERB_UIACTIVATE)。记住,这是选择,而不是激活。正如在Patron中CTenant::FSelect函数所执行的那样
d、当滚动文档时……(要知道这并不重要)
e、当把对象带回到被动状态(装载态)时,调用IOleInPlaceObject::InPlaceDeactivate。
这里最后一条是针对一个由里及表(嵌入)对象的,在装载和活动之间有一点小小的差别。这仅仅是“活动”和“UI活动”之间的区别。
小结:
定位激活(即可视编辑),是OLE2中的允许一个载体应用程序在载体文档窗口中内部之间激活一个嵌入对象,而无需显示一个单独的服务器窗口的功能。激活的整个进程在某种程度上是依据载体想要支持的集成化的程度的,其中包括与对象共享菜单条和允许对象在载体的框架或文档窗口中创建工具条和其他的补充内容。集成菜单和工具的进程是由载体和服务器在实现它们的定位接口时共同遵循的特定的规则来控制的。
大多数工作是发生在对象视图从其被装载状态进入到完全定位活动状态的过程中,以一个共享菜单和显示在载体窗口中的对象自减的工具来完成。另外还有一些要发生的事件,其中许多都需要在载体中的适当位置上配有少量的代码。这些事件就是滚动或变换一个文档窗口的尺寸、检测一个加速键,或激活一个Undo command。
大多数情况下,载体在一个定位对话过程中是一个被动的“当事人”;读者需要为定位支持制作的代码多数归结于3个必要的接口:IOleInPlaceSite、IOleInPlaceUIWindow和IOleInPlaceFrame。
编译一个这章的例子(Patron)服务器:
lz花了N多时间,尼玛的终于编译成功了
不过这个是问题多多了,不能Insert object也就算了,简单的连文档都不能打开(工具栏也显示不出来,坑爹啊 有木有)。。甚至依靠原来的代码根本不够编译……上传这个失败的代码到这里了= =
那个泪奔啊,最后果断换成了carrier这个项目,是原来的vc98自带的一个例子,不过这本书里面讲的原理,都可以直接用在这个项目上面。