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 相关文章推荐
Python 检查数组元素是否存在类似PHP isset()方法
Oct 14 Python
Python读取ini文件、操作mysql、发送邮件实例
Jan 01 Python
python获取远程图片大小和尺寸的方法
Mar 26 Python
python使用paramiko实现远程拷贝文件的方法
Apr 18 Python
用virtualenv建立多个Python独立虚拟开发环境
Jul 06 Python
Django使用paginator插件实现翻页功能的实例
Oct 24 Python
pycham查看程序执行的时间方法
Nov 29 Python
用python求一重积分和二重积分的例子
Dec 06 Python
python3注册全局热键的实现
Mar 22 Python
Python3 webservice接口测试代码详解
Jun 23 Python
Pytorch中的数据集划分&正则化方法
May 27 Python
Python+DeOldify实现老照片上色功能
Jun 21 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
php array_intersect()函数使用代码
2009/01/14 PHP
PHP向socket服务器收发数据的方法
2015/01/24 PHP
php实现俄罗斯乘法实例
2015/03/07 PHP
PHP实现微信提现功能
2018/09/30 PHP
Linux下 php7安装redis的方法
2018/11/01 PHP
javascript浏览器窗口之间传递数据的方法
2015/01/20 Javascript
jquery实现对联广告的方法
2015/02/05 Javascript
jQuery平滑旋转幻灯片特效代码分享
2015/09/07 Javascript
基于javascript实现简单的抽奖系统
2020/04/15 Javascript
JavaScript实现输入框与清空按钮联动效果
2016/09/09 Javascript
彻底解决 webpack 打包文件体积过大问题
2017/07/07 Javascript
JavaScript编程设计模式之观察者模式(Observer Pattern)实例详解
2017/10/25 Javascript
AngularJS日期格式化常见操作实例分析
2018/05/17 Javascript
解决vue中post方式提交数据后台无法接收的问题
2018/08/11 Javascript
angular 实现同步验证器跨字段验证的方法
2019/04/11 Javascript
解决微信小程序云开发中获取数据库的内容为空的方法
2019/05/15 Javascript
Node.js API详解之 querystring用法实例分析
2020/04/29 Javascript
JS实现4位随机验证码
2020/10/19 Javascript
[05:37]DOTA2-DPC中国联赛 正赛 Elephant vs iG 选手采访
2021/03/11 DOTA
python使用xauth方式登录饭否网然后发消息
2014/04/11 Python
深入解析Python中的上下文管理器
2016/06/28 Python
Python爬虫中urllib库的进阶学习
2018/01/05 Python
对Python中实现两个数的值交换的集中方法详解
2019/01/11 Python
python正则-re的用法详解
2019/07/28 Python
Python实现TCP通信的示例代码
2019/09/09 Python
Keras 利用sklearn的ROC-AUC建立评价函数详解
2020/06/15 Python
德国大型的家具商店:Pharao24.de
2016/10/02 全球购物
英国度假别墅预订:Sykes Cottages
2017/06/12 全球购物
美国电子产品主要品牌的授权在线零售商:DataVision
2019/03/23 全球购物
中国文明网签名寄语
2014/01/18 职场文书
乡镇纠风工作实施方案
2014/03/22 职场文书
爱祖国演讲稿
2014/05/04 职场文书
党委书记个人对照检查材料
2014/09/15 职场文书
对外汉语专业大学生职业生涯规划书
2014/10/11 职场文书
Python import模块的缓存问题解决方案
2021/06/02 Python
python中取整数的几种方法
2021/11/07 Python