2010年7月8日星期四

使用Python编写Qt程序

使用Python编写Qt程序

Qt

Qt是一个C++的跨平台框架。
它最基本的是一个跨平台的Gui库。可以在Windows下X11下Mac OS X下以及若干嵌入设备中使用,Qt提供了对平台相关的Api的封装。
不过随着发展功能变得日渐丰富,Qt已经包含了众多跨平台的实用库与组件。除了QtCore和QtGui,还有特定用途的模块提供。
同时实现了对C++语言的扩充,一方是Qt自身的模块提供了比C++标准库更丰富的功能为开发带来便捷,另一方面是通过预处理机制下的QObject实现了Signals and Slots和Meta-Object System增强了对象的功能。
在Qt框架中也包含了对设计模式的重用,使用树状结构实现对象的管理,MVC在Gui中的运用,以及在其他Api的设计中体现出的。这可以提高新代码的编写速度与质量。
Qt中也提供了若干实用的工具程序。


PyQt

Python是一个通用的动态类型语言。
它的特性使得它可以提供更高层次的抽象,并且可以直接解释运行,让专注点侧重到所要处理的事务上,以获得高效的开发进展。
Py设计为一种胶水语言,很便于第三方库的绑定。PyQt便是目前主要的在Python中使用Qt的方式。
在使用PyQt中依然能够使用Qt强大的功能,但是通过Python的环境这种使用变得更加方便和高效。
不过Qt本身目前也有使用JavaScript进行脚本化的趋势,这个暂时观望态度,等4.7出来再看看。
毕竟这里仍是在使用Qt,PyQt只是下文将使用的一个界面接口而已,它能够为使用提供一定的便捷便足够了。
也就是说这里所关注的仍然如何更好的使用Qt这一方面的话题。
在版本的选择上这里将以Python2.5和Qt4.5中向后兼容的部分为参照来使用PyQt。


避免

目前PyQt是一个商业的开源项目,不过也有一个LGPL的实现叫PySide也在迅速开发中,它们使用了相一致的接口。
不过因为是Qt上又包装了一层而且Qt中各种零散的对象相当多,感觉使用PyQt4现在是比较容易撞到Bug的,所以使用时至少了解一下开发版的changelog,免得不知所措。
同时一般使用Python是在一些对执行性能要求不严格的场合。同样的C++代码转为Py写有后,在某些操作上是会有可察觉的界面上的迟钝的,而结构相同的C++代码却表现良好,这表明可以一些优化的手段。
此外对于一些特定平台来说,Qt自身的C++会是一个更合适的语言。
总体来说,这些不足不影响这里在使用Python上获得的好处。而且在Python的众多Gui绑定中,目前PyQt是一个可取的选择。


开始

这里的主要参考文档是 PyQt4的参考指南 以及 Qt的参考文档。
可以通过以下在线地址查阅PyQt4/Qt
不过安装好PyQt和Qt的话就会获得一份离线的副本,查看起来会更方便一些(比如使用QtAssistant)。
还有本实体书叫《C++ GUI Programming with Qt 4, Second Edition》,虽然不是必备的文档,但是其编排的体系还是有参考价值的。
也有本专门讲PyQt的实体书来着,那个叫什么来着的,我也不清楚版本的使用情况了。
目录什么还是可以看一下吧,看看说到了那些东西,这样查文档也会方向明确一点吧。
本文的定位是对Qt的基本概念已经有所了解,使用Python也能编写简单的脚本,而只是希望有更快捷高效的尝试。
像Qt这样的东西是了解一些原理后可以拿来使用而非学习的东西,其中一些组件是用到了才会产生使用价值的,不过呢只有简单尝试对它更加熟悉之后才能更好运用出它的价值吧。
下面文本将简略一些,不过文本展开思路什么的就按照这样了。


模板

这是一个模板代码:
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = QWidget()
w.show()
sys.exit(a.exec_())
可以复制下来作为新的代码的开始。

另一种尝试的方式是在交互环境中依次执行下列语句:
>>> from PyQt4.QtGui import *
>>> a = QApplication([])
>>> w = QWidget()
>>> w.show()
>>> w.resize(200,200)
>>> w.hide()
这样每一步执行后便能立刻看到结果。

这里的代码部分使用了标签
<pre></pre>
代码的缩进会正常显示的

Qt的Python绑定

进化

下面从QWidget派生出新的窗口类型
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class mainwindow(QWidget):
def __init__(self,p=None):
QWidget.__init__(self,p)

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = mainwindow()
w.show()
sys.exit(a.exec_())
如果不实现对特定成员函数的OverRide的话,这里的派生不是必需的。
但是出于一种Qt惯例与一种可取的设计的角度,仍然建议这样做。
总之我们将用OverRide和Connect来为我们的QWidget的子类增加功能。

仅仅是添加一个新Widget的话,这样的代码就能工作了
#import missing
a = QApplication([])
w = QWidget()
QLineEdit(w)
w.show()
a.exec_()
这个w.show()包含了隐式的对匿名QLineEdit实例的show消息。交互环境下试这样的代码会方便一些。

于是再放两个文本框,就得到这样的代码
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class mainwindow(QWidget):
def __init__(self):
QWidget.__init__(self)

self.txt1=QLineEdit()
self.txt2=QLineEdit()
Layout=QHBoxLayout()
Layout.addWidget(self.txt1)
Layout.addWidget(self.txt2)
self.setLayout(Layout)

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = mainwindow()
w.show()
sys.exit(a.exec_())
这里为了让界面排列的好看一点,使用了一下Qt的布局功能。


属性

下面在代码添加一段简单的事件处理
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class mainwindow(QWidget):
def __init__(self,p=None):
QWidget.__init__(self,p)

self.txt1=QLineEdit()
self.txt2=QLineEdit()
Layout=QHBoxLayout()
Layout.addWidget(self.txt1)
Layout.addWidget(self.txt2)
self.setLayout(Layout)

self.txt1.textChanged.connect(self.on_txt1_changed)
def on_txt1_changed(self):
self.txt2.setText(self.txt1.text())

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = mainwindow()
w.show()
sys.exit(a.exec_())
这里使用了Signals and Slots机制,不过是使用了PyQt提供的额外的接口,传统的Qt方式也是可以的。
在Qt中Slot属于QObject的成员,PyQt中无此限定,所以这里的继承依然不是必须的。

这段代码如果写成属性操作的形式的话也行
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class mainwindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(200,300)
self.txt1=QLineEdit(self,textChanged=self.on_txt1_changed,text=QString("hello world") )
self.txt3=QLineEdit(self)
self.txt3.move(20,20)

#@pyqtSlot()
def on_txt1_changed(self):
self.txt3.pyqtConfigure(text=self.txt1.text())

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = mainwindow()
w.show()
sys.exit(a.exec_())
对属性的操作涉及读与写两种操作,既可以使用Qt的setter/getter接口,也可以使用PyQt提供了额外的方式。


PyQt的接口

对Qt中提供的数据类型,PyQt提供了两种转换方式,比如QString就可作为一个例子:
一种是风格是将Qt中的类型的示例保存为单独的对象,仍然按照Qt中提供的对象的方法来操作,而Python中的类型则可以不作转换按PyQt_PyObject传给Qt。
这种风格就是按照Qt原本的接口来操作的,PyQt中默认便使用这种风格的接口。
另一种新的风格则是尽量将Qt中的类型转换为Python的类型,可以在Python提供的功能中完成对数据的操作,更贴近了Python的使用。
此外一些传引用的函数(需要多个返回值的函数),在PyQt绑定中改为了返回一个tuple对象。
本文这里依然以使用Qt为主,所以选择了目前默认使用的传统风格,而pickled对若干PyQt类型是可以工作的。
在使用Signals and Slots时,PyQt也提供了两种风格。一种传统风格接近Qt本身的方式,方便移植。另一种新的风格则有点像Tkinter,更加简洁易用。
不过这两种方式并不冲突,而对于新写代码使用新的方式显然会使代码更加直观与清晰。
所以这里一般就直接使用新的风格,其区别会在下一节展示。


Signals and Slots

Signals and Slots可以算是Qt中最明显的一个特性,其原理其参看Qt文档自身的描述。

首先是一个新风格的演示
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *

class O(QObject):
sig=pyqtSignal(str)
def __init__(self):
QObject.__init__(self)
self.sig.connect(self.handle)
#@pyqtSlot(str)
def handle(self,text):
print "hello %s"%text
def trigger(self):
self.sig.emit("world")
self.sig.disconnect()
self.sig.emit("black")

if __name__ == '__main__':
import sys
a = QCoreApplication(sys.argv)
O().trigger()
sys.exit(a.exec_())

这里显示的创建pyqtSignal是为了使用它的connect和emit方法。
可以显示的创建pyqtSlot指定参数类型,以实现C++中根据不同类型的overload。

传统风格用起来有些麻烦,Qt的预处理机制在这里无效
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *

class O(QObject):
def __init__(self):
QObject.__init__(self)
self.connect(self,SIGNAL("sig"),self.handle)
def handle(self,text):
print "hello %s"%text
def trigger(self):
self.emit(SIGNAL("sig"),"world")

if __name__ == '__main__':
import sys
a = QCoreApplication(sys.argv)
O().trigger()
sys.exit(a.exec_())
这段代码使用了Unbound Signals,并直接用了callable对象作了槽,已经有点新风格的特征了。
这段代码风格上不是很纯粹了,没有加pyqtSlot修饰并指定name属性(这个属性Qt中是预处理负责的,但是PyQt中创建的QObject实例是不知道自己的QMetaObject信息的。QMetaObject.connectSlotsByName()无法直接工作也是这个原因)。
属性则通过pyqtProperty创建,这里只写一种实用的方案,不是PyQt提供的全部。


Qt Designer

Qt Designer是Qt工具中可视化的图形界面创建工具。
在Qt中它编辑一个XML格式的ui文件,在由uic编译为C++文件。

在PyQt中也可以通过类似的方式使用
pyuic4 demo.ui -o ui_demo.py
可以在makefile中写
ui_%.py: %.ui
pyuic4 $< -o $@
这样可以简化版本上的依赖的管理

接下就是引入生成的文件并使用它
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ui_demo import Ui_Form
class M(QMainWindow):
def __init__(self,parent=None):
QMainWindow.__init__(self)
self.ui = Ui_Form()
self.ui.setupUi(self)

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
这里创建的窗口上的QWidget在self.ui这个命名空间下。

不过去看pyuic4生成的代码的话,里面是一个有setupUi成员方法的对象,并添加一些代码让connectSlotsByName可以工作。
这样的就可以使用Qt的Auto-Connection机制,而免去一些繁琐手工操作。

使用名为on_<object name>_<signal name>的成员函数来添加事件相关的代码
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ui_demo import Ui_Form
class M(QMainWindow):
def __init__(self,parent=None):
QMainWindow.__init__(self)
self.ui = Ui_Form()
self.ui.setupUi(self)
#self.ui.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
def on_lineEdit_textChanged(self):
print "hello world"
if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
这里注释掉的那个语句是不可以保留的了,不然就被connect了两次。

或者直接只用uic模块也是可以的
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.uic import *
if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
ui=loadUi("demo.ui")
ui.show()
sys.exit(a.exec_())
uic.loadUi返回的是一个QWidget实例,没有setupUi方法了。
不过这不影响connect的使用,跨对象或跨线程都是可以的。

其他工具

pyrcc4用于处理rcc文件生成类似于资源文件qrc的py文件,使用时import便可。
pylupdate4是用于i18n的工具,从源代码中生成.ts文件。

Qt的组件示例

QMainWindow
The Application Main Window是QtGui程序的主框架
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class M(QMainWindow):
list=[]
def __init__(self):
QMainWindow.__init__(self)
self.textEdit=QTextEdit()
self.setCentralWidget(self.textEdit)
self.createActions()
self.createMenus()
self.createContextMenu()
self.createToolBars()
self.createStatusBar()
self.createDockWidget()

self.readSettings()
def closeEvent(self,e):
r = QMessageBox.question(self,self.tr("Title"), self.tr("Close?"),
QMessageBox.Yes | QMessageBox.No)
if r == QMessageBox.Yes:
self.writeSettings()
return e.accept()
elif r == QMessageBox.No:
return e.ignore()

def contextMenuEvent(self,e):
m=QMenu(self)
m.addAction(self.closeAct)
m.exec_(e.globalPos())

def createActions(self):
self.closeAct = QAction("Cl&ose", self, shortcut="Ctrl+F4",
statusTip="Close the active window",
triggered=self.close)
def createMenus(self):
self.fileMenu = self.menuBar().addMenu("&File")
self.fileMenu.addAction(self.closeAct)
self.fileMenu.addSeparator()
action = self.fileMenu.addAction("&New")
action.triggered.connect(self.newWindow)
def createToolBars(self):
self.fileToolBar = self.addToolBar("File")
self.fileToolBar.addAction(self.closeAct)
def createStatusBar(self):
self.statusBar().showMessage("Ready")
l = QLabel("hello world")
self.statusBar().addPermanentWidget(l)
def createContextMenu(self):
self.textEdit.addAction(self.closeAct);
self.textEdit.setContextMenuPolicy(Qt.ActionsContextMenu);
def createDockWidget(self):
dockWidget = QDockWidget(self.tr("Dock Widget"),self)
dockWidget.setAllowedAreas(Qt.LeftDockWidgetArea |
Qt.RightDockWidgetArea);
#dockWidget->setWidget(dockWidgetContents);
self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget);

def newWindow(self):
w=M()
M.list.append(w)
w.show()
def readSettings(self):
settings = QSettings('ee.zsy', 'HelloWorld')
pos = settings.value('pos', QPoint(200, 200)).toPoint()
size = settings.value('size', QSize(400, 400)).toSize()
self.move(pos)
self.resize(size)

def writeSettings(self):
settings = QSettings('ee.zsy', 'HelloWorld')
settings.setValue('pos', self.pos())
settings.setValue('size', self.size())

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
涉及到的Qt类有:QAction,QActionGroup,QDockWidget,QMainWindow,QMdiArea,QMdiSubWindow,QMenu,QMenuBar,SizeGrip,QStatusBar,QToolBar,QWidgetAction。
用于构建窗口自身,以及菜单工具栏状态栏等等。


Dialog Windows

对话框是QWidget的子类,可以是有模式的,也可以是无模式的。
通常用于和用户简短的交互,以达成特定任务或获得返回值。
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class D(QDialog):
def __init__(self,parent=None):
QDialog.__init__(self,parent)
self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
Qt.Horizontal,self,
accepted=self.accept,rejected=self.reject)
self.bb.show()
QPushButton("return",self,
clicked=self.on_btn_clicked,pos=QPoint(200,200)
).setDefault(False)
def on_btn_clicked(self):
self.done(99)

class M(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setWindowTitle("Hello World")
self.btn1=QPushButton("modal dialog",clicked=self.on_btn1_clicked)
self.btn2=QPushButton("modelless dialog",clicked=self.on_btn2_clicked)
self.btn3=QPushButton("messageBox",clicked=self.on_btn3_clicked)
Layout=QVBoxLayout()
Layout.addWidget(self.btn1)
Layout.addWidget(self.btn2)
Layout.addWidget(self.btn3)
self.setLayout(Layout)

def on_btn1_clicked(self):
d = D(self)
r = d.exec_()
if r == QDialog.Accepted:
print "A"
elif r == QDialog.Rejected:
print "B"
def on_btn2_clicked(self):
d = QDialog(self)
d.show()
d.raise_()
d.activateWindow()
def on_btn3_clicked(self):
msgBox=QMessageBox()
msgBox.setText("The document has been modified.")
ret = msgBox.exec_()
ret = QMessageBox.warning(self, "My Application",
"The document has been modified.\n"
"Do you want to save your changes?",
QMessageBox.Save|QMessageBox.Discard
|QMessageBox.Cancel,QMessageBox.Save)
fileName = QFileDialog.getOpenFileName(self,
"Open Image", "/", "Image Files (*.png *.jpg *.bmp)")
if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
目前QDialog的派生类有QAbstractPrintDialog,QColorDialog, QErrorMessage,QFileDialog,QFontDialog,QInputDialog, QMessageBox,QPageSetupDialog,QPrintPreviewDialog,QProgressDialog以及QWizard。
注意connect是可以跨QObject进行的,对这里的QDialog也同样可以。


Layout Management

Layout和Widget这两样东西可以用来控制子Widget的摆放
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *


class P(QWidget):
def __init__(self):
QWidget.__init__(self)
self.sa = QScrollArea()
self.sp = QSplitter(Qt.Horizontal);
self.sp.addWidget(QTextEdit())
self.sp.addWidget(QTextEdit())
self.sp.setStretchFactor(1,7)
self.l = QListWidget()
self.l.addItem("A")
self.l.addItem("B")
self.l.addItem("C")
self.l.addItem("D")
self.l.setMaximumHeight(50)
self.l.setSizePolicy(QSizePolicy.Maximum,QSizePolicy.Maximum)
self.s = QStackedLayout()
self.s.addWidget(QLabel("A"))
self.s.addWidget(QLabel("B"))
self.s.addWidget(QLabel("C"))
self.s.addWidget(QLabel("D"))
self.l.currentRowChanged.connect(self.s.setCurrentIndex)
self.l.setCurrentRow(0)
self.f = QFormLayout()
self.f.addRow("&Index",self.l)
self.f.addRow("Page", self.s)
self.sa.setWidget(self.sp)
self.sa.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn);
self.sa.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn);
self.f.addRow("SP",self.sa)
self.setLayout(self.f)
if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = P()
w.show()
sys.exit(a.exec_())
这里两种方式混合着用了,有点乱乱的呵。
另外呢,QAbstractScrollArea有个子类叫QMdiArea,用于MDI布局。
这里QFormLayout和QGridLayout有点类似之处,都是网格状的布局。


Drag and Drop

拖放操作涉及程序本身的鼠标事件处理,以及用于程序间的信息传递
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class M(QMainWindow):
def __init__(self):
QWidget.__init__(self)
self.clipboard = QApplication.clipboard()
self.textedit = QTextEdit(self)
self.label = QLabel("Hello World",self)
self.label.move(0,50)
self.textedit.setAcceptDrops(False)
self.setAcceptDrops(True)
def dragEnterEvent(self,event):
if event.mimeData().hasFormat("text/plain"):
event.acceptProposedAction()
def dropEvent(self,event):
text=event.mimeData().text()
self.setWindowTitle(text)
self.clipboard.setText(text)

def mousePressEvent(self,event):
if (event.button() == Qt.LeftButton and
self.label.geometry().contains(event.pos())):

drag = QDrag(self)
mimeData = QMimeData()
mimeData.setText("hello world")
drag.setMimeData(mimeData)
dropAction = drag.exec_(Qt.CopyAction | Qt.MoveAction)

if dropAction == Qt.MoveAction :
self.label.hide()
def dragEnterEvent(self,event):
if event.mimeData().hasFormat("text/plain"):
event.accept()
else:
event.ignore()
def mouseMoveEvent(self,event):
pass

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
有QDropEvent::pos可获得方位。
拖放也可以结合Item Views使用。


Graphics.

这一段是说2D图像的绘制
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class M(QWidget):
def paintEvent(self,event):
img=QPixmap(self.size())
p=QPainter(img)
p.initFrom(self)
p.drawRect(10,10,50,50)
p=QPainter(self)
p.setRenderHint(QPainter.Antialiasing, True)
p.setWindow(-200, -10, 800, 600);
transform=QTransform()
transform.rotate(+45.0)
p.setWorldTransform(transform)
p.setPen(QPen(Qt.black, 5, Qt.DashDotLine, Qt.RoundCap))
p.setBrush(QBrush(Qt.green, Qt.Dense2Pattern))
p.drawEllipse(80, 80, 400, 240)
gradient=QLinearGradient(50, 100, 300, 350)
gradient.setColorAt(0.0, Qt.white)
gradient.setColorAt(0.2, Qt.green)
gradient.setColorAt(1.0, Qt.black)
p.save()
p.setBrush(gradient)
p.drawPie(80, 80, 400, 240, 60 * 16, 270 * 16)
p.restore()
p.drawPixmap(100,200,img)
p.setCompositionMode(QPainter.CompositionMode_Xor)
path=QPainterPath()
path.moveTo(8, 32)
path.cubicTo(200, 80, 320, 80, 480, 320)
p.drawPath(path)
p.scale(3,3)
p.translate(-50.0, -50.0)
p.drawText(100,100,900,500,Qt.AlignLeft,"Hello world")

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
屏幕,图像,打印都可以用来绘制。


Binary Data

这里来说Qt中IO相关的
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *

class O(QObject):
def __init__(self):
QObject.__init__(self)
def run(self):
print "hello world"
l=QStringList()
l.append("A")
l.append("B")
file=QFile("output.dat")
if not file.open(QIODevice.WriteOnly):
print "Error: %s"%file.errorString()
return

i=QDataStream(file);
i.setVersion(QDataStream.Qt_4_5);
i << l

if __name__ == '__main__':
import sys
a = QCoreApplication(sys.argv)
O().run()
sys.exit(a.exec_())
此外QDir用于操作目录,QProcess用于操作进程。
不过这个例子有点怪怪的,应为在PyQt中以Python的方式在读存文件会更直接一些。
对Qt中要保存的数据去使用Python的pickle模块。


事件

要处理事件的话Reimplementing或者Installing Filters都可以
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class M(QWidget):
def __init__(self):
QWidget.__init__(self)
self.btn = QPushButton("start",self,clicked=self.run)
self.btn.installEventFilter(self)
def run(self):
QApplication.setOverrideCursor(Qt.WaitCursor)
for i in xrange(20000):
self.setWindowTitle(str(i))
qApp.processEvents(QEventLoop.ExcludeUserInputEvents)
QApplication.restoreOverrideCursor()

def keyPressEvent(self,event):
if event.key() == Qt.Key_Up:
self.setWindowTitle("Hello world")
else:
QWidget.keyPressEvent(self,event)

def eventFilter(self,target,event):
if target == self.btn:
if event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Down:
self.setWindowTitle("Hello world")
return True
return QWidget.eventFilter(self,target, event)

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
直接操纵QApplication也是可以的。
这里的事件包括鼠标键盘事件窗口时间等。


Qt Designer Plugins

见/examples/designer/plugins
    zoom = QtCore.pyqtProperty(int, getZoom, setZoom, resetZoom)
会看到这样的东西,
并由QtDesigner.QPyDesignerCustomWidgetPlugin派生,实现特定的成员函数。


Container

Qt中STL的容器和算法的实现,可用Python中类似特性混合使用。


框架与特性

Graphics View

这是基于项目的2D Graphics的管理与显示
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class N(QGraphicsRectItem):
def __init__(self):
QGraphicsRectItem.__init__(self)
self.setRect(0,0,100,100)
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable|
QGraphicsItem.ItemSendsGeometryChanges)
self.setPen(QPen(Qt.darkRed, 1.0))
def itemChange(self,change,value):
if change == self.ItemPositionChange:
print value.toPointF()
return QGraphicsItem.itemChange(self,change, value)
def mouseDoubleClickEvent(event):
pass

class M(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.scene = QGraphicsScene(0, 0, 600, 500)
view = QGraphicsView()
view.setScene(self.scene)
view.setDragMode(QGraphicsView.RubberBandDrag)
view.setRenderHints(QPainter.Antialiasing
| QPainter.TextAntialiasing)
view.setContextMenuPolicy(Qt.ActionsContextMenu)
self.setCentralWidget(view)
self.createNode()
def createNode(self):
n=N()
self.scene.addItem(n)
self.scene.clearSelection()
n.setSelected(True)


if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
这里示例没能展现出Graphics View的整体。
Graphics View在结构上有Scene,View,Item,并有坐标转换机制。
还涉及到其包含的变换,拖拽,光标,动画,Groups等特性上的要点。


Item View

这部分是用来展示数据的
#!/usr/bin/env python
#coding:utf-8
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class M(QWidget):
def __init__(self):
QWidget.__init__(self)
self.m=QDirModel()#QStandardItemModel
self.v=QTreeView(self,pos=QPoint(250,0))
self.v.setModel(self.m)
self.v.setSortingEnabled(True)
self.m.setNameFilters(QStringList()<<"*.*")

self.listWidget = QListWidget(self)
self.listWidget.setIconSize(QSize(60, 60))

self.listWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
for i in range(1,10):
item=QListWidgetItem(str(i),self.listWidget)
item.setToolTip("this is "+str(i))
self.listWidget.item(0).setText("hello")

btn1 = QPushButton(self,pos=QPoint(0,200),clicked=self.lwm)

def lwm(self):
item=self.listWidget.currentItem()
if item:
QMessageBox.information(self,"",item.text())
self.v.header().swapSections(0,3)

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
MVC模式是模型视图控制器的组合,M将改变通知给V,而V上的操作可以使M得到改变。
以上是
不涉及代理类型,自定义模型/视图 以及委托。


Databases

数据库
#!/usr/bin/env python
#coding:utf-8

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtSql import *

class O(QObject):

def __init__(self):
QObject.__init__(self)
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("db.sqlite")
if not db.open():
QMessageBox.critical(0,"Database Error",db.lastError().text())
def run(self):
print "hello world"
QSqlDatabase.database().transaction()

query=QSqlQuery()
query.exec_("SELECT name, salary FROM employee WHERE salary > 50000")
while query.next() :
r = query.value(0).toString()
QSqlDatabase.database().commit()

model=QSqlTableModel()
model.setTable("cd")
model.setFilter("year >= 1998")
model.select()
#model.setQuery("SELECT * FROM cd WHERE year >= 1998");
for i in range(model.rowCount()):
record = model.record(i)
title = record.value("title").toString()

if __name__ == '__main__':
import sys
a = QCoreApplication(sys.argv)
O().run()
sys.exit(a.exec_())
这段代码非常不完整。
涉及到了使用SQL语句和SQL模型。
对应模型有一种关联模型,View用于显示和编辑,QDataWidgetMapper用于生成窗口。

Multithreading

多线程
#!/usr/bin/env python
#coding:utf-8

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtSql import *

class T(QThread):
def __init__(self):
QThread.__init__(self)
self.mutex=QMutex()

def run(self):
self.mutex.lock()
while 1:
print "t"
self.mutex.unlock()
#QMetaObject.invokeMethod

class M(QWidget):
def __init__(self):
QWidget.__init__(self)
self.t=T()
QPushButton("start",self,clicked=self.start)
QPushButton("stop",self,clicked=self.stop,pos=QPoint(0,50))
def start(self):
self.t.start()
def stop(self):
self.t.terminate()

if __name__ == '__main__':
import sys
a = QApplication(sys.argv)
w = M()
w.show()
sys.exit(a.exec_())
涉及同步问题和通讯问题。
可以混合signal–slot使用,涉及到Qt提供的组件是否是可重入的。


Networking

网络通讯
#!/usr/bin/env python
#coding:utf-8

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *

class O(QObject):
def __init__(self):
QObject.__init__(self)
self.h=QHttp(done=self.httpDone)
def run(self):
self.h.get("http://google.com")
def httpDone(self,error):
print "hello world"

if __name__ == '__main__':
import sys
a = QCoreApplication(sys.argv)
O().run()
sys.exit(a.exec_())
这是QHttp,还有链接的各种相关属性与状态。
另一种用法是QTcpSocket和QTcpServer的组合,是有固定用法的,QtDemo里的很完整了。
这个模块与Python的功能重叠。


XML

读取的话,可以三种方式
QXmlStreamReader,QDomDocument,QXmlContentHandler。
写XML的话用QXmlStreamWriter,保存DOM,或字符串

其他

其他
QDesktopServices::openUrl(url);
i18n
窗口样式

实用类
正则
验证
进程
测试

...

----
以上就是烂尾,其实也较待续。。。

这篇看样子是随手笔记式的类型了,
关于Qt的主要参考是其文档以及官方的那本教程。
在以上的文字里,仅仅是大致的去涉及了一些Qt中常见的模块。
而Qt本身也是在不断扩充的,其中一些功能重写的代价也不高,不过Qt中包含的话就可以重用。
所以这里就先大略说这些了。

没有评论: