我想使用PyQt5在UI设计中完善自己的游戏。我觉得PyQt5中用于UI定制的资源不容易找到。可以尝试制作个性化的小部件,但总体方法似乎不规范。
我需要构建一个可悬停,可与其他小部件重叠并且高度定制的箭头小部件。正如我在this教程和其他文章中所读到的,可以使用paintEvent
来完成您需要做的事情。因此,这就是我尝试过的方法,但是总的来说,我觉得该方法非常凌乱,并且我希望了解有关构建复杂的定制通用小部件的一些准则。这是我所拥有的:
自定义形状:我基于this
构建了代码 可悬停属性:我到处都读到,修改项目styleSheet
通常是可行的方法,特别是如果您想使Widget具有通用性并适应颜色,则问题是我找不到正确使用self.palette
来获取QApplication styleSheet当前颜色的方法。我觉得我可能不得不使用enterEvent
和leaveEvent
,但是我试图在这些函数中使用画家重新绘制整个小部件,并说
QPainter::begin: Painter already active
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
可重叠的属性:我发现了以前的post,它似乎已经找到了解决方案:创建第二个作为主窗口小部件的子窗口小部件,以便能够移动周围的孩子。我尝试过,但是无论我给小部件什么位置,它似乎都不想移动。
这是我的代码:
import sys
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QGraphicsDropShadowEffect, QApplication, QFrame, QPushButton
from PyQt5.QtCore import Qt, QPoint, QLine
from PyQt5.QtGui import QPainter, QPen, QColor, QPalette
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.resize(500, 500)
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.myPush = QPushButton()
self.layout.addWidget(self.myPush)
self.arrow = ArrowWidget(self)
position = QPoint(-40, 0)
self.layout.addWidget(self.arrow)
self.arrow.move(position)
class ArrowWidget(QWidget):
def __init__(self, parent=None):
super(ArrowWidget, self).__init__(parent)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.w = 200
self.h = 200
self.blurRadius = 20
self.xO = 0
self.yO = 20
self.resize(self.w, self.h)
self.layout = QHBoxLayout()
# myFrame = QFrame()
# self.layout.addWidget(myFrame)
self.setLayout(self.layout)
self.setStyleSheet("QWidget:hover{border-color: rgb(255,0,0);background-color: rgb(255,50,0);}")
shadow = QGraphicsDropShadowEffect(blurRadius=self.blurRadius, xOffset=self.xO, yOffset=self.yO)
self.setGraphicsEffect(shadow)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.begin(self)
# painter.setBrush(self.palette().window())
# painter.setPen(QPen(QPalette, 5))
ok = self.frameGeometry().width()/2-self.blurRadius/2-self.xO/2
oky = self.frameGeometry().height()/2-self.blurRadius/2-self.yO/2
painter.drawEllipse(QPoint(self.frameGeometry().width()/2-self.blurRadius/2-self.xO/2, self.frameGeometry().height()/2-self.blurRadius/2-self.yO/2), self.w/2-self.blurRadius/2-self.yO/2-self.xO/2, self.h/2-self.blurRadius/2-self.yO/2-self.xO/2)
painter.drawLines(QLine(ok-25, oky-50, ok+25, oky), QLine(ok+25, oky, ok-25, oky+50))
painter.end()
if __name__ == '__main__':
app = QApplication(sys.argv)
testWidget = MainWidget()
testWidget.show()
sys.exit(app.exec_())
如果有人可以帮助我完成这项工作,并一路解释以帮助我们更好地了解自定义窗口小部件的结构,并解释一种更好的方法,而这种方法不会像这样杂乱无章,那么我认为这对初学者是一个加分像我一样,使用PyQt5作为UI制作的主要框架。
答案 0 :(得分:1)
自定义窗口小部件没有“标准”方法,但通常是 paintEvent
覆盖。
您的示例中存在其他问题,我将尝试解决。
如果您希望窗口小部件“可重叠”,则不得将其添加到布局中。将小部件添加到布局将意味着它将在布局内具有其“插槽”,这将依次尝试计算其大小(基于其包含的小部件);同样,一个布局通常每个“布局插槽”只有一个小部件,几乎不可能使小部件重叠。 QGridLayout是一种特殊情况,它允许(仅通过代码,不使用Designer)将更多的窗口小部件添加到同一插槽,或使某些窗口小部件重叠。最后,小部件一旦成为布局的一部分,就不能自由移动或调整大小(除非您设置了fixedSize)。
唯一真正的解决方案是使用父项创建窗口小部件。这样将可以使用move()
和resize()
,但仅在父级边界内。
虽然大多数小部件确实可以使用样式表中的:hover
选择器,但它仅适用于标准小部件,这些小部件可以自己完成大部分画图(通过QStyle函数)。与此相关,虽然可以使用样式表进行一些自定义绘制,但通常用于非常特殊的情况,即使在这种情况下,也没有简便的方法来访问样式表属性。
对于您而言,不需要使用样式表,只需覆盖enterEvent
和leaveEvent
,在其中设置绘画所需的任何颜色,然后在最后调用self.update()
。
收到这些警告的原因是因为您在声明使用绘画设备作为参数的QPainter之后调用begin
:创建后它会自动使用设备参数调用begin
。另外,通常不需要调用end()
,因为QPainter销毁时会自动调用它,因为它是局部变量,所以在paintEvent返回时会发生这种情况。
我根据您的问题创建了一个小示例。它在QGridLayout中创建一个带有按钮和标签的窗口,并在它们的下方 使用一个QFrame集(因为它首先被添加了),显示了我之前写的“重叠”布局。然后是您的箭头小部件,它以主窗口为父窗口创建,可以通过单击并拖动它来移动。
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class ArrowWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
# since the widget will not be added to a layout, ensure
# that it has a fixed size (otherwise it'll use QWidget default size)
self.setFixedSize(200, 200)
self.blurRadius = 20
self.xO = 0
self.yO = 20
shadow = QtWidgets.QGraphicsDropShadowEffect(blurRadius=self.blurRadius, xOffset=self.xO, yOffset=self.yO)
self.setGraphicsEffect(shadow)
# create pen and brush colors for painting
self.currentPen = self.normalPen = QtGui.QPen(QtCore.Qt.black)
self.hoverPen = QtGui.QPen(QtCore.Qt.darkGray)
self.currentBrush = self.normalBrush = QtGui.QColor(QtCore.Qt.transparent)
self.hoverBrush = QtGui.QColor(128, 192, 192, 128)
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
self.mousePos = event.pos()
def mouseMoveEvent(self, event):
# move the widget based on its position and "delta" of the coordinates
# where it was clicked. Be careful to use button*s* and not button
# within mouseMoveEvent
if event.buttons() == QtCore.Qt.LeftButton:
self.move(self.pos() + event.pos() - self.mousePos)
def enterEvent(self, event):
self.currentPen = self.hoverPen
self.currentBrush = self.hoverBrush
self.update()
def leaveEvent(self, event):
self.currentPen = self.normalPen
self.currentBrush = self.normalBrush
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# painting is not based on "pixels", to get accurate results
# translation of .5 is required, expecially when using 1 pixel lines
qp.translate(.5, .5)
# painting rectangle is always 1px smaller than the actual size
rect = self.rect().adjusted(0, 0, -1, -1)
qp.setPen(self.currentPen)
qp.setBrush(self.currentBrush)
# draw an ellipse smaller than the widget
qp.drawEllipse(rect.adjusted(25, 25, -25, -25))
# draw arrow lines based on the center; since a QRect center is a QPoint
# we can add or subtract another QPoint to get the new positions for
# top-left, right and bottom left corners
qp.drawLine(rect.center() + QtCore.QPoint(-25, -50), rect.center() + QtCore.QPoint(25, 0))
qp.drawLine(rect.center() + QtCore.QPoint(25, 0), rect.center() + QtCore.QPoint(-25, 50))
class MainWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.button = QtWidgets.QPushButton('button')
layout.addWidget(self.button, 0, 0)
self.label = QtWidgets.QLabel('label')
self.label.setAlignment(QtCore.Qt.AlignCenter)
layout.addWidget(self.label, 0, 1)
# create a frame that uses as much space as possible
self.frame = QtWidgets.QFrame()
self.frame.setFrameShape(self.frame.StyledPanel|self.frame.Raised)
self.frame.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# add it to the layout, ensuring it spans all rows and columns
layout.addWidget(self.frame, 0, 0, layout.rowCount(), layout.columnCount())
# "lower" the frame to the bottom of the widget's stack, otherwise
# it will be "over" the other widgets, preventing them to receive
# mouse events
self.frame.lower()
self.resize(640, 480)
# finally, create your widget with a parent, *without* adding to a layout
self.arrowWidget = ArrowWidget(self)
# now you can place it wherever you want
self.arrowWidget.move(220, 140)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
testWidget = MainWidget()
testWidget.show()
sys.exit(app.exec_())