PySide(Python/Qt)中的自定义小部件不显示
我正在尝试用pyside(python/Qt)做我的第一个原型应用。这个应用启动得很好,能够根据我的布局创建一个窗口和一些控件。线程也正常启动并执行,一切都很顺利。可是……
我想通过添加一些自定义控件来增强图形界面,以显示线程的执行状态。所以我想用闪烁的LED灯来实现这个功能。为此,我正在尝试实现一个自定义的LED控件。
请记住,我现在正在学习python,所以可能会有一些奇怪的做法。不过,这里是我目前的LED控件类的代码:
from PySide import QtCore, QtGui
class LED(QtGui.QWidget):
class Mode:
STATIC_OFF = 0
STATIC_ON = 1
FLASH_SLOW = 2
FLASH_MEDIUM = 2
FLASH_FAST = 3
class Color:
BLACK = '#000000'
GREEN = '#00FF00'
RED = '#FF0000'
BLUE = '#0000FF'
YELLOW = '#FFFF00'
WHITE = '#FFFFFF'
mode = Mode.STATIC_ON
color = Color.BLACK
radius = 10
status = False
timer = None
outdated = QtCore.Signal()
def __init__(self, mode, color, radius, parent=None):
super(LED, self).__init__(parent)
self.outdated.connect(self.update)
self.setMode(mode,False)
self.setColor(color,False)
self.setRadius(radius,False)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.adjustAppearance)
self.adjustAppearance()
def getCenter(self):
return QtCore.QPoint(self.radius, self.radius)
def getBox(self):
return QtCore.QRect(self.radius, self.radius)
def setColor(self, color, update=True):
assert color in (self.Color.GREEN,self.Color.RED,self.Color.BLUE,self.Color.YELLOW,self.Color.WHITE), "invalid color"
self.color = color
if update:
self.adjustAppearance()
def setMode(self, mode, update=True):
assert mode in (self.Mode.STATIC_OFF,self.Mode.STATIC_ON,self.Mode.FLASH_SLOW,self.Mode.FLASH_MEDIUM,self.Mode.FLASH_FAST),"invalid mode"
self.mode = mode
if update:
self.adjustAppearance()
def setRadius(self, radius, update=True):
assert isinstance(radius, int), "invalid radius type (integer)"
assert 10<=radius<=100, "invalid radius value (0-100)"
self.radius = radius
if update:
self.adjustAppearance()
def switchOn(self):
self.status = True
self.adjustAppearance()
def switchOff(self):
self.status = False
self.adjustAppearance()
def adjustAppearance(self):
if self.mode is self.Mode.STATIC_OFF:
self.status = False
self.timer.stop()
elif self.mode is self.Mode.STATIC_ON:
self.status = True
self.timer.stop()
elif self.mode is self.Mode.FLASH_SLOW:
self.status = not self.status
self.timer.start(200)
elif self.mode is self.Mode.FLASH_SLOW:
self.status = not self.status
self.timer.start(500)
elif self.mode is self.Mode.FLASH_SLOW:
self.status = not self.status
self.timer.start(1000)
self.outdated.emit()
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
self.drawWidget(event, painter)
painter.end()
def drawWidget(self, event, painter):
if self.status:
shade = QtGui.QColor(self.color).darker
else:
shade = QtGui.QColor(self.color).lighter
#painter.setPen(QtGui.QColor('black'), 1, QtCore.Qt.SolidLine)
painter.setPen(QtGui.QColor('black'))
painter.setBrush(QtCore.Qt.RadialGradientPattern)
painter.drawEllipse(self.getCenter(), self.radius, self.radius)
我的问题是,当我把这个控件添加到窗口布局时,它根本不显示。其他控件(那些普通的Qt控件)都能显示,所以我猜这不是创建控件的问题,也不是我使用控件的问题。不过,这里是我创建这个控件的(简化版)代码:
class View(QtGui.QMainWindow):
ui = None
def __init__(self, config, parent=None):
log.debug("window setup")
self.config = config
super(View, self).__init__(parent)
try:
self.ui = self.Ui(self)
self.setObjectName("appView")
self.setWindowTitle("AvaTalk")
self.show()
except RuntimeError as e:
log.error(e.message)
class Ui(QtCore.QObject):
# [...]
iconDetector = None
buttonDetector = None
# [...]
def __init__(self, window, parent=None):
log.debug("ui setup")
super(View.Ui, self).__init__(parent)
self.window = window
# central widget
log.debug("widget setup")
self.centralWidget = QtGui.QWidget()
self.widgetLayout = QtGui.QVBoxLayout(self.centralWidget)
# create toolbars
#self.createMenubar()
#self.createCanvas()
self.createToolbar()
#self.createStatusbar()
# visualize widget
self.window.setCentralWidget(self.centralWidget)
# actions
log.debug("actions setup")
self.actionQuit = QtGui.QAction(self.window)
self.actionQuit.setObjectName("actionQuit")
self.menuFile.addAction(self.actionQuit)
self.menubar.addAction(self.menuFile.menuAction())
log.debug("connections setup")
QtCore.QObject.connect(self.actionQuit, QtCore.SIGNAL("activated()"), self.window.close)
QtCore.QMetaObject.connectSlotsByName(self.window)
def createToolbar(self):
log.debug("toolbar setup")
self.toolbar = QtGui.QHBoxLayout()
self.toolbar.setObjectName("toolbar")
self.toolbar.addStretch(1)
# camera
# detector
self.iconDetector = LED(LED.Mode.STATIC_OFF,LED.Color.GREEN,10,self.window)
self.buttonDetector = IconButton("Detector", "detector",self.window)
self.toolbar.addWidget(self.iconDetector)
self.toolbar.addWidget(self.buttonDetector)
self.toolbar.addStretch(1)
# analyzer
# extractor
# layout
self.widgetLayout.addLayout(self.toolbar)
可能我在使用QPainter进行绘制时还存在问题。我还没有测试过这一点:实际上在测试时,我发现isVisible()
在设置完成后返回False
。所以我猜我遗漏了一个关键点。不幸的是,我找不到我错过了什么……
也许有人能帮我找出我的问题?谢谢!
1 个回答
2
在实现自定义小部件(widget),也就是从 QWidget
继承的类时,有一点需要注意:默认情况下,sizeHint
或 minimumSizeHint
返回的 QSize
是无效的。这意味着,如果这个小部件被添加到一个布局中,可能会因为其他小部件的影响而缩小到 0
。这样一来,它就变得“不可见”了。虽然 isVisible
仍然会返回 True
,表示这个小部件是“可见”的,但它其实没有任何内容可以显示(大小为0)。所以,如果你得到 False
,那肯定是有其他问题。
因此,有必要为这两个方法定义合理的大小:
class LED(QtGui.QWidget):
# ...
def sizeHint(self):
size = 2 * self.radius + 2
return QtCore.QSize(size, size)
def minimumSizeHint(self):
size = 2 * self.radius + 2
return QtCore.QSize(size, size)
注意:还有其他问题:
- 比如将
mode
、color
等定义为类属性,然后用实例属性覆盖它们。这样做不会出错,但没有意义。 painter.setBrush(QtCore.Qt.RadialGradientPattern)
是错误的。你不能用QtCore.Qt.RadialGradientPattern
创建画刷。这个东西存在是为了让brush.style()
能返回一些东西。如果你想要渐变图案,应该用QGradient
构造函数来创建画刷。if self.mode is self.Mode.STATIC_OFF
:用is
来比较是错误的。is
是用来比较对象的身份的,这里你应该用==
。另外,FLASH_SLOW
和FLASH_MEDIUM
的值都是2
。assert
是用来调试和单元测试的。在实际代码中不应该使用它。如果需要,可以抛出一个异常。或者设置合理的default
值,用来替换无效的值。