PyQt5桌面应用开发(18):自定义控件界面设计与实现

news2024/12/24 19:58:56

本文目录

  • PyQt5桌面应用系列
  • 定制控件-界面设计
  • 本文目标
    • 功能分析
  • 功能设计
    • 绘制方形图案
    • 鼠标交互
    • 事件和属性
  • 完整代码
  • 总结

PyQt5桌面应用系列

  • PyQt5桌面应用开发(1):需求分析
  • PyQt5桌面应用开发(2):事件循环
  • PyQt5桌面应用开发(3):并行设计
  • PyQt5桌面应用开发(4):界面设计
  • PyQt5桌面应用开发(5):对话框
  • PyQt5桌面应用开发(6):文件对话框
  • PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
  • PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
  • PyQt5桌面应用开发(9):经典布局QMainWindow
  • PyQt5桌面应用开发(10):界面布局基本支持
  • PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
  • PyQt5桌面应用开发(12):QFile与线程安全
  • PyQt5桌面应用开发(13):QGraphicsView框架
  • PyQt5桌面应用开发(14):数据库+ModelView+QCharts
  • PyQt5桌面应用开发(15):界面动画
  • PyQt5桌面应用开发(16):定制化控件-QPainter绘图
  • PyQt5桌面应用开发(17):类结构+QWebEngineView
  • PyQt5桌面应用开发(18):自定义控件界面设计与实现

定制控件-界面设计

前面定制控件那里,我们主要讲了QPainter这个类,这个类是用来绘制图形的,但是我们在实际的开发中,很多时候需要绘制的不是图形,而是界面。这个时候,我们就需要用到QWidget这个类了。

QWidget是Qt中所有界面控件的基类,它提供了一些基本的功能,比如绘图、事件处理、布局管理等。我们可以通过继承QWidget来实现自己的界面控件。

要实现一个自定义的界面,就需要做下面几件事情:

  1. 继承QWidget类,实现自己的界面控件
  2. 重写paintEvent函数,实现绘图
  3. 重写resizeEvent函数,实现界面大小变化时的处理,或者设置sizePolicy和sizeHint函数,实现界面大小的控制
  4. 重写mousePressEvent、mouseMoveEvent、mouseReleaseEvent函数,实现鼠标事件的处理
  5. 重写keyPressEvent函数,实现键盘事件的处理
  6. 重写timerEvent函数,实现定时器事件的处理
  7. 重写其他事件处理函数,比如focusInEvent、focusOutEvent、enterEvent、leaveEvent等
  8. 重写函数showEvent、hideEvent、closeEvent等
  9. 重写函数setGeometry、setFixedSize、setMinimumSize、setMaximumSize等
  10. 重写函数setStyleSheet、setCursor、setToolTip等
  11. 重写setFocusPolicy、setFocus、clearFocus等

如果只是比较简单的界面,则只需要考虑前面几项。

本文目标

这里我们以一个电池电量显示控件为例,来讲解如何实现一个自定义的界面控件。

功能分析

这个控件,我们希望有两个方面的功能:

  1. 显示一个按百分比描述的量,例如电量,体现一种焦虑感;
  2. 通过鼠标交互来设置一个百分比显示的量,例如设置电量,体现一种控制感。
  3. 能够提供别的控件来设置这个百分比,例如滑块,体现一种便捷感。
  4. 能够构成动画,例如电量充电,体现一种活力感。

大概就如下面的动画所示:

在这里插入图片描述

功能设计

按照上面的功能分析,需要实现如下几个内容:

  1. 按照区域和当前值(0-100)来绘制方形图案;
  2. 在控件中心显示一个数值;
  3. 提供设置值的接口(pyqtSlot);
  4. 提供值发生改变的信号(pyqtSignal);
  5. 提供设置值的动画效果(pyqtproperty)。

1,2是界面显示的内容,3,4是控件的功能,5是控件的附加功能。后面三项功能的技术,在前面的文章中已经讲过了,这里就不再赘述了。

绘制方形图案

这里我们需要绘制一个方形的图案,这个图案的大小是根据控件的大小来自适应的,而且图案的颜色也是根据当前值来自适应的。这里我们需要用到QPainter的几个函数:

  1. QPainter::fillRect:填充矩形
  2. QPainter::setBrush:设置画刷
  3. QPainter::setPen:设置画笔
  4. QPainter::setFont: 设置字体
  5. QPainter::drawText: 绘制字符串

这里首先,是从QWidget得到一个QPainter对象,然后设置画刷和画笔,最后调用fillRect函数来绘制矩形;然后利用setFont设置字体,通过drawText绘制字符串。

    def paintEvent(self, e):
        painter = QPainter(self)

        brush = QBrush()
        brush.setColor(self._background_color)
        brush.setStyle(Qt.SolidPattern)
        rect = QtCore.QRect(0, 0, painter.device().width(), painter.device().height())
        painter.fillRect(rect, brush)

        # Get current state.
        vmin, vmax = self._minimum, self._maximum
        value = self.value

        # Define our canvas.
        d_height = painter.device().height() - (self._padding * 2)
        d_width = painter.device().width() - (self._padding * 2)

        # Draw the bars.
        step_size = d_height / self.n_steps
        bar_height = step_size * self._bar_solid_percent
        bar_spacer = step_size * (1 - self._bar_solid_percent) / 2

        # Calculate the y-stop position, from the value in range.
        pc = (value - vmin) / (vmax - vmin)
        n_steps_to_draw = int(pc * self.n_steps)

        for n in range(n_steps_to_draw):
            brush.setColor(QtGui.QColor(self.steps[n]))
            rect = QtCore.QRectF(
                self._padding,
                self._padding + d_height - ((1 + n) * step_size) + bar_spacer,
                d_width,
                bar_height
            )
            painter.fillRect(rect, brush)

        # draw text in the midddle of the bar
        painter.setPen(QColor('white'))
        rect = QRectF(
            self._padding,
            self._padding,
            d_width - self._padding,
            d_height - self._padding
        )
        # change font size to 20
        font = painter.font()
        font.setPointSize(18)
        font.setFamily("Arial")
        font.setBold(True)
        painter.setFont(font)
        painter.drawText(rect, Qt.AlignCenter, f"{value}%")
        painter.end()

具体的位置和尺寸计算都比较简单,只需要注意x和y方向坐标轴分别是向右和向下的,而不是向上和向右的。

鼠标交互

鼠标交互主要是实现点击设置相应的值。这里我们需要重写mousePressEvent和mouseMovement函数,来实现鼠标点击事件的处理。值得注意的是后者只有在鼠标按键被按下后才会出发(前提是setMouseTracking(False),当然这是默认值)。

    def mouseMoveEvent(self, e):
        self._calculate_clicked_value(e)

    def mousePressEvent(self, e):
        self._calculate_clicked_value(e)

事件和属性

信号、槽和属性的使用,这里就不再赘述了,可以参考前面的文章。

唯一值得注意的是:信号的触发(emit)最好是只在定义该信号的类中发生,这是Qt5官方文档中的建议。这里也是这样实现的,所有对value的修改都是通过property来实现的,而不是直接修改_value的值。只有一个地方触发了信号,也只有在那一个地方,修改了_value的值。

    # signal and slots framework
    valueChanged = pyqtSignal(int)

    @pyqtSlot(int)
    def setValue(self, value):
        self.value = value

    @pyqtProperty(int)
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value
        self.update()
        self.valueChanged.emit(value)

完整代码

import sys

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt, pyqtSlot, pyqtProperty, pyqtSignal, QRectF, QSize, QPropertyAnimation
from PyQt5.QtGui import QColor, QBrush, QPainter
from PyQt5.QtWidgets import QWidget, QSizePolicy, QApplication


class PowerBar(QWidget):
    """
    Custom Qt Widget to show a power bar.
    Demonstrating compound and custom-drawn widget.
    """

    def __init__(self, steps=5, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._value = 0
        self._minimum = 0
        self._maximum = 100

        self.setSizePolicy(
            QSizePolicy.MinimumExpanding,
            QSizePolicy.MinimumExpanding
        )

        if isinstance(steps, list):
            # list of colors.
            self.n_steps = len(steps)
            self.steps = steps

        elif isinstance(steps, int):
            # int number of bars, defaults to red.
            self.n_steps = steps
            self.steps = ['red'] * steps

        else:
            raise TypeError('steps must be a list or int')

        self._bar_solid_percent = 0.8
        self._background_color = QColor('black')
        self._padding = 4.0  # n-pixel gap around edge.

    def paintEvent(self, e):
        painter = QPainter(self)

        brush = QBrush()
        brush.setColor(self._background_color)
        brush.setStyle(Qt.SolidPattern)
        rect = QtCore.QRect(0, 0, painter.device().width(), painter.device().height())
        painter.fillRect(rect, brush)

        # Get current state.
        vmin, vmax = self._minimum, self._maximum
        value = self.value

        # Define our canvas.
        d_height = painter.device().height() - (self._padding * 2)
        d_width = painter.device().width() - (self._padding * 2)

        # Draw the bars.
        step_size = d_height / self.n_steps
        bar_height = step_size * self._bar_solid_percent
        bar_spacer = step_size * (1 - self._bar_solid_percent) / 2

        # Calculate the y-stop position, from the value in range.
        pc = (value - vmin) / (vmax - vmin)
        n_steps_to_draw = int(pc * self.n_steps)

        for n in range(n_steps_to_draw):
            brush.setColor(QtGui.QColor(self.steps[n]))
            rect = QtCore.QRectF(
                self._padding,
                self._padding + d_height - ((1 + n) * step_size) + bar_spacer,
                d_width,
                bar_height
            )
            painter.fillRect(rect, brush)

        # draw text in the midddle of the bar
        painter.setPen(QColor('white'))
        rect = QRectF(
            self._padding,
            self._padding,
            d_width - self._padding,
            d_height - self._padding
        )
        # change font size to 20
        font = painter.font()
        font.setPointSize(18)
        font.setFamily("Arial")
        font.setBold(True)
        painter.setFont(font)
        painter.drawText(rect, Qt.AlignCenter, f"{value}%")
        painter.end()

    def sizeHint(self):
        return QSize(30, 120)

    def _trigger_refresh(self):
        self.update()

    def _calculate_clicked_value(self, e):
        min_val, max_val = self._minimum, self._maximum
        d_height = self.size().height() + (self._padding * 2)
        step_size = d_height / self.n_steps
        click_y = e.y() - self._padding - step_size / 2

        pc = (d_height - click_y) / d_height
        value = min_val + pc * (max_val - min_val)
        if value > self._maximum:
            value = self._maximum
        if value < self._minimum:
            value = self._minimum
        self.value = int(value)

    def mouseMoveEvent(self, e):
        self._calculate_clicked_value(e)

    def mousePressEvent(self, e):
        self._calculate_clicked_value(e)

    def setColor(self, color):
        self.steps = [color] * self.n_steps
        self.update()

    def setColors(self, colors):
        self.n_steps = len(colors)
        self.steps = colors
        self.update()

    def setBarPadding(self, i):
        self._padding = int(i)
        self.update()

    def setBarSolidPercent(self, f):
        self._bar_solid_percent = float(f)
        self.update()

    def setBackgroundColor(self, color):
        self._background_color = QColor(color)
        self.update()

    # signal and slots framework
    valueChanged = pyqtSignal(int)

    @pyqtSlot(int)
    def setValue(self, value):
        self.value = value

    @pyqtProperty(int)
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value
        self.update()
        self.valueChanged.emit(value)


if __name__ == '__main__':
    app = QApplication([])
    volume = PowerBar([QColor(255, 255 - i, 0) for i in range(0, 255, 15)])

    # set volume window without minimize and maximize button
    volume.setWindowFlags(Qt.WindowCloseButtonHint)

    volume.resize(100, 500)
    anim = QPropertyAnimation(volume, b"value")
    anim.setDuration(5000)
    anim.setStartValue(0)
    anim.setKeyValueAt(0.8, 100)
    anim.setEndValue(0)

    volume.show()

    anim.finished.connect(lambda: volume.valueChanged.connect(lambda: volume.setWindowTitle(f"{volume.value}%")))

    anim.start()

    sys.exit(app.exec_())

在程序的主函数里面,展示了一个属性动画。并在属性动画完成之后,把值改变的信号连接到了窗口标题的改变上面。这样就可以实时显示变化(其实跟控件中间的字符串一样)。

最终实现的PowerBar,可以用来显示音量,电量等等,其构造函数可以输入一个颜色列表,也可以输入一个整数,表示颜色的数量。如果输入的是一个整数,那么默认的颜色是红色。

总结

  1. 本文介绍了重载paintEvent方法,绘制自定义控件的方法。
  2. 本文再次复习了QPropertyAnimation属性动画的实现方法。
  3. 自定义控件中需要注意的是,信号与槽函数是比较重要的外部接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/565856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:PythonTile:一种强大的界面构建工具

Python Tile&#xff1a;一种强大的界面构建工具 Python Tile是一种基于Python编程语言的界面构建工具&#xff0c;其目的是帮助开发者快速创建精美的用户界面&#xff0c;从而提高应用程序的用户体验。本文将介绍Python Tile的功能和优势&#xff0c;并讨论其在实际开发中的应…

Doris数据库BE——LoadChannelMgr原理解析

数据在经过清洗过滤后&#xff0c;会通过Open/AddBatch请求分批量将数据发送给存储层的BE节点上。在一个BE上支持多个LoadJob任务同时并发写入执行。LoadChannelMgr负责管理这些任务&#xff0c;并对数据进行分发。 internal service Open/AddBatch请求接口使用BRPC&#xff…

STP 生成树协议

STP&#xff08;Spanning-Tree Protocol&#xff09;的来源 在网络三层架构中&#xff0c;我们会使用冗余这一技术&#xff0c;也就是对三层架构中的这些东西进行备份。冗余包含了设备冗余、网关冗余、线路冗余、电源冗余。 在二层交换网络中进行线路冗余&#xff0c;如图&am…

Linux :: 【基础指令篇 :: 用户管理:(2)】::设置用户密码(及本地Xshell 登录云服务器操作演示) :: passwd

前言&#xff1a;本篇是 Linux 基本操作篇章的内容&#xff01; 笔者使用的环境是基于腾讯云服务器&#xff1a;CentOS 7.6 64bit。 目录索引&#xff1a; 1. 基本语法 2. 基本用法 3. 注意点 4. 补充&#xff1a;指定用户设置密码操作实例测试及登录本地 Xshell 登录演…

Linux命令(18)之file

Linux命令之file 1.file介绍 file命令用来识别文件类型。 2.file用法 file [参数] [文件或目录] file常用参数 参数说明-L显示符号廉洁所指向文件的类别-i显示MIME类别-b显示结果时&#xff0c;不显示文件名称 3.实例 3.1显示ztj.sh文件类型 3.2显示ztj.sh文件类型(不显示…

记一次docker中的oracle连接问题

起因是客户登陆时报错TNS-12537 登陆上上服务器后&#xff0c;发现了几个特点。 1、没有oracle用户 2、数据文件的位置和spfile里面写的不一样 3、pmon进程存在&#xff0c;但是父进程ID不是1 4、配置oracle用户及环境变量&#xff0c;但是as sysdba无法登录到数据库 查看…

Linux命令(19)之userdel

Linux命令之userdel 1.userdel介绍 userdel命令用来说删除useradd命令创建的Linux账户。 useradd创建用户&#xff1a; Linux命令(15)之useradd_小黑要上天的博客-CSDN博客 2.userdel用法 userdel [参数] [用户账户名称] userdel常用参数 参数说明-r递归删除&#xff0c;…

【网络编程】协议定制+Json序列化与反序列化

目录 一、序列化与反序列化的概念 二、自定义协议设计一个网络计算器 2.1TCP协议&#xff0c;如何保证接收方收到了完整的报文呢&#xff1f; 2.2自定义协议的实现 2.3自定义协议在客户端与服务器中的实现 三、使用Json进行序列化和反序列化 3.1jsoncpp库的安装 3.2改造…

学系统集成项目管理工程师(中项)系列26_新兴信息技术

1. 云计算 1.1. 基于互联网的超级计算模式&#xff0c;通过互联网来提供大型计算能力和动态易扩展的虚拟化资源 1.2. 通过网络提供可动态伸缩的廉价计算能力 1.3. 特点 1.3.1. 【19上选23】 1.3.2. 超大规模 1.3.3. 虚拟化 1.3.4. 高可靠性 1.3.5. 通用性 1.3.6. 高可…

linux共享内存总结

共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成 头文件&#xff1a; #include <sys/ipc..h> #include<sys/shm.h> // 创建或获取一个共享内存: 成功返回共享内存ID&#xff0c;失败返回-1 int shmget (key_t key, size_t_size, int flag); // 连接共享内…

Java修饰符

4 修饰符(static关键字) 4.1 权限修饰符 4.2 状态修饰符 final(最终态)static(静态)4.2.1 final的特点 final 关键字是最终的意思,可以修饰成员变量,成员方法,类final修饰的特点: 1.修饰方法:表示该方法是最终方法,不能被重写2.修饰变量:表示该变量是常量,不能被…

深入了解Nginx:高性能的开源Web服务器与反向代理

一、Nginx是什么 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;也可以作为负载均衡器和HTTP缓存服务器使用。它采用事件驱动、异步非阻塞的处理方式&#xff0c;能够处理大量并发连接和高流量负载&#xff…

推荐试试这个简单好用的手机技巧

技巧一&#xff1a;一键锁屏 除了按住手机电源键进行锁屏外&#xff0c;还有其他一些快捷方法可以实现锁屏操作。 对于苹果手机用户&#xff0c;可以按照以下步骤进行设置&#xff1a; 1.打开手机的设置应用&#xff0c;通常可以在主屏幕或应用列表中找到该图标。 2.在设置…

chatgpt赋能python:Pythonunittest跳过用例:使用unittest中跳过测试用例的方法

Python unittest 跳过用例&#xff1a;使用unittest中跳过测试用例的方法 如果你正在开发一个Python项目&#xff0c;你可能已经使用了Python的unittest模块来编写并运行测试用例。在编写测试用例时&#xff0c;有些情况下你可能不想运行某些测试用例&#xff0c;这时就需要使…

Window10安装SQL Server

一、安装SQL Server 1、进入官网根据个人所需下载对应版本即可&#xff0c;本文是基于SQL Server 2022 Express的安装过程 SQL Server 下载 | Microsofthttps://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2、下载完毕&#xff0c;运行安装指引程序 如若不熟悉…

LC-3机器码编程实验 求成绩等级

一、实验目的 分析和理解指定的需解决问题。利用LC-3的汇编代码设计实现相关程序。通过LC-3仿真器调试和运行相关程序并得到正确的结果。 二、实验内容 对学生的成绩使用数组进行排序。 背景&#xff1a;一位老师需要你帮忙决定学生的成绩&#xff0c;她想要根据学生分数在…

【分立元件】MOSFET如何用于同步整流

在电力电子中我们会使用二极管做开关,当二极管导时,相当于开关闭合,当二极管截止时,相当于开关断开。但是二极管在导通时的管压降在低压电源电路中是一个损耗来源,所以一般我们首选使用的是肖特基二极管,因为肖特基二极管的管压降比较低。 如下所示为非同步BUCK电源拓朴…

小黑坐等政审,论文成果毕业事项基本提交,着手眼睛手术明天准备体检然后出发独自夜爬华山的leetcode之旅:81. 搜索旋转排序数组 II

去除相等的二分法 class Solution:def search(self, nums: List[int], target: int) -> bool:# 数组长度n len(nums)# 双指针left 0right n-1# 二分法迭代while left < right:mid (left right) // 2if nums[mid] target:return Trueif nums[left] nums[mid]:left…

原来CSS的登录界面可以变得这么好看

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大一在校生&#xff0c;web前端开发专业 &#x1f921; 个人主页&#xff1a;几何小超 &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目…