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编程-将Python程序转化为可执行程序[整理]
Apr 09 Python
Python中函数的参数定义和可变参数用法实例分析
Jun 04 Python
python实现的希尔排序算法实例
Jul 01 Python
详谈Python基础之内置函数和递归
Jun 21 Python
python利用微信公众号实现报警功能
Jun 10 Python
python判断完全平方数的方法
Nov 13 Python
Python 加密与解密小结
Dec 06 Python
python解析json串与正则匹配对比方法
Dec 20 Python
Python正则表达式匹配和提取IP地址
Jun 06 Python
Python动态语言与鸭子类型详解
Jul 01 Python
Django中的用户身份验证示例详解
Aug 07 Python
pygame实现打字游戏
Feb 19 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 上传文件类型判断函数(避免上传漏洞 )
2010/06/08 PHP
PHP中strtotime函数使用方法分享
2012/01/10 PHP
Symfony2学习笔记之模板用法详解
2016/03/17 PHP
在Win2003(64位)中配置IIS6+PHP5.2.17+MySQL5.5的运行环境
2016/04/04 PHP
PHP 5.6.11中CURL模块问题的解决方法
2016/08/08 PHP
php中通过eval实现字符串格式的计算公式
2017/03/18 PHP
PHP实现的DES加密解密封装类完整实例
2017/04/29 PHP
表单的一些基本用法与技巧
2006/07/15 Javascript
js wmp操作代码小结(音乐连播功能)
2008/11/08 Javascript
javascript延时加载之defer测试
2012/12/28 Javascript
jQuery 复合选择器应用的几个例子
2014/09/11 Javascript
JS+CSS实现滑动切换tab菜单效果
2015/08/25 Javascript
Bootstrap每天必学之折叠(Collapse)插件
2016/04/25 Javascript
微信小程序教程之本地图片上传(leancloud)实例详解
2016/11/16 Javascript
canvas实现环形进度条效果
2017/03/23 Javascript
jQuery实现的简单在线计算器功能
2017/05/11 jQuery
jQuery Validate 无法验证 chosen-select元素的解决方法
2017/05/17 jQuery
Vue学习笔记进阶篇之函数化组件解析
2017/07/21 Javascript
Node.js使用supervisor进行开发中调试的方法
2019/03/26 Javascript
vue component 中引入less文件报错 Module build failed
2019/04/17 Javascript
JS如何实现手机端输入验证码效果
2020/05/13 Javascript
vue 通过base64实现图片下载功能
2020/12/19 Vue.js
[01:57]2016完美“圣”典风云人物:国士无双专访
2016/12/04 DOTA
使用Python进行AES加密和解密的示例代码
2018/02/02 Python
Python使用Pyqt5实现简易浏览器(最新版本测试过)
2020/04/27 Python
python使用nibabel和sitk读取保存nii.gz文件实例
2020/07/01 Python
python opencv肤色检测的实现示例
2020/12/21 Python
酒吧创业计划书
2014/01/18 职场文书
服务理念标语
2014/06/18 职场文书
员工工作自我评价
2014/09/26 职场文书
国际贸易实务实训报告
2014/11/05 职场文书
高一军训决心书
2015/02/05 职场文书
幼儿园万圣节活动总结
2015/05/05 职场文书
2016孝老爱亲模范事迹材料
2016/02/26 职场文书
自制短波长线天线频率预选器 - 成功消除B2K之流的镜像
2021/04/22 无线电
Win11怎么把合并的任务栏分开 Win11任务栏合并分开教程
2022/04/06 数码科技