Qt 显示透明flash和编写QtWebkit插件

时间:2023-12-10 16:15:56

Qt 有两种方法可以显示flash.

1. 通过QAxWidget 调用com形式显示flash, 需要本机安装IE flash插件

2. 直接通过qwebview显示flash, 需要下载webkit 的flash插件 NPSWF32.dll

1. 通过IE显示flash 是通常做法. QAxWidget提供很方便的嵌入, 只是IE对页面的支持可能不是很好

但是QAxWidget有个最大的问题, 不支持透明的flash

如果直接设置frameless 和tranparent background的话, qaxwidget会整个窗口直接透明的.

//flash->setWindowFlags(Qt::FramelessWindowHint);

//flash->setAttribute(Qt::WA_TranslucentBackground);

摸索了很久, 最开始的解决方案为使用IWebBrowser2模拟出一个浏览器, 代码复杂的很, 继承一堆com 的东西. 估计有点像简洁版的mfc html控件

概要如下:

class CBrowserEventBridge :public IDispatch
{
public:
CBrowserEventBridge();
virtual ~CBrowserEventBridge(); public:
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP GetTypeInfoCount(UINT* pCountTypeInfo);
STDMETHODIMP GetTypeInfo(UINT iTypeInfo, LCID lcid, ITypeInfo** ppITypeInfo);
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId);
STDMETHODIMP Advise(IUnknown* pUnkCP, const IID& iid);
STDMETHODIMP Unadvise(IUnknown* pUnkCP, const IID& iid);
STDMETHODIMP Advise(CFlashPlayer *pPlayer, IWebBrowser2* pIE);
STDMETHODIMP Unadvise();
STDMETHODIMP Invoke(DISPID dispidMember,REFIID riid, LCID lcid, WORD wFlags,DISPPARAMS* dispParams,VARIANT* pvarResult,EXCEPINFO* pExcepInfo,UINT* puArgErr);
private:
IWebBrowser2* m_webBrowser;
DWORD m_dwCookie;
CFlashPlayer *m_pPlayer;
}; class CFlashPlayer
: public IOleClientSite
, public IOleInPlaceSite
, public IDocHostUIHandler
, public IOleInPlaceFrame
{
public:
class IFlashPlayerDelegate
{
public:
//播放完毕回调
virtual HRESULT OnFlashPlayEnd() = ;
}; CFlashPlayer(IFlashPlayerDelegate *pDelegate);
~CFlashPlayer(); //imp IUnknown
ULONG STDMETHODCALLTYPE AddRef( void);
ULONG STDMETHODCALLTYPE Release( void);
HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid,__RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject); //imp IOleClientSite
HRESULT STDMETHODCALLTYPE SaveObject( void);
HRESULT STDMETHODCALLTYPE GetMoniker( DWORD dwAssign,DWORD dwWhichMoniker,__RPC__deref_out_opt IMoniker **ppmk);
HRESULT STDMETHODCALLTYPE GetContainer(__RPC__deref_out_opt IOleContainer **ppContainer);
HRESULT STDMETHODCALLTYPE ShowObject( void);
HRESULT STDMETHODCALLTYPE OnShowWindow( BOOL fShow);
HRESULT STDMETHODCALLTYPE RequestNewObjectLayout( void); //imp IOleInPlaceSite
HRESULT STDMETHODCALLTYPE CanInPlaceActivate( void);
HRESULT STDMETHODCALLTYPE OnInPlaceActivate( void);
HRESULT STDMETHODCALLTYPE OnUIActivate( void);
HRESULT STDMETHODCALLTYPE GetWindowContext( __RPC__deref_out_opt IOleInPlaceFrame **ppFrame, __RPC__deref_out_opt IOleInPlaceUIWindow **ppDoc, __RPC__out LPRECT lprcPosRect, __RPC__out LPRECT lprcClipRect, __RPC__inout LPOLEINPLACEFRAMEINFO lpFrameInfo);
HRESULT STDMETHODCALLTYPE Scroll( SIZE scrollExtant);
HRESULT STDMETHODCALLTYPE OnUIDeactivate( BOOL fUndoable);
HRESULT STDMETHODCALLTYPE OnInPlaceDeactivate( void);
HRESULT STDMETHODCALLTYPE DiscardUndoState( void);
HRESULT STDMETHODCALLTYPE DeactivateAndUndo( void);
HRESULT STDMETHODCALLTYPE OnPosRectChange( __RPC__in LPCRECT lprcPosRect); //imp IOleInPlaceFrame
HRESULT STDMETHODCALLTYPE InsertMenus( __RPC__in HMENU hmenuShared, __RPC__inout LPOLEMENUGROUPWIDTHS lpMenuWidths);
HRESULT STDMETHODCALLTYPE SetMenu( __RPC__in HMENU hmenuShared,__RPC__in HOLEMENU holemenu,__RPC__in HWND hwndActiveObject);
HRESULT STDMETHODCALLTYPE RemoveMenus( __RPC__in HMENU hmenuShared);
HRESULT STDMETHODCALLTYPE SetStatusText( __RPC__in_opt LPCOLESTR pszStatusText);
HRESULT STDMETHODCALLTYPE TranslateAccelerator( __RPC__in LPMSG lpmsg, WORD wID); //imp IOleInPlaceUIWindow
HRESULT STDMETHODCALLTYPE GetBorder( __RPC__out LPRECT lprectBorder);
HRESULT STDMETHODCALLTYPE RequestBorderSpace( __RPC__in_opt LPCBORDERWIDTHS pborderwidths);
HRESULT STDMETHODCALLTYPE SetBorderSpace( __RPC__in_opt LPCBORDERWIDTHS pborderwidths);
HRESULT STDMETHODCALLTYPE SetActiveObject( __RPC__in_opt IOleInPlaceActiveObject *pActiveObject, __RPC__in_opt_string LPCOLESTR pszObjName); //imp IOleWindow
HRESULT STDMETHODCALLTYPE GetWindow( __RPC__deref_out_opt HWND *phwnd);
HRESULT STDMETHODCALLTYPE ContextSensitiveHelp( BOOL fEnterMode); //imp IDocHostUIHandler
HRESULT STDMETHODCALLTYPE ShowContextMenu( DWORD dwID, POINT *ppt, IUnknown *pcmdtReserved,IDispatch *pdispReserved);
HRESULT STDMETHODCALLTYPE GetHostInfo( DOCHOSTUIINFO *pInfo);
HRESULT STDMETHODCALLTYPE ShowUI( DWORD dwID, IOleInPlaceActiveObject *pActiveObject, IOleCommandTarget *pCommandTarget, IOleInPlaceFrame *pFrame,IOleInPlaceUIWindow *pDoc);
HRESULT STDMETHODCALLTYPE HideUI( void);
HRESULT STDMETHODCALLTYPE UpdateUI( void);
HRESULT STDMETHODCALLTYPE EnableModeless( BOOL fEnable);
HRESULT STDMETHODCALLTYPE OnDocWindowActivate( BOOL fActivate);
HRESULT STDMETHODCALLTYPE OnFrameWindowActivate( BOOL fActivate);
HRESULT STDMETHODCALLTYPE ResizeBorder( LPCRECT prcBorder,IOleInPlaceUIWindow *pUIWindow,BOOL fRameWindow);
HRESULT STDMETHODCALLTYPE TranslateAccelerator( LPMSG lpMsg, const GUID *pguidCmdGroup,DWORD nCmdID);
HRESULT STDMETHODCALLTYPE GetOptionKeyPath( __out LPOLESTR *pchKey,DWORD dw);
HRESULT STDMETHODCALLTYPE GetDropTarget( IDropTarget *pDropTarget, IDropTarget **ppDropTarget);
HRESULT STDMETHODCALLTYPE GetExternal( IDispatch **ppDispatch);
HRESULT STDMETHODCALLTYPE TranslateUrl( DWORD dwTranslate,__in __nullterminated OLECHAR *pchURLIn,__out OLECHAR **ppchURLOut);
HRESULT STDMETHODCALLTYPE FilterDataObject( IDataObject *pDO,IDataObject **ppDORet); public:
void Resize(int width, int height);
HRESULT BeforeNavigate2(std::wstring strUrl, DISPPARAMS* dispParams);
static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); private:
void _initialize(HINSTANCE hInstApp );
bool _makeTransparent();
bool _registWndClass(HINSTANCE hInstApp );
void _createWindow(HINSTANCE hInstApp);
bool _embedBrowser();
void _unenbedBrowser();
void _openWebPage(std::wstring strUrl); private:
HWND m_hWnd;
IWebBrowser2* m_pWeb;
IOleObject *m_pOleObj;
IFlashPlayerDelegate *m_pDelegate;
CBrowserEventBridge *m_pEvtBridge; };

再后来, 又发现了一个比较好的解决方法:

代码如下, 直接通过handle控制qaxwidget的窗口属性.

	HWND hWnd = (HWND)flash->winId();
LONG lStyle = ::GetWindowLong(hWnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU);
::SetWindowLong(hWnd, GWL_STYLE, lStyle);
LONG lExStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
::SetWindowLong(hWnd, GWL_EXSTYLE, lExStyle|WS_EX_LAYERED|WS_EX_TOPMOST|WS_EX_TRANSPARENT);
typedef int (WINAPI* LPFUNC)(HWND, COLORREF , BYTE, DWORD);
HINSTANCE hins = ::LoadLibraryW(L"User32.DLL");
if(!hins)
return ;
LPFUNC func2 = (LPFUNC)GetProcAddress(hins,"SetLayeredWindowAttributes");
if(!func2)
return ;
COLORREF clrMask = RGB(255,255,255);
func2(hWnd, clrMask, 0, LWA_COLORKEY);
FreeLibrary(hins); flash->setControl(QString::fromUtf8("{d27cdb6e-ae6d-11cf-96b8-444553540000}"));
//connect(flash,SIGNAL(FSCommand(QString,QString)),this,SLOT(flashAction(QString,QString))); //用于处理FLASH传来的字符串
flash->dynamicCall("LoadMovie(long,string)", 0, "d:/9023.swf"); //调用方法
flash->dynamicCall("WMode", "transparent");

  

就这几行代码, 被搞了好久.  幸好, 完美解决透明问题

2. 通过webview直接显示flash

下载 NPSWF32_13_0_0_182.dll

放置在exe目录下的plugins, 否则可能无法加载flash 插件

目录结构:

test.exe

plugins

└────NPSWF32_13_0_0_182.dll

显示本地flash 需要加file:/// , 如 file:///d:/myswf.swf

npswf.dll 在本人写文章时, 最新版是13, 大小15M.

估计大部分可能无法接受这个大小

所以, 如果你没有用到很高级的flash特性的话, 建议找版本老一点的npswf.

比如本人用的就是9.x版的npswf.dll, 大小仅为2.6M

还有另外一种方法, 为QWebView编写插件, 手动解析flash

这个方法是有很严重的bug, 这里只是说下思路, 并且该方法也可以用于解析pdf等等

1). 写一个webkit的插件

WebKitPluginInterface.h

#pragma once

#include <QWebPluginFactory>
#include <QtPlugin>
class WebKitPluginInterface
{
public:
WebKitPluginInterface(){};
virtual ~WebKitPluginInterface(){};
virtual QList<QWebPluginFactory::Plugin> plugins()const =;
virtual QObject *create(const QString &mimeType,
const QUrl &url,
const QStringList &argumentNames,
const QStringList &argumentValues) const =;
};
QT_BEGIN_NAMESPACE
//声明WebKitPluginInterface为一个接口
Q_DECLARE_INTERFACE(WebKitPluginInterface, "org.Qt-Plugin.WebkitPluginFlash")
QT_END_NAMESPACE
bluefish.h

#include "WebKitPluginInterface.h"

class  bluefish: public QObject, public WebKitPluginInterface  {
Q_OBJECT
//Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.WebkitPluginFlash" FILE "bluefish.json")
Q_INTERFACES(WebKitPluginInterface) //声明WebKitPluginInterface是一个接口 public:
bluefish();
~bluefish();
QList<QWebPluginFactory::Plugin> plugins()const ;
QObject *create(const QString &mimeType,
const QUrl &url,
const QStringList &argumentNames,
const QStringList &argumentValues) const ; protected:
};
#include "bluefish.h"
#include <QUrl>
#include <QDebug>
#include <QAxWidget> bluefish::bluefish(): WebKitPluginInterface(){ }; bluefish::~bluefish(){}; QList<QWebPluginFactory::Plugin> bluefish::plugins()const
{
qDebug()<<"bluefish::plugins";
QWebPluginFactory::MimeType mimeType;
mimeType.name="application/x-shockwave-flash";
mimeType.description=QObject::tr("flash");
mimeType.fileExtensions.append(".flv");
mimeType.fileExtensions.append(".f4v");
mimeType.fileExtensions.append(".swf"); QList<QWebPluginFactory::MimeType> mimeTypes;
mimeTypes.append(mimeType); QWebPluginFactory::Plugin plugin;
plugin.name = QObject::tr("ldh flash plugin");
plugin.description=QObject::tr("ldh flash plugin description");
plugin.mimeTypes=mimeTypes; QList<QWebPluginFactory::Plugin> plugins ;
plugins.append(plugin);
return plugins;
} QObject *bluefish::create(const QString &mimeType,
const QUrl &url,
const QStringList &argumentNames,
const QStringList &argumentValues) const
{
qDebug()<<"bluefish::create";
QString strUrl = url.toString(); // QAxWidget *flash = new QAxWidget(, );
flash->setControl(QString::fromUtf8("{d27cdb6e-ae6d-11cf-96b8-444553540000}"));
//connect(flash,SIGNAL(FSCommand(QString,QString)),this,SLOT(flashAction(QString,QString))); //用于处理FLASH传来的字符串
flash->dynamicCall("LoadMovie(long,string)", , strUrl.toLocal8Bit().data()); //调用方法 //flash->setControl("{ca8a9780-280d-11cf-a24d-444553540000}");
//flash->dynamicCall("LoadFile( const string& )", "d:/bcomps.1.pdf"); //for(int i = 0; i < argumentNames.length(); i++)
//{
// flash->dynamicCall(argumentNames[i].toLocal8Bit().data(), argumentValues[i].toLocal8Bit().data());
//} Q_UNUSED(argumentNames);
Q_UNUSED(argumentValues); return flash;
}

bluefish是可以作为dll 动态载入的. 这里我为了方便, 就去掉了导出接口的部分.

怎么编写一个Qt插件, 可以参考Qt exsample中的样例代码

把QAxWidget对象返回, webkit会自动显示.

同理, 也可以在webkit中嵌入QtextEdit等等widget

ps:

//flash->setControl("{ca8a9780-280d-11cf-a24d-444553540000}");

这行代码就是显示pdf.

然后继承QWebPluginFactory载入我们编写的插件

qt_modules.h

#include <QWebPluginFactory>
#include <QUrl>
#include "WebKitPluginInterface.h" class WebkitPluginFactory : public QWebPluginFactory
{
Q_OBJECT
public:
WebkitPluginFactory();
QObject *create ( const QString & mimeType, const QUrl & url, const QStringList & argumentNames, const QStringList & argumentValues ) const;
QList<QWebPluginFactory::Plugin> plugins () const; private:
mutable QList<QList<QWebPluginFactory::Plugin> > pluginslist; // 插件列表
mutable QList<WebKitPluginInterface *> interfaces; //插件接口,这个接口是我们自定义的插件的同意接口。
};
qt_modules.cpp

#include "qt_modules.h"
#include <QtNetwork\QNetworkProxyFactory>
#include <Qdir>
#include <QPluginLoader>
#include <QLibrary>
#include <QDebug>
#include <QDir>
#include "bluefish.h" WebkitPluginFactory::WebkitPluginFactory() : QWebPluginFactory()
{
qDebug()<<"=== ldh === WebkitPluginFactory::WebkitPluginFactory";
} QList<QWebPluginFactory::Plugin> WebkitPluginFactory::plugins () const
{
qDebug()<<"=== ldh === WebkitPluginFactory::create";
static bool isFirst=true; static QList<QWebPluginFactory::Plugin> plugins;
if(!isFirst)
{
return plugins;
}
isFirst=false;
plugins.clear(); bluefish *obj = new bluefish; WebKitPluginInterface * interface= qobject_cast<WebKitPluginInterface*> (obj);//载入自定义的接口,支持动态插件创建
plugins.append(interface->plugins());
pluginslist.append(interface->plugins());
interfaces.append(interface); if(plugins.isEmpty()){
qDebug()<<"no plugins is loaded!";
} //QDir dir(QCoreApplication::applicationDirPath());
//QStringList filters;
//QString abspath=dir.absolutePath();
//qDebug()<<"the webkit plugin dir is:"<< abspath; ////获取指定目录下的所有插件,linux下是插件库的后缀为so,windows下则是dll
//filters<<"*.dll";
//QStringList files=dir.entryList(filters);
//foreach(QString file,files)
//{
// file=dir.filePath(file);//dir.filePath(file); //file.absoluteFilePath();
// qDebug()<<"the webkit plugin path is: "<<file; // QPluginLoader loader(file);
// QObject * obj= loader.instance();
// if(!obj)
// {
// qDebug()<<"error: "<<loader.errorString();
// continue ;
// }
// WebKitPluginInterface * interface= qobject_cast<WebKitPluginInterface*> (obj);//载入自定义的接口,支持动态插件创建
// qDebug() << "loading plugins is: " << interface->plugins().at(0).name;
// plugins.append(interface->plugins());
// pluginslist.append(interface->plugins());
// interfaces.append(interface);
//}
//if(plugins.isEmpty()){
// qDebug()<<"no plugins is loaded!";
//}
return plugins;
} QObject * WebkitPluginFactory::create ( const QString & mimeType, const QUrl & url, const QStringList & argumentNames, const QStringList & argumentValues ) const
{
qDebug()<<"=== ldh === WebkitPluginFactory::create";
for(int i=;i<pluginslist.size();i++)
{
for( int j=;j< pluginslist[i].size();j++)
{
foreach(QWebPluginFactory::MimeType mt, pluginslist[i][j].mimeTypes)
{
if(mt.name == mimeType) //根据MIME类型,创建相应的插件实例
{
return interfaces[i]->create( mimeType, url, argumentNames, argumentValues);
}
}
}
}
return NULL; //如果没有,直接返回NULL,webkit会进行处理的
}

注释部分是动态载入.  但其实不是必须的. 如果有多个插件的话, 可以这样做

    //初始化flash插件
QNetworkProxyFactory::setUseSystemConfiguration (true);
pWebView->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
pWebView->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
pWebView->page()->setPluginFactory(new WebkitPluginFactory());

最后对QWebView 设置下就可以了

这个方法有个很严重的bug, 不能动态增加axwidget. 而对于普通QWidget对象, 则没有问题

即: 如果html在载入时必须确定所有的object(flash, pdf等). 不能动态增加

比如在html中用js增加html代码

这会导致webkit崩溃. 本人无法解决该bug. 无力-_-!

猜测原因可能是resize时, 绘图对象被提前删了

然后对于QWidget来说, 可以随意添加. 定制webkit还是很方便的