首页 > 代码库 > PyQt 实战:简易便签软件的制作

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

便签软件

为什么写便签软件

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

它具有什么特点

  • 我和几个同学交流过,从用户角度上讲,一个便签首先要简易,其中操作需要简单,界面不需要花哨,要实用。

便签的开发

功能

  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)

    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的一些基础知识学牢固,自己组织软件的时候把需求想清楚,把软件的结构理清楚。欢迎_交流_与_指正_或者_提出更好的方法和建议_。思维的碰撞总会有意想不到的惊喜

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