PyQt5实现无边框窗口的标题拖动和窗口缩放


Posted in Python onApril 19, 2018

网上找了半天都找不到好用的PyQt5无边框窗口的实现,借鉴部分前辈的窗口拖放代码,自己实现了一下无边框窗口,问题可能还有一点,慢慢改吧

先做个笔记

py文件

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QFont, QCursor

class QTitleLabel(QLabel):
  """
  新建标题栏标签类
  """
  def __init__(self, *args):
    super(QTitleLabel, self).__init__(*args)
    self.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
    self.setFixedHeight(30)

class QTitleButton(QPushButton):
  """
  新建标题栏按钮类
  """
  def __init__(self, *args):
    super(QTitleButton, self).__init__(*args)
    self.setFont(QFont("Webdings")) # 特殊字体以不借助图片实现最小化最大化和关闭按钮
    self.setFixedWidth(40)

class QUnFrameWindow(QWidget):
  """
  无边框窗口类
  """
  def __init__(self):
    super(QUnFrameWindow, self).__init__(None, Qt.FramelessWindowHint) # 设置为顶级窗口,无边框
    self._padding = 5 # 设置边界宽度为5
    self.initTitleLabel() # 安放标题栏标签
    self.setWindowTitle = self._setTitleText(self.setWindowTitle) # 用装饰器将设置WindowTitle名字函数共享到标题栏标签上
    self.setWindowTitle("UnFrameWindow")
    self.initLayout() # 设置框架布局
    self.setMinimumWidth(250)
    self.setMouseTracking(True) # 设置widget鼠标跟踪
    self.initDrag() # 设置鼠标跟踪判断默认值

  def initDrag(self):
    # 设置鼠标跟踪判断扳机默认值
    self._move_drag = False
    self._corner_drag = False
    self._bottom_drag = False
    self._right_drag = False

  def initTitleLabel(self):
    # 安放标题栏标签
    self._TitleLabel = QTitleLabel(self)
    self._TitleLabel.setMouseTracking(True) # 设置标题栏标签鼠标跟踪(如不设,则标题栏内在widget上层,无法实现跟踪)
    self._TitleLabel.setIndent(10) # 设置标题栏文本缩进
    self._TitleLabel.move(0, 0) # 标题栏安放到左上角

  def initLayout(self):
    # 设置框架布局
    self._MainLayout = QVBoxLayout()
    self._MainLayout.setSpacing(0)
    self._MainLayout.addWidget(QLabel(), Qt.AlignLeft) # 顶一个QLabel在竖放框架第一行,以免正常内容挤占到标题范围里
    self._MainLayout.addStretch()
    self.setLayout(self._MainLayout)

  def addLayout(self, QLayout):
    # 给widget定义一个addLayout函数,以实现往竖放框架的正确内容区内嵌套Layout框架
    self._MainLayout.addLayout(QLayout)

  def _setTitleText(self, func):
    # 设置标题栏标签的装饰器函数
    def wrapper(*args):
      self._TitleLabel.setText(*args)
      return func(*args)
    return wrapper

  def setTitleAlignment(self, alignment):
    # 给widget定义一个setTitleAlignment函数,以实现标题栏标签的对齐方式设定
    self._TitleLabel.setAlignment(alignment | Qt.AlignVCenter)

  def setCloseButton(self, bool):
    # 给widget定义一个setCloseButton函数,为True时设置一个关闭按钮
    if bool == True:
      self._CloseButton = QTitleButton(b'\xef\x81\xb2'.decode("utf-8"), self)
      self._CloseButton.setObjectName("CloseButton") # 设置按钮的ObjectName以在qss样式表内定义不同的按钮样式
      self._CloseButton.setToolTip("关闭窗口")
      self._CloseButton.setMouseTracking(True) # 设置按钮鼠标跟踪(如不设,则按钮在widget上层,无法实现跟踪)
      self._CloseButton.setFixedHeight(self._TitleLabel.height()) # 设置按钮高度为标题栏高度
      self._CloseButton.clicked.connect(self.close) # 按钮信号连接到关闭窗口的槽函数


  def setMinMaxButtons(self, bool):
    # 给widget定义一个setMinMaxButtons函数,为True时设置一组最小化最大化按钮
    if bool == True:
      self._MinimumButton = QTitleButton(b'\xef\x80\xb0'.decode("utf-8"), self)
      self._MinimumButton.setObjectName("MinMaxButton") # 设置按钮的ObjectName以在qss样式表内定义不同的按钮样式
      self._MinimumButton.setToolTip("最小化")
      self._MinimumButton.setMouseTracking(True) # 设置按钮鼠标跟踪(如不设,则按钮在widget上层,无法实现跟踪)
      self._MinimumButton.setFixedHeight(self._TitleLabel.height()) # 设置按钮高度为标题栏高度
      self._MinimumButton.clicked.connect(self.showMinimized) # 按钮信号连接到最小化窗口的槽函数
      self._MaximumButton = QTitleButton(b'\xef\x80\xb1'.decode("utf-8"), self)
      self._MaximumButton.setObjectName("MinMaxButton") # 设置按钮的ObjectName以在qss样式表内定义不同的按钮样式
      self._MaximumButton.setToolTip("最大化")
      self._MaximumButton.setMouseTracking(True) # 设置按钮鼠标跟踪(如不设,则按钮在widget上层,无法实现跟踪)
      self._MaximumButton.setFixedHeight(self._TitleLabel.height()) # 设置按钮高度为标题栏高度
      self._MaximumButton.clicked.connect(self._changeNormalButton) # 按钮信号连接切换到恢复窗口大小按钮函数

  def _changeNormalButton(self):
    # 切换到恢复窗口大小按钮
    try:
      self.showMaximized() # 先实现窗口最大化
      self._MaximumButton.setText(b'\xef\x80\xb2'.decode("utf-8")) # 更改按钮文本
      self._MaximumButton.setToolTip("恢复") # 更改按钮提示
      self._MaximumButton.disconnect() # 断开原本的信号槽连接
      self._MaximumButton.clicked.connect(self._changeMaxButton) # 重新连接信号和槽
    except:
      pass

  def _changeMaxButton(self):
    # 切换到最大化按钮
    try:
      self.showNormal()
      self._MaximumButton.setText(b'\xef\x80\xb1'.decode("utf-8"))
      self._MaximumButton.setToolTip("最大化")
      self._MaximumButton.disconnect()
      self._MaximumButton.clicked.connect(self._changeNormalButton)
    except:
      pass

  def resizeEvent(self, QResizeEvent):
    # 自定义窗口调整大小事件
    self._TitleLabel.setFixedWidth(self.width()) # 将标题标签始终设为窗口宽度
    # 分别移动三个按钮到正确的位置
    try:
      self._CloseButton.move(self.width() - self._CloseButton.width(), 0)
    except:
      pass
    try:
      self._MinimumButton.move(self.width() - (self._CloseButton.width() + 1) * 3 + 1, 0)
    except:
      pass
    try:
      self._MaximumButton.move(self.width() - (self._CloseButton.width() + 1) * 2 + 1, 0)
    except:
      pass
    # 重新调整边界范围以备实现鼠标拖放缩放窗口大小,采用三个列表生成式生成三个列表
    self._right_rect = [QPoint(x, y) for x in range(self.width() - self._padding, self.width() + 1)
              for y in range(1, self.height() - self._padding)]
    self._bottom_rect = [QPoint(x, y) for x in range(1, self.width() - self._padding)
             for y in range(self.height() - self._padding, self.height() + 1)]
    self._corner_rect = [QPoint(x, y) for x in range(self.width() - self._padding, self.width() + 1)
                  for y in range(self.height() - self._padding, self.height() + 1)]

  def mousePressEvent(self, event):
    # 重写鼠标点击的事件
    if (event.button() == Qt.LeftButton) and (event.pos() in self._corner_rect):
      # 鼠标左键点击右下角边界区域
      self._corner_drag = True
      event.accept()
    elif (event.button() == Qt.LeftButton) and (event.pos() in self._right_rect):
      # 鼠标左键点击右侧边界区域
      self._right_drag = True
      event.accept()
    elif (event.button() == Qt.LeftButton) and (event.pos() in self._bottom_rect):
      # 鼠标左键点击下侧边界区域
      self._bottom_drag = True
      event.accept()
    elif (event.button() == Qt.LeftButton) and (event.y() < self._TitleLabel.height()):
      # 鼠标左键点击标题栏区域
      self._move_drag = True
      self.move_DragPosition = event.globalPos() - self.pos()
      event.accept()

  def mouseMoveEvent(self, QMouseEvent):
    # 判断鼠标位置切换鼠标手势
    if QMouseEvent.pos() in self._corner_rect:
      self.setCursor(Qt.SizeFDiagCursor)
    elif QMouseEvent.pos() in self._bottom_rect:
      self.setCursor(Qt.SizeVerCursor)
    elif QMouseEvent.pos() in self._right_rect:
      self.setCursor(Qt.SizeHorCursor)
    else:
      self.setCursor(Qt.ArrowCursor)
    # 当鼠标左键点击不放及满足点击区域的要求后,分别实现不同的窗口调整
    # 没有定义左方和上方相关的5个方向,主要是因为实现起来不难,但是效果很差,拖放的时候窗口闪烁,再研究研究是否有更好的实现
    if Qt.LeftButton and self._right_drag:
      # 右侧调整窗口宽度
      self.resize(QMouseEvent.pos().x(), self.height())
      QMouseEvent.accept()
    elif Qt.LeftButton and self._bottom_drag:
      # 下侧调整窗口高度
      self.resize(self.width(), QMouseEvent.pos().y())
      QMouseEvent.accept()
    elif Qt.LeftButton and self._corner_drag:
      # 右下角同时调整高度和宽度
      self.resize(QMouseEvent.pos().x(), QMouseEvent.pos().y())
      QMouseEvent.accept()
    elif Qt.LeftButton and self._move_drag:
      # 标题栏拖放窗口位置
      self.move(QMouseEvent.globalPos() - self.move_DragPosition)
      QMouseEvent.accept()

  def mouseReleaseEvent(self, QMouseEvent):
    # 鼠标释放后,各扳机复位
    self._move_drag = False
    self._corner_drag = False
    self._bottom_drag = False
    self._right_drag = False


if __name__ == "__main__":
  from PyQt5.QtWidgets import QApplication
  import sys
  app = QApplication(sys.argv)
  app.setStyleSheet(open("./UnFrameStyle.qss").read())
  window = QUnFrameWindow()
  window.setCloseButton(True)
  window.setMinMaxButtons(True)
  window.show()
  sys.exit(app.exec_())

qss文件

/**********Title**********/
QTitleLabel{
    background-color: Gainsboro;
    font: 100 10pt;
}


/**********Button**********/
QTitleButton{
    background-color: rgba(255, 255, 255, 0);
    color: black;
    border: 0px;
    font: 100 10pt;
}
QTitleButton#MinMaxButton:hover{
    background-color: #D0D0D1;
    border: 0px;
    font: 100 10pt;
}
QTitleButton#CloseButton:hover{
    background-color: #D32424;
    color: white;
    border: 0px;
    font: 100 10pt;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
1分钟快速生成用于网页内容提取的xslt
Feb 23 Python
Python实现简单http服务器
Apr 12 Python
Python的UTC时间转换讲解
Feb 26 Python
python字符串和常用数据结构知识总结
May 21 Python
对django后台admin下拉框进行过滤的实例
Jul 26 Python
django 多对多表的创建和插入代码实现
Sep 09 Python
Python中的上下文管理器相关知识详解
Sep 19 Python
PYTHON绘制雷达图代码实例
Oct 15 Python
Python3标准库之functools管理函数的工具详解
Feb 27 Python
python 对一幅灰度图像进行直方图均衡化
Oct 27 Python
详解python百行有效代码实现汉诺塔小游戏(简约版)
Oct 30 Python
通过代码简单了解django model序列化作用
Nov 12 Python
利用numpy和pandas处理csv文件中的时间方法
Apr 19 #Python
Python处理CSV与List的转换方法
Apr 19 #Python
python3+PyQt5重新实现QT事件处理程序
Apr 19 #Python
python3+PyQt5重新实现自定义数据拖放处理
Apr 19 #Python
python之从文件读取数据到list的实例讲解
Apr 19 #Python
python实现读取大文件并逐行写入另外一个文件
Apr 19 #Python
python按行读取文件,去掉每行的换行符\n的实例
Apr 19 #Python
You might like
德生S2000电路分析
2021/03/02 无线电
PHP模板引擎smarty详细介绍
2015/05/26 PHP
有关PHP 中 config.m4 的探索
2020/08/26 PHP
javascript实现动态CSS换肤技术的脚本
2007/06/29 Javascript
jQuery 加上最后自己的验证
2009/11/04 Javascript
javascript 处理事件绑定的一些兼容写法
2009/12/24 Javascript
seajs1.3.0源码解析之module依赖有序加载
2012/11/07 Javascript
JS连接SQL数据库与ACCESS数据库的方法实例
2013/11/21 Javascript
js输入框邮箱自动提示功能代码实现
2013/12/10 Javascript
jquery 表格排序、实时搜索表格内容(附图)
2014/05/19 Javascript
document.forms用法示例介绍
2014/06/26 Javascript
常用的jQuery前端技巧收集
2014/12/24 Javascript
浅谈$(document)和$(window)的区别
2015/07/15 Javascript
15位和18位身份证JS校验的简单实例
2016/07/18 Javascript
vue.js表格组件开发的实例详解
2016/10/12 Javascript
angular十大常见问题
2017/03/07 Javascript
Bootstrap媒体对象学习使用
2017/03/07 Javascript
微信小程序开发入门基础教程
2017/04/19 Javascript
Node.JS 循环递归复制文件夹目录及其子文件夹下的所有文件
2017/09/18 Javascript
使用webpack将ES6转化ES5的实现方法
2019/10/13 Javascript
六个窍门助你提高Python运行效率
2015/06/09 Python
Python+django实现文件上传
2016/01/17 Python
Python实现处理逆波兰表达式示例
2018/07/30 Python
使用Python处理BAM的方法
2018/09/28 Python
Python+OpenCV图片局部区域像素值处理改进版详解
2019/01/23 Python
python Django 创建应用过程图示详解
2019/07/29 Python
利用Python绘制有趣的万圣节南瓜怪效果
2019/10/31 Python
Keras 加载已经训练好的模型进行预测操作
2020/06/17 Python
英国邮购活的植物主要供应商:Gardening Direct
2019/01/28 全球购物
女大学生自我鉴定
2013/12/09 职场文书
家具厂厂长岗位职责
2014/01/01 职场文书
优质服务活动实施方案
2014/05/02 职场文书
起诉状范本
2015/05/20 职场文书
拯救大兵瑞恩观后感
2015/06/09 职场文书
技术入股协议书
2016/03/22 职场文书
mysql创建存储过程及函数详解
2021/12/04 MySQL