vs2017开发ActiveX(主讲OCX)(三)、MFC ActiveX控件向导中的控件设置
前言
先把图片上上来:
官方称之为:MFC ActiveX控件:优化
官方是这么介绍的:
可见时关闭激活和在非活动时提供鼠标交互在激活之前不创建窗口的控件。无窗口激活永远不会创建窗口的控件,即使它们被激活也是如此。
Windows对OLE对象有两个主要缺点:它们在活动时防止对象变为透明或非矩形,并且它们在实例化和控件显示中增加了大量开销。通常,创建窗口需要60%的控件创建时间。使用单个共享窗口(通常是容器)和一些调度代码,控件接收相同的窗口服务,通常不会降低性能。拥有一个窗口对于对象来说几乎是不必要的开销。
在某些容器中使用控件时,某些优化不一定会提高性能。例如,1996年之前发布的容器不支持无窗口激活,因此实现此功能不会在旧容器中提供好处。但是,几乎每个容器都支持持久性,因此优化控件的持久性代码可能会提高其在任何容器中的性能。如果您的控件专门用于某种特定类型的容器,您可能需要研究该容器支持哪些优化。但是,一般情况下,您应该尝试实现适用于您的特定控件的许多这些技术,以确保您的控件在各种容器中都能执行。
MFC ActiveX控件向导
您可以通过“ 控制设置”页面上的“ MFC ActiveX控件向导”实现许多这些优化。
可见时激活
控件有两种基本状态:激活和非激活。传统上,这些状态的区别在于控件是否有窗口。一个激活的控件有一个窗口; 一个非激活的控件则没有。随着无窗激活的引入,这种区别不再普遍,但仍适用于许多控件。
与通常由ActiveX控件执行的其余初始化相比,窗口的创建是非常消耗资源的操作。理想情况下,控件会推迟创建窗口,直到绝对必要。
许多控件在容器中可见的整个时间内不需要处于激活状态。通常,控件可以保持在非激活状态,直到用户执行要求其变为激活状态的操作(例如,用鼠标单击或按TAB键)。要使控件在容器需要激活之前保持非激活状态,请从控件的其他标志中删除OLEMISC_ACTIVATEWHENVISIBLE标志:
static const DWORD BASED_CODE _dwNVC_MFC_AxOptOleMisc =
OLEMISC_SETCLIENTSITEFIRST |
OLEMISC_INSIDEOUT |
OLEMISC_CANTLINKINSIDE |
OLEMISC_RECOMPOSEONRESIZE;
如果在创建控件时关闭MFC ActiveX控件向导的“控件设置”页面中的“当可见时激活”选项,则会自动省略OLEMISC_ACTIVATEWHENVISIBLE标志。
无窗口激活
窗口创建代码(即,调用时发生的所有事情CreateWindow)执行成本很高。维护屏幕窗口的控件必须管理窗口的消息。因此,无窗口控件比带窗口的控件更快。
无窗控制的另一个优点是,与窗口控件不同,无窗口控件支持透明绘画和非矩形屏幕区域。透明控件的常见示例是具有透明背景的文本控件。控件绘制文本而不是背景,因此文本下的任何内容都显示出来。较新的表单通常使用非矩形控件,例如箭头和圆形按钮。
通常,控件不需要自己的窗口,而是可以使用其容器的窗口服务,前提是已编写容器以支持无窗口对象。无窗口控件向后兼容旧容器。在未编写为支持无窗口控件的旧容器中,无窗口控件在活动时创建窗口。
由于无窗口控件没有自己的窗口,因此容器(具有窗口)负责提供本来由控件自己的窗口提供的服务。例如,如果您的控件需要查询键盘焦点,捕获鼠标或获取设备上下文,则这些操作由容器管理。容器使用该IOleInPlaceObjectWindowless接口将发送到其窗口的用户输入消息路由到适当的无窗口控件。(有关此接口的说明,请参阅ActiveX SDK。)COleControl成员函数从容器中调用这些服务。
要使您的控件使用无窗口激活,请在COleControl :: GetControlFlags返回的标志集中包含windowlessActivate标志。例如:
DWORD CMyAxOptCtrl::GetControlFlags()
{
DWORD dwFlags = COleControl::GetControlFlags();
// The control can activate without creating a window.
dwFlags |= windowlessActivate;
return dwFlags;
}
如果在MFC ActiveX控件向导的“ 控制设置”页面上选择“ 无窗口激活”选项,则会自动生成包含此标志的代码。
启用无窗口激活后,容器会将输入消息委托给控件的IOleInPlaceObjectWindowless界面。COleControl在适当调整鼠标坐标后,此接口的实现通过控件的消息映射调度消息。您可以通过将相应的条目添加到消息映射来处理消息,如普通窗口消息。在这些消息的处理程序中,避免使用m_hWnd成员变量(或使用它的任何成员函数),而不首先检查其值是否为NULL。
未剪辑的设备上下文
如果您完全确定您的控件不会在其客户端矩形之外绘制,则可以通过禁用对COleControl进行的IntersectClipRect调用来实现小但可检测的速度增益。 为此,请从COleControl :: GetControlFlags返回的标志集中删除clipPaintDC标志。 例如:
DWORD CMyAxOptCtrl::GetControlFlags()
{
DWORD dwFlags = COleControl::GetControlFlags();
dwFlags &= ~clipPaintDC;
return dwFlags;
}
如果在使用MFC ActiveX控件向导创建控件时在“ 控制设置”页面上选择“未剪辑的设备上下文”选项,则会自动生成删除此标志的代码。
如果您使用无窗口激活,则此优化不起作用。
无闪烁激活
如果您的控件在非活动状态和活动状态下绘制相同(并且不使用无窗口激活),则可以消除在非活动状态和活动状态之间进行转换时通常发生的绘制操作和伴随的视觉闪烁。为此,请在COleControl :: GetControlFlags返回的标志集中包含noFlickerActivate标志。例如:
DWORD CMyAxOptCtrl::GetControlFlags()
{
DWORD dwFlags = COleControl::GetControlFlags();
dwFlags |= noFlickerActivate;
return dwFlags;
}
如果在使用MFC ActiveX控件向导创建控件时在“ 控制设置”页面上选择“无闪烁”激活选项,则会自动生成包含此标志的代码。
如果您使用无窗口激活,则此优化不起作用。
不活动时有鼠标指针通知
如果未立即激活控件,您可能仍希望它处理WM_SETCURSOR和WM_MOUSEMOVE消息,即使控件没有自己的窗口。这可以通过启用接口COleControl的实现来IPointerInactive实现,默认情况下禁用该接口。(有关此接口的说明,请参阅ActiveX SDK。)要启用它,请在COleControl :: GetControlFlags返回的标志集中包含pointerInactive标志:
DWORD CMyAxOptCtrl::GetControlFlags()
{
DWORD dwFlags = COleControl::GetControlFlags();
// The control can receive mouse notifications when inactive.
dwFlags |= pointerInactive;
return dwFlags;
}
如果在使用MFC ActiveX控件向导创建控件时在“ 控制设置”页面上选择“ 非活动时鼠标指针通知”选项,则会自动生成包含此标志的代码。
IPointerInactive启用该接口后,容器会向其委派WM_SETCURSOR和WM_MOUSEMOVE消息。在适当调整鼠标坐标后,通过控件的消息映射调度消息COleControl的实现IPointerInactive。您可以通过将相应的条目添加到消息映射中来处理消息,就像普通窗口消息一样。在这些消息的处理程序中,避免使用m_hWnd成员变量(或使用它的任何成员函数),而不首先检查其值是否为NULL。
您可能还希望非活动控件成为OLE拖放操作的目标。这需要在用户在其上拖动对象时激活控件,以便控件的窗口可以注册为放置目标。要在拖动期间激活,请覆盖COleControl :: GetActivationPolicy,并返回POINTERINACTIVE_ACTIVATEONDRAG标志:
DWORD CMyAxOptCtrl::GetActivationPolicy()
{
return POINTERINACTIVE_ACTIVATEONDRAG;
}
启用IPointerInactive界面通常意味着您希望控件始终能够处理鼠标消息。要在不支持IPointerInactive接口的容器中获取此行为,您需要在可见时始终激活控件,这意味着控件应在其杂项标志中包含OLEMISC_ACTIVATEWHENVISIBLE标志。但是,要防止此标志在支持的容器中生效IPointerInactive,您还可以指定OLEMISC_IGNOREACTIVATEWHENVISIBLE标志:
static const DWORD BASED_CODE _dwMyOleMisc =
OLEMISC_ACTIVATEWHENVISIBLE |
OLEMISC_IGNOREACTIVATEWHENVISIBLE |
OLEMISC_SETCLIENTSITEFIRST |
OLEMISC_INSIDEOUT |
OLEMISC_CANTLINKINSIDE |
OLEMISC_RECOMPOSEONRESIZE;
优化的绘图代码
当指示控件将自身绘制到容器提供的设备上下文中时,它通常会将GDI对象(如笔,画笔和字体)选择到设备上下文中,执行其绘制操作,并恢复以前的GDI对象。如果容器具有要在同一设备上下文中绘制的多个控件,并且每个控件选择它所需的GDI对象,则如果控件不单独还原先前选定的对象,则可以节省时间。绘制完所有控件后,容器可以自动恢复原始对象。
要检测容器是否支持此技术,控件可以调用COleControl :: IsOptimizedDraw成员函数。如果此函数返回TRUE,则控件可以跳过恢复先前选定对象的正常步骤。
考虑具有以下(未优化)OnDraw功能的控件:
void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& /*rcInvalid*/)
{
CPen pen(PS_SOLID, 0, TranslateColor(GetForeColor()));
CBrush brush(TranslateColor(GetBackColor()));
CPen* pPenSave = pdc->SelectObject(&pen);
CBrush* pBrushSave = pdc->SelectObject(&brush);
pdc->Rectangle(rcBounds);
pdc->SelectObject(pPenSave);
pdc->SelectObject(pBrushSave);
}
此示例中的笔和画笔是局部变量,这意味着当它们超出范围时(OnDraw函数结束时)将调用它们的析构函数。析构函数将尝试删除相应的GDI对象。但是如果您计划在返回时将它们选择到设备上下文中,则不应删除它们OnDraw。
要防止CPen和CBrush对象在OnDraw完成时被销毁,请将它们存储在成员变量而不是局部变量中。在控件的类声明中,添加两个新成员变量的声明:
class CMyAxOptCtrl : public COleControl
{
CPen m_pen;
CBrush m_brush;
};
然后,该OnDraw函数可以重写如下:
void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& /*rcInvalid*/)
{
CPen pen(PS_SOLID, 0, TranslateColor(GetForeColor()));
CBrush brush(TranslateColor(GetBackColor()));
CPen* pPenSave = pdc->SelectObject(&pen);
CBrush* pBrushSave = pdc->SelectObject(&brush);
pdc->Rectangle(rcBounds);
pdc->SelectObject(pPenSave);
pdc->SelectObject(pBrushSave);
}
这种方法避免了每次OnDraw调用时都会产生笔和画笔。速度的提高是以维护额外的实例数据为代价的。
如果ForeColor或BackColor属性更改,则需要再次创建笔或画笔。为此,请覆盖OnForeColorChanged和OnBackColorChanged成员函数:
void CMyAxOptCtrl::OnForeColorChanged()
{
m_pen.DeleteObject();
}
void CMyAxOptCtrl::OnBackColorChanged()
{
m_brush.DeleteObject();
}
最后,为了消除不必要的SelectObject调用,修改OnDraw如下:
void CMyAxOptCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& /*rcInvalid*/)
{
if (m_pen.m_hObject == NULL)
m_pen.CreatePen(PS_SOLID, 0, TranslateColor(GetForeColor()));
if (m_brush.m_hObject == NULL)
m_brush.CreateSolidBrush(TranslateColor(GetBackColor()));
CPen* pPenSave = pdc->SelectObject(&m_pen);
CBrush* pBrushSave = pdc->SelectObject(&m_brush);
pdc->Rectangle(rcBounds);
if (! IsOptimizedDraw())
{
pdc->SelectObject(pPenSave);
pdc->SelectObject(pBrushSave);
}
}
下一篇开始介绍绘制ActiveX控件。