How To implementing multi layer frame or widget in Qt? - qt

How can I implement this in Qt, I am using PyQt and Qt Designer but confusing how to use layering like in the following image.
I want to first layer in index 0 to show the image or video,
then the second layer in index 2 is media control which can hide when no move from mouse,
and layer 3 in index 3 when change the volume in example
layer 4 in index 4 for notification etc.
is possible to do this in Qt?

There is a simple way to implement "layers" - you can add child widgets without any layout and resize/move them on resize event of host widget (using event filter). First layer can be organized with any layout as usual.
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt
class Layer(QtCore.QObject):
def __init__(self, host, child, alignment = Qt.AlignLeft, setWidth = False, setHeight = False, parent = None):
super().__init__(parent)
self._host = host
self._child = child
self._alignment = alignment
self._setWidth = setWidth
self._setHeight = setHeight
child.setParent(host)
host.installEventFilter(self)
def eventFilter(self, watched, event):
if watched != self._host or event.type() != QtCore.QEvent.Resize:
return False
hostSize = event.size()
childSize = self._child.sizeHint()
alignment = self._alignment
x = 0
y = 0
dWidth = max(0, hostSize.width() - childSize.width())
dHeight = max(0, hostSize.height() - childSize.height())
if alignment & Qt.AlignRight:
x = dWidth
elif alignment & Qt.AlignHCenter:
x = dWidth / 2
if alignment & Qt.AlignVCenter:
y = dHeight / 2
elif alignment & Qt.AlignBottom:
y = dHeight
width = hostSize.width() if self._setWidth else childSize.width()
height = hostSize.height() if self._setHeight else childSize.height()
self._child.setGeometry(x, y, width, height)
return False
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget()
label1 = QtWidgets.QLabel("right label")
label2 = QtWidgets.QLabel("bottom label")
layer1 = Layer(widget, label1, Qt.AlignRight)
layer2 = Layer(widget, label2, Qt.AlignBottom | Qt.AlignHCenter, True)
widget.show()
sys.exit(app.exec_())

This is one of the very few cases for which using a layout is not suggested, as the visible widgets are "floating" and should be possibly moved around.
The solution is to create a "container" widget that has all those controls as children.
Then, some widgets will need some repositioning and resizing (for instance, the control bar should always be on bottom and occupy the whole width), and that can be implemented in the resizeEvent().
from PyQt5 import QtCore, QtGui, QtWidgets
class ControlBar(QtWidgets.QFrame):
def __init__(self, parent):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
layout.addWidget(self.slider)
buttons = QtWidgets.QHBoxLayout()
layout.addLayout(buttons)
buttons.addWidget(QtWidgets.QToolButton(text='play'))
buttons.addWidget(QtWidgets.QToolButton(text='stop'))
buttons.addStretch()
class VolumeWidget(QtWidgets.QFrame):
def __init__(self, parent):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(2, 2, 2, 0)
layout.setSpacing(1)
handle = QtWidgets.QFrame()
handle.setFixedHeight(12)
handle.setStyleSheet('''
QFrame {
border: 1px solid darkGray;
border-radius: 2px;
background: #aa646464;
}
''')
layout.addWidget(handle)
volumeLayout = QtWidgets.QHBoxLayout()
layout.addLayout(volumeLayout)
for i in range(4):
volumeLayout.addWidget(QtWidgets.QSlider(QtCore.Qt.Vertical))
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.startPos = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
delta = event.pos() - self.startPos
self.move(self.pos() + delta)
class Notification(QtWidgets.QFrame):
def __init__(self, parent):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.label = QtWidgets.QLabel('Notification', alignment=QtCore.Qt.AlignCenter)
layout.addWidget(self.label)
class PlayerWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.video = QtWidgets.QLabel(self)
self.video.setPixmap(QtGui.QPixmap('movie.png'))
self.video.setScaledContents(True)
self.controlBar = ControlBar(self)
self.notification = Notification(self)
self.volumeWidget = VolumeWidget(self)
self.volumeWidget.move(30, 30)
self.setStyleSheet('''
VolumeWidget, ControlBar {
border: 1px outset darkGray;
border-radius: 4px;
background: #aad3d3d3;
}
VolumeWidget:hover, ControlBar:hover {
background: #d3d3d3;
}
Notification {
border: 1px outset darkGray;
border-radius: 4px;
background: #aa242424;
}
Notification QLabel {
color: white;
}
''')
def sizeHint(self):
if self.video.pixmap() and not self.video.pixmap().isNull():
return self.video.pixmap().size()
return QtCore.QSize(640, 480)
def resizeEvent(self, event):
# set the geometry of the "video"
videoRect = QtCore.QRect(
QtCore.QPoint(),
self.video.sizeHint().scaled(self.size(), QtCore.Qt.KeepAspectRatio))
videoRect.moveCenter(self.rect().center())
self.video.setGeometry(videoRect)
# control panel
controlHeight = self.controlBar.sizeHint().height()
controlRect = QtCore.QRect(0, self.height() - controlHeight,
self.width(), controlHeight)
self.controlBar.setGeometry(controlRect)
# notification
notificationWidth = max(self.notification.sizeHint().width(), self.width() * .6)
notificationRect = QtCore.QRect(
(self.width() - notificationWidth) * .5, 20,
notificationWidth, self.notification.sizeHint().height()
)
self.notification.setGeometry(notificationRect)
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.fillRect(self.rect(), QtCore.Qt.black)
Note that in this simple case I only used an image. If you want to play video, you should use the Qt Multimedia module, and in order to correctly have transparency of controls a QGraphicsView with a QGraphicsVideoItem must be used.
In this case, PlayerWidget can directly be a subclass of QGraphicsView.

Related

QPalette and app.setStyleSheet() not impacting child Widget colors

I've got a pretty large PyQt5 application that I'm finally polishing with some colors. I've created a QPalette() and pass that to the app at launch. For the most part, it works (yay!). But not all of the child widgets are picking up the QPalette settings, and so I added a StyleSheet, but that again isn't working consistently. The only way I've been able to impact a widget color is by directly adding it when the widget is created, which is fine in something small.
The Main window:
class MyBigApplication(QMainWindow, QWidget):
def __init__(self):
super(MyBigApplication, self).__init__()
# ... load the various pieces, which include prompting user login and making some background connections.
# Build the GUI
def init_ui(self):
self.statusBar().showMessage('Welcome to MyBigApplication!')
self.grid_layout = QGridLayout()
# Initialize tab screen
self.tabs = QTabWidget()
self.tabs.setTabShape(QTabWidget.Triangular)
self.foo = fooTab(self, self.tabs)
self.bar = barTab(self, self.tabs)
self.baz = bazTab(self, self.tabs)
self.grid_layout.addWidget(self.tabs,0,1,4,1)
main_widget = QWidget()
main_widget.setLayout(self.grid_layout)
self.setCentralWidget(main_widget)
# Additional setup of menus and such
if __name__.endswith('__main__'):
app = QCoreApplication.instance()
while app is not None:
app.close()
app = QApplication(sys.argv)
app.setStyle('Fusion')
dark_palette = QPalette()
# Define some colors to get started
light_grey = QColor(243,243,243)
medium_grey = QColor(211,216,219)
dark_grey = QColor(52,59,64)
dark_palette.setColor(QPalette.Window, QColor(dark_grey))
dark_palette.setColor(QPalette.AlternateBase, QColor(medium_grey))
dark_palette.setColor(QPalette.Button, QColor(dark_grey))
dark_palette.setColor(QPalette.Base, QColor(25, 25, 25)) # almost black
dark_palette.setColor(QPalette.Link, QColor(green))
dark_palette.setColor(QPalette.Highlight, QColor(half_green))
dark_palette.setColor(QPalette.WindowText, QColor(light_grey))
dark_palette.setColor(QPalette.ToolTipBase, QColor(light_grey))
dark_palette.setColor(QPalette.ToolTipText, QColor(light_grey))
dark_palette.setColor(QPalette.Text, QColor(light_grey))
dark_palette.setColor(QPalette.ButtonText, QColor(light_grey))
dark_palette.setColor(QPalette.BrightText, Qt.red)
dark_palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(dark_palette)
app.setFont(QFont('Franklin Gothic Book', 9))
app.setStyleSheet("""
QMainWindow {
}
QToolTip {
color: #f3f3f3;
background-color: #2a82da;
border: 1px solid white;
}
QTableView { # This works
border: 1px solid #218a21
}
QPushButton { # And this works
padding: 10px 15px 10px 15px,
}
QPushButton:hover { # But this does not
background-color: red,
}
QTableView::item:alternate { # And this also does not
background-color: #d3d8db,
}
""")
execute = MyBigApplication()
sys.exit(app.exec_())
The fooTab includes tables of data:
class fooTab(QWidget):
def __init__(self, parent, tabs):
super(fooTab,self).__init__()
self.root = parent
self.tabs = tabs
def init_foo_one(self):
self.foo_tab = QWidget()
self.tabs.addTab(self.foo_tab, 'FOO')
tab_layout = QVBoxLayout()
foo_id_box = QGroupBox('FOO DATA')
clear_button = QPushButton('Clear Foo Data Table')
clear_button.clicked.connect(self.clear_foo_table)
# Set up the Table Model/View/Proxy
self.foo_id_table = QTableView()
self.foo_data_model = fooTableModel() # This is a QAbstractTableModel class
self.foo_data_model.setDataDict(data)
self.foo_id_table_columns = ['1','2','3','4']
self.foo_resizable_cols = [0,1,2,3,4]
self.foo_data_model.setDataHeader(self.foo_id_table_columns)
self.foo_table_proxy.setSourceModel(self.foo_data_model)
self.foo_id_table.setModel(self.foo_table_proxy)
self.foo_id_table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.foo_id_table.setSortingEnabled(True)
self.foo_id_table.setWordWrap(False)
self.foo_id_table.setAlternatingRowColors(True)
# Create a layout for that box using a grid
foo_id_box_layout = QGridLayout()
# Add the widgets into the layout
foo_id_box_layout.addWidget(self.foo_id_table,0,0,1,5)
foo_id_box_layout.addWidget(clear_button,1,2,1,1)
# Setup the layout to be displayed in the box
foo_id_box.setLayout(foo_id_box_layout)
tab_layout.addWidget(foo_id_box)
self.foo_tab.setLayout(tab_layout)
The bazTab:
class BazTab(QWidget):
def __init__(self, parent, tabs):
super(BazTab,self).__init__()
self.root = parent
self.tabs = tabs
self.h1_font = QFont()
self.h1_font.setBold(True)
self.h1_font.setPointSize(16)
self.h2_font = QFont()
self.h2_font.setBold(False)
self.h2_font.setPointSize(12)
self.h3_font = QFont()
self.h3_font.setBold(True)
self.init_ui()
def init_ui(self):
self.component_tab = QScrollArea()
self.tabs.addTab(self.baz_tab, 'Baz')
self.tab_layout = QHBoxLayout()
self.component_tab.setLayout(self.tab_layout)
component_button_box = QGroupBox('Various Buttons')
component_button_layout = QVBoxLayout()
component_button_layout.setAlignment(Qt.AlignTop)
component_button_box.setLayout(component_button_layout)
self.tab_layout.addWidget(component_button_box)
first_button = QPushButton('Request #1')
first_button.clicked.connect(self.request_one)
component_button_layout.addWidget(first_button)
second_button = QPushButton('Request #2')
second_button.clicked.connect(self.request_two)
component_button_layout.addWidget(second_button)
# Several more buttons created in here
# None of these buttons look like the buttons in the info dialog (which have a color from the QPalette)
I can manually edit the QTableView to show alternating colors, only if I add it to each instance.
I cannot get some of the QPushButtons to change, even when I add each instance's styleSheet.
Using the QPalette has saved a ton, by not having to modify widget by widget. For the extra details, I'm fine to use the app.setStyleSheet except that it doesn't always work.
Is it me, or is this just the way it is?

PyQt5 QGroupBox - how to display title in line with the top outside line for the QGroupBox?

This example:
# test1.py
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QPushButton, QRadioButton, \
QGroupBox, QButtonGroup
from PyQt5.Qt import Qt
def main():
app = QApplication([])
mainForm = MainForm()
mainForm.show()
app.exec()
# end function
class MainForm(QWidget):
def __init__(self):
super().__init__()
self.rdoRed = QRadioButton('Red')
self.rdoGreen = QRadioButton('Green')
self.rdoBlue = QRadioButton('Blue')
self.vblColor = QVBoxLayout()
self.vblColor.addWidget(self.rdoRed)
self.vblColor.addWidget(self.rdoGreen)
self.vblColor.addWidget(self.rdoBlue)
self.gbColor = QGroupBox('Choose Color')
self.gbColor.setStyleSheet('QGroupBox:title {'
'subcontrol-origin: padding; }')
self.gbColor.setLayout(self.vblColor)
self.vboxLayout = QVBoxLayout()
self.vboxLayout.addWidget(self.gbColor)
self.setLayout(self.vboxLayout)
# end function
# end class
if __name__ == '__main__':
main()
Renders like this on Ubuntu 18.04:
I'd like the Choose Color text currently just above the top of the QGroupBox boundary line to instead be in line with the top of the QGroupBox boundary, like the Qt docs show:
Notice how Package selection is in line with the top boundary line of the QGroupBox, I'd like to do the same with the Choose Color title in the example I have above.
Is this an operating system limitation and what I'm asking isn't possible? I'm using Ubuntu 18.04.
Or is there a setStyleSheet parameter I can pass in to achieve this? As you can see in the example above I attempted to set some style sheet properties to achieve this but I was unable to find such a property. Or is there some other means with which this can be achieved?
Try it:
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QPushButton, QRadioButton, \
QGroupBox, QButtonGroup
from PyQt5.Qt import *
class MainForm(QWidget):
def __init__(self):
super().__init__()
self.rdoRed = QRadioButton('Red')
self.rdoGreen = QRadioButton('Green')
self.rdoBlue = QRadioButton('Blue')
self.vblColor = QVBoxLayout()
self.vblColor.addWidget(self.rdoRed)
self.vblColor.addWidget(self.rdoGreen)
self.vblColor.addWidget(self.rdoBlue)
self.gbColor = QGroupBox('Choose Color')
self.gbColor.setStyleSheet('''
QGroupBox {
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E0E0E0, stop: 1 #FFFFFF);
border: 2px solid #999999;
border-radius: 5px;
margin-top: 2ex; /*leave space at the top for the title */
font-size: 13px;
color: black;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top center; /* position at the top center */
padding: 0 3px;
font-size: 13px;
color: black;
}
''')
self.gbColor.setLayout(self.vblColor)
self.vboxLayout = QVBoxLayout()
self.vboxLayout.addWidget(self.gbColor)
self.setLayout(self.vboxLayout)
def main():
app = QApplication([])
mainForm = MainForm()
mainForm.show()
app.exec()
if __name__ == '__main__':
main()

Qt text alignment in QCheckBox

I am looking for a way to align text in a QCheckBox to both the right and left side. I cannot seems to find any ways to modify just the alignment of the text and not the checkbox itself.
I don't know if you can access the label associated with the checkbox or not, but if you cannot, a hack would be to set the checkbox label with an empty string and use another QLabel where you can use setAlignment(Qt::AlignJustify) to adjust your text to both the right and left side.
But then I don't know if you consider this modify the checkbox itself and not just the alignement.
I solved it using a trick.
Create a QCheckBox without putting any text in it.
And create a QLable so that you can center the text and click it.
Then it's possible.
Below is the example code.
You can refer to it.
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MyApp(QWidget):
###### Define StyleSheet ######
DEFAULT_COLOR = {
'border': '#000000',
'hover': '#29adff'
}
CP_DEFAULT_STYLE = '''
QCheckBox::indicator:hover {{
border: 1px solid {hover};
background: white;
}}
QCheckBox::indicator {{
border: 1px solid {border};
background: white;
}}
'''
CP_DEFAULT_STYLE_SET_VALUE = CP_DEFAULT_STYLE.format(**DEFAULT_COLOR)
################################
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.cb = QCheckBox(self)
cp_label = QLabel('Click\nHere Me!!', self)
self.cb.setStyleSheet(self.CP_DEFAULT_STYLE_SET_VALUE)
cp_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
cp_label.setGeometry(75+30, 50+15, 60, 30)
self.cb.move(75+90, 50+20)
self.cb.clicked.connect(self.cpClick)
cp_label.mousePressEvent = self.cpLabelClick
cp_label.leaveEvent = self.cpLabelLeave
cp_label.enterEvent = self.cpLabelEnter
self.setWindowTitle('QCheckBox')
self.setGeometry(300, 300, 300, 200)
self.show()
def cpClick(self):
if not self.cb.isChecked():
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE_SET_VALUE
)
else:
self.cb.setStyleSheet('')
def cpLabelClick(self, _):
self.cb.setStyleSheet('')
self.cb.setChecked(
not self.cb.isChecked()
)
def cpLabelLeave(self, _):
self.cb.setStyleSheet('')
def cpLabelEnter(self, _):
if not self.cb.isChecked():
setColer = self.DEFAULT_COLOR.copy()
setColer['border'] = self.DEFAULT_COLOR['hover']
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE.format(**setColer)
)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
or
If you use Qt Designer,
After creating one Qwidget, make QCheckBox and QLabel inside
Bring the QLabel to the front and make the QCheckBox deselected.
And if you write it like the code below, it works perfectly!
import os
import sys
from PyQt5.uic import loadUi
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
def resource_path(*relative_Path_AND_File):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = getattr(sys, '_MEIPASS', os.path.dirname(
os.path.abspath(__file__)))
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, '/'.join(relative_Path_AND_File))
class MyApp_Define:
cb: QCheckBox
cb_label: QLabel
class MyApp(QMainWindow, MyApp_Define):
###### Define StyleSheet ######
DEFAULT_COLOR = {
'border': '#000000',
'hover': '#29adff'
}
CP_DEFAULT_STYLE = '''
QCheckBox::indicator:hover {{
border: 1px solid {hover};
background: white;
}}
QCheckBox::indicator {{
border: 1px solid {border};
background: white;
}}
'''
CP_DEFAULT_STYLE_SET_VALUE = CP_DEFAULT_STYLE.format(**DEFAULT_COLOR)
################################
def __init__(self):
super().__init__()
loadUi(resource_path("TEST.ui"), self)
self.cb_label.installEventFilter(self)
self.cb_label.leaveEvent = self.cbLabelLeave
self.cb_label.mousePressEvent = self.cbLabelClick
def eventFilter(self, source: QObject, event: QEvent):
# 자동 종료 체크박스 안에 들어갈시
if (
source is self.cb_label and
event.type() == QEvent.Type.MouseMove and
not self.cb.isChecked()
):
self.cbLabelEnter()
return super().eventFilter(source, event)
def cbLabelClick(self, _):
self.cb.setStyleSheet('')
self.cb.setChecked(
not self.cb.isChecked()
)
if not self.cb.isChecked():
self.cbLabelEnter()
def cbLabelLeave(self, _):
self.cb.setStyleSheet('')
def cbLabelEnter(self, _=None):
if not self.cb.isChecked():
setColer = self.DEFAULT_COLOR.copy()
setColer['border'] = self.DEFAULT_COLOR['hover']
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE.format(**setColer)
)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec_())
The image below is an example of QtDesigner.

Scaling of QMovie content not working while paused

I am looking into using QMovie for some animated icons in my application. The code below demonstrates some basic usage of this class which I would like to also add as functionality to my application. The problem that I'm facing is that whenever QMovie is paused, scaling the content doesn't seem to work (see screenshots after code). The behaviour is observed in both ways - upscaling (resulting from increase of size of the window) and downscaling (resulting from the decrease of size of the window).
Any idea how to fix that?
from sys import exit, argv
from PyQt4.QtGui import QApplication, QWidget, QCheckBox, QPushButton, QLabel, QGridLayout, QIcon, QSizePolicy, QMovie
from PyQt4.QtCore import Qt, QSize, pyqtSlot, pyqtSignal, QPoint, QRect
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.toggled = False
self.initUI()
def initUI(self):
self.layout = QGridLayout()
self.label = QLabel()
self.label.setMinimumWidth(100)
self.label.setMinimumHeight(100)
self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.label_animation = QMovie('circles.gif')
self.label_animation.setScaledSize(QSize(100, 100))
self.label.setMovie(self.label_animation)
self.label_animation.start()
self.label_animation.setPaused(True)
# self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.layout.addWidget(self.label, 0, 0)
self.button = QPushButton()
self.button.setObjectName('button')
# self.button.setIcon(QIcon('icon.png'))
self.button.setStyleSheet('QPushButton#button{border-top-right-radius: 10px; border-bottom-right-radius: 10px; border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-image: url("icon.png"); background: transparent;}')
self.button.setMinimumWidth(100)
self.button.setMinimumHeight(100)
self.button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.button.clicked.connect(self.toggle)
self.layout.addWidget(self.button, 0, 1)
self.setGeometry(200, 200, 400, 400)
self.setWindowTitle('Icons, Images && Video with PyQt')
self.setLayout(self.layout)
self.resize(self.layout.sizeHint())
def resizeEvent(self, event):
self.label_animation.setScaledSize(QSize(self.label.width(), self.label.height()))
#pyqtSlot()
def toggle(self):
style = 'QPushButton#button{border-top-right-radius: 10px; border-bottom-right-radius: 10px; border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-image: url("icon.png"); '
if not self.toggled:
style = style + 'background-color: rgb(255, 0, 0);}'
else:
style = style + 'background: transparent;}'
self.button.setStyleSheet(style)
self.label_animation.setPaused(self.toggled)
self.toggled = not self.toggled
def main():
app = QApplication(argv)
ex = Example()
ex.show()
exit(app.exec_())
if __name__ == '__main__':
main()
Screenshots:
Resizing window (paused QMovie) - QPushButton on the right with its border image is resized properly however on the left the QMovie's content inside the QLabel (label itself IS resized!). The screenshot below demonstrates failure to upscale the animation.
Resizing window (unpaused QMovie) - content of QMovie gets scaled properly the moment the animation is unpaused.

Is it possible to set the text of the QTableView corner button?

QTableView has a corner button, occupying the intersection between the horizontal and the vertical header. Clicking this will select all cells in the table. What I'm wondering is if it's possible to set this button's text, and if so, how?
I have implemented a working solution with PyQt 5.3, and it took surprisingly little code. My solution is based on code posted in this question at Qt Centre.
from PyQt5 import QtWidgets, QtCore
class TableView(QtWidgets.QTableView):
"""QTableView specialization that can e.g. paint the top left corner header.
"""
def __init__(self, nw_heading, parent):
super(TableView, self).__init__(parent)
self.__nw_heading = nw_heading
btn = self.findChild(QtWidgets.QAbstractButton)
btn.setText(self.__nw_heading)
btn.setToolTip('Toggle selecting all table cells')
btn.installEventFilter(self)
opt = QtWidgets.QStyleOptionHeader()
opt.text = btn.text()
s = QtCore.QSize(btn.style().sizeFromContents(
QtWidgets.QStyle.CT_HeaderSection, opt, QtCore.QSize(), btn).
expandedTo(QtWidgets.QApplication.globalStrut()))
if s.isValid():
self.verticalHeader().setMinimumWidth(s.width())
def eventFilter(self, obj, event):
if event.type() != QtCore.QEvent.Paint or not isinstance(
obj, QtWidgets.QAbstractButton):
return False
# Paint by hand (borrowed from QTableCornerButton)
opt = QtWidgets.QStyleOptionHeader()
opt.initFrom(obj)
styleState = QtWidgets.QStyle.State_None
if obj.isEnabled():
styleState |= QtWidgets.QStyle.State_Enabled
if obj.isActiveWindow():
styleState |= QtWidgets.QStyle.State_Active
if obj.isDown():
styleState |= QtWidgets.QStyle.State_Sunken
opt.state = styleState
opt.rect = obj.rect()
# This line is the only difference to QTableCornerButton
opt.text = obj.text()
opt.position = QtWidgets.QStyleOptionHeader.OnlyOneSection
painter = QtWidgets.QStylePainter(obj)
painter.drawControl(QtWidgets.QStyle.CE_Header, opt)
return True
You can add text on the corner button by finding a child from QTableWidget and add a QLabel to it by using the vertical layout as below.
QAbstractButton* button = qFindChild< QAbstractButton* >(TableWidget);
if (button)
{
QVBoxLayout* lay = new QVBoxLayout(button);
lay->setContentsMargins(0, 0, 0, 0);
QLabel* label = new QLabel("No");
label->setStyleSheet("QLabel {font-face: ArialMT; font-size: 10px; color: #FFFFFF; font-weight: bold; }""QToolTip { color: #ffffff; background-color: #000000; border: 1px #000000; }");
label->setAlignment(Qt::AlignCenter);
label->setToolTip("Text");
label->setContentsMargins(2, 2, 2, 2);
lay->addWidget(label);
}

Resources