PyQt5中带有框架的自定义标题栏

Pra*_*ore 1 python qt window pyqt pyqt5

我正在为Windows / Linux开发开放源代码降价支持的最小笔记应用程序。我正在尝试删除标题栏并添加自己的按钮。我想要一个标题栏,只有两个自定义按钮,如图所示在此处输入图片说明

目前我有这个:

在此处输入图片说明

我试过修改窗口标志:

  • 没有窗口标志时,窗口既可调整大小又可移动。但是没有自定义按钮。
  • 使用self.setWindowFlags(QtCore.Qt.FramelessWindowHint),窗口没有边界,但是无法移动或调整窗口大小
  • 使用self.setWindowFlags(QtCore.Qt.CustomizeWindowHint),窗口可以调整大小,但不能移动,也不能摆脱窗口顶部的白色部分。

任何帮助表示赞赏。您可以在Github上找到该项目在这里

谢谢..

这是我的python代码:

from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, uic
import sys
import os
import markdown2 # https://github.com/trentm/python-markdown2
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QFont

simpleUiForm = uic.loadUiType("Simple.ui")[0]

class SimpleWindow(QtWidgets.QMainWindow, simpleUiForm):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.markdown = markdown2.Markdown()
        self.css = open(os.path.join("css", "default.css")).read()
        self.editNote.setPlainText("")
        #self.noteView = QtWebEngineWidgets.QWebEngineView(self)
        self.installEventFilter(self)
        self.displayNote.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        #self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

    def eventFilter(self, object, event):
        if event.type() == QtCore.QEvent.WindowActivate:
            print("widget window has gained focus")
            self.editNote.show()
            self.displayNote.hide()
        elif event.type() == QtCore.QEvent.WindowDeactivate:
            print("widget window has lost focus")
            note = self.editNote.toPlainText()
            htmlNote = self.getStyledPage(note)
            # print(note)
            self.editNote.hide()
            self.displayNote.show()
            # print(htmlNote)
            self.displayNote.setHtml(htmlNote)
        elif event.type() == QtCore.QEvent.FocusIn:
            print("widget has gained keyboard focus")
        elif event.type() == QtCore.QEvent.FocusOut:
            print("widget has lost keyboard focus")
        return False
Run Code Online (Sandbox Code Playgroud)

UI文件按以下层次结构创建

在此处输入图片说明

yur*_*snm 5

以下是您必须遵循的步骤:

  1. 使用MainWindow,可以是QMainWindow或QWidget,也可以是要继承的任何[widget]。
  2. 设置其标志self.setWindowFlags(Qt.FramelessWindowHint)
  3. 实施自己的移动。
  4. 实施自己的按钮(关闭,最大,最小)
  5. 实施自己的调整大小。

这是一个带有移动和按钮实现的小示例。您仍然必须使用相同的逻辑来实现调整大小。

import sys

from PyQt5.QtCore import QPoint
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget



class MainWindow(QWidget):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.layout  = QVBoxLayout()
        self.layout.addWidget(MyBar(self))
        self.setLayout(self.layout)
        self.layout.setContentsMargins(0,0,0,0)
        self.layout.addStretch(-1)
        self.setMinimumSize(800,400)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.pressing = False


class MyBar(QWidget):

    def __init__(self, parent):
        super(MyBar, self).__init__()
        self.parent = parent
        print(self.parent.width())
        self.layout = QHBoxLayout()
        self.layout.setContentsMargins(0,0,0,0)
        self.title = QLabel("My Own Bar")

        btn_size = 35

        self.btn_close = QPushButton("x")
        self.btn_close.clicked.connect(self.btn_close_clicked)
        self.btn_close.setFixedSize(btn_size,btn_size)
        self.btn_close.setStyleSheet("background-color: red;")

        self.btn_min = QPushButton("-")
        self.btn_min.clicked.connect(self.btn_min_clicked)
        self.btn_min.setFixedSize(btn_size, btn_size)
        self.btn_min.setStyleSheet("background-color: gray;")

        self.btn_max = QPushButton("+")
        self.btn_max.clicked.connect(self.btn_max_clicked)
        self.btn_max.setFixedSize(btn_size, btn_size)
        self.btn_max.setStyleSheet("background-color: gray;")

        self.title.setFixedHeight(35)
        self.title.setAlignment(Qt.AlignCenter)
        self.layout.addWidget(self.title)
        self.layout.addWidget(self.btn_min)
        self.layout.addWidget(self.btn_max)
        self.layout.addWidget(self.btn_close)

        self.title.setStyleSheet("""
            background-color: black;
            color: white;
        """)
        self.setLayout(self.layout)

        self.start = QPoint(0, 0)
        self.pressing = False

    def resizeEvent(self, QResizeEvent):
        super(MyBar, self).resizeEvent(QResizeEvent)
        self.title.setFixedWidth(self.parent.width())

    def mousePressEvent(self, event):
        self.start = self.mapToGlobal(event.pos())
        self.pressing = True

    def mouseMoveEvent(self, event):
        if self.pressing:
            self.end = self.mapToGlobal(event.pos())
            self.movement = self.end-self.start
            self.parent.setGeometry(self.mapToGlobal(self.movement).x(),
                                self.mapToGlobal(self.movement).y(),
                                self.parent.width(),
                                self.parent.height())
            self.start = self.end

    def mouseReleaseEvent(self, QMouseEvent):
        self.pressing = False


    def btn_close_clicked(self):
        self.parent.close()

    def btn_max_clicked(self):
        self.parent.showMaximized()

    def btn_min_clicked(self):
        self.parent.showMinimized()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    mw = MainWindow()
    mw.show()
    sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)

这里有一些提示:

选项1:

  1. 在每个角和侧面(例如,左,左上,菜单栏,右上,右,右下,右下和左下)都有一个带有小部件的QGridLayout
  2. 使用方法(1),您将知道单击每个边框时,只需定义每个尺寸并将其添加到其位置即可。
  3. 当您单击每个按钮时,请以各自的方式处理它们,例如,如果单击左侧的按钮并向左拖动,则必须将其调整为更大的尺寸,同时将其向左移动,以使其看起来像停在正确的位置并增加宽度。
  4. 将此推理应用于每个边,每个边的行为方式都必须如此。

选项2:

  1. 不用QGridLayout,您可以通过click pos来检测要单击的位置。

  2. 验证点击的x是否小于移动的pos的x,以了解它是向左还是向右移动以及被点击的位置。

  3. 计算方法与Option1相同

选项3:

  1. 可能还有其他方法,但是那些只是我刚刚想到的方法。例如,您说使用CustomizeWindowHint可以调整大小,因此您只需实现我给您的示例即可。美丽!

提示:

  1. 注意localPos(在自己的小部件内部),globalPos(与您的屏幕有关)。例如:如果单击左侧窗口小部件的最左侧,则其“ x”将为零;如果单击中间(内容)的最左侧,则其“ x”也将为零。尽管如果您将mapToGlobal设置为不同的值根据屏幕的位置。
  2. 在调整大小或移动大小,必须增加宽度或减少宽度,或仅移动大小或两者同时进行时,请注意,我建议您在纸上画图并弄清楚调整大小的逻辑,然后再以蓝色实现。

好运:D


mus*_*nte 5

虽然接受的答案可以被认为是有效的,但它存在一些问题。

  • usingsetGeometry()不合适(并且使用它的原因是错误的),因为它没有考虑样式设置的可能的帧边距;
  • 位置计算不必要地复杂;
  • 将标题栏大小调整为总宽度是错误的,因为它没有考虑按钮,并且在某些情况下还会导致递归问题(例如设置主窗口的最小尺寸);另外,如果标题太大,则无法调整主窗口的大小;
  • 按钮不应接受焦点;
  • 设置布局会对“主小部件”或布局产生限制,因此不应添加标题而应使用小部件的内容边距;

我修改了代码为主窗口提供更好的基础,简化了移动代码,并添加了其他功能,例如 QtwindowTitle()属性支持、按钮的标准 QStyle 图标(而不是文本)以及适当的最大化/正常按钮图标。请注意,标题标签添加到布局中。

class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)

        self.titleBar = MyBar(self)
        self.setContentsMargins(0, self.titleBar.height(), 0, 0)

        self.resize(640, self.titleBar.height() + 480)

    def changeEvent(self, event):
        if event.type() == event.WindowStateChange:
            self.titleBar.windowStateChanged(self.windowState())

    def resizeEvent(self, event):
        self.titleBar.resize(self.width(), self.titleBar.height())


class MyBar(QWidget):
    clickPos = None
    def __init__(self, parent):
        super(MyBar, self).__init__(parent)
        self.setAutoFillBackground(True)
        
        self.setBackgroundRole(QPalette.Shadow)
        # alternatively:
        # palette = self.palette()
        # palette.setColor(palette.Window, Qt.black)
        # palette.setColor(palette.WindowText, Qt.white)
        # self.setPalette(palette)

        layout = QHBoxLayout(self)
        layout.setContentsMargins(1, 1, 1, 1)
        layout.addStretch()

        self.title = QLabel("My Own Bar", self, alignment=Qt.AlignCenter)
        # if setPalette() was used above, this is not required
        self.title.setForegroundRole(QPalette.Light)

        style = self.style()
        ref_size = self.fontMetrics().height()
        ref_size += style.pixelMetric(style.PM_ButtonMargin) * 2
        self.setMaximumHeight(ref_size + 2)

        btn_size = QSize(ref_size, ref_size)
        for target in ('min', 'normal', 'max', 'close'):
            btn = QToolButton(self, focusPolicy=Qt.NoFocus)
            layout.addWidget(btn)
            btn.setFixedSize(btn_size)

            iconType = getattr(style, 
                'SP_TitleBar{}Button'.format(target.capitalize()))
            btn.setIcon(style.standardIcon(iconType))

            if target == 'close':
                colorNormal = 'red'
                colorHover = 'orangered'
            else:
                colorNormal = 'palette(mid)'
                colorHover = 'palette(light)'
            btn.setStyleSheet('''
                QToolButton {{
                    background-color: {};
                }}
                QToolButton:hover {{
                    background-color: {}
                }}
            '''.format(colorNormal, colorHover))

            signal = getattr(self, target + 'Clicked')
            btn.clicked.connect(signal)

            setattr(self, target + 'Button', btn)

        self.normalButton.hide()

        self.updateTitle(parent.windowTitle())
        parent.windowTitleChanged.connect(self.updateTitle)

    def updateTitle(self, title=None):
        if title is None:
            title = self.window().windowTitle()
        width = self.title.width()
        width -= self.style().pixelMetric(QStyle.PM_LayoutHorizontalSpacing) * 2
        self.title.setText(self.fontMetrics().elidedText(
            title, Qt.ElideRight, width))

    def windowStateChanged(self, state):
        self.normalButton.setVisible(state == Qt.WindowMaximized)
        self.maxButton.setVisible(state != Qt.WindowMaximized)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.clickPos = event.windowPos().toPoint()

    def mouseMoveEvent(self, event):
        if self.clickPos is not None:
            self.window().move(event.globalPos() - self.clickPos)

    def mouseReleaseEvent(self, QMouseEvent):
        self.clickPos = None

    def closeClicked(self):
        self.window().close()

    def maxClicked(self):
        self.window().showMaximized()

    def normalClicked(self):
        self.window().showNormal()

    def minClicked(self):
        self.window().showMinimized()

    def resizeEvent(self, event):
        self.title.resize(self.minButton.x(), self.height())
        self.updateTitle()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    mw = MainWindow()
    layout = QVBoxLayout(mw)
    widget = QTextEdit()
    layout.addWidget(widget)
    mw.show()
    mw.setWindowTitle('My custom window with a very, very long title')
    sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)