簡體   English   中英

PyQt 保持縱橫比固定

[英]PyQt keep aspect ratio fixed

我正在研究 PyQt5 GUI,到目前為止,我只是有使用 python 腳本的經驗,並沒有深入研究創建用戶界面。

GUI 必須用於不同的屏幕(可能還有一些舊的 4:3 比例屏幕),並且需要在不同尺寸下看起來不錯。 現在,我讓我的生活更輕松的方法是強制執行窗口的固定縱橫比並根據窗口大小調整不同元素的大小。

from PyQt5 import QtCore, QtGui, QtWidgets

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent= None):
        super().__init__(parent)
        self.form_widget = FormWidget(self)
        self.setCentralWidget(self.form_widget)
        self.resize(200, 400)
        self.sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        self.sizePolicy.setHeightForWidth(True)
        self.setSizePolicy(self.sizePolicy)

    def heightForWidth(self, width):
        return width * 2

class FormWidget(QtWidgets.QWidget):

    def __init__(self, parent):
        super().__init__(parent)

    def resizeEvent(self, event):
        f = self.font()
        temp = event.size().height()
        f.setPixelSize(temp / 16)
        self.setFont(f)

        return super().resizeEvent(event)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

根據窗口大小調整元素大小工作正常,但根本不保留窗口縱橫比。 我從舊的 PyQt4 線程中使用heightForWidth復制了這種方法。 這種方法在 PyQt5 中不再有效了嗎? 我錯過了什么嗎?

如果我理解您的問題,您應該嘗試在主窗口內使用布局。

我這樣做了:

from PyQt5 import QtCore, QtGui, QtWidgets

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent= None):
        super().__init__(parent)
        self.central_widget = QtWidgets.QWidget()
        self.central_layout = QtWidgets.QVBoxLayout()
        self.setCentralWidget(self.central_widget)
        self.central_widget.setLayout(self.central_layout)
        # Lets create some widgets inside
        self.label = QtWidgets.QLabel()
        self.list_view = QtWidgets.QListView()
        self.push_button = QtWidgets.QPushButton()
        self.label.setText('Hi, this is a label. And the next one is a List View :')
        self.push_button.setText('Push Button Here')
        # Lets add the widgets
        self.central_layout.addWidget(self.label)
        self.central_layout.addWidget(self.list_view)
        self.central_layout.addWidget(self.push_button)
      
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

如果您調整窗口大小,其中的小部件也會調整大小。

在此處輸入圖片說明

在此處輸入圖片說明

首先,由 Marc 和 codeling 在這個問題中回答heightForWidth僅支持QGraphicsLayout的子類。

其次,如何在qt(或pyqt)中制作一個固定長寬比的窗口(或頂級widget) 是一個多年來被問到的問題 但是,據我所知,沒有標准的方法可以做到這一點,而且很難實現。 簡而言之,我的做法是使用Qt.FramelessWindowHint創建一個沒有系統移動和調整大小功能的無框窗口,並實現自定義移動和調整大小。

解釋重要機制:

移動:

  1. mousePressEvent ,保留我們上次單擊小部件的位置(可拖動區域)。
  2. mouseMoveEvent ,計算最后點擊的點與當前鼠標位置之間的距離。 根據這個距離移動窗口。

調整大小:

  1. 通過將窗口的最小寬度和高度除以它們的最大公因數,找到寬度和高度的增加或減少步長。
  2. 使用步長增加或減少窗口大小以保持縱橫比。

顯示它可以根據縱橫比調整大小的屏幕截圖。

以下代碼適用於 PyQt5 和 Pyside2。

from PyQt5.QtCore import Qt, QRect, QPoint, QEvent
from PyQt5.QtWidgets import (QLabel, QMainWindow, QApplication, QSizePolicy,
                             QVBoxLayout, QWidget, QHBoxLayout, QPushButton)
from enum import Enum


class MainWindow(QMainWindow):

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.setWindowFlags(Qt.FramelessWindowHint)

        self.createCostumTitleBar()

        self.setContentsMargins(0, 0, 0, 0)

        self.central = QWidget()
        self.central.setStyleSheet("background-color: #f8ecdf")

        self.centralLayout = QVBoxLayout()
        self.central.setLayout(self.centralLayout)
        self.centralLayout.addWidget(
            self.costumsystemmenu, alignment=Qt.AlignTop)
        self.centralLayout.setContentsMargins(0, 0, 0, 0)

        self.setCentralWidget(self.central)

        # Set the minimum size to avoid window being resized too small.

        self.setMinimumSize(300, 400)
        self.minheight = self.minimumHeight()
        self.minwidth = self.minimumWidth()

        self.resize(300, 400)

        # make sure your minium size have the same aspect ratio as the step.
        self.stepY = 4
        self.stepX = 3

        # install the event filter on this window.
        self.installEventFilter(self)
        self.grabarea.installEventFilter(self)

        self.cursorpos = CursorPos.DEFAULT
        self.iswindowpress = False

    def createCostumTitleBar(self):
        self.costumsystemmenu = QWidget()
        self.costumsystemmenu.setStyleSheet("background-color: #ccc")
        self.costumsystemmenu.setContentsMargins(0, 0, 0, 0)
        self.costumsystemmenu.setMinimumHeight(30)

        self.grabarea = QLabel("")
        self.grabarea.setStyleSheet("background-color: #ccc")
        self.grabarea.setSizePolicy(
            QSizePolicy.Expanding, QSizePolicy.Preferred)

        titlebarlayout = QHBoxLayout()
        titlebarlayout.setContentsMargins(11, 11, 11, 11)
        titlebarlayout.setSpacing(0)

        self.closeButton = QPushButton("X")
        self.closeButton.setSizePolicy(
            QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.closeButton.clicked.connect(self.close)

        self.costumsystemmenu.setLayout(titlebarlayout)
        titlebarlayout.addWidget(self.grabarea)
        titlebarlayout.addWidget(self.closeButton, alignment=Qt.AlignRight)

        self.istitlebarpress = False

    def eventFilter(self, object, event):
        # The eventFilter() function must return true if the event
        # should be filtered, (i.e. stopped); otherwise it must return false.
        # https://doc.qt.io/qt-5/qobject.html#eventFilter

        # check if the object is the mainwindow.
        if object == self:

            if event.type() == QEvent.HoverMove:
                if not self.iswindowpress:
                    self.setCursorShape(event)
                return True

            elif event.type() == QEvent.MouseButtonPress:
                self.iswindowpress = True
                # Get the position of the cursor and map to the global coordinate of the widget.
                self.globalpos = self.mapToGlobal(event.pos())
                self.origingeometry = self.geometry()

                return True

            elif event.type() == QEvent.MouseButtonRelease:
                self.iswindowpress = False
                return True

            elif event.type() == QEvent.MouseMove:
                if self.cursorpos != CursorPos.DEFAULT and self.iswindowpress:
                    self.resizing(self.globalpos, event,
                                  self.origingeometry, self.cursorpos)

                return True

            else:
                return False

        elif object == self.grabarea:
            if event.type() == QEvent.MouseButtonPress:
                if event.button() == Qt.LeftButton and self.iswindowpress == False:
                    self.oldpos = event.globalPos()
                    self.oldwindowpos = self.pos()
                    self.istitlebarpress = True

                return True
            elif event.type() == QEvent.MouseButtonRelease:
                self.istitlebarpress = False
                return True
            elif event.type() == QEvent.MouseMove:
                if (self.istitlebarpress):
                    distance = event.globalPos()-self.oldpos
                    newwindowpos = self.oldwindowpos + distance
                    self.move(newwindowpos)
                return True
            else:
                return False
        else:
            return False

    # Change the cursor shape when the cursor is over different part of the window.
    def setCursorShape(self, event, handlersize=11):
        rect = self.rect()
        topLeft = rect.topLeft()
        topRight = rect.topRight()
        bottomLeft = rect.bottomLeft()
        bottomRight = rect.bottomRight()

        # get the position of the cursor
        pos = event.pos()

        # make the resize handle include some space outside the window,
        # can avoid user move too fast and loss the handle.
        # top handle
        if pos in QRect(QPoint(topLeft.x()+handlersize, topLeft.y()-2*handlersize),
                        QPoint(topRight.x()-handlersize, topRight.y()+handlersize)):
            self.setCursor(Qt.SizeVerCursor)
            self.cursorpos = CursorPos.TOP

        # bottom handle
        elif pos in QRect(QPoint(bottomLeft.x()+handlersize, bottomLeft.y()-handlersize),
                          QPoint(bottomRight.x()-handlersize, bottomRight.y()+2*handlersize)):
            self.setCursor(Qt.SizeVerCursor)
            self.cursorpos = CursorPos.BOTTOM

        # right handle
        elif pos in QRect(QPoint(topRight.x()-handlersize, topRight.y()+handlersize),
                          QPoint(bottomRight.x()+2*handlersize, bottomRight.y()-handlersize)):
            self.setCursor(Qt.SizeHorCursor)
            self.cursorpos = CursorPos.RIGHT

        # left handle
        elif pos in QRect(QPoint(topLeft.x()-2*handlersize, topLeft.y()+handlersize),
                          QPoint(bottomLeft.x()+handlersize, bottomLeft.y()-handlersize)):
            self.setCursor(Qt.SizeHorCursor)
            self.cursorpos = CursorPos.LEFT

        # topRight handle
        elif pos in QRect(QPoint(topRight.x()-handlersize, topRight.y()-2*handlersize),
                          QPoint(topRight.x()+2*handlersize, topRight.y()+handlersize)):
            self.setCursor(Qt.SizeBDiagCursor)
            self.cursorpos = CursorPos.TOPRIGHT

        # topLeft handle
        elif pos in QRect(QPoint(topLeft.x()-2*handlersize, topLeft.y()-2*handlersize),
                          QPoint(topLeft.x()+handlersize, topLeft.y()+handlersize)):
            self.setCursor(Qt.SizeFDiagCursor)
            self.cursorpos = CursorPos.TOPLEFT

        # bottomRight handle
        elif pos in QRect(QPoint(bottomRight.x()-handlersize, bottomRight.y()-handlersize),
                          QPoint(bottomRight.x()+2*handlersize, bottomRight.y()+2*handlersize)):
            self.setCursor(Qt.SizeFDiagCursor)
            self.cursorpos = CursorPos.BOTTOMRIGHT

        # bottomLeft handle
        elif pos in QRect(QPoint(bottomLeft.x()-2*handlersize, bottomLeft.y()-handlersize),
                          QPoint(bottomLeft.x()+handlersize, bottomLeft.y()+2*handlersize)):
            self.setCursor(Qt.SizeBDiagCursor)
            self.cursorpos = CursorPos.BOTTOMLEFT

        # Default is the arrow cursor.
        else:
            self.setCursor(Qt.ArrowCursor)
            self.cursorpos = CursorPos.DEFAULT

    def resizing(self, originpos, event, geo, cursorpos):
        newpos = self.mapToGlobal(event.pos())

        # find the distance between new and old cursor position.
        dist = newpos - originpos

        # calculate the steps to grow or srink.
        if cursorpos in [CursorPos.TOP, CursorPos.BOTTOM,
                         CursorPos.TOPRIGHT,
                         CursorPos.BOTTOMLEFT, CursorPos.BOTTOMRIGHT]:
            steps = dist.y()//self.stepY
        elif cursorpos in [CursorPos.LEFT, CursorPos.TOPLEFT, CursorPos.RIGHT]:
            steps = dist.x()//self.stepX

        # if the distance moved is too stort, grow or srink by 1 step.
        if steps == 0:
            steps = -1 if dist.y() < 0 or dist.x() < 0 else 1

        oldwidth = geo.width()
        oldheight = geo.height()

        oldX = geo.x()
        oldY = geo.y()

        if cursorpos in [CursorPos.TOP, CursorPos.TOPRIGHT]:

            width = oldwidth - steps * self.stepX
            height = oldheight - steps * self.stepY

            newX = oldX
            newY = oldY + (steps * self.stepY)

            # check if the new size is within the size limit.
            if height >= self.minheight and width >= self.minwidth:
                self.setGeometry(newX, newY, width, height)

        elif cursorpos in [CursorPos.BOTTOM, CursorPos.RIGHT, CursorPos.BOTTOMRIGHT]:

            width = oldwidth + steps * self.stepX
            height = oldheight + steps * self.stepY

            self.resize(width, height)

        elif cursorpos in [CursorPos.LEFT, CursorPos.BOTTOMLEFT]:

            width = oldwidth - steps * self.stepX
            height = oldheight - steps * self.stepY

            newX = oldX + steps * self.stepX
            newY = oldY

            # check if the new size is within the size limit.
            if height >= self.minheight and width >= self.minwidth:
                self.setGeometry(newX, newY, width, height)

        elif cursorpos == CursorPos.TOPLEFT:

            width = oldwidth - steps * self.stepX
            height = oldheight - steps * self.stepY

            newX = oldX + steps * self.stepX
            newY = oldY + steps * self.stepY

            # check if the new size is within the size limit.
            if height >= self.minheight and width >= self.minwidth:
                self.setGeometry(newX, newY, width, height)

        else:
            pass

# cursor position
class CursorPos(Enum):
    TOP = 1
    BOTTOM = 2
    RIGHT = 3
    LEFT = 4
    TOPRIGHT = 5
    TOPLEFT = 6
    BOTTOMRIGHT = 7
    BOTTOMLEFT = 8
    DEFAULT = 9


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

最后,我要特別感謝這個問題的作者和編輯,GLHF、DRPK、Elad Joseph 和 SimoN SavioR。 如果沒有他們對社區的貢獻,就不可能提出這個答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM