Microsoft的ActiveX技术允许应用程序与其他应用程序或者库提供的用户接口组件一起工作。它构建于Microsoft的COM基础上,为使用组件的应用程序定义了一套接口,并且为提供组件的应用程序和库提供了另外一套接口。
Qt/Windows桌面版(Qt/Windows Desktop Edition)提供了ActiveQt框架,用以为ActiveX和Qt提供完美结合。ActiveQt由两个模块组成:
本文的例子将会在一个使用了QAxContainer模块的Qt应用程序中嵌入一个Windows Media Player(如下图所示)。这个Qt应用程序在Windows Media Player的ActiveX控件上添加了一个Open按钮、一个Play/Pause按钮、一个Stop按钮以及一个滑动条。
需要获取所调用ActiveX控件的常用参数和成员函数。可通过如下方法获取。
(1)获取控件的CLSID。比如调用WMP,首先需要知道WMP的CLSID,这里为:{
22D6F312-B0F6-11D0-94AB-0080C74C7E95}。
(2)Qt提供了dumpdoc命令来自动生成对应CLSID控件的说明文档,打开编译器命令行,输入指令格式为:
dumpdoc {CLISID} -o xxx.html
(3)查看生成的.html文件,获取参数、信号和槽函数。
(1)新建项目,名称为“mediaplayer”。
(2)在项目文件.pro中添加如下语句:
QT += axcontainer
(3)新建C++类,名称为“PlayerWindow”,基类为QWidget。
(4)在代码中添加如下头文件:
#include <QAxWidget> //用于显示WMP
#include <QAxObject> // 用于操作WMP对象
这个应用程序主窗口的类是PlayerWindow:
class PlayerWindow : public QWidget
{
Q_OBJECT
Q_ENUMS(ReadyStateConstants)
public:
enum PlayStateConstants { Stopped = 0, Paused = 1, Playing = 2 };
enum ReadyStateConstants { Uninitialized = 0, Loading = 1,
Interactive = 3, Complete = 4 };
PlayerWindow();
protected:
void timerEvent(QTimerEvent *event);
private slots:
void onPlayStateChange(int oldState, int newState);
void onReadyStateChange(ReadyStateConstants readyState);
void onPositionChange(double oldPos, double newPos);
void sliderValueChanged(int newValue);
void openFile();
private:
QAxWidget *wmp;
QToolButton *openButton;
QToolButton *playPauseButton;
QToolButton *stopButton;
QSlider *seekSlider;
QString fileFilters;
int updateTimer;
};
PlayerWindow类继承了QWidget。这里的Q_ENUMS()宏(就像下面的Q_OBJECT一样)是必需的,用来告诉moc:在onReadyStateChange()槽中使用的MPReadyStateConstants类型是一个枚举类型。在private段,我们声明了一个QAxWidget*的成员变量。
分段查看构造函数的代码:
PlayerWindow::PlayerWindow()
{
wmp = new QAxWidget;
wmp->setControl("{22D6F312-B0F6-11D0-94AB-0080C74C7E95}");
在构造函数中,我们从创建一个QAxWidget对象封装Windows Media Player的ActiveX控件开始。QAxContainer模块由三个类组成:QAxObject封装一个COM对象,QAxWidget封装一个ActiveX控件,而QAxBase则为QAxObject和QAxWidget实现了COM的核心功能。这三个类之间的关系如下图所示。
我们使用Windows Media Player 6.4控件的类的ID作为参数,对QAxWidget调用setControl()。这样将会创建一个所需的实例。从那时起,这个ActiveX控件的所有属性,事件和方法都可以通过QAxWidget对象而作为Qt的属性、信号和槽来加以使用。
如下图中总结的那样,COM数据类型可以自动转换为相应的Qt类型。例如,一个类型为VARIANT_BOOL的输入参数可以转换成一个boo变量,并且一个类型为VARIANT_BOOL的输出参数也可以转换成一个bool &变量。如果结果类型是一个Qt类(比如像QString,QDateTime等),那么输入参数的类型将会是一个常量引用(例如,const QString &)。
我们继续查看PlayerWindow的构造函数:
wmp->setProperty("ShowControls", false);
wmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(wmp, SIGNAL(PlayStateChange(int, int)),
this, SLOT(onPlayStateChange(int, int)));
connect(wmp, SIGNAL(ReadyStateChange(MPReadyStateConstants)),
this, SLOT(onReadyStateChange(MPReadyStateConstants)));
在调用QAxWidget::setControl()之后,通过调用QObject::setProperty(),可以把这个Windows Media Player的ShowControls属件设置为false,因为我们自己提供了用来操作这个组件的按钮。函数QObject::setProperty()既可以用于COM属性又可以用于普通的Qt属性。它的第二个参数的类型是QVariant。
接下来调用setSizePolicy(),让这个ActiveX控件占有布局中所有可用的空间,并且把COM组件中的ActiveX事件连接到槽中。
…
stopButton = new QToolButton;
stopButton->setText(tr("&Stop"));
stopButton->setEnabled(false);
connect(stopButton, SIGNAL(clicked()), wmp, SLOT(Stop()));
…
除了把一些Qt信号和这个COM对象提供的槽[Play() ,Pause()和Stop()]连接起来以外,Playerwindow构造函数中的其余部分都与我们在平常模式下的情况一样。由于这些按钮的功能实现都有相似性,所以这里只给出了Stop按钮的实现代码。
让我们离开这个构造函数,来看一看timerEvent()函数:
void PlayerWindow::timerEvent(QTimerEvent *event)
{
if (event->timerId() == updateTimer) {
double curPos = wmp->property("CurrentPosition").toDouble();
onPositionChange(-1, curPos);
} else {
QWidget::timerEvent(event);
}
}
当正在播放一个多媒体片断的时候,每隔一定时间就会调用函数一次。我们使用它推进滑动条的滑块。通过调用ActiveX控件上的property()获得CurrentPosition属性的QVariant类型的值,然后调用toDouble()把它转换成double值,就可以实现滑块的推进。然后,我们调用onPositionChange()来执行更新。
当拖动滑块时,视频当前位置通过下面的函数更新:
void PlayerWindow::sliderValueChanged(int newValue)
{
seekSlider->blockSignals(true);
wmp->setProperty("CurrentPosition", double(newValue) / 60);
seekSlider->blockSignals(false);
}
这里调用了setProperty()函数对属性进行设置。我们不再查看其余的代码了,因为它们中的绝大多数都不直接和ActiveX相关。
在处理多个COM对象时,经常需要能够直接调用一个COM方法(而不是把它连接到一个Qt信号上)。要做到这一点,最容易的方法就是使用这个方法的名字和签名作为调用QAxBase::dynamicCall()的第一个形式参数,并且把这个方法的实际参数作为额外参数。例如:
wmp->dynamicCall("TitlePlay(uint)", 6);
这个dynamicCall()函数最多可以带8个QVariant类型的参数,并且它可以返回—个QVariant。如果需要使用这种方法传递IDispatch *或者IUnkown *,就可以把这个组件封装到一个QAxObject中,并且对它调用asVariant(),以将其转换成一个QVariant()。如果需要调用能够返问Dispatch *或IUnkown *的COM方法,或者如果需要访问一个具有上述类型之一的COM属性,那么就可以使用querySubObject()来代替:
QAxObject *session = outlook.querySubObject("Session");
QAxObject *defaultContacts =
session->querySubObject("GetDefaultFolder(OlDefaultFolders)",
"olFolderContacts");
如果我们希望调用一些函数,而这些函数的参数列表中还有一些不支持的数据类型,那么就可以使用QAxBase::querylnterface()来取得COM的接口并且直接调用这个函数。就像往常使用COM一样,在我们已经完成了对COM接口的使用时,必须调用Release()。如果需要经常调用这样的函数,那么可以派生QAxObject或者QAxWidget,并且再提供一些封装这些COM接口调用的成员函数即可。需要注意的是,这些QAxObject和QAxWidget的子类不能定义它们自己的属性、信号或槽。
—————————————————