QT中.pro文件写法概述

December 15th, 2016 by JasonLe's Tech 1,971 views

最近在实现一个自动化编译qt工程的pro生成器,pro文件是组织qt项目的核心。

如果我们开始没有使用pro文件组织项目的话,可以先使用qmake -project生成一份pro文件,然后再着手对其进行修改。下面是我使用命令生成的一个pro文件:

######################################################################
# Automatically generated by qmake (2.01a) ?? 1? 5 19:54:06 2017
######################################################################

TEMPLATE = app
TARGET = 
DEPENDPATH += . include . src
INCLUDEPATH += .

# Input
HEADERS += include/ISCAS.h include/ISCASDef.h
SOURCES += src/ISCAS.cpp

TEMPLATE 参数指的是要编译出来什么东西,是独立运行的app还是dll文件或者是其他,
TEMPLATE = app

A> app -建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用。
B> lib – 建立一个库的makefile。
C> vcapp – 建立一个应用程序的VisualStudio项目文件。
D> vclib – 建立一个库的VisualStudio项目文件。
E> subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。

TARGET 指定生成的应用程序名,有时候我们将pro文件转换成为vcxproj文件(qmake -r -tp vc),我们想同时指定编译文件名字,比如release版本不带后缀d,而debug版本带d,程序员只需要切换编译模式就可以生成不同名字的文件。

TARGET = ProjectName
TARGET = $$qtLibraryTarget($$TARGET)

DEPENDPATH 指定程序编译时依赖的相关路径 DEPENDPATH += . forms include qrc sources

INCLUDEPATH 指的是头文件包含路径 INCLUDEPATH += .

后面的 HEADERS 和 SOURCES 就是指定 编译的头文件和源代码

下面来看另外一个pro文件模板:

PLATFORM_VERSION =  x86_32
ALLONE_DIR = E://

QT +=  xml quick sql xml network script webkit xmlpatterns testlib declarative

TEMPLATE = lib
TARGET = ISCAS
DEPENDPATH += .
INCLUDEPATH += ./include $$ALLONE_DIR/include
DEFINES += ISCAS_LIB
OBJECTS_DIR = ./tmp/obj
MOC_DIR = ./tmp/moc
DESTDIR = $$ALLONE_DIR/lib/component
CONFIG += qt warn_on debug_and_release console

# Input
HEADERS +=	./include/ISCAS.h	\
				./include/ISCASDef.h
SOURCES +=	./src/ISCAS.cpp

FORMS	+= ./forms/CSSC.ui
RCC_DIR = forms
UI_DIR = ./tmp
TRANSLATIONS = CSSC.ts
win32{
	CONFIG(release,release|debug){
	    LIBS += $$ALLONE_DIR/lib/win32/ComponentBase.lib
	    
	    LIBS += $$ALLONE_DIR/lib/win32/AllOneFoundation.lib
        
	    LIBS += $$ALLONE_DIR/lib/win32/EventNotification.lib
        
	    LIBS += $$ALLONE_DIR/lib/win32/DDSMsgHandle.lib
	}

	CONFIG(debug,release|debug) {
	    LIBS += $$ALLONE_DIR/lib/win32/ComponentBased.lib
	    
	    LIBS += $$ALLONE_DIR/lib/win32/AllOneFoundationd.lib
        
	    LIBS += $$ALLONE_DIR/lib/win32/EventNotificationd.lib
        
	    LIBS += $$ALLONE_DIR/lib/win32/DDSMsgHandled.lib  
	}
}

PLATFORM_VERSION 表示编译的程序版本是32位还是64位。

这个模板里ALLONE_DIR是一个变量,我们可以在下面的位置通过$$ALLONE_DIR使用该变量。

QT 就是Qt的各个lib库。

DEFINES += XX_XX_XXX //定义编译选项,在.h文件中就可以使用 :#ifdef xx_xx_xxx,例如:

#if defined ISCAS_LIB
#  define ISCAS_EXPORT Q_DECL_EXPORT
#else
#  define ISCAS_EXPORT Q_DECL_IMPORT
#endif

Q_DECL_EXPORT宏主要为了导出这个类,生成.dll的同时生成.lib文件。提供给外部接口供其他类使用。

OBJECTS_DIR 为存放中间文件的目录,一般我们在编译我们的工程的时候会生成许多的中间文件,如.o文件(linux下)或.obj(windows下),这样我们的工程就很乱,所以我们可以定义这个选择。

MOC_DIR指定moc命令将含Q_OBJECT的头文件转换成标准.h文件的存放目录。

DESTDIR指定生成的应用程序放置的目录。

CONFIG用来指定qmake关于应用程序的配置信息,在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项是更安全的。

A> qt部分告诉qmake这个应用程序是使用Qt来连编的。这也就是说qmake在连接和为编译添加所需的包含路径的时候会考虑到Qt库的。
B> warn_on部分告诉qmake要把编译器设置为输出警告信息的。
C> debug_and_release 部分告诉qmake应用程序生成debug版和release版的程序。
D> console指定该程序需要写控制台,包括cout cerr 等。

20170105220525

CONFIG(release,release|debug) 指定括号里里的LIBS+=是针对release版的,CONFIG(debug,release|debug)是针对 debug版的。

LIBS: 这里可以选择我们要包含的库,例如我们的工程要用到libz.so的一个压缩库,(如果是在Linux下,则去掉lib和后缀名,windows下去掉后缀名即可)在linux下我可以这样写:
LIBS += -lz  windows 下的libz.dll 为 LIBS += -llibz
或是用到image目录下的libimage.so库,那么可以这么写:
LIBS += -L../image/image

RESOURCES 定义工程资源描述文件,工程里需要很多图片,那么这些图片的定义都可以放在backup.qrc文件中进行描述,那么这里就可以指出我们的qrc文件。RESOURCES += backup.qrc

FORMS 指定工程中包含的.ui设计文件

UI_DIR 指定uic命令将.ui文件转化成ui_*.h文件的存放的目录,比如下面是存放到forms目录
UI_DIR += forms

RCC_DIR 指定rcc命令将.qrc文件转换成qrc_*.h文件的存放目录
RCC_DIR += ../tmp

TRANSLATIONS = CSSC.ts 就是使用lupdate -verbose CSSC.pro 生成ts文件。然后使用Qt linguist 翻译完毕以后,再使用lrelease -verbose CSSC.pro生成 .qm文件。

 

参考:

【1】http://blog.csdn.net/fjb2080/article/details/4833666

【2】http://blog.csdn.net/adriano119/article/details/5878169

【3】http://www.ithao123.cn/content-4528049.html

Qt 国际化

December 4th, 2016 by JasonLe's Tech 1,486 views

Qt中的国际化的方法有很多,常用的有使用QTextCodec类和使用tr()函数。前者将编码名称写到代码里面,除非你使用Unicode编码,否则国际化依然是一个问题;后者就不会有这个问题,并且这也是Qt推荐的做法。因此,我们主要来说使用tr()函数的方法进行应用程序的国际化。

首先,我们需要在pro文件中增加一行: TRANSLATIONS += myapp.ts

myapp.ts是我们需要创建的翻译文件。这个文件的名字是任意的,不过后缀名需要是ts。然后我们打开命令提示符,进入到工程所在目录,比如我的是E:\My Documents\Workspace\Qt\MyApp,也就是pro文件所在的文件夹,然后输入命令

lupdate -verbose MyApp.pro

如果出现的是命令不存在,请注意将Qt的bin目录添加到环境变量中(PATH第一个位置)。此时,如果更新的数目,说明ts文件创建成功。Qt提供了一个工具,Qt Linguist,你可以在开始菜单的Qt项下面的Tools中找到。用它可以打开我们的ts文件,然后进行我们的翻译工作,翻译工作完毕后使用

lrelease -verbose MyApp.pro

之后会生成MyApp.qm文件,该文件是二进制的形式,可以被Qt直接使用。下面我们要修改main()函数,使之加载这个qm文件:

int main(int argc,char *argv[]){
   QApplication a(argc, argv);
   QTranslator qtTranslator;
   qtTranslator.load("myapp.qm");
   a.installTranslator(&qtTranslator);
   MainWindow w;
   w.resize(800, 600);
   w.show();
   return a.exec();
}

这个界面已经加载该qm文件,qm文件其实是动态加载到exe文件中的,而不是直接编译进去的。

参考:

http://www.cnblogs.com/oloroso/p/4596740.html

 

使用NSIS工具制作安装包

November 21st, 2016 by JasonLe's Tech 1,263 views

发布自己编写的程序是一件令人兴奋的事,在windows平台有InstallSheild和NSIS工具。

下面我使用NSIS来打包我的程序,对于打包不熟悉的人,该工具提供了向导方式,我们可以一步一步打包自己的程序。NSIS 大体布局如下:

  • 预设参数(包括外部压缩器选择、编译选项、宏定义以及文件包含等)
  • 普通安装设置
  • 自定义函数
  • 安装程序区域内容
  • 安装程序回调函数及其相关函数定义
  • 卸载程序区域内容
  • 卸载程序回调函数及其相关函数定义

如果对打包程序要求不太高的话,我们的工作主要集中安装程序区域内容和卸载程序区域内容,其他部分我们均可以使用向导默认生成,【1】中讲的比较细致,我们可以按照教程一步一步操作。在要选择要打包的文件时,直接把要打包的目录内容全部添加进来,注意两处*.*,以及勾选“包含子目录”,不要勾选”单独添加每个文件“,编译的时候会把所有的文件打包进来的。

281552003955288

我之前就是没有加*.*导致只加了工作目录下的文件,而那些子目录完全没有打包进去。之后就是设置一些快捷方式什么的,在结束向导的时候,我们需要勾选”保存脚本“,以及”转换文件路径到相对路径“,这样可以方便下次打包使用。

281552197992145

以上是通过图形化方式打包生成脚本,最后按Ctrl+F9编译成安装包。而通过编辑脚本的方式就是改脚本代码啦,虽然这种方式比较晦涩,但是可以更清楚展示软件包安装过程。【2】是一个实例脚本,我们只需要关注这两条语法:

; 循环包含目录下全部内容
File /r "F:\12\1\*.*"
; 只包含一个文件
File "F:\12\gf.gif"

Delete "$TEMP\magiclime.exe"
RmDir /r "SMPROGRAMSNSIS"

加入/r 就是循环加入,而不加/r 就是只添加该文件。RmDir主要是删除该子文件夹下的所有文件和子文件夹,而Delete是删除该文件  ,/r就是循环删除。

 

参考:

【1】http://www.cnblogs.com/modou/p/3573772.html

【2】示例脚本

使用QUrl打开外部程序的方式

November 14th, 2016 by JasonLe's Tech 1,186 views

最近几天忙于发布软件版本,可是在打包的时候,发现一个诡异的问题,那就是我在本机上通过QDesktopServices::openUrl(QUrl(QApplication::applicationDirPath()+‘xxxxx’));成功,而在客户环境下调用的外部程序,一直失败,但是我确定我的外部程序放在工作目录中,是哪里出了问题了呢?

查询Qt Document 也没有明确说为什么。最后我发现如果程序安装路径出现类似C:\Program Files (x86)这种带有括号的时候会失败,最后使用QDesktopServices::openUrl(QUrl::fromLocalFile(QApplication::applicationDirPath()+‘xxxxx’));才调用成功。

参考fromLocalFile函数

QUrl QUrl::fromLocalFile ( const QString & localFile ) [static]
Returns a QUrl representation of localFile, interpreted as a local file. This function accepts paths separated by slashes as well as the native separator for this platform.

可以看出该函数专门解析本机数据,接收本地/ \各种类型的分隔符。

 

参考:

http://www.pfeng.org/archives/808

PyQt 的快速学习教程

October 10th, 2016 by JasonLe's Tech 1,848 views

大家对于Qt已经非常了解了,但是什么事PyQt呢?首先他是一个对于qt框架的python封装,最后还是调用的c++的接口,所以很多参数传递也要遵照C++的函数接口传入,所以并不是qt就不用学习了。

既然要学习PyQt,那么我们就需要了解Python 类的用法,由于Python是解释性语言,相比编译型的语言显得比较简单,这里不过多展开叙述Python 类的写法,就说一个需要注意的事项:

class Example(QtGui.QWidget):
    value = 0
    __privateValue = 1
    def __init__(self):
        super(Example, self).__init__()
        self.initUI()


    def initUI(self):
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Icon')
        self.setWindowIcon(QtGui.QIcon('web.png'))        

        self.show()

class Example括号里面代表继承的父类,__init__(self)是class Example的构造函数,而super(Example, self)返回了Example的父类(即QtGui.QWidget),接着调用父类的构造函数。value是Class Example的公有成员变量,__privateValue 以两个下划线开头是属性的私有变量,私有方法也是以__开头。

class Parent(object):        # 定义父类
   parentAttr = 100
   def __init__(self):
      print '调用父类构造函数'


class Child(Parent): # 定义子类
   def __init__(self):
      super(Child, self).__init__()
      print "调用子类构造方法"

c = Child()          # 实例化子类

对于Python类继承顺序,并不是很多blog种使用super方式访问父类,虽然从表象上,使用super(Child, self).__init__()可以访问父类的构造函数,但实际上super的真正实现不是这样子,super返回的是继承顺序中的下一个!详见 https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ 这个关键字在多继承中出出现与常规OOP语言中不同的现象!https://www.zhihu.com/question/20040039这里就是不再展开了。只需要记住:在 MRO(Method Resolution Order) 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。

def main():
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':#Python中常用的定义入口点方式
    main()

我们只需要关注与main函数中的实现就好。除此之外还默认提供了getattr、setattr方法。

  • getattr(obj, name[, default]) : 访问对象的属性。obj就是类的实例,第二个就是类成员变量。
  • hasattr(obj,name) : 检查是否存在一个属性。
  • setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
  • delattr(obj, name) : 删除属性。

PyQt中采用了与qt类似的信号嘈机制,只是写法与c++中略有不同,比如我们声明一个控件exitAction,我们只需要把exitAction.clicked.connect(QtCore.QCoreApplication.instance().quit)就可以退出当前的对话框。在写代码这块代码的时候,我发现 fileMenu = menubar.addMenu(‘&File’) 这种类型的语句,&File ,这个&不是PyQt强制要求的,而是Qt中关于唤出菜单功能的使用(就是Alt+F)。The ampersand in the menu item’s text sets Alt+F as a shortcut for this menu. (You can use “&&” to get a real ampersand in the menu bar.)

页面布局

Qt中的页面布局主要依靠 QHBoxLayout和QtGui.QVBoxLayout实现,它们可以将部件水平或垂直排列。PyQt与之类似:

def initUI(self):
        okButton = QtGui.QPushButton("OK")
        cancelButton = QtGui.QPushButton("Cancel")

        hbox = QtGui.QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(okButton)
        hbox.addWidget(cancelButton)

        vbox = QtGui.QVBoxLayout()
        vbox.addStretch(1)
        vbox.addLayout(hbox)

        self.setLayout(vbox)    

先创建一个水平布局存放弹簧,OK和Cancle,然后再将这个水平布局放到一个垂直布局中,最后设置布局即可。而QGridLayout是网格布局,主要把控件一个一个的放进去。

grid = QtGui.QGridLayout()
self.setLayout(grid)

Qt中存在事件句柄,我们可以理解为事件处理程序。类似于中断处理程序(这么理解方便我理解),比如鼠标点击,键盘某个按键按下都会触发事件处理程序

def keyPressEvent(self, e):

     if e.key() == QtCore.Qt.Key_Escape:
        self.close()

 

20161010204313

PyQt中也存在sender函数,通过这个函数可以知道是哪个变量发来的信号,以便对这个信号进行处理。

        btn1.clicked.connect(self.buttonClicked)            
        btn2.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')

如果在多个窗口之间进行信号传递就要使用emit的方式发射信号,当然,我们先要把信号槽建立起来才可以。

class Communicate(QtCore.QObject):
    closeApp = QtCore.pyqtSignal()
    def __init__(self):
        super(Communicate, self).__init__()

class Example(QtGui.QMainWindow):
    def __init__(self):
        super(Example, self).__init__()
        self.initUI()

    def initUI(self):

        self.c = Communicate()
        self.c.closeApp.connect(self.close)
        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Emit signal')
        self.show()

    def mousePressEvent(self, event):
        self.c.closeApp.emit()

通过这个代码可以看到class Communicate被实例化后创建信号closeApp,然后这个信号与close连接。其中closeApp的作用域是整个class Communicate。局部函数里定义的变量的生命周期在函数中。根据文档:pyqtSingal是用来定义signal,而pyqtSlot 则是用于定义slot函数。通过以上知识点,我们就可以开始编写基于PyQt的应用程序了。

参考:

http://www.runoob.com/python/python-object.html