How to implement list of checkable buttons with pyqt - python

I want to implement list of radiobuttons, but radiobutton shouldn't have a circle and if radiobutton is checked, then the background color is different from the others.
Like in this photo.
(https://i.stack.imgur.com/LsOeE.png)
I think I can use radiobuttons or tabs, but i don't know how to change the styles of this things. Tell me a widget with similar logic or how to change the style of radiobuttons/tabs.

If you don't need the circle of a radio button, then you probably don't need a radio button at all.
Instead, use standard QPushButtons (or QToolButtons) with setCheckable(True) and add them to a QButtonGroup.
win = QWidget()
win.setStyleSheet('''
QPushButton[checkable="true"]::checked {
background: red;
}
''')
layout = QHBoxLayout(win)
group = QButtonGroup()
for title in 'cdefgh':
button = QPushButton(title)
layout.addWidget(button)
button.setCheckable(True)
group.addButton(button)
group.buttons()[0].setChecked(True)

i set css rule:
css = '''
QRadioButton::indicator {
image: none;
}
QRadioButton::checked
{
background-color : red;
}
'''
radioButton.setStyleSheet(css)

Related

Styling the tab bar of QtADS in a PyQt project

I am working on a PyQt project, which makes use of the library QtAds (Qt Advanced Docking System). In order to redesign the software correctly, I'd like to be able to style every widget with QSS, including the ones added by QtAds, which should be able to be styled like any other standard Widget. However, I can't find the proper selector to style the tab bar of the ADS DockWidgets. Here is a MWE :
import sys
from PyQt5.QtWidgets import QPushButton,QApplication
import os
from PyQt5 import uic
from PyQtAds import QtAds
from customWidgets import *
UI_FILE = os.path.join(os.path.dirname(__file__), 'ui/MainWindow.ui')
MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE)
class MainWindow(MainWindowUI, MainWindowBase):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.dock_manager = QtAds.CDockManager(self)
self.setStyleSheet("""
QToolButton {
background-color: #2c3e50;
border : 1px solid #bdc3c7;
width : 30px;
height : 30px;
}
QToolButton:hover {
background-color: #7f8c8d;
}
QTabWidget::tab {
background : red;
}
""")
scope_settings_dockable = QtAds.CDockWidget("Test")
scope_settings_dockable.setWidget(QPushButton())
# scope_settings_dockable.tabWidget().setStyleSheet("* { background : red; }") # That works
self.menuView.addAction(scope_settings_dockable.toggleViewAction())
self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, scope_settings_dockable)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Here there is only one dockable widget which contains a QPushButton. The stylesheet defined here is able to change the styles on the buttons controlling the dockable widget, which are plain QToolButton and therefore are easy to edit. However, whenever I try to style the tab themselves, I can't find the right selector. I have tried ads--CDockWidgetTab, which should be the right selector according to the documentation, but it doesn't change the background. Tried with only CDockWidgetTab, and a few other syntaxes, but I can't get it to be styled.
As commented in the example, I found a way to style each tab individually, with dockable.tabWidget.setStylesheet(), but that is obviously not a nice solution since when having multiple widgets, I will have to restyle them individually, and will lead to a lot of redundancy.
Do anyone knows which selectors would allow me to style QtAds widgets ?

Qt Dialog in Main Window, make the background dimmed

I am trying to create a dynamic custom dialog class. That takes the central widget as a parameter and that centralwidget will be the main widget of this custom dialog. (for dynamic)
When It will show itself, it will make the background dark/dimmed.
Probably I should use exec_ function for not clickable out of dialog area.
The important thing is to add himself to the layout of the mainwindow, so it can automatically adjust itself when the mainwindow's size/position changes.
I can do this with hooking resizeEvent/moveEvent but I am looking better way to do this. If I add this custom dialog to mainwindow's layout, it is gonna be better.
Thanks.
Creating a child widget that is drawn over the current content of the window and resized in the resizeEvent() override is absolutely not a problem. In fact, it's practically what Qt does every time a widget using a layout manager is resized. The benefit of this approach is that you can completely "cover" all contents of the window, including the menu bar, the status bar and any dock/toolbar.
If you still want them to be usable and only want to cover the main widget, you can do the same by setting the "cover" as a child of the main widget itself, instead of using the window as a parent.
An alternative could be to use a QStackedWidget as a central widget, and set the layout (which is a QStackedLayout) to use the StackAll stackingMode, which will allow you to show all "pages" of the stacked layout superimposed.
Note that this approach has an important drawback: you must take care of the tab-focus. Since all widgets (including widgets that belong to another "page") are shown and enabled, changing focus through Tab will allow changing the focus to widgets that are not part of your "dialog".
I'll leave you with a basic example, the central widget is a QTableWidget, and it shows the "popup" whenever an item is doubleclicked.
Please carefully study it and try to understand what it does.
from PyQt5 import QtCore, QtWidgets
class Container(QtWidgets.QWidget):
def showEvent(self, event):
if not event.spontaneous():
self.setFocus()
# certain widgets might want to keep focus on tab
# so we delay the focusNextChild
QtCore.QTimer.singleShot(0, self.focusNextChild)
def focusNextPrevChild(self, isNext):
# keep tab focus on this widget
super().focusNextPrevChild(isNext)
return self.isAncestorOf(QtWidgets.QApplication.focusWidget())
def paintEvent(self, event):
# stylesheets set on QWidget subclasses need this
qp = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOption()
opt.initFrom(self)
qp.drawPrimitive(QtWidgets.QStyle.PE_Widget, opt)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.menuBar().addMenu('Test').addAction('Action')
self.stack = QtWidgets.QStackedWidget(self)
self.setCentralWidget(self.stack)
self.stack.layout().setStackingMode(QtWidgets.QStackedLayout.StackAll)
table = QtWidgets.QTableWidget(20, 30)
self.stack.addWidget(table)
table.cellDoubleClicked.connect(self.showDialog)
self.resize(QtWidgets.QApplication.primaryScreen().size() * 2 / 3)
def showDialog(self, row, column):
background = QtWidgets.QWidget(objectName='background')
background.setStyleSheet('''
#background {
background: rgba(64, 64, 64, 64);
}
Container {
background: palette(window);
border: 1px outset palette(window);
border-radius: 5px;
}
''')
backLayout = QtWidgets.QVBoxLayout(background)
container = Container()
backLayout.addWidget(container, alignment=QtCore.Qt.AlignCenter)
container.setAutoFillBackground(True)
layout = QtWidgets.QVBoxLayout(container)
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(20)
font = self.font()
font.setPointSize(font.pointSize() * 3)
layout.addWidget(QtWidgets.QLabel(
'Hello!', font=font, alignment=QtCore.Qt.AlignCenter))
layout.addWidget(QtWidgets.QLabel(
'You doubleclicked cell {}, {}'.format(row + 1, column + 1)))
button = QtWidgets.QPushButton('Close')
layout.addWidget(button)
self.centralWidget().addWidget(background)
self.centralWidget().setCurrentWidget(background)
# Important! you must always delete the widget when you don't need it
# anymore. Alternatively, hide it if you want to reuse it again later
button.clicked.connect(background.deleteLater)
app = QtWidgets.QApplication([])
win = MainWindow()
win.show()
app.exec()

PyQt5 Calendar Widget Children

I want to change the style of the calendar widget; however, I couldn't change the background of the months drop-down menu (which I guess is ComboBox).Also there are some dark gray rects at the sides of the 'Dec 2021' text. How could I change them, too? Thanks in advance.
Here is what I've done so far;
self.dateEdit.setStyleSheet(
f"QDateEdit{{font-size: {int(settings['FONT_SIZE_PRIMARY']*0.6)}px; font-family: {settings['FONT']};\
color: {settings['COLOR_PRIMARY']};background-color: {settings['COLOR_BG_PRIMARY']};}}"
f"QCalendarWidget{{font-size: {int(settings['FONT_SIZE_SECONDARY']*0.7)}px;\
font-family: {settings['FONT']};}}"
f"QAbstractItemView{{background-color: {settings['COLOR_PRIMARY']};}}"
)
The month selection popup is actually a QMenu, so you need to use the appropriate selector.
The navigation bar has a hardcoded object name (qt_calendar_navigationbar), so you can use the #id selector.
QMenu {
background: orange;
}
QMenu::item:selected {
background: yellow;
border-radius: 2px;
}
#qt_calendar_navigationbar {
background: rgb(255, 168, 88)
}
All buttons in the navigation bar have object names (always have a look at the sources to check for those), so you can style them individually:
qt_calendar_prevmonth
qt_calendar_nextmonth
qt_calendar_monthbutton
qt_calendar_yearbutton
qt_calendar_yearedit

Is there a way to change the stylesheet of just OK button of all the QMessageBox?

I would like to have the OK button of all the QMessageBox of my GUI (which is quite complex and so there are a lot of them) set a different color with respect to the other buttons (Cancel, No etc.).
I don't want to set its color every time I create a new QMessageBox but I would like to set it through the stylesheet of my application once and for all.
I tried different options but none of them worked:
- QMessageBox#okButton {background-color:blue}
- QMessageBox::okButton {...}
- QMessageBox:okButton {...}
- QMessageBox#ok-button {...}
- QMessageBox QPushButton#okButton {...}
and others...
Is there a way or do I have to give up?
A possible solution is to set the objectName() to be a selector for each button and for this you can use the notify() method of the QApplication:
from PySide2.QtCore import QEvent
from PySide2.QtWidgets import *
class Application(QApplication):
def notify(self, receiver, event):
if isinstance(receiver, QMessageBox) and event.type() == QEvent.Show:
for button in receiver.buttons():
sb = receiver.standardButton(button)
if not button.objectName():
button.setObjectName(sb.name.decode())
button.style().unpolish(button)
button.style().polish(button)
return super().notify(receiver, event)
def main():
app = Application()
app.setStyle("fusion")
app.setStyleSheet(
"""
QPushButton#Ok { background-color: green }
QPushButton#Cancel { background-color: red }
"""
)
QMessageBox.critical(
None, "Title", "text", buttons=QMessageBox.Ok | QMessageBox.Cancel
)
msgBox = QMessageBox()
msgBox.setText("The document has been modified.")
msgBox.setInformativeText("Do you want to save your changes?")
msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msgBox.exec_()
if __name__ == "__main__":
main()
Unfortunately, there's no direct solution, since QDialogButtonBox (which is what QMessageBox uses for its buttons) doesn't provide selectors for button roles.
For staticly created messagebox, the only way is to use the [text="button text"] selector, but that would just be a guess, which could change depending on the localization and the style (which could set a mnemonic, and you cannot even know what that would be for sure). Also, this requires setting the stylesheet for the QApplication.
A possible application wide stylesheet for these situations would look like this:
app.setStyleSheet('''
QDialogButtonBox > QPushButton[text="&OK"] {
background-color: orange;
}
QDialogButtonBox > QPushButton[text="&Cancel"] {
background-color: green;
}
''')
Note that I used the Parent > Class selector in order to ensure that only buttons of QDialogButtonBox are styled with those rules, otherwise any "Ok" button would be orange, etc.
Nonetheless, in the case above on my computer it only works for the Ok button, since my localization (Italian) uses "&Annulla" for the other.
On the other hand, if you're creating QMessageBox instances, there's more freedom and flexibility using selectors based on object names.
The only issue is that since the object name is set after the creation, the stylesheet is not applied, so the buttons must be "unpolished" after instantiation.
A simple subclass could provide a standard interface without complicating things too much:
class ColoredMessageBox(QtWidgets.QMessageBox):
StandardNames = {
QtWidgets.QMessageBox.Ok: 'Ok',
QtWidgets.QMessageBox.Cancel: 'Cancel',
QtWidgets.QMessageBox.Save: 'Save',
# ...
}
def exec_(self):
for button in self.buttons():
objName = self.StandardNames.get(self.standardButton(button))
if objName:
button.setObjectName(objName)
self.style().unpolish(button)
return super().exec_()
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet('''
QMessageBox > QDialogButtonBox > QPushButton#Ok {
background-color: orange;
}
QMessageBox > QDialogButtonBox > QPushButton#Cancel {
background-color: blue;
}
''')
w = ColoredMessageBox(QtWidgets.QMessageBox.Information, 'Hello', 'How are you?',
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
w.exec_()

How can i click on a QGroupBox's CheckBox programatically

I want to click on the builtin (enabling) checkbox of a QGroupBox programmatically during testing with pytest-qt.
But i can't find out how to access the underlying checkbox widget of the groupbox via an attribute or similar methods.
Basically i could just use the .setChecked(True) method in my test but this would not be a "real" mouse click.
Is there a to access the checkbox widget of the groupbox directly?
Click action involves pressButton and releaseButton mouse events. So we can emulate click action by sending two sequential events to the destination widget.
Widget::Widget(QWidget* parent)
: QWidget(parent)
, m_groupBox(new QGroupBox)
{
m_groupBox->setCheckable(true);
QPushButton* generateClickButton = new QPushButton("generate click");
connect(generateClickButton, &QPushButton::clicked, [this]
{
clickAt(m_groupBox, Qt::LeftButton);
});
setLayout(new QVBoxLayout);
layout()->addWidget(m_groupBox);
layout()->addWidget(generateClickButton);
resize(100, 100);
}
Widget::~Widget()
{}
void Widget::clickAt(QWidget* receiver, Qt::MouseButton button)
{
if (receiver)
{
QMouseEvent pressEvent(QEvent::MouseButtonPress, receiver->pos(), button, 0, 0);
QMouseEvent releaseEvent(QEvent::MouseButtonRelease, receiver->pos(), button, 0, 0);
QApplication::sendEvent(receiver, &pressEvent);
QApplication::sendEvent(receiver, &releaseEvent);
}
}
Better alternative: QTest::mouseClick does the same

Categories

Resources