Posts Tagged ‘Qt开发’

字符串乱码解决之道

January 5th, 2017

最近工作压力太大,blog也逐渐荒废,怎么也得写点东西了,要不说不过去。。。。。

Qt为字节流和字符串分别提供了QByteArray和QString两个类(还有QLatin1String等其他类,但这两个是最主要的)。当我们涉及到I/O时,比如读写文件、读写网络socket、控制台输入输出、读写串口… 操作的都是字节流,如果我们此时需要操作的内容是字符串,则需要二者之间的相互转换。在C和C++中,我们一般都是将 “Hello World!” 这种称为字符串。但是就目前而言,当我们提字符串时,一般是指一个Unicode字符串,其由一个一个的Unicode字符构成;当我们提字节流时,是指一个一个的字节。或许我们可以说,ANSI C/C++截止目前只有字节流,而缺乏对字符串的支持。另外各个编译器对编码的支持又严重不一, Qt为解决这个问题提供了QTextCodec。

QTextCodec * textc = QTextCodec::codecForName("GBK");
1.QTextCodec::setCodecForCStrings(textc);
2.QTextCodec::setCodecForTr(textc); 
3.QTextCodec::setCodecForLocale(textc);

QString 是不存在中文支持问题的,很多人遇到问题,并不是本身 QString 的问题,而是没有将自己希望的字符串正确赋给QString。很简单的问题,”我是中文”这样写的时候,它是传统的char 类型的窄字符串,我们需要的只不过是通过某种方式告诉QString 这四个汉字采用的那种编码。而问题一般都出在很多用户对自己当前的编码没太多概念。另外文件是有编码的,但是这种纯文本文件却不会记录自己采用的编码,这个是问题的根源。真的是 QString 乱码了吗?其实很简单的一个问题,当你从窄字符串 char* 转成Unicode的QString字符串的时候,你需要告诉QString你的这串char* 中究竟用的是什么编码?GBK、BIG5还是Latin-1。理想情况就是:将char* 传给QString时,同时告诉QString自己的编码是什么;但是QString 提供的成员函数,远远满足不了大家的需求,于是只有采取语句1的办法。

tr(“中文”);与QString(“中文”);一样,你必须告诉tr这个窄字符串是何种编码?你不告诉它,它就用latin1。于是所谓的乱码问题就出来了。如何告诉tr你写的这几个汉字在磁盘中保存的是何种编码呢?这正是语句2所做的。如果你的编码采用的utf8,可以直接使用trUtf8而不必设置setCodecForTr()。

对于语句3应该没什么好说的,在绝大多数情况下,我们在代码中应该都用不到这个函数(默认的system应该比我们所能设置的要好)。
下面讲一下关于编码转换问题:
QT中的QString内容使用Unicode作为文本编码。但是实际系统中通常采用的是其他编码,例如GBK,utf8等。为了便于兼容这些格式,QT中还设置了两个字符串类型:
QCString类: C类型字符串,必须以\0结尾,也就是中间不能含有\0. 例如GBK编码的字符串
QByteArray类: 中间可以含有\0.例如utf8编码的字符串

在设置下面的代码基础上:

QTextCodec *gbk = QTextCodec::codecForName("gb18030"); 
QTextCodec *utg8 = QTextCodec::codecForName("utf-8");
QTextCodec::setCodecForTr(gbk);
QTextCodec::setCodecForLocale(gbk);
QTextCodec::setCodecForCStrings(gbk);

1. UTF-8 转换 GBK

QString U2G(QString utfStr)
{
   return gbk->toUnicode(utfStr.toLocal8Bit());
}

2 GBK 转换 UTF-8

QString U2G(QString gbkStr)
{
   return utg8->toUnicode(gbkStr.toUtf8());
}

代码示例:
———————————————————————————–

 QTextCodec *gbk = QTextCodec::codecForName("gb18030");
 QTextCodec *utf8 = QTextCodec::codecForName("utf-8");

 QTextCodec::setCodecForTr(gbk);
 QTextCodec::setCodecForLocale(gbk);
 QTextCodec::setCodecForCStrings(gbk);


 QFile file("../test.txt");
 file.open(QIODevice::ReadOnly);
 QByteArray readByte = file.readAll();
 QString readStr = utf8->toUnicode(readByte.data());
 file.close();
 QString utfStr = QObject::trUtf8(readByte); //utf-8
 QString gbkStr = QObject::tr("中文"); // gbk

 QString utf2gbk = gbk->toUnicode(readStr.toLocal8Bit()); // utf8 conver gbk
 QString gbk2utf1 = utf8->toUnicode(utf2gbk.toUtf8()); // gbk convert utf8
 QString g2u = gbk->toUnicode(gbk->fromUnicode(readStr)); // gbk convert utf8

 qDebug() << "gbk:" << gbkStr;
 qDebug() << "utf8:" << utfStr;
 qDebug() << "readStr:" << readStr;

 qDebug() << "read_size:" << readByte.length();
 qDebug() << "utf2gbk:" <<utf2gbk << "length:" << readStr.toLocal8Bit().length();
 qDebug() << "gbk2utf8-1:" << gbk2utf1 << " length: " << utf2gbk.toUtf8().length();
 qDebug() << "g2u" << g2u << "length:" << gbk->fromUnicode(utfStr).length();

 QLabel *label = new QLabel(utf2gbk);
 label->show();

参考:

【1】http://www.cnblogs.com/bingcaihuang/archive/2011/03/17/1986714.html

【2】http://knight4576.blog.51cto.com/2761974/703963

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&nbsp;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