配色: 字号:
PyQt4编程简介
2012-12-27 | 阅:  转:  |  分享 
  
1

PyQt4编程简介

作者:

柴树杉[翻译](chaishushan@gmail.com)

日期:

2007-12-22于武汉

注解:

该文档根据"IntroductiontoPyQt4"翻译,依照创作公用约定发布。

该文档的doxygen源文件可以从pyqt-doc-cn下载。

开始

创建一个PyQt4一般可以通过很少的步骤完成。通常的方法是用Qt提供的QtDesigner工具创建

界面。使用QtDesigner,可以方便地创建复杂的GUI界面。然后,可以在窗口上创建部件,添加

名字等。创建一个PyQt4一般需要:

1.使用QtDesigner创建GUI界面

2.在属性编辑器中修改部件的名字

3.使用pyuic4工具生成一个python类

4.通过GUI对应类来运行程序

5.通过设置自己的slots来扩展功能

6.当使用窗口部件的时候,可以从"PyQt''sClasses"查询。Qt采用易于理解的方式来命名

函数,例如:"setText"。

教程列表

1.简易的文本编辑器-PyQt4第一个程序

2.增加文本编辑器的功能-增加更多的功能

3.QYolkI-PyQt4中的列表部件-怎么使用PyQt4中的列表部件

4.QYolkII-容器部件-怎么使用TabWidget

5.PyQt4文本编辑器-最终版-PyQt4的一些高级特性

6.QYolkIII-升级包列表-新的特性

?下载教程代码(缺少的部分请从原网站下载)

简介

打开QtDesigner,会出现"Hello...CloseButton"对话框,让我们选择类型类型:



我们选择widget类型,然后在窗口中添加一个PushButton按纽:



通过鼠标右键来修改pushButton显示的内容:

2



当窗口建好之后,我们可以QtDesigner来编辑一些Qt预定义的信号/槽。这里我们使用的是

"close()"槽函数来关闭程序。首先切换到信号/槽边界模式:



用鼠标移到pushButton区域,然后拖动:



3

弹出一个信号/槽选择框:



信号选择clicked(),槽选择close()。将窗口保存为test.ui文件。切换到test.ui所在的目录,然后

输入以下命令:

pyuic4test.ui>test_ui.py

下一步是创建一个test.py文件:

importsys

fromPyQt4importQtCore,QtGui



fromtest_uiimportUi_Form



classMyForm(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_Form()

self.ui.setupUi(self)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=MyForm()

myapp.show()

sys.exit(app.exec_())

4

运行test.py:

pythontest.py

现在应该出现响应的窗口,当你点击按钮的时候退出程序。



提示

Ui_Form是用pyuic4工具从"Form"窗口生成的对应python类的名字。你可以在QtDesigner自己

喜欢的名字一个类的名字(下一节我们会讲到)。

5



简易的文本编辑器

我们将要实现一个简单的文本编辑器,如图。用QtDesigner创建一个"Widget"类型的窗口。我们

使用两个PushButton和一个TextEdit:



"关闭"按钮被连接到窗口的"close()"槽函数,可以被用来关闭窗口。修改"打开"按钮的对象名字为

"button_open";修改TextEdit部件的对象名字为"editor_window";修改窗口的名字为"notepad"

(开始为"MainWindow")。选择要该名字的对象,然后出现的属性编辑器中可以修改名字。



保存窗口,并生成对应的类:

pyuic4edytor.ui>edytor.py

得到一个"Ui_notepad"类。我们还需要自己添加一些代码,创建start.py:

importsys

6

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notepad



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

运行start.py启动程序,点击“关闭”关闭程序。



下面我们编辑自己的slot函数:

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notepad



classStartQt4(QtGui.QMainWindow):

7

8

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)

#hereweconnectsignalswithourslots



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)

deffile_dialog(self):

self.ui.editor_window.setText(''aaaaaaaaaa'')



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

当你点击“打开”的时候,在编辑框中将出现"aaaaaaaaaa"内容。那是因为我们把“打开”信号连接

到了我们自己实现的slot函数:

QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)

self.ui对应窗口,通过它我们可以访问窗口中的部件。因此,self.ui.button_open对应“打开”按钮。

self.file_dialog是信号对应的函数,它是比较重要的部分,例如:

deffile_dialog(self):

self.ui.editor_window.setText(''aaaaaaaaaa'')

self.ui.editor_window对应TextEdit,setText方法用来设置文本的内容。下面我们用QFileDialog来

选择文件,代码如下:

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notepad



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)

deffile_dialog(self):

fd=QtGui.QFileDialog(self)

plik=open(fd.getOpenFileName()).read()

self.ui.editor_window.setText(plik)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

fd.getOpenFileName()弹出一个文件选择框。fd.getOpenFileName()用于返回我们选择文件的名

字。但是,如果我们没有选择文件的话,将得到一个空的文件名,程序出现以下错误:

IOError:[Errno2]Niematakiegoplikuanikatalogu:
at0x2b6465569738>



继续完善代码:

Sowehavetoimproveourcode:

importsys

fromPyQt4importQtCore,QtGui

9

10

fromedytorimportUi_notepad



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)

#tutajdajemywlasnepolaczeniaslotow



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)

deffile_dialog(self):

fd=QtGui.QFileDialog(self)

self.filename=fd.getOpenFileName()

fromos.pathimportisfile

ifisfile(self.filename):

text=open(self.filename).read()

self.ui.editor_window.setText(text)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

目前我们可以浏览文件了,但是并不能保存文件的任何修改。还需要增加一个“保存”按钮用来保存

文件。在QtDesigner中添加一个pushButton,名字改为"button_save",保存.ui文件。然后重

新生成对应的类:

pyuic4edytor.ui>edytor.py

现在程序外观如图:



我们给"Save"按钮的click信号连接对应的槽函数:

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notepad



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)



QtCore.QObject.connect(self.ui.button_save,QtCore.SIGNAL("clicked()"),

self.file_save)

deffile_dialog(self):

fd=QtGui.QFileDialog(self)

self.filename=fd.getOpenFileName()

fromos.pathimportisfile

ifisfile(self.filename):

text=open(self.filename).read()

self.ui.editor_window.setText(text)

11

deffile_save(self):

12

fromos.pathimportisfile

ifisfile(self.filename):

file=open(self.filename,''w'')

file.write(self.ui.editor_window.toPlainText())

file.close()



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

现在程序虽然可以基本工作,但是并不完善!它只能处理ASCII格式的文件。因此,我们最好给

它增加UTF-8格式的支持:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notepad



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)



QtCore.QObject.connect(self.ui.button_save,QtCore.SIGNAL("clicked()"),

self.file_save)

deffile_dialog(self):

fd=QtGui.QFileDialog(self)

self.filename=fd.getOpenFileName()

fromos.pathimportisfile

ifisfile(self.filename):

importcodecs

s=codecs.open(self.filename,''r'',''utf-8'').read()

self.ui.editor_window.setPlainText(s)

deffile_save(self):

13

fromos.pathimportisfile

ifisfile(self.filename):

importcodecs

s=codecs.open(self.filename,''w'',''utf-8'')

s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

现在可以完美地处理UTF-8格式的文件了。下一节我们将演示更多的特性。

增加文本编辑器的功能

现在我们给编辑器增加两个新的功能。同时也可以练习我们查阅文档的技巧。

禁用"Save"按钮

当没有打开任何文件,或者是文件没有改动的时候,禁用"Save"按钮。在QtDesigner工具的属性

编辑器中,我们将"Save"按钮设置"enabled"属性为"False"。



textEdit部件含有"textChanged()"信号,因此检测文本是否改动比较简单。但是,pushButton并没

有类似的"enabled"操作。查阅文档可以发现:pushButton从QAbstractButton继承,

QAbstractButton从QWidget继承,而QWidget刚好有setEnabled()函数。因此可以修改代码:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notatnik



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)

self.filename=None



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)



QtCore.QObject.connect(self.ui.button_save,QtCore.SIGNAL("clicked()"),

self.file_save)



QtCore.QObject.connect(self.ui.editor_window,QtCore.SIGNAL("textChanged()"),

self.enable_save)

14

15

deffile_dialog(self):

fd=QtGui.QFileDialog(self)

self.filename=fd.getOpenFileName()

fromos.pathimportisfile

ifisfile(self.filename):

importcodecs

s=codecs.open(self.filename,''r'',''utf-8'').read()

self.ui.editor_window.setPlainText(s)

#insertingtextemitstextChanged()sowedisablethe

button:)

self.ui.button_save.setEnabled(False)

defenable_save(self):

self.ui.button_save.setEnabled(True)

deffile_save(self):

fromos.pathimportisfile

ifisfile(self.filename):

importcodecs

s=codecs.open(self.filename,''w'',''utf-8'')

s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()

self.ui.button_save.setEnabled(False)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

增加以下连接:

QtCore.QObject.connect(self.ui.editor_window,QtCore.SIGNAL("textChanged()"),

self.enable_save)

defenable_save(self):

self.ui.button_save.setEnabled(True)

当我们修改文本的时候,file_dialog函数将由"textChanged()"触发,因此我们在函数末尾应该禁止

"Save"按钮:

self.ui.editor_window.setPlainText(s)

#insertingtextemitstextChanged()sowedisablethebutton:)

self.ui.button_save.setEnabled(False)

保存修改

如果修改了文件没有保存的时候,又尝试打开新的文件,我们应该给出相关的提示信息。可以使

用QMessageBox提供的功能:

message=QtGui.QMessageBox(self)

message.exec_()



提示窗口还需要进一步改进,增加一些按钮和信息。首先修改file_dialog函数,如果文本没有被保

存的时候显示提示消息。但是怎么才能知道文本没有被保存呢?答案是"Save"没有被禁用的时候

(self.ui.button_save.isEnabled())。修改start.py:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notatnik



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)

self.filename=None



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)



QtCore.QObject.connect(self.ui.button_save,QtCore.SIGNAL("clicked()"),

self.file_save)



QtCore.QObject.connect(self.ui.editor_window,QtCore.SIGNAL("textChanged()"),

self.enable_save)

deffile_dialog(self):

response=False

16

17

#buttonstexts

SAVE=''Save''

DISCARD=''Discard''

CANCEL=''Cancel''

#ifwehavechangesthenaskaboutthem

ifself.ui.button_save.isEnabled()andself.filename:

message=QtGui.QMessageBox(self)

message.setText(''Whattodoaboutunsavedchanges?'')

message.setWindowTitle(''Notepad'')

message.setIcon(QtGui.QMessageBox.Question)

message.addButton(SAVE,QtGui.QMessageBox.AcceptRole)

message.addButton(DISCARD,

QtGui.QMessageBox.DestructiveRole)

message.addButton(CANCEL,QtGui.QMessageBox.RejectRole)

message.setDetailedText(''Unsavedchangesinfile:''+

str(self.filename))

message.exec_()

response=message.clickedButton().text()

#savefile

ifresponse==SAVE:

self.file_save()

self.ui.button_save.setEnabled(False)

#discardchanges

elifresponse==DISCARD:

self.ui.button_save.setEnabled(False)

#ifwedidn''tcancelledshowthefiledialogue

ifresponse!=CANCEL:

fd=QtGui.QFileDialog(self)

self.filename=fd.getOpenFileName()

fromos.pathimportisfile

ifisfile(self.filename):

importcodecs

s=codecs.open(self.filename,''r'',''utf-8'').read()

self.ui.editor_window.setPlainText(s)

self.ui.button_save.setEnabled(False)

defenable_save(self):

self.ui.button_save.setEnabled(True)

deffile_save(self):

fromos.pathimportisfile

18

ifisfile(self.filename):

importcodecs

s=codecs.open(self.filename,''w'',''utf-8'')

s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()

self.ui.button_save.setEnabled(False)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_()))

新增加的代码如下:

response=False

#buttonstexts

SAVE=''Save''

DISCARD=''Discard''

CANCEL=''Cancel''

#ifwehavechangesthenaskaboutthem

ifself.ui.button_save.isEnabled()andself.filename:

message=QtGui.QMessageBox(self)

message.setText(''Whattodoaboutunsavedchanges?'')

message.setWindowTitle(''Notepad'')

message.setIcon(QtGui.QMessageBox.Question)

message.addButton(SAVE,QtGui.QMessageBox.AcceptRole)

message.addButton(DISCARD,QtGui.QMessageBox.DestructiveRole)

message.addButton(CANCEL,QtGui.QMessageBox.RejectRole)

message.setDetailedText(''Unsavedchangesinfile:''+str(self.filename))

message.exec_()

response=message.clickedButton().text()

#savefile

ifresponse==SAVE:

self.file_save()

self.ui.button_save.setEnabled(False)

#discardchanges

elifresponse==DISCARD:

self.ui.button_save.setEnabled(False)

#ifwedidn''tcancelledshowthefiledialogue

ifresponse!=CANCEL:

我们使用QtGui.QMessageBox生成信息提示框,然后设置文本信息、标题、图标、并且增件了三

个按钮。第二个参数设置每个按纽对应的ButtonRole(具体细节可以参考文档)。setDetailedText

设置详细的提示信息。然后通过exec_()来运行消息提示框。消息框返回被按下的按纽值,因此

我们可以根据返回值来选择下一步要进行的操作。消息框外观如下:



19

QYolkI-PyQt4中的列表部件

注解:

yolk的安装步骤请参考安装yolk

在PyQt4中我们有三种列表部件可以使用:ListView、TreeView和TableView。这些部件都可以

在列表框架中使用(ModelandItemBased)。这里我们使用的是ItemBased:



在这个例子中,我们使用的是TreeView。当你在TreeView部件上点击鼠标右键时,会出现“编

辑项目”选项,通过它可以给部件增加列。需要注意的是列从0开始编号:



20



TreeView给我们提供了丰富的功能,具体细节请参考文档。

QYolk

Yolk可以通过easy_install命令安装:

easy_installyolk

运行命令:

yolk-l

可以看到已经安装的包。我们将要把这些包列表通过QtreeWidget显示出来。数据通过以下方式

获取:

fromyolkimportyolklib

packages=yolklib.Distributions()

forpkginpackages.get_distributions(''all''):

printpkg[0]

printpkg[1]

print''#####''

函数get_distributions返回包的信息:name+version,以及包的状态(Active/not-active)。我们

把列表部件命名为treeList,把窗口命名为QYolk,然后保存到qyolk.ui。生成qyolk.py文件:

21



创建start.py文件:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)

#setthewidthsofthecolumns

self.ui.treeList.setColumnWidth(0,200)

self.ui.treeList.setColumnWidth(1,100)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

setColumnWidth用于设置QtreeView中每列的宽度。0对应第一列,1对应第二列。下面我们利

用QtreeWidgetItem向列表中添加数据:

22

23

a=QtGui.QtreeWidgetItem(self.ui.treeList)

a.setText(0,''a'')

a.setText(1,''b'')

a.setText(2,''c'')

QtreeWidgetItem需要指定一个QtreeWidget对象,表示要添加数据的列表。方法setText(Column

ID,Text)用来设置对应列的数据。我们通过一个循环来完成操作:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)

#setthewidthsofthecolumns

self.ui.treeList.setColumnWidth(0,200)

self.ui.treeList.setColumnWidth(1,100)

#generatorwhichretunslistofinstalledpackages

packages=yolklib.Distributions()

forpkginpackages.get_distributions(''all''):

a=QtGui.QtreeWidgetItem(self.ui.treeList)

pk=str(pkg[0]).split('''')

ifpkg[1]:

status=''Active''

else:

status=''NotActive''

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,status)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

24

sys.exit(app.exec_())

列表数据已经读出来了。我们还可以根据包的活动状态设置不同的颜色:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)

#setthewidthsofthecolumns

self.ui.treeList.setColumnWidth(0,200)

self.ui.treeList.setColumnWidth(1,100)

#generatorwhichretunslistofinstalledpackages

packages=yolklib.Distributions()

forpkginpackages.get_distributions(''all''):

a=QtGui.QtreeWidgetItem(self.ui.treeList)

pk=str(pkg[0]).split('''')

ifpkg[1]:

status=''Active''

a.setTextColor(0,QtGui.QColor(0,0,255))

a.setTextColor(1,QtGui.QColor(0,0,255))

a.setTextColor(2,QtGui.QColor(0,0,255))

else:

status=''NotActive''

a.setTextColor(0,QtGui.QColor(128,128,128))

a.setTextColor(1,QtGui.QColor(128,128,128))

a.setTextColor(2,QtGui.QColor(128,128,128))

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,status)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

setTextColor(ColumnID,QtGui.QColor(R,G,B))根据RGB来设置颜色。下面是运行效果:



安装yolk

注解:

该节为译者补充

由于我在自己的电脑上一直没有成功安装easy_install,因此只好手工安装yolk了。这里是在

Win-XP下安装yolk步骤。

?下载setuptools-0.6c7.win32-py2.5.exe

?下载yolk-0.0.7.tar.gz

从本地下载:

?下载setuptools-0.6c7.win32-py2.5.exe

?下载yolk-0.0.7.tar.gz

首先安装setuptools,然后将yolk-0.0.7压缩包中的yolk子目录复制到Python25-packages中。现

在应该就可以使用了:)

25

QYolkII-容器部件

在这节教程中,我们将使用容器部件——TabWidget。下图是上次教程实现的程序:



现在我们将使用TabWidget在三个tab中分别显示allpackages/active/notactive包列表。

在QtDesigner中创建一个TabWidget,然后在第一个tab中创建一个treewidget。当你把一个部

件放置到其他的容器部件中的时候,容器部件会高亮显示。如果你要编辑主窗口中的部件,主窗

口也会高亮显示。



通过右键菜单给TabWidget添加tab页。然后在属性编辑器中修改每个标签页显示的文本。并且

从第一个tab中复制treewidget到后两个tab页中。

26



QtabWidget重新命名为"pkgTabs"。三个treewidget分别命名为

"allList"/"activeList"/"notActiveList"。QLabel部件重新命名为"infoLabel"(目前还没有用到)。然

后修改start.py:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)

#setthewidthsofthecolumns

################

#Allpackages

################

self.ui.allList.setColumnWidth(0,200)

self.ui.allList.setColumnWidth(1,100)

#generatorwhichretunslistofinstalledpackages

packages=yolklib.Distributions()

27

forpkginpackages.get_distributions(''all''):

28

a=QtGui.QtreeWidgetItem(self.ui.allList)

pk=str(pkg[0]).split('''')

ifpkg[1]:

status=''Active''

else:

status=''NotActive''

a.setTextColor(0,QtGui.QColor(128,128,128))

a.setTextColor(1,QtGui.QColor(128,128,128))

a.setTextColor(2,QtGui.QColor(128,128,128))

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,status)

################

#ActivePackages

################

#setthewidthsofthecolumns

self.ui.activeList.setColumnWidth(0,200)

self.ui.activeList.setColumnWidth(1,100)

#generatorwhichretunslistofactivepackages

forpkginpackages.get_distributions(''active''):

a=QtGui.QtreeWidgetItem(self.ui.activeList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''Active'')

################

#NotActivePackages

################

#setthewidthsofthecolumns

self.ui.notActiveList.setColumnWidth(0,200)

self.ui.notActiveList.setColumnWidth(1,100)

#generatorwhichretunslistofnotactivepackages

forpkginpackages.get_distributions(''nonactive''):

a=QtGui.QtreeWidgetItem(self.ui.notActiveList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''NotActive'')



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

运行结果:



有参数的Singals/Slots

在切换QtabWidget的tab页的时候,会发射currentChanged信号:

voidcurrentChanged(int)

该信号有一个int类型的参数。之前我们使用的信号都是没有参数的。在这里,int参数对应tab页

的编号,并且每个tab页从0开始顺序编号。我们在连接Singals/Slots的时候,给Slots函数增

加一个参数:

QtCore.QObject.connect(self.ui.pkgTabs,QtCore.SIGNAL("currentChanged(int)"),

self.tab_change)

deftab_change(self,tab_id):

printtab_id

tab_id是我们增加的一个参数,表示切换tab页的编号。signal的参数和slot的参数依次对应。这

样我们就可以在切换tab页的时候,同步更新提示信息:

29

30

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)

#setthewidthsofthecolumns

################

#Allpackages

################

self.ui.allList.setColumnWidth(0,200)

self.ui.allList.setColumnWidth(1,100)

#generatorwhichretunslistofinstalledpackages

packages=yolklib.Distributions()

forpkginpackages.get_distributions(''all''):

a=QtGui.QtreeWidgetItem(self.ui.allList)

pk=str(pkg[0]).split('''')

ifpkg[1]:

status=''Active''

else:

status=''NotActive''

a.setTextColor(0,QtGui.QColor(128,128,128))

a.setTextColor(1,QtGui.QColor(128,128,128))

a.setTextColor(2,QtGui.QColor(128,128,128))

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,status)

################

#ActivePackages

################

#setthewidthsofthecolumns

self.ui.activeList.setColumnWidth(0,200)

self.ui.activeList.setColumnWidth(1,100)

#generatorwhichretunslistofactivepackages

31

forpkginpackages.get_distributions(''active''):

a=QtGui.QtreeWidgetItem(self.ui.activeList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''Active'')

################

#NotActivePackages

################

#setthewidthsofthecolumns

self.ui.notActiveList.setColumnWidth(0,200)

self.ui.notActiveList.setColumnWidth(1,100)

#generatorwhichretunslistofnot-activepackages

forpkginpackages.get_distributions(''nonactive''):

a=QtGui.QtreeWidgetItem(self.ui.notActiveList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''NotActive'')



#Signals



QtCore.QObject.connect(self.ui.pkgTabs,QtCore.SIGNAL("currentChanged(int)"),

self.tab_change)

deftab_change(self,tab_id):

iftab_id==0:

self.ui.infoLabel.setText(''QYolk:Browsingall

installedcheeseshoppackages'')

eliftab_id==1:

self.ui.infoLabel.setText(''QYolk:Browsingactive

packages'')

eliftab_id==2:

self.ui.infoLabel.setText(''QYolk:Browsingnot

activepackages(olderversions)'')



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

32

sys.exit(app.exec_())

这里我们使用self.ui.infoLabel.setText来修改提示信息。下个QYolk程序我们将实现更多的功能。

33

PyQt4文本编辑器-最终版

我们先描述新的特性:1.使用QFileSystemWatcher来提示文件是否被外部改变;2.创建并保存

保存新的文件。下面是start.py文件:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromedytorimportUi_notepad

importcodecs

importcodecs

fromos.pathimportisfile



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_notepad()

self.ui.setupUi(self)

self.watcher=QtCore.QFileSystemWatcher(self)



QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"),

self.file_dialog)



QtCore.QObject.connect(self.ui.button_save,QtCore.SIGNAL("clicked()"),

self.file_save)



QtCore.QObject.connect(self.ui.editor_window,QtCore.SIGNAL("textChanged()"),

self.enable_save)



QtCore.QObject.connect(self.watcher,QtCore.SIGNAL("fileChanged(const

QString&)"),self.file_changed)

self.filename=False

deffile_dialog(self):

response=False

#buttonstexts

SAVE=''Save''

DISCARD=''DiscardChanges''

CANCEL=''Cancel''

#ifwehavechangesthenaskaboutthem

ifself.ui.button_save.isEnabled():

34

message=QtGui.QMessageBox(self)

message.setText("Changeshaven''tbeensaved")

message.setWindowTitle(''Notepad'')

message.setIcon(QtGui.QMessageBox.Question)

message.addButton(SAVE,QtGui.QMessageBox.AcceptRole)

message.addButton(DISCARD,

QtGui.QMessageBox.DestructiveRole)

message.addButton(CANCEL,QtGui.QMessageBox.RejectRole)

message.setDetailedText(''UnsavedChangesin:''+

str(self.filename))

message.exec_()

response=message.clickedButton().text()

#savefile

ifresponse==SAVE:

self.file_save()

self.ui.button_save.setEnabled(False)

#discardchanges

elifresponse==DISCARD:

self.ui.button_save.setEnabled(False)

#ifwedidn''tcanceledshowthefiledialog

ifresponse!=CANCEL:

fd=QtGui.QFileDialog(self)

#removeoldfilefromwatcher

ifself.filename:

self.watcher.removePath(self.filename)

self.filename=fd.getOpenFileName()

ifisfile(self.filename):

s=codecs.open(self.filename,''r'',''utf-8'').read()

self.ui.editor_window.setPlainText(s)

self.ui.button_save.setEnabled(False)

#addfiletowatcher

self.watcher.addPath(self.filename)

defenable_save(self):

self.ui.button_save.setEnabled(True)



deffile_changed(self,path):

response=False

#buttonstexts

SAVE=''SaveAs''

35

RELOAD=''ReloadFile''

CANCEL=''Cancel''

message=QtGui.QMessageBox(self)

message.setText(''Openfilehavebeenchanged!'')

message.setWindowTitle(''Notepad'')

message.setIcon(QtGui.QMessageBox.Warning)

message.addButton(SAVE,QtGui.QMessageBox.AcceptRole)

message.addButton(RELOAD,QtGui.QMessageBox.DestructiveRole)

message.addButton(CANCEL,QtGui.QMessageBox.RejectRole)

message.setDetailedText(''Thefile"''+str(path)+''"havebeen

changedorremovedbyotherapplication.Whatdoyouwanttodo?'')

message.exec_()

response=message.clickedButton().text()

#savecurrentfileunderaneworoldname

ifresponse==SAVE:

fd=QtGui.QFileDialog(self)

newfile=fd.getSaveFileName()

ifnewfile:

s=codecs.open(newfile,''w'',''utf-8'')



s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()

self.ui.button_save.setEnabled(False)

#newfile,removeoldandaddthenewonetothewatcher

ifself.filenameandstr(newfile)!=

str(self.filename):

self.watcher.removePath(self.filename)

self.watcher.addPath(newfile)

self.filename=newfile

#reloadthetextintheeditor

elifresponse==RELOAD:

s=codecs.open(self.filename,''r'',''utf-8'').read()

self.ui.editor_window.setPlainText(s)

self.ui.button_save.setEnabled(False)



deffile_save(self):

#savechangestoexistingfile

ifself.filenameandisfile(self.filename):

#don''treactonourchanges

36

self.watcher.removePath(self.filename)

s=codecs.open(self.filename,''w'',''utf-8'')

s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()

self.ui.button_save.setEnabled(False)

self.watcher.addPath(self.filename)

#saveanewfile

else:

fd=QtGui.QFileDialog(self)

newfile=fd.getSaveFileName()

ifnewfile:

s=codecs.open(newfile,''w'',''utf-8'')



s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()

self.ui.button_save.setEnabled(False)



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

函数file_changed(self,path)在文件被外部程序修改的时候,由QFileSystemWatcher的

fileChanged(constQString&)信号引发:

deffile_changed(self,path):

response=False

#buttonstexts

SAVE=''SaveAs''

RELOAD=''ReloadFile''

CANCEL=''Cancel''

message=QtGui.QMessageBox(self)

message.setText(''Openfilehavebeenchanged!'')

message.setWindowTitle(''Notepad'')

message.setIcon(QtGui.QMessageBox.Warning)

message.addButton(SAVE,QtGui.QMessageBox.AcceptRole)

message.addButton(RELOAD,QtGui.QMessageBox.DestructiveRole)

message.addButton(CANCEL,QtGui.QMessageBox.RejectRole)

37

message.setDetailedText(''Thefile"''+str(path)+''"havebeen

changedorremovedbyotherapplication.Whatdoyouwanttodo?'')

message.exec_()

response=message.clickedButton().text()

#savecurrentfileunderaneworoldname

ifresponse==SAVE:

fd=QtGui.QFileDialog(self)

newfile=fd.getSaveFileName()

ifnewfile:

s=codecs.open(newfile,''w'',''utf-8'')



s.write(unicode(self.ui.editor_window.toPlainText()))

s.close()

self.ui.button_save.setEnabled(False)

#newfile,removeoldandaddthenewonetothewatcher

ifself.filenameandstr(newfile)!=

str(self.filename):

self.watcher.removePath(self.filename)

self.watcher.addPath(newfile)

self.filename=newfile

#reloadthetextintheeditor

elifresponse==RELOAD:

s=codecs.open(self.filename,''r'',''utf-8'').read()

self.ui.editor_window.setPlainText(s)

self.ui.button_save.setEnabled(False)

通过QMessageBox让用户选择,是重新装载文件,还是将当前文件作为一个新文件重新保存。

保存文件名通过QFileDialog的getSaveFileName获取。在使用QFileSystemWatcher的时候,

我们需要通过adding/removing来添加/删除要观察的路径列表。

38

QYolkIII-升级包列表

在这个练习中,我们将增加一个tab页,用于显示-升级包列表。通过下面代码获取列表:

fromyolk.cliimportget_pkglist

fromyolk.yolklibimportget_highest_version,Distributions

fromyolk.pypiimportCheeseShop

importpkg_resources

defget_fresh_updates(package_name="",version=""):

ret=[]

pypi=CheeseShop()

dists=Distributions()

forpkginget_pkglist():

for(dist,active)indists.get_distributions("all",pkg,

dists.get_highest_installed(pkg)):

(project_name,versions)=

pypi.query_versions_pypi(dist.project_name,True)

ifversions:

newest=get_highest_version(versions)

ifnewest!=dist.version:

if

pkg_resources.parse_version(dist.version)<

pkg_resources.parse_version(newest):

ret.append([project_name,

dist.version,newest])

returnret

printget_fresh_updates()

在检测包是否有新版本的时候,运行速度可能有些慢。在后面我们会设计一个缓冲来处理这个问题。

先给QtreeWidget添加一个tab页,里面包含一个名字为"updatesList"的treelist。



更新start.py:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)

#setthewidthsofthecolumns

################

#Allpackages

################

self.ui.allList.setColumnWidth(0,200)

self.ui.allList.setColumnWidth(1,100)

#generatorwhichretunslistofinstalledpackages

packages=yolklib.Distributions()

forpkginpackages.get_distributions(''all''):

a=QtGui.QtreeWidgetItem(self.ui.allList)

39

40

pk=str(pkg[0]).split('''')

ifpkg[1]:

status=''Active''

else:

status=''NotActive''

a.setTextColor(0,QtGui.QColor(128,128,128))

a.setTextColor(1,QtGui.QColor(128,128,128))

a.setTextColor(2,QtGui.QColor(128,128,128))

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,status)

################

#ActivePackages

################

#setthewidthsofthecolumns

self.ui.activeList.setColumnWidth(0,200)

self.ui.activeList.setColumnWidth(1,100)

#generatorwhichretunslistofactivepackages

forpkginpackages.get_distributions(''active''):

a=QtGui.QtreeWidgetItem(self.ui.activeList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''Active'')

################

#NotActivePackages

################

#setthewidthsofthecolumns

self.ui.notActiveList.setColumnWidth(0,200)

self.ui.notActiveList.setColumnWidth(1,100)

#generatorwhichretunslistofnot-activepackages

forpkginpackages.get_distributions(''nonactive''):

a=QtGui.QtreeWidgetItem(self.ui.notActiveList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''NotActive'')



#Signals

41



QtCore.QObject.connect(self.ui.pkgTabs,QtCore.SIGNAL("currentChanged(int)"),

self.tab_change)

deftab_change(self,tab_id):

iftab_id==0:

self.ui.infoLabel.setText(''QYolk:Browsingall

installedcheeseshoppackages'')

eliftab_id==1:

self.ui.infoLabel.setText(''QYolk:Browsingactive

packages'')

eliftab_id==2:

self.ui.infoLabel.setText(''QYolk:Browsingnot

activepackages(olderversions)'')

eliftab_id==3:

self.ui.infoLabel.setText(''QYolk:Browsing

availableupdates'')

################

#ListUpdates

################

#setthewidthsofthecolumns

self.ui.updatesList.setColumnWidth(0,200)

self.ui.updatesList.setColumnWidth(1,150)

forpkginget_fresh_updates():

a=QtGui.QtreeWidgetItem(self.ui.updatesList)

a.setText(0,pkg[0])

a.setText(1,pkg[1])

a.setText(2,pkg[2])



fromyolk.cliimportget_pkglist

fromyolk.yolklibimportget_highest_version,Distributions

fromyolk.pypiimportCheeseShop

importpkg_resources

defget_fresh_updates(package_name="",version=""):

ret=[]

pypi=CheeseShop()

dists=Distributions()

forpkginget_pkglist():

for(dist,active)indists.get_distributions("all",pkg,

dists.get_highest_installed(pkg)):

(project_name,versions)=

pypi.query_versions_pypi(dist.project_name,True)

ifversions:

newest=get_highest_version(versions)

ifnewest!=dist.version:

if

pkg_resources.parse_version(dist.version)<

pkg_resources.parse_version(newest):

ret.append([project_name,

dist.version,newest])

returnret



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

译者:这里我没有运行成功,因此用的是原网站的截图。



新改动是在get_fresh_updates函数中,增加了新tab页的处理:

42

43

eliftab_id==3:

self.ui.infoLabel.setText(''QYolk:Browsing

availableupdates'')

################

#ListUpdates

################

#setthewidthsofthecolumns

self.ui.updatesList.setColumnWidth(0,200)

self.ui.updatesList.setColumnWidth(1,150)

forpkginget_fresh_updates():

a=QtGui.QtreeWidgetItem(self.ui.updatesList)

a.setText(0,pkg[0])

a.setText(1,pkg[1])

a.setText(2,pkg[2])

当切换到"Updates"tab的时候,可能会花一些时间(依赖网络环境)。我们使用Pickle来缓冲信

息:

fromos.pathimportexpanduser

importcPickle

userpath=expanduser(''~'')

f=open(userpath+''/test'',''w'')

cPickle.dump([''a'',''b'',''c''],f)

读pickled数据:

f=open(userpath+''/test'',''r'')

printcPickle.load(f)

给get_fresh_updates增加缓冲:

defget_fresh_updates(package_name="",version=""):

userpath=expanduser(''~'')

now=datetime.now()

#dowehavethecache

ifisfile(userpath+''/.qyolk''):

f=open(userpath+''/.qyolk'',''r'')

cache=cPickle.load(f)

check_time=now-timedelta(hours=24)

ifcache[0]>check_time:

44

#freshcache,useit

returncache[1]



#nocache,getupdatesandcreatethecace

ret=[]

pypi=CheeseShop()

dists=Distributions()

forpkginget_pkglist():

for(dist,active)indists.get_distributions("all",pkg,

dists.get_highest_installed(pkg)):

(project_name,versions)=

pypi.query_versions_pypi(dist.project_name,True)

ifversions:

newest=get_highest_version(versions)

ifnewest!=dist.version:

if

pkg_resources.parse_version(dist.version)<

pkg_resources.parse_version(newest):

ret.append([project_name,

dist.version,newest])

f=open(userpath+''/.qyolk'',''w'')

cPickle.dump([now,ret],f)

returnret

cPickle保存一个列表[Date,listofupgrades]。我们检测缓冲文件是否存在,如果存在检测文件是

否大于24小时没有更新。如果文件不存在,我们将创建一个新的缓冲文件。下面是start.py:

#--coding:utf-8--

importsys

fromPyQt4importQtCore,QtGui

fromqyolkimportUi_QYolk

fromyolkimportyolklib

fromos.pathimportexpanduser

importcPickle

fromyolk.cliimportget_pkglist

fromyolk.yolklibimportget_highest_version,Distributions

fromyolk.pypiimportCheeseShop

importpkg_resources

fromos.pathimportisfile

fromdatetimeimporttimedelta

45

fromdatetimeimportdatetime



classStartQt4(QtGui.QMainWindow):

def__init__(self,parent=None):

QtGui.QWidget.__init__(self,parent)

self.ui=Ui_QYolk()

self.ui.setupUi(self)



#splash=QtGui.QSplashScreen(self)

#splash.drawContents()



################

#Allpackages

################

self.ui.allList.setColumnWidth(0,200)

self.ui.allList.setColumnWidth(1,100)

#generatorwhichretunslistofinstalledpackages

packages=yolklib.Distributions()

forpkginpackages.get_distributions(''all''):

a=QtGui.QtreeWidgetItem(self.ui.allList)

pk=str(pkg[0]).split('''')

ifpkg[1]:

status=''Active''

else:

status=''NotActive''

a.setTextColor(0,QtGui.QColor(128,128,128))

a.setTextColor(1,QtGui.QColor(128,128,128))

a.setTextColor(2,QtGui.QColor(128,128,128))

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,status)

################

#ActivePackages

################

#setthewidthsofthecolumns

self.ui.activeList.setColumnWidth(0,200)

self.ui.activeList.setColumnWidth(1,100)

#generatorwhichretunslistofactivepackages

forpkginpackages.get_distributions(''active''):

46

a=QtGui.QtreeWidgetItem(self.ui.activeList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''Active'')

################

#NotActivePackages

################

#setthewidthsofthecolumns

self.ui.notActiveList.setColumnWidth(0,200)

self.ui.notActiveList.setColumnWidth(1,100)

#generatorwhichretunslistofnot-activepackages

forpkginpackages.get_distributions(''nonactive''):

a=QtGui.QtreeWidgetItem(self.ui.notActiveList)

pk=str(pkg[0]).split('''')

a.setText(0,pk[0])

a.setText(1,pk[1])

a.setText(2,''NotActive'')

################

#ListUpdates

################

#setthewidthsofthecolumns

self.ui.updatesList.setColumnWidth(0,200)

self.ui.updatesList.setColumnWidth(1,150)

forpkginget_fresh_updates():

a=QtGui.QtreeWidgetItem(self.ui.updatesList)

a.setText(0,pkg[0])

a.setText(1,pkg[1])

a.setText(2,pkg[2])



#Signals



QtCore.QObject.connect(self.ui.pkgTabs,QtCore.SIGNAL("currentChanged(int)"),

self.tab_change)

deftab_change(self,tab_id):

iftab_id==0:

self.ui.infoLabel.setText(''QYolk:Browsingall

installedcheeseshoppackages'')

eliftab_id==1:

47

self.ui.infoLabel.setText(''QYolk:Browsingactive

packages'')

eliftab_id==2:

self.ui.infoLabel.setText(''QYolk:Browsingnot

activepackages(olderversions)'')

eliftab_id==3:

self.ui.infoLabel.setText(''QYolk:Browsing

availableupdates'')



defget_fresh_updates(package_name="",version=""):

userpath=expanduser(''~'')

now=datetime.now()

#dowehavethecache

ifisfile(userpath+''/.qyolk''):

f=open(userpath+''/.qyolk'',''r'')

cache=cPickle.load(f)

check_time=now-timedelta(hours=24)

ifcache[0]>check_time:

#freshcache,useit

returncache[1]



#nocache,getupdatesandcreatethecace

ret=[]

pypi=CheeseShop()

dists=Distributions()

forpkginget_pkglist():

for(dist,active)indists.get_distributions("all",pkg,

dists.get_highest_installed(pkg)):

(project_name,versions)=

pypi.query_versions_pypi(dist.project_name,True)

ifversions:

newest=get_highest_version(versions)

ifnewest!=dist.version:

if

pkg_resources.parse_version(dist.version)<

pkg_resources.parse_version(newest):

ret.append([project_name,

dist.version,newest])

f=open(userpath+''/.qyolk'',''w'')

cPickle.dump([now,ret],f)

48

returnret



if__name__=="__main__":

app=QtGui.QApplication(sys.argv)

myapp=StartQt4()

myapp.show()

sys.exit(app.exec_())

更新包的信息我们移动到了__init__——它将在程序启动的时候运行。如果缓冲文件不存在的话,

窗口将在成功更新包信息之后被创建(这中间可能要花一些时间)。如果缓冲文件已经则不会有

太大的问题。在下一次的教程中,我们将用QSplashScreen给程序增加一个启动画面,这样在窗

口被创建的时候可以给用户提供一些有用的信息。

更多内容,请参阅:

http://www.rkblog.rk.edu.pl/w/p/introduction-pyqt4/

献花(0)
+1
(本文系希望生活馆6...首藏)