PyQt 的快速学习教程

October 10th, 2016 by JasonLe's Tech 1,998 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

Qt中的QDataStream

September 20th, 2016 by JasonLe's Tech 1,864 views

之前在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

Qt下的QVariant类

September 12th, 2016 by JasonLe's Tech 2,127 views

QVariant是一个数据类型类,存储不同类型的数据结构,一般情况下C++的class都不允许没有构造函数和析构函数存在。为了方便数据库对象存取,可以使用QVariant class。一个QVariant对象在一个时间内只保留一种类型的值。我们可以使用canConvert来查询是否能够转换当前的类型。转换类型一般以toT()命名。

QVariant可以保存很多Qt的数据类型,通过查看Document,可以知道发现支持很多Qt特定的数据类型,并且还有C++基本类型,如int、float等。QVariant还能保存很多集合类型,如QMap<QString, QVariant>, QStringList和QList<QVariant>等,因此QVariant完全满足现有编程需要。

今天在写代码过程中,尤其是读取数据库大量记录时,这个类足够好用。

struct s_field
{
      int type;
      QString fieldnum;
}
struct s_record
{
      QList<s_field> fieldlist;
      QString tableName;
}

struct s_recorddata
{
      QList<QVariant> datalist;
}
QList<s_recorddata> listdata;

通过这个数据结构我们可以把读到的数据放到listdata中,而每一个datalist都是一条记录,datalist中每一个的QVariant都是一个字段,我们不仅可以通过这个字段读取到数据,也可以判断具体的数据类型。

Type QVariant::type() const

Returns the storage type of the value stored in the variant. Although this function is declared as returning QVariant::Type, the return value should be interpreted as QMetaType::Type. In particular, QVariant::UserType is returned here only if the value is equal or greater than QMetaType::User.

Note that return values in the ranges QVariant::Char through QVariant::RegExp and QVariant::Font through QVariant::Transform correspond to the values in the ranges QMetaType::QChar through QMetaType::QRegExp and QMetaType::QFont through QMetaType::QQuaternion.

Pay particular attention when working with char and QChar variants. Note that there is no QVariant constructor specifically for type char, but there is one for QChar. For a variant of type QChar, this function returns QVariant::Char, which is the same as QMetaType::QChar, but for a variant of type char, this function returns QMetaType::Char, which is not the same as QVariant::Char.

Also note that the types void*, long, short, unsigned long, unsigned short, unsigned char, float, QObject*, and QWidget* are represented in QMetaType::Type but not in QVariant::Type, and they can be returned by this function. However, they are considered to be user defined types when tested against QVariant::Type.

To test whether an instance of QVariant contains a data type that is compatible with the data type you are interested in, use canConvert().

按照上面的数据结构和声明,只要listdata[i].datalist[j].type()就可以返回具体枚举类型,然后想返回具体值就是使用listdata[i].datalist[j].toInt() 或者其他的toxxx()即可。赋值的具体用法就是直接赋值即可。QVariant var; var = datatime …. var = 5 … var = 5.0 …

 

参考:

http://doc.qt.io/qt-4.8/qvariant.html#QVariant-2

Java序列化与反序列化

August 8th, 2016 by JasonLe's Tech 1,217 views

最近在对一个大型Java项目进行调试,这个Java程序当点击一个按钮后就会未响应,后来经过调试发现代码中存在大量的io操作,他们将从文件读取的数据存入到HashMap、LinkList中。找到了程序性能瓶颈,我设计一种缓存思路,将这种Object直接写入文件,只要存在我要找的Object文件,我就不会费时费力的重新存入HashMap,而是从Object中读取。

根据查询Java Docs api,我发现一组Interface Serializable,只要class继承了Serializable接口,那么这个类就可以被序列化,因此我把该类中所有的成员变量所在class都继承了Serializable接口。具体读写Object文件的方式和一般文件读取非常类似。

存Object文件:

class T implements Serializable
{
	int i = 10;
	int j = 9;
	double d = 2.3;
	transient int k = 15;
}
....
FileOutputStream fos = new FileOutputStream("...");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(t);

写Object文件:

FileInputStream fis = new FileInputStream("....");
ObjectInputStream ois = new ObjectInputStream(fis);
T t= (T)ois.readObject();

Java提供了transient 关键字,他的意思就是表明该成员变量不会被序列化,也就是说反序列化后该成员变量保存的值会消失。如果继承了Serializable,我们是无法干预jre对class对象的序列化,所以writeObject、readObject对象一定要顺序相同,因为只有我们知道序列化的内容。

jre为每个序列化的class都赋值了serialVersionUID ,以此来计算反序列化时该值是否相同,不同的话也就意味着该class不能被恢复出来。

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender’s class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named “serialVersionUID” that must be static, final, and of type long:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果我们想控制序列化的时机,那么我们应该让class继承Externalizable接口,Externalizable接口事实上又继承了 Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

我们可以在writeExternal()和readExternal()中自定义序列化的方式,这样子可以增强灵活性。也就是说每次writeObject时,都会调用WriteExtenal()方法,而在WriteExtenal()方法中我们需要将当前对象的值写入到流中;而每次readObject()时,调用的是默认的构造函数,如果我们不在 readExternal()方法中读取每个成员变量,那么成员变量就是null!

 

参考:

http://www.cnblogs.com/chenfei0801/archive/2013/04/06/3002146.html

解决乱码思路—Java读写文件中遇到的问题

July 26th, 2016 by JasonLe's Tech 1,452 views

最近在做一个大型Java项目的移植工作,遇到大量字符编码问题,传统的Windows平台主要使用GBK编码,而linux上面为了保持字符的统一性,使用UTF8编码,这个就导致从windows迁移过来的配置文件和资源文件乱码。之前我写过一篇《字符编码浅析》,简单介绍了下这几种编码的不同。

这里我首先解决Linux平台下的乱码问题:如果只是要看一个不同编码的文件,我们可以用vim打开,然后:set fileencoding=utf8  gbk….的方式改变查看文档的编码。如果我们想转换文档编码形式,那么使用以下的命令(将一个GBK编码的文件转换成UTF-8编码):

$enconv -L zh_CN -x UTF-8 filename

文档编码转换完成,这里我从Java的角度阐述如果读写。因为网上关于Java读写的博文足够多,我主要来总结一下:

Java 的IO有两种流的类型:那就是字节流(InputStream、OutputStream)和字符流(Reader、Writer)。我们在读写中文的时候务必使用字符流,因为中文是2个字节组成,如果读取一个字节必然出现问题。

InputStream常用的类是如下,直接对读数据的是节点流,浅色的是处理流,依靠InputStream对数据进行读写,拥有更强大的功能。

20160808200550

OutputStream常用类如下,直接写的是节点流,浅色的是处理流。

20160808200918

二进制读写的时候要使用Stream的方式进行,而读写字符就使用Reader、Writer。

20160808201307

20160808201400

总结一下两种类型,节点流为以下表格,通过这些对象直接对数据源进行读写。

类型 字符流 字节流
File FileReader、FileWriter FileInputStream、FileOutputStream
Memory Array CharArrayReader、CharArrayWriter ByteArrayInputStream、ByteArrayOutputStream
Memory String StringReader、StringWriter
Pipe PipedReader、PipedWriter PipedInputStream、PipedOutputStream
  • 但是节点流提供功能单一,需要使用处理流对其进行拓展,其中尤其是 BufferedReader、BufferedWriter最为常用,先看一个表格:

 

处理类型 字符流 字节流
Buffering BufferedReader、BufferedWriter BufferedInputStream、BufferedOutputStream
 Filtering  FilterReader、FilterWriter FilterInputStream、FilterOutputStream
字节流字符流转换 InputStreamReader、OutputStreamWriter
Object序列化 ObjectInputStream、ObjectOutputStream
数据转换 DataInputStream、DataOutputStream
计数 LineNumberReader LineNumberInputStream
Peeking ahead PushbackReader PushbackInputStream
Print PrintWriter PrintStream
  • BufferedReader和BufferedWriter来读写文件(字符读写,只要不是二进制文件,都可以用字符,字节是给二进制文件使用的),这种方式读写效率最好(而且可以选择想要的字符集),这个主要将数据先缓冲起来,然后一起写入或者读取出来。经常使用的是readLine()方法,一次读取一行数据。其中我们可以根据装饰者模式修改BufferReader和BufferWriter中的类,比如new InputStreamReader(new FileInputStream(…), StandardCharsets.UTF_8),通过这种方式,就可以以UTF-8的方式读取文件,或者以new OutputStreamWriter(new FileOutputStream(“test.txt”), “UTF-8”)写文件

使用方式 FileReader->BufferedReader->readLine、FileWriter->BufferedWriter->write

private static void read() throws FileNotFoundException, IOException {  
        File file = new File("a.txt");// 指定要读取的文件  
        // 获得该文件的缓冲输入流  
        BufferedReader bufferedReader = new BufferedReader(new FileReader(file));  
        String line = "";// 用来保存每次读取一行的内容  
        while ((line = bufferedReader.readLine()) != null) {  
            System.out.println(line);  
        }  
        bufferedReader.close();// 关闭输入流  
    }  
private static void write() throws IOException {  
        File file = new File("a.txt");// 指定要写入的文件  
        if (!file.exists()) {// 如果文件不存在则创建  
            file.createNewFile();  
        }  
        // 获取该文件的缓冲输出流  
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));  
        bufferedWriter.write("你好世界");  
        bufferedWriter.newLine();
        bufferedWriter.write("hello world");  
        bufferedWriter.flush();// 清空缓冲区  
        bufferedWriter.close();// 关闭输出流  
    }  

与他类似的就是BufferedInputStream和BufferedOuputStream,用这两个读取二进制文件,不同的在于读取的是二进制文件。

FileInputStream ——>BufferedInputStream -> read、FileOutputStream->BufferedOutputStream->write

        // 指定要读取文件的缓冲输入字节流  
        BufferedInputStream in = new BufferedInputStream(new FileInputStream("a.jpg"));  
        File file = new File("b.jpg");  
        if (file != null) {  
            file.createNewFile();  
        }  
        // 指定要写入文件的缓冲输出字节流  
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));  
        byte[] bb = new byte[1024];// 用来存储每次读取到的字节数组  
        int n;// 每次读取到的字节数组的长度  
        while ((n = in.read(bb)) != -1) {  
            out.write(bb, 0, n);// 写入到输出流  
        }  
        out.close();// 关闭流  
        in.close();  

有的时候我们还要使用File对象,这个时候可以查看FileInputStream、FileOutputStream、FileReader、FileWriter的构造方法。类似于FileReader(File file)。

字符流到字节流的转换也很重要,类似于:

InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
    String s = null;
    try {
      s = br.readLine();
...

OutputStreamWriter osw = new OutputStreamWriter(
           new FileOutputStream("...."));
osw.write("mircosoftibmsunapplehp");

Java中io类太多了,有时候容易眼花,不知道用哪个,这个时候只要记住字符流和字节流的异同,合理使用类,并查询java api即可。

参考:

http://blog.csdn.net/dangnianmingyue_gg/article/details/47361323