PyQt 實戰:簡易便簽軟件的制作

jopen 10年前發布 | 109K 次閱讀 PyQt GUI開發框架 QT

為什么寫便簽軟件

  • 一直都有做一個筆記軟件的想法,而我給筆記軟件設計的一個特色功能就是它的便簽功能。不過由于各種原因,筆記軟件無法完成,但是他的便簽功能也可以脫離筆記單獨存在。不過功能也隨著有著相應的變化
  • 我們可能每天都需要一個計劃表來幫助我們更加高效的工作,在windows上我們可能會使用它自帶的便簽軟件,也有一些其他的改進版,但是我認為他們不夠友好。于是我非常期待一個功能出色的便簽。(我自己寫的這個也只能說是個雛形,需要以后進行加工)

它具有什么特點

  • 我和幾個同學交流過,從用戶角度上講,一個便簽首先要簡易,其中操作需要簡單,界面不需要花哨,要實用。

便簽的開發

功能

  1. 添加、刪除、修改和編輯“事件”,托盤圖標,windows全局快捷鍵(已實現)

  2. 鬧鐘提醒功能 (未實現)

  3. 對于“事件”的保存 (關機重啟之后仍然可以顯示之前的未完成“事件”)(未實現)

  4. 桌面浮動提醒,界面的動畫交互 … 等 (未實現)

    對于這些功能,也不是要單單的實現這些功能,我們可以通過一些手段讓這些普通的功能更加受用戶的喜愛,比如說:鬧鐘提示:你可以添加一個貼心的小功能進去,當是、用戶使用電腦時間過久,便簽自動進行一些人性化的提醒之類。(這只是功能發散的一個方向)

便簽的界面截圖

PyQt 實戰:簡易便簽軟件的制作
PyQt 實戰:簡易便簽軟件的制作

便簽軟件的結構

從文件角度

  • 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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!