Custom widget background color in pyside2 - python

Trying to work out how to set the background color in a QWidget. Here is my code:
class ParentTester(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
left = ColorTester(self)
right = QFrame(self)
right.setFrameStyle(QFrame.Panel | QFrame.Sunken)
layout = QHBoxLayout()
layout.addWidget(left)
layout.addWidget(right)
self.setLayout(layout)
class ColorTester(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
palette = self.palette()
palette.setColor(QPalette.Window, QColor(128, 0, 0))
self.setPalette(palette)
def main():
import sys
from PySide2.QtWidgets import QApplication
app = QApplication([])
works = True
if works:
win = ColorTester()
else:
win = ParentTester()
win.show()
win.resize(640, 480)
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This works if I create the class as a topmost window. However if I make it a child of another control, the background color goes back to the default. Some of the other color roles do take effect, but not the background color. Not only that, but the colors get passed through to child controls.
How can I change the background color of a control but not its child controls?

By default, the child widgets take the color of the window, so you observe that effect, if you want the custom background color to be used then you must enable the autoFillBackground property:
class ColorTester(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
palette = self.palette()
palette.setColor(QPalette.Window, QColor(128, 0, 0))
self.setPalette(palette)
self.setAutoFillBackground(True)

I finally settled on overriding paintEvent for my widget. Setting the color in the palette always seems to pass the color down to child controls, which is not what I wanted. Here is an example of what worked for me. This is a QFrame that takes the default background color and darkens and green-shifts it slightly.
class GreenFrame(QFrame):
def __init__(self, parent=None):
super().__init__(parent)
r, g, b, a = self.palette().color(QPalette.Window).toTuple()
self._bgcolor = QColor(r * 7 // 8, g, b * 7 // 8)
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(event.rect(), self._bgcolor)
super().paintEvent(event)

Related

QPaintEvent event rect for scrollable widget

I have a QPaintEvent override for a custom widget that has a fixed size set. This fixed size can change per instance but in this simple example, ive set it. however the PaintEvent doesn't take it into account so when the users scrolls to the right the rectangle shouldn't paint rounded corners since the widget extends past the visible viewport. How do i fix this?
Full widget painted correctly...
When i resize dialog and scroll right, you'll see rounded corners appear on the left side... when it should NOT.
They should look like this...
Code
import os
import sys
from PySide2 import QtGui, QtWidgets, QtCore, QtSvg
class Card(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Card, self).__init__(parent=parent)
self.label = QtWidgets.QLabel('Help This Paint Event Is Broken')
self.label.setFixedHeight(40)
self.label.setFixedWidth(300)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.label)
# overrides
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
painter.setOpacity(1.0)
painter.setRenderHints(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QColor(0, 0, 0, 128))
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtGui.QColor('#F44336'))
painter.drawRoundedRect(event.rect(), 12, 12)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.end()
class ListViewExample(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ListViewExample, self).__init__(parent)
self.resize(200,200)
self.listView = QtWidgets.QListWidget()
self.listView.setSpacing(10)
self.listView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.listView.verticalScrollBar().setSingleStep(10)
# layout
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.setContentsMargins(0,0,0,0)
self.mainLayout.addWidget(self.listView)
self.setLayout(self.mainLayout)
for x in range(50):
wgt = Card()
self.appendItem(wgt)
def appendItem(self, widget):
lwi = QtWidgets.QListWidgetItem()
lwi.setSizeHint(widget.sizeHint())
self.listView.addItem(lwi)
self.listView.setItemWidget(lwi, widget)
################################################################################
# Widgets
################################################################################
def unitTest_CardDelegate():
app = QtWidgets.QApplication(sys.argv)
window = ListViewExample()
window.show()
app.exec_()
if __name__ == '__main__':
pass
unitTest_CardDelegate()
QPaintEvent::rect() returns the visible rectangle, not the rectangle of the widget itself, so you observe this behavior. The solution is:
painter.drawRoundedRect(self.rect(), 12, 12)

PyQt5: setParent on QPushButton does not add it to parent

In reference to this answer on adding a new tab button to QTabWidget,
I am unsure where the QPushButton is added to the QTabBar.
I assume the setParent method on the pushButton adds it to the tab bar.
But when I try to implement it, the pushButton doesnt seem to appear anywhere on the tab bar even if I add hard values to the move operation.
Here is a minimum reproducible example,
from PyQt5 import QtGui, QtCore, QtWidgets
class TabBarPlus(QtWidgets.QTabBar):
"""Tab bar that has a plus button floating to the right of the tabs."""
plusClicked = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
# Plus Button
self.plusButton = QtWidgets.QPushButton("+")
self.plusButton.setParent(self)
self.plusButton.setFixedSize(20, 20) # Small Fixed size
self.plusButton.clicked.connect(self.plusClicked.emit)
self.movePlusButton() # Move to the correct location
# end Constructor
def sizeHint(self):
"""Return the size of the TabBar with increased width for the plus button."""
sizeHint = QtWidgets.QTabBar.sizeHint(self)
width = sizeHint.width()
height = sizeHint.height()
return QtCore.QSize(width+25, height)
# end tabSizeHint
def resizeEvent(self, event):
"""Resize the widget and make sure the plus button is in the correct location."""
super().resizeEvent(event)
self.movePlusButton()
# end resizeEvent
def tabLayoutChange(self):
"""This virtual handler is called whenever the tab layout changes.
If anything changes make sure the plus button is in the correct location.
"""
super().tabLayoutChange()
self.movePlusButton()
# end tabLayoutChange
def movePlusButton(self):
"""Move the plus button to the correct location."""
# Find the width of all of the tabs
size = sum([self.tabRect(i).width() for i in range(self.count())])
# size = 0
# for i in range(self.count()):
# size += self.tabRect(i).width()
# Set the plus button location in a visible area
h = self.geometry().top()
w = self.width()
if size > w: # Show just to the left of the scroll buttons
self.plusButton.move(w-54, h)
else:
self.plusButton.move(size, h)
# end movePlusButton
# end class MyClass
class CustomTabWidget(QtWidgets.QTabWidget):
"""Tab Widget that that can have new tabs easily added to it."""
def __init__(self, parent=None):
super().__init__(parent)
# Tab Bar
self.tab = TabBarPlus()
self.setTabBar(self.tab)
# Properties
self.setMovable(True)
self.setTabsClosable(True)
# Signals
self.tab.plusClicked.connect(self.addTab)
# self.tab.tabMoved.connect(self.moveTab)
# self.tabCloseRequested.connect(self.removeTab)
# end Constructor
# end class CustomTabWidget
class AppDemo(QtWidgets.QMainWindow):
def __init__(self):
super(AppDemo, self).__init__()
self.centralwidget = QtWidgets.QWidget(self)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setContentsMargins(0, -1, 0, -1)
self.playlist_manager = CustomTabWidget(self.centralwidget)
self.horizontalLayout.addWidget(self.playlist_manager)
blankWidget = QtWidgets.QWidget(self.playlist_manager)
self.playlist_manager.addTab(blankWidget, "New")
self.setCentralWidget(self.centralwidget)
self.show()
# end class AppDemo
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = AppDemo()
w.setWindowTitle('AppDemo')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Expected behvaiour is that a "+" button appears at the right of all the tabs,
but no such button appears.
Okay so after some brainstorming, I figured the issue out. Unlike PyQt4. The QTabBar width does not span the entire width of the QTabWidget, and as such the PlusButton when moved to the right of all the tabs will exceed the width of the parent widget and disappear.
The Solution to this is to add the QPushButton in the QTabWidget itself and emit layoutchange from the QTabBar.
Here is a working example, I have modified values to fit my use case
class tabBarPlus(QTabBar):
layoutChanged = pyqtSignal()
def resizeEvent(self, event):
super().resizeEvent(event)
self.layoutChanged.emit()
def tabLayoutChange(self):
super().tabLayoutChange()
self.layoutChanged.emit()
class customTabWidget(QTabWidget):
plusClicked = pyqtSignal()
def __init__(self, parent=None):
super(customTabWidget, self).__init__(parent)
self.tab = tabBarPlus()
self.setTabBar(self.tab)
self.plusButton = QPushButton('+', self)
self.plusButton.setFixedSize(35, 25)
self.plusButton.clicked.connect(self.plusClicked.emit)
self.setMovable(True)
self.setTabsClosable(True)
self.tab.layoutChanged.connect(self.movePlusButton)
def movePlusButton(self):
size = sum([self.tab.tabRect(i).width() for i in range(self.tab.count())])
h = max(self.tab.geometry().bottom() - 24, 0)
w = self.tab.width()
print(size, w, h)
if size > w:
self.plusButton.move(w-self.plusButton.width(), h)
else:
self.plusButton.move(size-2, h)

Why QWidget behaves differently when it is inherited

When I use inherited QWidget, the margins and spacing have NO background color unlike using QWidget directly (the code is basically the same).
Pure QWidget:
class App(QWidget):
def __init__(self):
super().__init__()
self.start()
def start(self):
self.layout = QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.setGeometry(10, 10, 500, 100)
grid_layout = QGridLayout()
grid_layout.setSpacing(10)
widget = QWidget()
widget.setLayout(grid_layout)
widget.setStyleSheet('background: green')
grid_layout.addWidget(QLabel("first"), 0, 0)
grid_layout.addWidget(QLabel("second"), 0, 1)
grid_layout.addWidget(QLabel("third"), 0, 2)
self.layout.addWidget(widget)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = App()
#ex.start_card_holder()
sys.exit(app.exec_())
Inherited QWidget:
class App(QWidget):
....
class MainWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
def start(self):
...
widget = App.MainWidget()
...
Can anyone tell what I did wrong?
The following concepts must be clear to understand the behavior:
By default the classes that inherit from QWidget do not implement the painting based on the Qt QStyleSheet as indicated by the following questions:
Qt Stylesheet for custom widget
CSS Stylesheet is not applied to custom QWidget
Set background colour for a custom QWidget
By setting the "background: green" you are indicating that the widget takes that color if it is enabled and its children.
In your case the "widget" if it is a QWidget then it will be painted but if it is a MainWidget no.
To check what I indicate, it is only necessary to enable the background color using one of the answers to the questions indicated above:
# ...
widget = App.MainWidget()
widget.setAttribute(Qt.WA_StyledBackground, True)
# ...

How to add a second pixmap to an abstract button when pushed in PySide/PyQt

I'm very new to PySide/PyQt environment. I'm trying to make a menu of buttons on top and assign a task to each so that when they are clicked a function draws a painting on the central window. But I also want to make the button change when they are clicked.
I think this might be an straighforward problem to solve if I use QPushButton, but my buttons are images and I'm using the method suggested HERE and use QAbstractButton to create them.
It is mentioned there that
You can add second pixmap and draw it only when the mouse pointer is
hover over button.
And I'm trying to do exactly that. My question is this:
what are possible ways to achieve this? Are the same methods in QPushButtons applicable here? If so, are there any examples of it somewhere?
Here is a snippet of my code:
import sys
from PySide import QtGui, QtCore
BACKGROUND_COLOR = '#808080'
ICON_PATH_ACTIVE = 'icons/activ'
ICON_PATH_PASSIVE = 'icons/pasiv'
class MainWindow(QtGui.QMainWindow):
def __init__(self, app=None):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
dockwidget = QtGui.QWidget()
self.setGeometry(200, 200, 400, 300)
hbox = QtGui.QHBoxLayout()
1_button = PicButton(QtGui.QPixmap("icons/pasiv/1.png"))
2_button = PicButton(QtGui.QPixmap("icons/pasiv/2.png"))
3_button = PicButton(QtGui.QPixmap("icons/pasiv/3.png"))
hbox.addWidget(1_button)
hbox.addWidget(2_button)
hbox.addWidget(3_button)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.setAlignment(hbox, QtCore.Qt.AlignTop)
dockwidget.setLayout(vbox)
self.setCentralWidget(dockwidget)
class PicButton(QtGui.QAbstractButton):
def __init__(self, pixmap, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
self.setFixedSize(100, 100)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(event.rect(), self.pixmap)
def main():
app = QtGui.QApplication(sys.argv)
central = MainWindow()
central.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Thank you.
Use a regular QPushButton with an icon.
iplay = QtGui.QIcon("path/play_icon.png")
ipause = QtGui.QIcon("path/pause_icon.png")
btn = QtGui.QPushButton(ipause, "", None)
def toggle_play():
if btn.icon() == ipause:
btn.setIcon(iplay)
# Do Pause Action
else:
btn.setIcon(ipause)
# Do Play Action
btn.clicked.connect(toggle_play)
btn.show()
If you want hover functionality then you will have to subclass the QPushButton
class MyButton(QtGui.QPushButton):
custom_click_signal = QtCore.Signal()
def enterEvent(self, event):
super().enterEvent(event)
# Change icon hove image here
def leaveEvent(self, event):
super().leaveEvent(event)
# Change icon back to original image here.
def mousePressEvent(self, event):
super().mousePressEvent(event)
self.custom_click_signal.emit()
# connect to signal btn.custom_click_signal.connect(method)
Icons are probably the easiest way instead of manually managing the paint event. There are also mousePressEvent and mouseReleaseEvents if you want the icon to change for someone holding the button down.

Animate using a pixmap or image sequence in Python with QT4

I have a small Python script that makes a transparent window for displaying a graphic on screen and I'd like to animate that graphic, but am entirely unsure how or where to even start. Here's what I do have at least:
import sys
from PyQt4 import QtGui, Qt, QtCore
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
self.setAutoFillBackground(True)
pixmap = QtGui.QPixmap("test1.gif")
pixmap2 = QtGui.QPixmap("test2.gif")
width = pixmap.width()
height = pixmap.height()
self.setWindowTitle("Status")
self.resize(width, height)
self.label = QtGui.QLabel(self)
def animateEvent():
imgnumber = 0
try:
if imgnumber == 1:
self.label.setPixmap(QtGui.QPixmap("test1.gif"))
self.setMask(pixmap.mask())
imgnumber = 0
else:
self.label.setPixmap(QtGui.QPixmap("test2.gif"))
self.setMask(pixmap2.mask())
imgnumber = 1
finally:
QtCore.QTimer.singleShot(1000, animateEvent)
animateEvent()
def paintEvent(self,event):
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
x = Transparent()
x.show()
app.exec_()
This feels like it has the right ingredients, but the pixmap doesn't update.
I tried QMovie, but then the area of the window that is supposed to be transparent is filled with black instead.
check out this code from www.daniweb.com and see if you can modify it to your needs:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MoviePlayer(QWidget):
def __init__(self, gif, parent=None):
super(MoviePlayer, self).__init__(parent)
self.setGeometry(200, 200, 400, 400)
self.setWindowTitle("QMovie to show animated gif")
self.movie_screen = QLabel()
self.movie_screen.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.movie_screen.setAlignment(Qt.AlignCenter)
btn_start = QPushButton("Start Animation")
btn_start.clicked.connect(self.start)
btn_stop = QPushButton("Stop Animation")
btn_stop.clicked.connect(self.stop)
main_layout = QVBoxLayout()
main_layout.addWidget(self.movie_screen)
main_layout.addWidget(btn_start)
main_layout.addWidget(btn_stop)
self.setLayout(main_layout)
self.movie = QMovie(gif, QByteArray(), self)
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.setSpeed(100)
self.movie_screen.setMovie(self.movie)
def start(self):
"""
Start animation
"""
self.movie.start()
def stop(self):
"""
Stop the animation
"""
self.movie.stop()
app = QApplication(sys.argv)
player = MoviePlayer("/path/to/image.gif")
player.show()
sys.exit(app.exec_())
This ended up being a simple correction of an oversight in the end.
imgnumber needed to be outside of the def as self.imgnumber and needed to be named self.imgnumber each time it was changed.
First, just make sure your animated gif really does have a proper transparent background. The following code works for me, using this fire image as a source:
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
filename = "test.gif"
size = QtGui.QImage(filename).size()
self.setWindowTitle("Status")
layout = QtGui.QVBoxLayout(self)
layout.setMargin(0)
self.movie = QtGui.QMovie(filename)
self.label = QtGui.QLabel(self)
self.label.setMovie(self.movie)
layout.addWidget(self.label)
self.resize(size)
self.movie.start()
This will create a completely transparent and frameless window, with the animated gif playing in a QMovie. There is no black being drawn behind the image. It should fully see through to what ever is underneath.
It is not so far off from your original code. You shouldn't need to set the mask, or do a paint event.

Categories

Resources