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之间的传递,天然代码就会写在不一样的文件中,这也是此次实践遇到的一个问题,怎样在代码行数增多时,仍然保持它的可读性和可维护性
    • 其中我我的认为代码行数上四位数就最好给代码配上相应的文档,各个函数的注释(功能方面,依赖性,)也是要写清楚。
    • 各个模块的耦合度要低,否则当你须要对代码进行修改,发现改了这一个地方,其余一大堆须要更改。
  • 另外,不得不提的是,这代码的确写的比较烂,好比说可读性不高,维护不容易(如今多是靠本身对项目的记忆,从而进行修改)。×不晓得设计模式那些数都是写什么的×
  • 这样不大的项目可能不怎么要考虑架构(这个词的具体含义其实我也不懂),可是以前写笔记软件就发现数据不符合你软件的结构,那么最后注定失败。

我的博客地址

相关文章
相关标签/搜索