PyQt 實戰:簡易便籤軟件的製做

便籤軟件

爲何寫便籤軟件

  • 一直都有作一個筆記軟件的想法,而我給筆記軟件設計的一個特點功能就是它的便籤功能。不過因爲各類緣由,筆記軟件沒法完成,可是他的便籤功能也能夠脫離筆記單獨存在。不過功能也隨着有着相應的變化
  • 咱們可能天天都須要一個計劃表來幫助咱們更加高效的工做,在windows上咱們可能會使用它自帶的便籤軟件,也有一些其餘的改進版,可是我認爲他們不夠友好。因而我很是期待一個功能出色的便籤。(我本身寫的這個也只能說是個雛形,須要之後進行加工)

它具備什麼特色

  • 我和幾個同窗交流過,從用戶角度上講,一個便籤首先要簡易,其中操做須要簡單,界面不須要花哨,要實用。
  • 因此在windows上,能夠運行hotkey.py。可使用全局快捷鍵:~。按住該鍵一段時間,界面顯示,鬆開,界面即隱藏。

便籤的開發

功能

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

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

  3. 對於「事件」的保存 (關機重啓以後仍然能夠顯示以前的未完成「事件」)(未實現)git

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

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

便籤的界面截圖

在此輸入圖片描述 在此輸入圖片描述

便籤軟件的結構

從文件角度

  • widget.py: 程序運行入口,主界面的實現
  • 'trayicon': 負責系統托盤功能的實現
  • 'myLabel, myButton, myMeny': 重載一些Qt類,實現自定義的相應組件

github

https://github.com/zjuysw/memo.git設計模式

從功能角度講解PyQt在其中的使用

>若是沒有使用過軟件,可能對下面的代碼註釋會有點不理解架構

主界面佈局

  • 主界面採用HBoxLayout,其中包含兩個VBoxLayout。(實現起來沒什麼困難)

界面前端關鍵技術

  • 主界面設置背景圖片: 採用QPalette。注:使用stylesheet會讓子widget繼承。(widget.py)app

    <!-- lang: python -->ide

    backImg = QPixmap('./img/1.png').scaled(self.size())
      palette = QPalette()
      palette.setBrush(self.backgroundRole(), QBrush(backImg))
      self.setPalette(palette)
  • 圖標的背景圖片和樣式:採用stylesheet函數

  • 特效(透明):採用QGraphicsOpacityEffect (mylable.py)

    <!-- lang: python -->

    self.opacity = QGraphicsOpacityEffect()
      self.opacity.setOpacity(0.7)
      self.setGraphicsEffect(self.opacity)

便籤的拖拽技術

  • 主要是重寫widget的鼠標事件 (mylable.py)

    <!-- lang: python -->

    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()

自定義信號發送和接受技術

  • 下面的代碼大概表示了這個技術的核心內容(實際運用請看項目完整代碼中的運用)

    <!-- lang: python -->

    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)

    <!-- lang: python -->

    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的右上角的左下方

    <!-- lang: python -->

    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)

    <!-- lang: python -->

    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)

    <!-- lang: python -->

    #!/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)

    <!-- lang: python -->
      # -*- 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之間的傳遞,天然代碼就會寫在不一樣的文件中,這也是此次實踐遇到的一個問題,怎樣在代碼行數增多時,仍然保持它的可讀性和可維護性
    • 其中我我的認爲代碼行數上四位數就最好給代碼配上相應的文檔,各個函數的註釋(功能方面,依賴性,)也是要寫清楚。
    • 各個模塊的耦合度要低,否則當你須要對代碼進行修改,發現改了這一個地方,其餘一大堆須要更改。
  • 另外,不得不提的是,這代碼的確寫的比較爛,好比說可讀性不高,維護不容易(如今多是靠本身對項目的記憶,從而進行修改)。×不曉得設計模式那些數都是寫什麼的×
  • 這樣不大的項目可能不怎麼要考慮架構(這個詞的具體含義其實我也不懂),可是以前寫筆記軟件就發現數據不符合你軟件的結構,那麼最後註定失敗。

我的博客地址

相關文章
相關標籤/搜索