ATL 无窗口 ActiveX 媒体容器





5.00/5 (18投票s)
功能齐全的 ATL 无窗口 ActiveX 容器。非常适合托管 Windows Media Player、透明 Flash 和 Silverlight 动画。
目录

引言
COM/ActiveX 控件已经存在十多年了。尽管如此,要获得一个完整的无窗口容器仍然非常困难。MFC 对话框有自己的无窗口容器,与 ATL 相比,它确实是无窗口的,但如果您的项目无法依赖 MFC,您就只能自己解决了。本文介绍的容器支持完整的无窗口控件。它是专门为 ATL/WTL 项目开发的,可用于创建复合控件。这意味着您可以将多个无窗口控件组合起来创建自己的组件。
想象一下,能够将 Adobe Flash、Windows Media Player 和 Silverlight 封装到您自己的组件中。这些类的另一个好处是,如果您之前有 ATL 经验,学习曲线几乎为零。您只需将对话框继承自新的对话框类,即可获得此容器的全部功能。
描述
拥有一个功能齐全的无窗口容器可能非常有用。我希望您能找到一些很好的用途。我将描述将容器集成到您自己的项目所需的步骤。稍后,在其他文章中,我们将探讨如何将其中一些内容提升到一个新的水平。
想想看,如果您能将 Silverlight 和 Flash 内容无缝集成到您的 C++ 应用程序中并与它们进行交互,那不是很好吗?
您越是思考,就越会发现这是完全可能的,毕竟网络浏览器在很大程度上就是这样做的。但在我们深入探讨之前,让我们快速回顾一下 ActiveX 控件容器,看看整个概念的强大之处,即使在今天也依然如此。如果您已经熟悉 ActiveX 和无窗口,或者不需要回忆,可以直接跳转到使用 ActiveXContainerImpl 类。
ActiveX 控件容器
如果您想精通无窗口控件及其容器的开发,您必须依赖 OC96 规范。基本上,您需要了解和理解几件事。
公寓线程模型感知
ActiveX 控件的开发应符合公寓线程模型规则,以确保其行为正确。也就是说,您必须考虑 UI 线程,并确保您的对象是线程安全的。如果您的对象使用全局变量,它应该同步对它们的访问。
无窗口
通过支持无窗口控件规范,控件可以有效地利用其容器的窗口,而不是拥有自己的窗口。这可以减少系统资源的占用,并通过更快的激活/停用来提高性能。提供无窗口支持不仅可以实现有趣的视觉效果的透明度,还可以进一步提高控件的性能。
命中检测
提供命中检测是为了让无窗口控件具有不规则形状,并完善透明度的视觉优势。容器调用控件的 IViewObjectEx::QueryHitPoint
。作为回报,控件会确定它是否被命中,并将指示返回给容器。
控件持久化数据
控件可以使用 IPersistStreamInit
、IPersistStorage
、IPersistMemory
和 IPersistPropertyBag
中的任何一个作为持久化嵌入机制。如果控件愿意,它可以实现多个这些接口。本文介绍的容器仅支持 IPersistStreamInit
,但未来可能会添加 IPersistPropertyBag
以支持更广泛的控件。
与容器交互
由于容器接口被传递给控件,因此容器的整个对象模型可用,并且可以被控件操作。例如,考虑与 Internet Explorer 相同的场景,控件访问整个 DOM 树 (IHTMLDocument
) 并更改文档的背景颜色。
CComPtr<IOleContainer> spContainer;
m_spClientSite->GetContainer(&spContainer);
CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spDoc(spContainer);
if (spDoc)
spDoc->put_bgColor(CComBSTR(_T("blue")));
这意味着,如果您开发自定义容器或复合控件,任何托管在您的对话框表面的控件都可以访问您的对象模型支持的方法和属性。
下图显示了涉及的主要接口。
基本上,要托管 ActiveX 组件,容器必须
- 调用对象的
SetClientSite
来通知对象其显示位置,称为“客户端站点”。 - 初始化对象属性;为此,它使用
IPersistStreamInit
、IPersistPropertyBag
。 - 调用对象的
SetAdvise
方法以接收复合文档事件的通知。 - 然后,调用对象的
DoVerb
方法并使用OLEIVERB_INPLACEACTIVATE
来激活对象。
然后,对象将通过以下方式进行:
- 通过调用
CanWindowlessActivate
来协商其激活,以了解它是否可以“无窗口”激活;如果不可能,控件将创建自己的窗口。 - 然后,它调用
OnInPlaceActivateEx
,但使用激活标志来指示它是否希望无窗口激活。 - 无窗口对象还调用
GetWindowContext
来查找其在容器中的位置。 - 最后,控件调用容器的
ShowObject
;这会通知容器控件已准备好显示。
容器还负责管理输入焦点、鼠标捕获和为对象关联标识符。容器通过调用 IOleInPlaceObjectWindowless
接口的 OnWindowMessage
方法将鼠标和键盘消息分派给无窗口控件。这样,当控件获得焦点时,它可以响应常规输入。这就是全部了,但 ActiveXContainerImpl
类使得这个过程非常透明。现在,让我们看一些实际应用。
使用 ActiveXContainerImpl 类
ActiveXContainerImpl
类支持以下功能:
- 托管无窗口和有窗口控件
- 在一个容器中托管多个 ActiveX 控件
- 限制单个控件(有窗口或无窗口)的鼠标或键盘输入
- 更改控件的 Z 顺序(控件可发送至:后退、置顶、置顶)
- 支持数据标识符(控件允许通过 http://, file:///, res:// 加载数据)
- 透明度支持(由 Z 顺序位置控制)
- 将无窗口控件渲染到内存 DC 或打印机(非屏幕)
在对话框中托管 ActiveX 控件基本上与您之前使用 ATL 的方式相同。您可以安全地将基类从 CAxDialogImpl
重命名为 CAxWindowlessHost
。此类继承自 CDialogImplBaseT
,因此将具有任何 Windows 对话框的相同功能。
class CMainDlg : public CAxWindowlessHost<CMainDlg>
{
public:
enum { IDD = IDD_MAINDLG };
BEGIN_MSG_MAP(CMainDlg)
CHAIN_MSG_MAP(CAxWindowlessHost<CMainDlg>)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
END_MSG_MAP()
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/,
HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID,
HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID,
HWND /*hWndCtl*/, BOOL& /*bHandled*/);
};
让我们通过一些示例开始。以下示例假定控件是使用对话框编辑器的“插入 ActiveX 控件”插入的。您仍然可以灵活地在运行时手动创建所有内容。
托管 Microsoft Windows Media Player
Windows Media Player 支持无窗口激活。假设您已将媒体播放器插入对话框,则以下示例显示了如何将其切换到无窗口模式。请注意,对于 Windows Media Player,还有一个自定义属性对话框,因此您可以在设计时配置控件。
HRESULT hr;
ActiveXSite* pSite;
pSite = CAxWindowlessHost<CMainDlg>::GetControlSite(IDC_WMP11);
if ( pSite != NULL )
{
pSite->SetAllowResize(false); // turn-off resize or media will
// resize itself to match its content
CComQIPtr<IWMPCore> wmp = pSite->ActiveXControl();
CComQIPtr<IWMPPlayer4> wmp4 = pSite->ActiveXControl();
if ( wmp4 ) {
hr = wmp4->put_windowlessVideo( VARIANT_TRUE );
hr = wmp4->put_uiMode( CComBSTR("Full") );
}
hr = wmp->put_URL( CComBSTR("enter resource URL") );
}
托管 Adobe Flash Player
您还可以托管 Flash 内容。Flash 动画可以从外部文件资源加载,也可以从互联网检索。例如,您可以添加对 YouTube 视频的支持,前提是您正确指定了资源的 URL。以下示例显示了如何初始化 Adobe Flash Player 以托管动画或视频。
HRESULT hr;
ActiveXSite* pSite;
pSite = CAxWindowlessHost<CMainDlg>::GetControlSite(IDC_SHOCKWAVEFLASH);
if ( pSite != NULL )
{
CComQIPtr<IShockwaveFlash> spFlash = pSite->ActiveXControl();
hr = spFlash->put_WMode( CComBSTR("Transparent") );
hr = spFlash->put_Movie( CComBSTR("enter resource URL") );
}
设置透明模式对于将 Adobe Flash 切换到无窗口模式是必要的。
托管 Microsoft Silverlight
XAML 和 Silverlight 应用程序包 (*.xap) 可以使用 Microsoft Silverlight ActiveX 控件进行托管。出于安全原因,您只能从 file:/// 或 http:// 标识符加载数据。如果支持资源协议 (res://) 会很好。以下示例显示了如何配置 Silverlight 来加载外部资源。
HRESULT hr;
ActiveXSite* pSite;
pSite = CAxWindowlessHost<CMainDlg>::GetControlSite(IDC_AGCONTROL1);
if ( pSite != NULL )
{
pSite->SetAllowRClick(false);
CComBSTR bstrUrl("enter resource URL");
pSite->SetUrl(bstrUrl);
CComQIPtr<IXcpControl2> slight = pSite->ActiveXControl();
hr = slight->put_Source(bstrUrl);
}
在此示例中,控件被阻止接收右键单击,因此上下文菜单永远不会显示。
演示展示了如何在无窗口模式下使用 Silverlight。
如果我必须考虑我们可用的所有 ActiveX 控件,将需要展示大量的演示。Windows Form ActiveX 控件也可以被您的应用程序托管。这里也提供了一个演示。
结论
本文提供了一个完整且真实的 ATL 无窗口 ActiveX 容器解决方案。我描述了将三个常用控件集成到您自己的对话框所需的步骤。使用其他 ActiveX 控件也很容易。该容器可用于开发自定义复合控件。也许最有趣的部分是展示了如何轻松地将 Silverlight 应用程序包 (*.xap,*.xaml) 集成到您的解决方案中。我希望您会考虑它提供的功能,并在您自己的程序中加以利用。
参考文献
历史
- 2009/02/19:初始版本 + Windows CE 端口
- 2009/03/05:更新以支持无窗口和查看器模式下的 Silverlight