PyQt 实战:简易便签软件的制作

原创
2014/09/25 00:11
阅读数 4.5K

便签软件

为什么写便签软件

  • 一直都有做一个笔记软件的想法,而我给笔记软件设计的一个特色功能就是它的便签功能。不过由于各种原因,笔记软件无法完成,但是他的便签功能也可以脱离笔记单独存在。不过功能也随着有着相应的变化
  • 我们可能每天都需要一个计划表来帮助我们更加高效的工作,在windows上我们可能会使用它自带的便签软件,也有一些其他的改进版,但是我认为他们不够友好。于是我非常期待一个功能出色的便签。(我自己写的这个也只能说是个雏形,需要以后进行加工)

它具有什么特点

  • 我和几个同学交流过,从用户角度上讲,一个便签首先要简易,其中操作需要简单,界面不需要花哨,要实用。
  • 所以在windows上,可以运行hotkey.py。可以使用全局快捷键:~。按住该键一段时间,界面显示,松开,界面即隐藏。

便签的开发

功能

  1. 添加、删除、修改和编辑“事件”,托盘图标,windows全局快捷键(已实现)

  2. 闹钟提醒功能 (未实现)

  3. 对于“事件”的保存 (关机重启之后仍然可以显示之前的未完成“事件”)(未实现)

  4. 桌面浮动提醒,界面的动画交互 ... 等 (未实现)

    对于这些功能,也不是要单单的实现这些功能,我们可以通过一些手段让这些普通的功能更加受用户的喜爱,比如说:闹钟提示:你可以添加一个贴心的小功能进去,当是、用户使用电脑时间过久,便签自动进行一些人性化的提醒之类。(这只是功能发散的一个方向)

便签的界面截图

在此输入图片描述 在此输入图片描述

便签软件的结构

从文件角度

  • widget.py: 程序运行入口,主界面的实现
  • 'trayicon': 负责系统托盘功能的实现
  • 'myLabel, myButton, myMeny': 重载一些Qt类,实现自定义的相应组件

github

https://github.com/zjuysw/memo.git

从功能角度讲解PyQt在其中的使用

>如果没有使用过软件,可能对下面的代码注释会有点不理解

主界面布局

  • 主界面采用HBoxLayout,其中包含两个VBoxLayout。(实现起来没什么困难)

界面前端关键技术

  • 主界面设置背景图片: 采用QPalette。注:使用stylesheet会让子widget继承。(widget.py)

    <!-- lang: python -->

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

个人博客地址

展开阅读全文
打赏
4
19 收藏
分享
加载中
https://github.com/xcool/memo
在楼主的基础上增加了logo和双击logo显示和隐藏其他控件。 13
2014/12/25 14:58
回复
举报
Cosven博主

引用来自“caol”的评论

不错,赞一个
谢谢,也欢迎提出建议和想法哈
2014/09/25 17:31
回复
举报
Cosven博主

引用来自“黎开宇”的评论

很不错啊, 下载了学习看看
谢谢,也欢迎提出建议哈
2014/09/25 17:30
回复
举报
很不错啊, 下载了学习看看
2014/09/25 15:16
回复
举报
不错,赞一个
2014/09/25 08:52
回复
举报
更多评论
打赏
5 评论
19 收藏
4
分享
返回顶部
顶部