组合使用QT的资源管理高级功能简化开发过程
使用 QT 进行团队开发的时候,常常碰到一个问题,就是如何共同管理资源?甚至一个人进行开发的时候如何简化资源的维护,避免无谓的消耗?
如果可以做到在开发的时候,大家把美工做的图片(往往是程序员先自己随便做一个然后等美工来替换)放到一个目录中,在程序中直接进行引用,等到发布的时候把这些图片(或者XML、声音等其他资源文件)进行打包,只发布一个二进制资源文件,并且程序中引用资源的地方不需要进行任何修改能有多好?
为了完成这个目标,首先想到的是仿照 Orge 实现一个 ZIP 包文件引擎,经过查找 QT 的源代码,发现可以使用QT 内部的 File Engine 系统,子类化 QAbstractFileEngine,并实现一个 QAbstractFileEngineHandler 的继承类,自动将自定义的ZipFileEngine 注册到 QT 的文件系统中。
经过一番资料的搜素与尝试,发现这个任务不是能够轻松完成的,遂开始思考把资源外部化的其他方法,看到了下列的两篇参考文章:
http://qt-project.org/doc/qt-5/resources.html
http://qt-project.org/doc/qt-5/qdir.html#setSearchPaths
在第一篇文章中提到资源外部化的方法,就是使用 rcc 工具对 qrc 资源描述文件进行二进制的编译,然后在程序中调用 QResource::registerResource 静态函数注册资源,请注意该函数的第二个参数:该资源可以挂载到资源树的特定节点路径上。
而在第二篇文章中提到可以使用 QDir::setSearchPaths 设定特定前缀的搜索路径,这样QFile 打开使用了特定前缀的文件时,首先根据设定的搜索路径列表确定文件位置,然后再打开,更妙的是:它的搜索路径可以指定文件系统路径也可以指定资源路径,设想一下启动画面使用 image:splash.png 这个文件,在开发是把image 的搜索路径设置为文件系统目录,那么只要设定的文件系统目录中存在 splash.png ,就可以进行调试了,资源会正确的加载,在发布时,把 image 的搜索路径设定为资源的 prefix,那么一切就简单了。
如果把上述的信息填写到配置文件中,程序启动时根据配置文件自动完成上述设定搜索路径的过程,那么程序就可以做到对资源存储路径的无关性;
在程序中加载资源的时候只需要这样写:
|
QPixmap pixmap("image:splash.png"); QIcon icon( "image:usergroup.png"); QFile file("data:database-init.sql"); |
至于最后 image 中的资源采用文件还是打包在资源中,和程序代码完全无关,一切尽在配置中。
好了,开发配合(程序员之间以及和美工之间)的问题基本上解决了,还有最后一个问题,开发的时候是存放在目录中,发布时如何打包这些资源呢?
最朴素的思想就是在发布时手工编辑一个 qrc 文件,使用 rcc –binary 命令编译这个资源文件,但是一个项目,很多开发人员放置资源,最终这些资源会很多,编写这个文件也需要耗时,而且发布的过程是迭代的,这个工程耗时并且容易出错,继续寻找解决办法…
如果可以自动扫描资源目录生成一个 qrc 文件就好了,经过多次尝试改进后发现rcc 工具提供了这个功能,rcc --project 参数可以扫描当前目录,生成一个 qrc 文件,该文件会包含当前目录以及子目录中的所有文件,但是该文件中没有资源前缀,没有关系,我们可以将不包含前缀的qrc 编译为二进制资源文件,在调用 registerResource函数注册资源的时候补充前缀就可以了,当前这个也可以使用配置的方式,例如使用 image=res:images.rcc 的配置项,那么就把 images.rcc 文件注册到 /image 资源路径上;
当然还有一个方法,就是在使用 rcc 编译资源的时候自动增加一个前缀,这使用 --root 参数就可以了,下面是我整理的批处理脚本以供参考
|
@ cd images @ rcc --project -o allres.qrc @ if exist ..\images.resx del /F ..\images.resx @ rcc --binary -o ..\images.resx --root "/image" allres.qrc @ del /f allres.qrc |
下面附上 debug 版和 release 版的配置文件样例
Debug 版
|
[Application] LocaleCodec=GB18030 Language=zh_CN Theme=Dark
[Resource]
[Plugins] Path=$(AppDir)/QT5
[Search] skin=$(AppDir)/res lang=$(AppDir)/res res=$(AppDir)/res image=$(AppDir)/res/images
[Splash] Image=image:splash.png |
Release 版
|
[Application] LocaleCodec=GB18030 Language=zh_CN Theme=Dark
[Resource] @=res:SLM.resx
[Plugins] Path=$(AppDir)/QT5
[Search] skin=$(AppDir)/res lang=$(AppDir)/res res=$(AppDir)/res image=:/image
[Splash] Image=image:splash.png |
下面是加载应用配置文件的程序
AppHelper.h
|
#ifndef APPHELPER_H #define APPHELPER_H
void initializeApplication( void );
QStringList & dirExpand( QStringList & pathList ); QString & dirExpand( QString & path ); QString dirExpand( const
void regResource( const QString & Path , const QString & Root );
void addTranslator( const QString & Name ); void setSkinStyles( const QString & Name );
// http://qt-project.org/doc/qt-5/richtext-html-subset.html void splashShow( const QString & text , const QColor & color = Qt::black ); void splashFini( QWidget & mainWidget ); void splashInit( void ); void splashHide( void );
#endif |
AppHelper.cpp
|
#include
// http://qt-project.org/doc/qt-5/qdir.html#setSearchPaths // http://qt-project.org/doc/qt-5/resources.html
struct QSettingGroup { QSettingGroup( QSettings & config , const QString & group ) : settings( config ) { settings.beginGroup( group ); }
template< typename T > T Value( const QString & Key , const QVariant & Def = QVariant( ) ) { return settings.value( Key , Def ).value< T >( ); }
~QSettingGroup( ) { settings.endGroup( ); }
QSettings & settings; };
struct QSplashArgs { explicit QSplashArgs( ) : splashScreen( NULL ){ }
QSplashScreen * splashScreen ; QString splashImageFile ; };
static QSplashArgs & splashArgs( ) { static QSplashArgs splashArgs; return splashArgs ; }
QString & dirExpand( QString & path ) { static QString appDir , binary , config ; if( appDir.isEmpty( ) || appDir.isNull( ) ) { binary = QCoreApplication::applicationDirPath( ); appDir = QDir( binary + "/.." ).canonicalPath( ); config = appDir + "/etc" ; }
path.replace( "$(AppDir)" , appDir ); path.replace( "$(Binary)" , binary ); path.replace( "$(Config)" , config );
return path; }
QString dirExpand( const
QStringList & dirExpand( QStringList & pathList ) { QStringList::iterator itr = pathList.begin( ); for( ; itr != pathList.end( ) ; itr ++ ) { dirExpand( * itr ); }
return pathList ; }
static { QSettingGroup config( settings , "Search" );
QStringList resDirs = settings.childKeys( ); foreach( const QString & resDir , resDirs ) { QStringList resPaths = config.Value< QStringList >( resDir ); QDir::setSearchPaths( resDir , dirExpand( resPaths ) ); } }
static { QSettingGroup config( settings , "Resource" );
QStringList resPaths = settings.childKeys( ); foreach( const QString & resRoot , resPaths ) { QString resFile = config.Value< QString >( resRoot ); regResource( resFile , resRoot ); } }
static { QSettingGroup config( settings , "Plugins" );
QStringList dirLibraries = config.Value< QStringList >( "Path" ); qApp->setLibraryPaths( dirExpand( dirLibraries ) + qApp->libraryPaths( ) ); }
static { QSettingGroup config( settings , "Application" );
// initialize resource file QString resFile = QCoreApplication::applicationFilePath( ); resFile.replace( ".exe" , ".rcc" , Qt::CaseInsensitive ); QResource::registerResource( resFile );
// initialize Locale Codec QByteArray codec = config.Value< QByteArray >( "LocaleCodec" , "GB18030" ); QTextCodec::setCodecForLocale( QTextCodec::codecForName( codec ) );
// install language translator addTranslator( config.Value< QString >( "Language" , "zh_CN" ) );
// initialize skin styles setSkinStyles( config.Value< QString >( "Theme" , "Default" ) ); }
static QString getString( QStringList & list , int index , const QString & def = QString( ) ) { return list.size( ) > index ? list[ index ] : def ; }
static { QSettingGroup config( settings , "Splash" ); QSplashArgs & Args = splashArgs( );
Args.splashImageFile = config.Value< QString >( "Image" , "Splash.png" ); }
void initializeApplication( void ) { QString appFile = QCoreApplication::applicationFilePath( ); appFile.replace( ".exe" , ".ini" , Qt::CaseInsensitive ); QDir::setCurrent( dirExpand( QString( "$(AppDir)" ) ) ); QSettings config( appFile , QSettings::IniFormat ); QDir::setCurrent( dirExpand( "$(AppDir)" ) );
initResourceSearch( config ); initResourceFiles( config ); initPluginsPaths( config ); initAppSettings( config ); initSplashArgs( config ); }
void addTranslator( const QString & Name ) { QTranslator * translator = new QTranslator( 0 ); translator->load( QString( "lang:" ) + Name + ".lang" ); qApp->installTranslator( translator ); }
void setSkinStyles( const QString & Name ) { QFile skinFile( QString( "skin:" ) + Name + ".skin" ); skinFile.open( QFile::ReadOnly | QFile::Unbuffered ); qApp->setStyleSheet( skinFile.readAll( ) ); skinFile.close( ); }
void regResource( const QString & Name , const QString & Root ) { QString name( Name ), root( Root ); if( root[ 0 ] == ':' ){ root[ 0 ] = '/' ; } if( root[ 0 ] == '$' ){ root[ 0 ] = '/' ; } if( root[ 0 ] == '@' ){ root[ 0 ] = '/' ; } if( root[ 0 ] != '/' ){ root.insert( 0 , '/' ); } QResource::registerResource( dirExpand( name ) , root ); }
struct QSplash : public QSplashScreen { void closeEvent( QCloseEvent * event );
explicit QSplash( ); };
void QSplash::closeEvent( QCloseEvent * event ) { QSplashScreen::closeEvent( event ); splashArgs( ).splashScreen = NULL; }
QSplash::QSplash( ) : QSplashScreen( QPixmap( splashArgs( ).splashImageFile ) ) {
}
void splashInit( void ) { if( splashArgs( ).splashScreen != NULL ) return;
if( QSplashScreen * splash = new QSplash( ) ) { splashArgs( ).splashScreen = splash; splash->show( ); } }
void splashShow( const QString & text , const QColor & color ) { if( QSplashScreen * splash = splashArgs( ).splashScreen ) { splash->showMessage( text , Qt::AlignCenter , color ); qApp->processEvents( ); } }
void splashFini( QWidget & mainWidget ) { if( QSplashScreen * splash = splashArgs( ).splashScreen ) { splash->finish( & mainWidget ); } }
void splashHide( void ) { if( QSplashScreen * splash = splashArgs( ).splashScreen ) { splashArgs( ).splashScreen = NULL ; splash->deleteLater( ); splash->close( ); } } |
Main.cpp 中的样例代码
|
#include #include
int main(int argc, char *argv[]) { QApplication app(argc, argv); initializeApplication( );
splashInit( );
QAppFrame appFrame; appFrame.show( );
splashFini( appFrame );
return app.exec( ); } |
组合使用QT的资源管理高级功能简化开发过程的更多相关文章
- 【英语魔法俱乐部——读书笔记】 3 高级句型-简化从句&倒装句(Reduced Clauses、Inverted Sentences) 【完结】
[英语魔法俱乐部——读书笔记] 3 高级句型-简化从句&倒装句(Reduced Clauses.Inverted Sentences):(3.1)从属从句简化的通则.(3.2)形容词从句简化. ...
- UE4高级功能--初探超大无缝地图的实现LevelStream
转自:http://blog.csdn.net/u011707076/article/details/44903223 LevelStream 实现超大无缝地图--官方文档学习 The Level S ...
- C#高级功能(三)Action、Func,Tuple
Action和Func泛型委托实际上就是一个.NET Framework预定义的委托,3.5引入的特性.基本涵盖了所有常用的委托,所以一般不用用户重新声明. Action系列泛型委托,是没有返回参数的 ...
- 为ASP.NET MVC应用程序使用高级功能
为ASP.NET MVC应用程序使用高级功能 这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译, ...
- Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性
简介 Tengine是由淘宝网发起的Web服务器项目.它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性.Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很 ...
- 为SSD编程(4)——高级功能和内部并行
原文 http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-pa ...
- vim 高级功能
本文章原创首发于公众号:编程三分钟 ,文末二维码. 文本编辑.跳转.删除.复制.替换这些操作用vim确实是快:但是好像仅仅是这样根本不能说服我vim超过鼠标的地方. 花点时间弄熟这些,除了炫技意外,主 ...
- Xen之初体验:XenMotion、 StorageMotion、Site Recovery、Power Management 各种新、高级功能免费
Xenserver 的新版本6.2现在已经全面开源,省掉了原有的序列号,也能免费体验曾经标题中的付费高级功能. 安装镜像:http://downloadns.citrix.com.edgesuite. ...
- MVC5 Entity Framework学习之Entity Framework高级功能(转)
在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中 ...
随机推荐
- linux系统瓶颈分析(精)
linux系统瓶颈分析(精) (2013-09-17 14:22:00) 分类: linux服务器瓶颈分析 1.0 性能监控介绍 性能优化就是找到系统处理中的瓶颈以及去除这些的过程,多数管理员相信 ...
- arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵
前段时间移植uboot细致研究过uboot启动过程,近期耐不住寂寞.想对kernel下手. Uboot启动过程分析博文连接例如以下: http://blog.csdn.net/skyflying201 ...
- CSS3实现加载中的动画效果
本篇文章由:http://xinpure.com/css3-implementations-of-loading-an-animation-effect/ Loading 的菊花图形组合的不太好,基本 ...
- PowerShell---Operators 介绍
1.Arithmetic operators(算术运算符) 算术运算符包括加.减.乘.除.取模 此外,加法运算符 (+) 和乘法运算符 (*) 还可对字符串.数组和哈希表进行运算.加法运算符将输入连接 ...
- mysql 添加缓存
解决方法一,修改my.ini文件 找到 query_cache_size =0 估计就是这个问题在造成的,没有开查询缓存 (一般1G 就64M缓存) 我的服务器的内存4G, 调整到 代码如下 复制 ...
- EularProject 32: 数字1-9排列构成乘法等式
Pandigital products Problem 32 We shall say that an n-digit number is pandigital if it makes use of ...
- Event-Souring模式
Event-Sourcing模式使用仅附加存储来记录或描写叙述域中数据所採取的动作,从而记录完整的一系列系列事件,而不是仅存储实体的当前状态.由于存储包括全部的事件,能够用来具体化域对象. Event ...
- atitit.userService 用户系统设计 v6 q413
atitit.userService 用户系统设计 v6 q413 1. 新特性1 2. Admin login1 3. 用户注册登录2 3.1. <!-- 会员注册使用 --> 商家 ...
- atitit.提升研发效率的利器---重型框架与类库的区别与设计原则
atitit.提升研发效率的利器---重型框架与类库的区别与设计原则 1. 框架的意义---设计的复用 1 1.1. 重型框架就是it界的重武器. 1 2. 框架 VS. 库 可视化图形化 1 2.1 ...
- @property (nonatomic,retain)中的nonatom和retain的意思
转自:http://yuanshoupeng2005.blog.163.com/blog/static/68235027201235113952886/ http://baike.baidu.com/ ...