PyQt 實戰:簡易便簽軟件的制作
為什么寫便簽軟件
- 一直都有做一個筆記軟件的想法,而我給筆記軟件設計的一個特色功能就是它的便簽功能。不過由于各種原因,筆記軟件無法完成,但是他的便簽功能也可以脫離筆記單獨存在。不過功能也隨著有著相應的變化
- 我們可能每天都需要一個計劃表來幫助我們更加高效的工作,在windows上我們可能會使用它自帶的便簽軟件,也有一些其他的改進版,但是我認為他們不夠友好。于是我非常期待一個功能出色的便簽。(我自己寫的這個也只能說是個雛形,需要以后進行加工)
它具有什么特點
- 我和幾個同學交流過,從用戶角度上講,一個便簽首先要簡易,其中操作需要簡單,界面不需要花哨,要實用。
便簽的開發
功能
添加、刪除、修改和編輯“事件”,托盤圖標,windows全局快捷鍵(已實現)
鬧鐘提醒功能 (未實現)
對于“事件”的保存 (關機重啟之后仍然可以顯示之前的未完成“事件”)(未實現)
桌面浮動提醒,界面的動畫交互 … 等 (未實現)
對于這些功能,也不是要單單的實現這些功能,我們可以通過一些手段讓這些普通的功能更加受用戶的喜愛,比如說:鬧鐘提示:你可以添加一個貼心的小功能進去,當是、用戶使用電腦時間過久,便簽自動進行一些人性化的提醒之類。(這只是功能發散的一個方向)
便簽的界面截圖
便簽軟件的結構
從文件角度
widget.py
: 程序運行入口,主界面的實現- 'trayicon': 負責系統托盤功能的實現
- 'myLabel, myButton, myMeny': 重載一些Qt類,實現自定義的相應組件
github
https://github.com/zjuysw/memo.git
從功能角度講解PyQt在其中的使用
>如果沒有使用過軟件,可能對下面的代碼注釋會有點不理解
主界面布局
- 主界面采用HBoxLayout,其中包含兩個VBoxLayout。(實現起來沒什么困難)
界面前端關鍵技術
主界面設置背景圖片: 采用QPalette。注:使用stylesheet會讓子widget繼承。(widget.py)
backImg = QPixmap('./img/1.png').scaled(self.size()) palette = QPalette() palette.setBrush(self.backgroundRole(), QBrush(backImg)) self.setPalette(palette)
圖標的背景圖片和樣式:采用stylesheet
特效(透明):采用QGraphicsOpacityEffect (mylable.py)
self.opacity = QGraphicsOpacityEffect() self.opacity.setOpacity(0.7) self.setGraphicsEffect(self.opacity)
便簽的拖拽技術
主要是重寫widget的鼠標事件 (mylable.py)
def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.dragPos = event.globalPos() - self.pos() event.accept() def mouseMoveEvent(self, QMouseEvent): pw = self.parentWidget() # 獲取父widget,也就是本程序中的主widget widget1 = pw.getTrashRect() # 獲取主widget 的 垃圾箱widget(函數名沒有改過來) flag = self.isCollide(widget1, self) # 檢測兩個widget的碰撞 if flag: self.emit(SIGNAL('collideTrash'), True) # 碰撞就發射collideTrash信號 else: self.emit(SIGNAL('collideTrash'), False) # 以下代碼用于進行widget的拖拽 if QMouseEvent.buttons() == Qt.LeftButton: self.move(QMouseEvent.globalPos() - self.dragPos) QMouseEvent.accept() if QMouseEvent.buttons() == Qt.RightButton: QMouseEvent.ignore() def mouseReleaseEvent(self, QMouseEvent): # 拖拽動作完成之后檢測是否碰撞以確定該widget是否被刪除 pw = self.parentWidget() widget1 = pw.getTrashRect() flag = self.isCollide(widget1, self) if flag: print "yes" self.emit(SIGNAL('collideTrash'), False) self.hide() self.destroy() else: self.emit(SIGNAL('collideTrash'), False) self.hide() self.show()
自定義信號發送和接受技術
下面的代碼大概表示了這個技術的核心內容(實際運用請看項目完整代碼中的運用)
parentWidget = QWidget() subWidget = QWidget(parentWidget) subWidget.emit(SIGNAL("sub")) parentWidget.connect(subWidget, SIGNAL("sub"), parentWidget.doSomething)
顯示和編輯的替換技術
思路:一個layout中包含兩個layout,其中layout各自包含2個widget,分別是:內容lable,時間lable,編輯框textedit和確定按鈕button。要顯示的時候,我們讓編輯框和按鈕隱藏,編輯的時候,我們讓內容和時間隱藏。(mylable.py)
def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton: self.label.hide() self.timeLabel.hide() self.textEdit.show() self.textEdit.setFocus() self.textEdit.setText(self.label.text()) self.okBtn.show()
widget的碰撞檢測技術
思路:假設垃圾桶為widget1,我們的顯示lable是widget2,由于剛開始的時候垃圾桶在顯示lable的左下角,所以他們如果碰撞(重疊)就必然會有:
widget2的右上角在widget1的左下角的右上方,widget2的左下角必定在widget1的右上角的左下方
def isCollide(self, widget1, widget2): dict1 = {} dict1['size'] = widget1.size() dict1['pos'] = widget1.pos() dict2 = {} dict2['size'] = widget2.size() dict2['pos'] = widget2.pos() r1TopRightX = dict1['pos'].x() + dict1['size'].width() r1TopRightY = dict1['pos'].y() r1BottomLeftX = dict1['pos'].x() r1BottomLeftY = dict1['pos'].y() + dict1['size'].height() r2TopRightX = dict2['pos'].x() + dict2['size'].width() r2TopRightY = dict2['pos'].y() r2BottomLeftX = dict2['pos'].x() r2BottomLeftY = dict2['pos'].y() + dict2['size'].height() if r1TopRightX > r2BottomLeftX and r1TopRightY < r2BottomLeftY \ and r2TopRightX > r1BottomLeftX and r2TopRightY < r1BottomLeftY: return True else: return False
編輯焦點檢測
直接運用QTextEdit的QFocusEvent (mylable.py)
def focusInEvent(self, event): print "edit" self.emit(SIGNAL("Editing")) def focusOutEvent(self, event): if event.reason() == 4: # popup focus event.ignore() else: self.emit(SIGNAL("EditFinish"))
windows的全局快捷鍵技術
使用python的ctypes模塊(Qt本身沒有相應全局快捷鍵處理類)(hotkey.py)
#!/usr/bin/env python # -*- coding: utf8-*- import sys import time from ctypes import * from ctypes.wintypes import * from PyQt4.QtGui import QApplication import widget delta = 0.3 lastTime = 0 WM_HOTKEY = 0x0312 MOD_ALT = 0x0001 MOD_CONTROL = 0x0002 MOD_SHIFT = 0x0004 WM_KEYUP = 0x0101 class MSG(Structure): _fields_ = [('hwnd', c_int), ('message', c_uint), ('wParam', c_int), ('lParam', c_int), ('time', c_int), ('pt', POINT)] key = 192 # ~ key hotkeyId = 1 if not windll.user32.RegisterHotKey(None, hotkeyId, None, key): sys.exit("Cant Register Hotkey") msg = MSG() app = QApplication(sys.argv) w = widget.mainUi() while True: if (windll.user32.GetMessageA(byref(msg), None, 0, 0) != 0): if msg.message == WM_HOTKEY and msg.wParam == hotkeyId: if (time.time() - lastTime) < delta: w.show() else: pass lastTime = time.time() if msg.message == WM_KEYUP: print "up" w.myHide() windll.user32.TranslateMessage(byref(msg)) windll.user32.DispatchMessageA(byref(msg))
系統托盤技術
基本上看看PyQt的文檔差不多了(trayicon.py)
# -*- coding:utf8 -*- import sys from PyQt4 import QtCore, QtGui from PyQt4.QtCore import * from PyQt4.QtGui import * class TrayIcon(QSystemTrayIcon): def __init__(self, parent=None): super(TrayIcon, self).__init__(parent) self.initObjects() self.setObjects() self.activated.connect(self.iconClicked) def initObjects(self): self.menu = QMenu() self.quitAction = QAction(u"退出", self, triggered=self.exitApp) self.icon = QIcon('./img/icon.png') def setObjects(self): self.menu.addAction(self.quitAction) self.setIcon(self.icon) self.setContextMenu(self.menu) def iconClicked(self, reason): print reason if reason==2 or reason==3: pw = self.parent() if pw.isVisible(): pw.hide() else: pw.show() def exitApp(self): self.setVisible(False) qApp.quit() sys.exit() if __name__ == "__main__": import sys app = QApplication(sys.argv) ti = TrayIcon() ti.show() sys.exit(app.exec_())
小結
好像自己編寫的過程中遇到的比較難的技術問題就這些,不過關鍵還是要把PyQt的一些基礎知識學牢固,自己組織軟件的時候把需求想清楚,把軟件的結構理清楚。歡迎_交流_與_指正_或者_提出更好的方法和建議_。思維的碰撞總會有意想不到的驚喜
注
- 讀程序代碼可能有點頭疼,因為注釋很少有。
- 尤其是信號的發送和接受,這些都是在不同widget之間的傳遞,自然代碼就會寫在不同的文件中,這也是這次實踐遇到的一個問題,
怎樣在代碼行數增多時,仍然保持它的可讀性和可維護性
。- 其中我個人認為代碼行數上四位數就最好給代碼配上相應的文檔,各個函數的注釋(功能方面,依賴性,)也是要寫清楚。
- 各個模塊的耦合度要低,不然當你需要對代碼進行修改,發現改了這一個地方,其他一大堆需要更改。
- 另外,不得不提的是,這代碼的確寫的比較爛,比如說可讀性不高,維護不容易(現在可能是靠自己對項目的記憶,從而進行修改)。×不曉得設計模式那些數都是寫什么的×
- 這樣不大的項目可能不怎么要考慮架構(這個詞的具體含義其實我也不懂),但是之前寫筆記軟件就發現數據不符合你軟件的結構,那么最后注定失敗。
來自:http://my.oschina.net/zjuysw/blog/318352