組合使用QT的資源管理高級功能簡化開發過程

使用 QT 進行團隊開發的時候,經常碰到一個問題,就是如何共同管理資源?甚至一我的進行開發的時候如何簡化資源的維護,避免無謂的消耗? html

若是能夠作到在開發的時候,你們把美工作的圖片(每每是程序員先本身隨便作一個而後等美工來替換)放到一個目錄中,在程序中直接進行引用,等到發佈的時候把這些圖片(或者XML、聲音等其餘資源文件)進行打包,只發佈一個二進制資源文件,而且程序中引用資源的地方不須要進行任何修改能有多好? 程序員

爲了完成這個目標,首先想到的是仿照 Orge 實現一個 ZIP 包文件引擎,通過查找 QT 的源代碼,發現可使用QT 內部的 File Engine 系統,子類化 QAbstractFileEngine,並實現一個 QAbstractFileEngineHandler  的繼承類,自動將自定義的ZipFileEngine 註冊到 QT 的文件系統中。 sql

通過一番資料的搜素與嘗試,發現這個任務不是可以輕鬆完成的,遂開始思考把資源外部化的其餘方法,看到了下列的兩篇參考文章: app

http://qt-project.org/doc/qt-5/resources.html ide

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,那麼一切就簡單了。 動畫

若是把上述的信息填寫到配置文件中,程序啓動時根據配置文件自動完成上述設定搜索路徑的過程,那麼程序就能夠作到對資源存儲路徑的無關性; spa

在程序中加載資源的時候只須要這樣寫: debug

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 char * path );

 

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_H

AppHelper.cpp

#include "AppHelper.h"

 

// 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 char * path ){ return dirExpand( QString( path ) ); }

 

QStringList & dirExpand( QStringList & pathList )

{

QStringList::iterator itr = pathList.begin( );

for( ; itr != pathList.end( ) ; itr ++ )

{

dirExpand( * itr );

}

 

return pathList ;

}

 

static void initResourceSearch( QSettings & settings )

{

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 void initResourceFiles( QSettings & settings )

{

QSettingGroup config( settings , "Resource" );

 

QStringList resPaths = settings.childKeys( );

foreach( const QString & resRoot , resPaths )

{

QString resFile = config.Value< QString >( resRoot );

regResource( resFile , resRoot );

}

}

 

static void initPluginsPaths( QSettings & settings )

{

QSettingGroup config( settings , "Plugins" );

 

QStringList dirLibraries = config.Value< QStringList >( "Path" );

qApp->setLibraryPaths( dirExpand( dirLibraries ) + qApp->libraryPaths( ) );

}

 

static void initAppSettings( QSettings & settings )

{

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 void initSplashArgs( QSettings & settings )

{

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 "AppFrame.h"

#include "AppHelper.h"

 

int main(int argc, char *argv[])

{

QApplication app(argc, argv);

initializeApplication( );

 

splashInit( );

 

QAppFrame appFrame;

appFrame.show( );

 

splashFini( appFrame );

 

return app.exec( );

}

相關文章
相關標籤/搜索