作者:leve1031


花了前后将近一个星期的时间,终于用PyQT实现了我的第一个程序。本程序实现了使用QThread后台处理数据、QThread的暂停,恢复、停止等功能。
主要有几个问题:
1、后台单独线程处理数据的问题;最初不明白QT在子线程中不能操作GUI的问题,因此查了很久的GUI的crash的问题

2、界面的布局;理解了gridLayout这个非常方便的布局方法。

代码片段 
 
1. # -*- coding: utf-8 -*-
2.  
3. """
4. Module implementing umd_MainWindow.
5. """
6. import sys, os, re, time,sip
7. from PyQt4 import QtGui, QtCore
8. from dict4ini  import DictIni
9. from Ui_ebook_txt  import Ui_umd_MainWindow
10. class eBookException(Exception):
11. pass
12. class ebookParseWorker(QtCore.QThread):
13. '''QT中子线程内不能操作GUI界面,切记切记'''
14. def __init__(self, parent=None):
15. .QThread.__init__(self, parent)
16. .exiting  = False
17. .isWait=False
18. .data={}
19. def setVar(self, name, value):
20. .data[name]=value
21. def __del__(self):
22.  
23. .exiting  = True
24. .wait()
25. def buildRegx(self):
26. .alert(regx)
27. if self.data['chmChapterRegx'] is None:
28. ='pages\[(\d+)\]\s*=\s*\[(.*)\];'
29. try:
30. .chapterInfo=re.compile(self.data['chmChapterRegx'])
31. except:
32. .alert('列表处理正则表达式不正确')
33. return False
34. .chapterList=[]
35. return True
36. def getChapterList(self):
37. if os.path.exists(unicode(self.data['chmChapterJs'])) ==False:
38. .alert('章节列表JS文件%s不存在'%(self.data['chmChapterJs']))
39. return False
40. if self.data['inputCharset'] is None or self.data['inputCharset'].strip()=='':
41. .data['inputCharset']='gbk'
42. =self.chapterInfo.findall(open(unicode(self.data['chmChapterJs']),'rb').read())
43. =''
44. for x  in m:
45. =x[1].split(',')
46. ={}
47. if len(oldChapterInfo)==4:
48. if oldChapterInfo[3].strip('"').strip("'")[0:5]!='<1:
49. ')
50.             return False
51.         return self.chapterList
52.     def run(self):
53.         #是否触发错误
54.         error=False
55.         self.alert("开始处理章节内容")
56. '] is None or self.data['inputCharset'].strip()=='':
57. ']='gbk'
58. '] is None or self.data['outputCharset'].strip()=='':
59. ']='gbk'
60. '] is None or self.data['oldChapterTxtDir'].strip()=='' :
61. ')
62.             error=True
63. '] is None or self.data['newChapterTxtDir'].strip()=='':
64. ']=self.data['oldChapterTxtDir']
65. '])) ==False:
66. ']))
67.         i=0
68. '
69. '
70. '
71.         currentNum=0
72.         self.emit(QtCore.SIGNAL("setProcegressBar(int)"),len(self.chapterList))
73. '] is None:
74.             regx=u""
75.  
76. '].split("\n"):
77.             #print x
78.             try:
79.                 re.compile(x)
80.             except:
81. [ %s ]部分不正确'%(x)))
82.                 error=True
83. ')
84.         if error==False:
85.             for x in self.chapterList:
86.                 while self.isWait:
87.                     self.sleep(1)
88.                     #self.wait(1)#使用wait的时候控制台会有输出
89.                 currentNum+=1
90. '])
91.                 #return
92. ']+'/'+x['filename']+self.data['oldChapterTxtExt']
93. ']!=tmpVolName:
94.                     chapterCount=1
95.                     i+=1
96. '+str(i)+'  '+x['volName']
97.                     if i>1:
98. <strong><span style="color: red;">处理(%s)完毕!</span><strong>'%(str(i-1).zfill(2)+'卷 '+tmpVolName)))
99. ']
100. '].replace(u':',u'-')#必须去掉:否则可能出现文件名被截断的情况
101. %s……'%(str(i).zfill(2)+'卷 '+volname)))
102.                 else:
103.                     chapterCount+=1
104.                 if os.path.isfile(unicode(filename)):
105. ']).encode('utf-8')
106. '].split("\n"):
107.  ',content)
108.                     #去掉document.write('
109. =re.sub("\s*document\.write\s*\(\s*['|\"]\s*", '',content)
110. ");这种标签
111. \s*['|\"]\s*\)\s*[;]?", "\r\n\r\n",content)
112.                     content=content.replace("\n","\r\n\r\n").replace("\");","\r\n\r\n")
113. )",'').replace("
114.  
115. ","\r\n\r\n")[4:]
116.                     #去掉多余的类似或的标签
117. <[a-z\s/]+>", '',content)
118.                     if len(x['chapterName'].split(' '))>1:
119.                         chapter=x['chapterName'][x['chapterName'].index(' ')+1:]
120.                     else:
121.                         chapter=x['chapterName']
122.                     if len(x['volName'].split(' '))==2:
123.                         volname=x['volName'][x['volName'].index(' ')+1:]
124.                     else:
125.                         volname=x['volName']
126.                     #递归判断去除章节名前的数字
127.                     try:
128.                         int(chapter[0:4])
129.                         chapter=chapter[4:]
130.                     except:
131.                         try:
132.                             int(chapter[0:3])
133.                             chapter=chapter[3:]
134.                         except:
135.                             try:
136.                                 int(chapter[0:2])
137.                                 chapter=chapter[2:]
138.                             except:
139.                                 try:
140.                                     int(chapter[0:1])
141.                                     chapter=chapter[1:]
142.                                 except:
143.                                     chapter=chapter
144.                     chapter=' 第'+str(chapterCount).zfill(3)+'章 '+chapter
145.  
146.                     filename=self.data['newChapterTxtDir']+'/第'+str(i).zfill(2)+'卷 '+volname+chapter+'.txt'
147.                     #self.alert(filename)
148.                     #return
149.                     filename=filename.strip()
150.                     try:
151.                         f=open(unicode(filename),'wb')
152.                     except:
153.                         self.alert(unicode('写入文件“%s”错误'%(filename)))
154.                         #self.alert('写入文件“%s”错误'%(filename))
155.                     '''try:
156.                         f.write(content.decode('utf-8').encode(self.data['outputCharset']))
157.                     except:
158.                         self.alert(unicode('写入文件(%s)失败!'%(filename)))
159.                         f.close()'''
160.                     f.write(content.decode('utf-8').encode(self.data['outputCharset']))
161.                     f.close()
162.                     #del content
163.                     #self.window.umd_progressBar.setValue(currentNum)
164. (int)"),currentNum)
165.                     #self.window.umd_textEditMessageOutPut.append(unicode('处理(%s)完毕!'%(str(i).zfill(2)+'卷 '+volname+chapter)))
166.                     self.alert(unicode('处理文件(%s)完毕!'%(str(i).zfill(2)+'卷 '+volname+chapter)))
167.                 else:
168.                     self.alert(unicode('处理失败: 章节文件不存在'))
169.             #self.alert('章节列表文件处理完毕')
170.             #import ctypes
171.             #libc = ctypes.CDLL('libc.so.6')
172.             #libc.printf('Hello world!')
173.     def alert(self, txt):
174.         #self.window.updateStatuBar(txt)
175. (QString)"),QtCore.QString(unicode(txt)))
176.         return
177.  
178. class umd_MainWindow(QtGui.QMainWindow, Ui_umd_MainWindow):
179. ""
180. Class documentation goes here.
181. """
182.     def __init__(self, parent = None):
183. ""
184.         Constructor
185. """
186.         QtGui.QMainWindow.__init__(self, parent)
187.         try:
188.             #某些版本必须使用reload(sys)来重新载入sys模块才包含有setdefaultencoding方法
189.             reload(sys)
190.             sys.setdefaultencoding('utf-8')
191.         except:
192.             sys.setappdefaultencoding('utf-8')
193.         #self.bmpdir=os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'res')
194.         self.setupUi(self)
195.         #以下为窗口自动居中
196.         screen = QtGui.QDesktopWidget().screenGeometry()
197.         size = self.geometry()
198.         self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
199.         #设置按钮为隐藏
200.         #self.umd_pushButton_pause.setHidden(True)
201.         self.umd_pushButton_pause.hide()
202.         self.umd_pushButton_Resume.hide()
203.         self.umd_pushButton_Stop.hide()
204.         #载入上一次的配置文件
205.         self.cfg=DictIni(os.getcwd()+'/ebook.ini',encoding= 'utf-8')
206.         if len(self.cfg.recentSetting)>0:
207.             self.loadRecentSetting()
208.         #以下开始设置进度条
209.         self.umd_progressBar.setRange(0, 100)
210.         self.umd_progressBar.setValue(0)
211.         #self.showFullScreen()
212.         #self.showNormal()
213.         #self.isMinimized()
214.         #创建一个文件处理线程
215.         self.thread = ebookParseWorker(parent)
216.         #线程退出
217. ()"), self.finished)
218. ()"), self.finished)
219.         #线程输出
220. (QString)"), self.message)
221. (int)"), self.updateProcegressBar)
222. (int)"), self.setProcegressBar)
223.         #open('./log.txt','wb').write(self.bmpdir)
224.         #线程定义结束
225.         pass
226.     def loadRecentSetting(self):
227.         self.umd_lineEditChapterTxtNew.setText(self.cfg.recentSetting.newTxtDir.decode('utf-8'))
228.         self.umd_textEditChapterContentRegx.setPlainText(self.cfg.recentSetting.chapterContentRegx.decode('utf-8'))
229.         self.umd_lineEditChapterJs.setText(self.cfg.recentSetting.chapterJs.decode('utf-8'))
230.         self.umd_lineEditChapterRegx.setText(self.cfg.recentSetting.chapterRegx.decode('utf-8'))
231.         self.umd_lineEditChapterTxtExt.setText(self.cfg.recentSetting.txtExt.decode('utf-8'))
232.         self.umd_lineEditChapterTxtOld.setText(self.cfg.recentSetting.oldTxtDir.decode('utf-8'))
233.         inputCharsetIndex=self.umd_comboBoxInputCharset.findText(self.cfg.recentSetting.inputCharset.decode('utf-8'))
234.         self.umd_comboBoxInputCharset.setCurrentIndex(inputCharsetIndex)
235.         outputCharsetIndex=self.umd_comboBoxOutputCharset.findText(self.cfg.recentSetting.outputCharset.decode('utf-8'))
236.         self.umd_comboBoxOutputCharset.setCurrentIndex(outputCharsetIndex)
237.  
238.         umdChapterContentRegx=str(self.umd_textEditChapterContentRegx.toPlainText().toUtf8())
239.         #self.umd_lineEditChapterJs.setText(umd_textEditChapterContentRegx)
240.         umdChapterRegx=str(self.umd_lineEditChapterRegx.text().toUtf8())
241.         umdChapterTxtExt=str(self.umd_lineEditChapterTxtExt.text().toUtf8())
242.         umdChapterTxtOld=str(self.umd_lineEditChapterTxtOld.text().toUtf8())
243.         umdChapterTxtNew=str(self.umd_lineEditChapterTxtNew.text().toUtf8())
244.         umdChapterJs=str(self.umd_lineEditChapterJs.text().toUtf8())
245.         #原始文件编码
246.         umdInputCharset=str(self.umd_comboBoxInputCharset.currentText().toUtf8())
247.         #新文件编码
248.         umdOutputCharset=self.umd_comboBoxOutputCharset.currentText().toUtf8().__str__()
249.         pass
250.     def closeEvent(self, event):
251. ", "消息", None, QtGui.QApplication.UnicodeUTF8),
252. ", "确认关闭窗口?", None, QtGui.QApplication.UnicodeUTF8), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
253.  
254.         if reply == QtGui.QMessageBox.Yes:
255.             event.accept()
256.         else:
257.             event.ignore()
258.     def alert(self, txt):
259.         response = False
260.         # buttons texts
261. ", "关闭", None, QtGui.QApplication.UnicodeUTF8)
262.         #RELOAD='reload'
263.         #CANCEL='cancel'
264.         message = QtGui.QMessageBox(self)
265. ",txt, None, QtGui.QApplication.UnicodeUTF8),)
266. ", "消息", None, QtGui.QApplication.UnicodeUTF8),)
267.         message.setIcon(QtGui.QMessageBox.Warning)
268.         message.addButton(Close, QtGui.QMessageBox.AcceptRole)
269.         #message.addButton(RELOAD, QtGui.QMessageBox.DestructiveRole)
270.         #message.addButton(CANCEL, QtGui.QMessageBox.RejectRole)
271.         #message.setDetailedText(txt)
272.         message.exec_()
273.         response = message.clickedButton().text()
274.         '''
275.         if response == SAVE:
276.             fd = QtGui.QFileDialog(self)
277.             newfile = fd.getSaveFileName()
278.             if newfile:
279.                     s = codecs.open(newfile,'w','utf-8')
280.                     s.write(unicode(self.ui.editor_window.toPlainText()))
281.                     s.close()
282.                     self.ui.button_save.setEnabled(False)
283.                     # new file, remove old and add the new one to the watcher
284.                     if self.filename and str(newfile) != str(self.filename):
285.                             self.watcher.removePath(self.filename)
286.                             self.watcher.addPath(newfile)
287.                             self.filename = newfile
288.     # reload the text in the editor
289.     elif response == RELOAD:
290.             s = codecs.open(self.filename,'r','utf-8').read()
291.             self.ui.editor_window.setPlainText(s)
292.             self.ui.button_save.setEnabled(False)
293.         '''
294. ")
295.     def on_umd_toolButtonChapterTxtNew_clicked(self):
296. ""
297. .
298. """
299.         #raise NotImplementedError
300.         dlg=QtGui.QFileDialog(self)
301.         oldDir=unicode(self.umd_lineEditChapterTxtNew.text())
302.         if oldDir.strip()=='':
303.             oldDir=os.getcwd()
304. ", "请选择处理后的TXT文件的位置", None, QtGui.QApplication.UnicodeUTF8), oldDir)
305.         if os.path.isdir(self.chapterTxtDirOld):
306.            self.umd_lineEditChapterTxtNew.setText(self.chapterTxtDirOld)
307.  
308.     def setProcegressBar(self, i):
309.         self.umd_progressBar.setRange(0, i)
310.         pass
311.     def updateProcegressBar(self, i):
312.         self.umd_progressBar.setValue(i)
313.         pass
314.     def message(self, txt):
315.         self.umd_textEditMessageOutPut.append(txt)
316.  
317.     def finished(self):
318.         #print '处理完毕'
319.         self.umd_textEditMessageOutPut.append(unicode('所有内容处理完毕'))
320.         #处理完毕,隐藏暂停和停止按钮
321.         self.umd_pushButton_pause.hide()
322.         self.umd_pushButton_Resume.hide()
323.         self.umd_pushButton_Stop.hide()
324.         self.umd_pushButton_submit.show()
325.         self.umd_pushButton_close.show()
326.         #self.umd_plainTextEditMessageOutPut.appendPlainText(unicode('所有内容处理完毕'))
327. ")
328.     def on_umd_pushButton_Resume_clicked(self):
329.         self.thread.isWait=False
330.         self.umd_pushButton_pause.show()
331.         self.umd_pushButton_Resume.hide()
332.         self.umd_pushButton_Stop.show()
333.         pass
334. ")
335.     def on_umd_pushButton_Stop_clicked(self):
336.         self.thread.terminate()
337.         self.umd_pushButton_submit.show()
338.         self.umd_pushButton_close.show()
339.         self.umd_pushButton_pause.hide()
340.         self.umd_pushButton_Resume.hide()
341.         self.umd_pushButton_Stop.hide()
342. ")
343.     def on_umd_pushButton_pause_clicked(self):
344.         self.thread.isWait=True
345.         #隐藏按钮
346.         self.umd_pushButton_pause.hide()
347.         self.umd_pushButton_Resume.show()
348.         self.umd_pushButton_Stop.show()
349.         pass
350. ")
351.     def on_umd_toolButtonChapterTxtOld_clicked(self):
352. ""
353. .
354. """
355.         dlg=QtGui.QFileDialog(self)
356.         oldDir=unicode(self.umd_lineEditChapterTxtOld.text())
357.         if oldDir.strip()=='':
358.             oldDir=os.getcwd()
359. ", "请选择CHM导出的TXT文件的位置", None, QtGui.QApplication.UnicodeUTF8), oldDir)
360.         if os.path.isdir(self.chapterTxtDirOld):
361.            self.umd_lineEditChapterTxtOld.setText(self.chapterTxtDirOld)
362.  
363. ")
364.     def on_umd_toolButtonChapterJs_clicked(self):
365. ""
366. .
367. """
368.         dlg=QtGui.QFileDialog(self)
369.         oldDir=unicode(self.umd_lineEditChapterJs.text())
370.         if oldDir.strip()=='':
371.             oldDir=os.getcwd()
372. ", "请选择目录列表JS位置", None, QtGui.QApplication.UnicodeUTF8), oldDir,QtGui.QApplication.translate("umd_MainWindow", "js文件(*.js);;文本文件(*.txt);;所有文件(*)", None, QtGui.QApplication.UnicodeUTF8))
373.         if os.path.isfile(self.chapterJsFilename):
374.             dir=os.path.dirname(unicode(self.chapterJsFilename))
375.             #对于从CHM中展开的电子书,一般保持这样的结构
376.             if dir[-2:]=='js':
377.                 dir=dir[0:len(dir)-2]+'txt'
378.             #self.alert(dir[-2:])
379.             self.umd_lineEditChapterJs.setText(self.chapterJsFilename)
380.             self.umd_lineEditChapterTxtOld.setText(dir)
381.  
382. ")
383.     def on_umd_pushButton_submit_clicked(self):
384. ""
385. .
386. """
387.         #raise NotImplementedError
388.         umdChapterContentRegx=str(self.umd_textEditChapterContentRegx.toPlainText().toUtf8())
389.         #print umdChapterContentRegx
390.         #self.umd_lineEditChapterJs.setText(umd_textEditChapterContentRegx)
391.         umdChapterRegx=str(self.umd_lineEditChapterRegx.text().toUtf8())
392.         umdChapterTxtExt=str(self.umd_lineEditChapterTxtExt.text().toUtf8())
393.         umdChapterTxtOld=str(self.umd_lineEditChapterTxtOld.text().toUtf8())
394.         umdChapterTxtNew=str(self.umd_lineEditChapterTxtNew.text().toUtf8())
395.         umdChapterJs=str(self.umd_lineEditChapterJs.text().toUtf8())
396.         #原始文件编码
397.         umdInputCharset=str(self.umd_comboBoxInputCharset.currentText().toUtf8())
398.         #新文件编码
399.         umdOutputCharset=self.umd_comboBoxOutputCharset.currentText().toUtf8().__str__()
400.         '''
401.         >>>print type(self.umd_comboBoxInputCharset.currentText())
402.         >>>
403.         >>>print type(self.umd_comboBoxInputCharset.currentText().toUtf8())
404.         >>>
405.         >>>print str(self.umd_comboBoxInputCharset.currentText()).encode('utf-8')
406.         >>>选择编码
407.         >>>dir(self.umd_comboBoxInputCharset.currentText())
408.         >>>['KeepEmptyParts', 'NormalizationForm', 'NormalizationForm_C', 'NormalizationForm_D', 'NormalizationForm_KC', 'NormalizationForm_KD', 'SectionCaseInsensitiveSeps', 'SectionDefault', 'SectionFlag', 'SectionFlags', 'SectionIncludeLeadingSep', 'SectionIncludeTrailingSep', 'SectionSkipEmpty', 'SkipEmptyParts', 'SplitBehavior', '__add__', '__class__', '__contains__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'append', 'arg', 'at', 'capacity', 'chop', 'clear', 'compare', 'contains', 'count', 'endsWith', 'fill', 'fromAscii', 'fromLatin1', 'fromLocal8Bit', 'fromUtf8', 'indexOf', 'insert', 'isEmpty', 'isNull', 'isRightToLeft', 'isSimpleText', 'lastIndexOf', 'left', 'leftJustified', 'length', 'localeAwareCompare', 'mid', 'normalized', 'number', 'prepend', 'push_back', 'push_front', 'remove', 'repeated', 'replace', 'reserve', 'resize', 'right', 'rightJustified', 'section', 'setNum', 'simplified', 'size', 'split', 'squeeze', 'startsWith', 'toAscii', 'toCaseFolded', 'toDouble', 'toFloat', 'toInt', 'toLatin1', 'toLocal8Bit', 'toLong', 'toLongLong', 'toLower', 'toShort', 'toUInt', 'toULong', 'toULongLong', 'toUShort', 'toUpper', 'toUtf8', 'trimmed', 'truncate']
409.         >>>self.umd_comboBoxInputCharset.currentText().__str__()
410.         >>>选择编码
411.         '''
412.         #保存设置
413.         self.cfg.recentSetting.oldTxtDir=umdChapterTxtOld
414.         self.cfg.recentSetting.newTxtDir=umdChapterTxtNew
415.         self.cfg.recentSetting.txtExt=umdChapterTxtExt
416.         self.cfg.recentSetting.chapterJs=umdChapterJs
417.         self.cfg.recentSetting.chapterRegx=umdChapterRegx
418.         self.cfg.recentSetting.chapterContentRegx=umdChapterContentRegx
419.         self.cfg.recentSetting.inputCharset=umdInputCharset
420.         self.cfg.recentSetting.outputCharset=umdOutputCharset
421.         self.cfg.save()
422.         #开始操作
423.         #self.alert(umdChapterContentRegx)
424.         #重置进度条
425.         self.umd_progressBar.setValue(0)
426.         #重置状态窗口
427.         self.umd_textEditMessageOutPut.clear()
428.         #self.umd_plainTextEditMessageOutPut.clear()
429.         '''self.thread.setVar('chmChapterRegx', unicode(umdChapterRegx))
430.         self.thread.setVar('chmChapterJs', unicode(umdChapterJs))
431.         self.thread.setVar('oldChapterTxtDir', unicode(umdChapterTxtOld))
432.         self.thread.setVar('inputCharset', unicode(umdInputCharset))
433.         self.thread.setVar('oldChapterTxtExt', unicode(umdChapterTxtExt))
434.         self.thread.setVar('chapterTxtRegx', unicode(umdChapterContentRegx))
435.         self.thread.setVar('newChapterTxtDir', unicode(umdChapterTxtNew))
436.         self.thread.setVar('outputCharset', unicode(umdOutputCharset))'''
437.         self.thread.setVar('chmChapterRegx', umdChapterRegx)
438.         self.thread.setVar('chmChapterJs', umdChapterJs)
439.         self.thread.setVar('oldChapterTxtDir', umdChapterTxtOld)
440.         self.thread.setVar('inputCharset', umdInputCharset)
441.         self.thread.setVar('oldChapterTxtExt', umdChapterTxtExt)
442.         self.thread.setVar('chapterTxtRegx', umdChapterContentRegx)
443.         self.thread.setVar('newChapterTxtDir', umdChapterTxtNew)
444.         self.thread.setVar('outputCharset', umdOutputCharset)
445.         #print self.thread.data
446.         #开始执行
447.         self.umd_pushButton_submit.setHidden(True)
448.         self.umd_pushButton_close.hide()
449.         #显示暂停和停止窗口
450.         self.umd_pushButton_pause.show()
451.         self.umd_pushButton_Stop.show()
452.         #
453.         self.thread.buildRegx()
454.         self.thread.getChapterList()
455.         self.thread.start()
456.  
457. ")
458.     def on_umd_pushButton_close_clicked(self):
459.         self.close()
460. if __name__ == "__main__