Qt - make selection background have similar background to item - css

How can I make the background for when an item is selected to be some sort of blend of current cell background?
The default item-selection background is blue and say if my cell is red, then I'd want it to be red with slightly less opacity:
Example image of how it is
Example image of how I'd like it to be
I have tried setting the color to be transparent:
setStyleSheet("selection-background-color: transparent")
And also the rgba feature with opacity 1%:
setStyleSheet("selection-background-color: rgba(255, 255, 255, 1)")
But neither retain the original color

This example will give you some insight how to manage with tablewidgets and item
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
data = {'col1':['red','blue','green']}
class MyTable(QTableWidget):
def __init__(self, data, *args):
QTableWidget.__init__(self, *args)
self.data = data
self.setmydata()
self.resizeColumnsToContents()
self.resizeRowsToContents()
self.setStyleSheet("""QTableView::item:focus
{
selection-background-color: rgba(255, 0, 0, 128);
}""")
def setmydata(self):
horHeaders = []
for n, key in enumerate(sorted(self.data.keys())):
horHeaders.append(key)
for m, item in enumerate(self.data[key]):
newitem = QTableWidgetItem(item)
self.setItem(m, n, newitem)
colorVal = "Qt.%s" % item
self.item(m, n).setBackground(eval(colorVal))
self.setHorizontalHeaderLabels(horHeaders)
def main(args):
app = QApplication(args)
table = MyTable(data, 3, 1)
table.show()
sys.exit(app.exec_())
if __name__=="__main__":
main(sys.argv)

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?

QComboBox text renders as white/transparent when item is selected

I have a QComboBox in my program with 5 items + 1 blank (default) item. I implemented a stylesheet to make the background transparent when nothing is changed (i.e. when the item selected is the blank/default one) and light-green, when the item selected, is different. My problem is: when I select a different item, Qt renders the selected item color as white:
When I click on other elements of the application (buttons, the QDateEdit, etc), it renders the value black (expected behavior):
I don't know what I'm missing here. I tried to look for a solution or even a diagnosis of this problem in both StackOverflow and the Qt forums. For reference, my QComboBox's stylesheet is the following:
QComboBox#access_level[changed="false"]{
background-color:"transparent";
}
QComboBox#access_level[changed="true"]{
background-color:rgba(171, 246, 183, 50);
color:rgb(0, 0, 0);
}
I also tried to remove the stylesheet and the problem persisted. Also, to implement the changed variable my code looks like the following:
class MassActionDialog(QDialog):
def __init__(self, parent=None):
'''
super(MassActionDialog, self).__init__(parent)
self.dialog = Ui_MassActionDialog()
self.dialog.setupUi(self)
.
.
self.original_access_level = 60
self.access_level = self.original_access_level
self.dialog.access_level.currentIndexChanged.connect(self.on_access_level_change)
def on_access_level_change(self, idx):
new_access_level = (idx + 1)*10
if new_access_level == self.original_access_level:
# we mark the "changed" property as false
self.dialog.access_level.setProperty("changed", False)
else:
self.dialog.access_level.setProperty("changed", True)
self.access_level = new_access_level
# reload the stylesheet of the object
self.dialog.access_level.style().unpolish(self.dialog.access_level)
self.dialog.access_level.style().polish(self.dialog.access_level)
Any help in this matter would be greatly appreciated!
Your problem is in QSS, just add color property to state with changed="false", like:
QComboBox#access_level[changed="false"]{
background-color:"transparent";
color:rgb(0, 0, 0);
}
QComboBox#access_level[changed="true"]{
background-color:rgba(171, 246, 183, 50);
color:rgb(0, 0, 0);
}
And one more thing - set changed property in __init__ with your default value.

How To implementing multi layer frame or widget in 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.

Cell spacing and focus rectangle in QTableView

I have a QTableView that shows some data from my custom subclass of QAbstractTableModel. The problem is that the text is pushed up against the left side of the cell. I found this question: How to set the padding of QTableView cells through CSS? It led me to try this:
self.tableView.setStyleSheet('QTableView::item {border: 0px; padding: 5px;} ')
That indented the text but introduced a new problem. Now when I click on a cell, the dotted focus rectangle is inset into the cell.
How can I have both cell spacing and a dotted focus rectangle that surrounds the entire cell (not inset)?
Using CSS is not the best way to do this, take 5 minutes of your time and write delegate, it's easy.
But you can try:
QTableView {
outline: 0; /* Disable focus rect*/
}
QTableView:item {
border: 0px;
padding: 0px 10px;
}
QTableView::item:focus { /*Emulate focus*/
color: black;
background-color: yellow;
border: 1px dashed black;
}
Here is what I was finally able to come up with. It puts the focus rectangle on the border of each cell, regardless of "padding" or "margin". It also preserves the stylesheet. At least it preserves background color and padding. I didn't test all stylesheet options. However, it does not preserve the text color in the cell with focus. (P.S. This is working with PySide 1.1.1)
class CellDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent):
super(CellDelegate, self).__init__(parent)
self._parent = parent
def paint(self, qPainter, option, qModelIndex):
v4Option = QtGui.QStyleOptionViewItemV4(option)
v4Option.index = qModelIndex
value = qModelIndex.data()
v4Option.text = str(value)
style = self._parent.style()
if (v4Option.state & QtGui.QStyle.State_HasFocus):
# --- The table cell with focus
# Draw the background
style.drawPrimitive(style.PE_PanelItemViewItem, v4Option, qPainter, self._parent)
# Draw the text
subRect = style.subElementRect(style.SE_ItemViewItemText, v4Option, self._parent)
alignment = qModelIndex.data(QtCore.Qt.TextAlignmentRole)
if not alignment:
alignment = int(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
if (v4Option.state & QtGui.QStyle.State_Enabled):
itemEnabled = True
else:
itemEnabled = False
textRect = style.itemTextRect(v4Option.fontMetrics, subRect, alignment, itemEnabled, value)
style.drawItemText(qPainter, textRect, alignment, v4Option.palette, v4Option.state, value)
# Draw the focus rectangle
focusOption = QtGui.QStyleOptionFocusRect()
focusOption.rect = v4Option.rect
style.drawPrimitive(style.PE_FrameFocusRect, focusOption, qPainter, self._parent)
else:
# --- All other table cells
style.drawControl(style.CE_ItemViewItem, v4Option, qPainter, self._parent)
Here is some example code showing how to use it. The goal is that the stylesheet would be set in a .ui file. This is just a self-contained example:
class TestTableModel(QtCore.QAbstractTableModel):
headerNames = ('Column 1', 'Column 2')
def __init__(self):
super(TestTableModel, self).__init__()
self._data = [['test', 'text'], ['yyy', 'zzz']]
#----- Overridden Functions ------------------------------------------------
def columnCount(self, parentIndex):
return len(self.headerNames)
def data(self, qModelIndex, role=QtCore.Qt.DisplayRole):
if qModelIndex.isValid():
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
dataItem = self._data[qModelIndex.row()][qModelIndex.column()]
return dataItem
return None
def headerData(self, colNum, orientation, role):
if (orientation == QtCore.Qt.Horizontal) and (role == QtCore.Qt.DisplayRole):
return self.headerNames[colNum]
return None
def rowCount(self, parentIndex=QtCore.QModelIndex()):
return len(self._data)
#------------------------------------------------------------------------------
class TestTableViewSpacing(QtGui.QMainWindow):
def __init__(self, parent=None):
super(TestTableViewSpacing, self).__init__(parent)
self.tableView = QtGui.QTableView()
self.setCentralWidget(self.tableView)
tableModel = TestTableModel()
self.tableView.setModel(tableModel)
self.tableView.setStyleSheet('QTableView::item {border: 0px; padding: 5px; margin: 5px; color: yellow; '
'background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #330055, stop: 1 #000000);} '
'QTableView::item:focus {border: 0px; background-color: darkred; color: yellow;}')
# VERY IMPORTANT!! Must pass the table view to the delegate, or it will not work!
newDelegate = CellDelegate(self.tableView)
self.tableView.setItemDelegate(newDelegate)

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