Archive for the ‘Qt开发’ category

QT中.pro文件写法概述

December 15th, 2016

最近在实现一个自动化编译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

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

 

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

November 14th, 2016

最近几天忙于发布软件版本,可是在打包的时候,发现一个诡异的问题,那就是我在本机上通过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

大家对于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

Qt中的QDataStream

September 20th, 2016

之前在Java中遇到了需要序列化和反序列化的场景,Qt也为我们提供了类似的数据流类—QDataStream和QTextStream,可以从任意的输入输出设备读取或写入数据。其中QDataStream用于读写二进制数据,他可以在读写数据的时候已经严格定义了数据流的类型和每个类型的长度,不用关心编码转换的问题。TextStream用于读写文本(如HTML、XML和源代码等)的格式,解决了二进制文件格式无法直接阅读和编辑的缺点。QTextStream考虑了Unicode编码与系统本地编码或其他编码之间的转换问题,并考虑了不同操作系统之间行尾符切换的问题(MS “\r\n”, Mac “ \n”)。

QDataStream 与  QIODevice 联系十分紧密,QIODevice代表数据的输入输出媒介,我们拿 QFile 举例。从下面代码示例可以看到QDataStream通过QFile对文件进行修改

QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);   // we will serialize the data into the file
out << QString("the answer is");   // serialize a string
out << (qint32)42;        // serialize an integer
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);    // read the data serialized from the file
QString str;
qint32 a;
in >> str >> a;           // extract "the answer is" and 42

通过以上代码,我们也可以将复杂的QHash,QSet直接写入QDataStream中,然后再从文件中读出该数据结构,具体代码见main。通过查看Document,我们发现除了QFile,我们还可以把QAbstractSocket, QBuffer, QFile, QLocalSocket, QNetworkReply和QProcess传入QDataStream,进行数据的序列化保存。最令我惊讶的是QAbstractSocket和QProcess,也就是说我们对一个进程进行dump,类似于criu提供的快照功能,这个是非常强大的。

类似的Qt在序列化数据的时候也要设置setVersion(),out.setVersion(QDataStream::Qt_4_8); in.setVersion(QDataStream::Qt_4_8);这样在反序列化的时候不会出现数据格式的问题,类似于Java中的serialVersionUID。

另外一个与QDataStream结合比较紧密的就是QByteArray,我在编写网络程序中,会使用到QByteArray。这个class相比const char *更加强大,使用这个类也就意味着我们不用考虑字符串结束符\0,但是需要注意的是每次字符串赋值都是一次深拷贝,这样避免了出现内存问题。如果我们不需要深拷贝的时候,可以使用fromRawData ( const char * data, int size )直接对字符串进行修改。除此之外还可以使用resize方法对大小进行调整,这个在socket数据通信中十分有用。

QDataStream::QDataStream ( QByteArray * a, QIODevice::OpenMode mode )
Constructs a data stream that operates on a byte array, a. The mode describes how the device is to be used.Alternatively, you can use QDataStream(const QByteArray &) if you just want to read from a byte array.Since QByteArray is not a QIODevice subclass, internally a QBuffer is created to wrap the byte array.

 

参考:

http://doc.qt.io/qt-4.8/qbytearray.html