Reputation: 2110
I have a bunch of widgets in a layout, and the layout is the child of a QFrame. This allows me to create a border around this layout. Now when any of the children receive focus, I would like to change the border color of the QFrame to indicate to the user that is where the focus currently is. How best to do this without subclassing the focuInEvent/focusOutEvent of every child with callbacks to the stylesheet of their parent widget (the QFrame)? When testing to focusInEvent of the QFrame I can never get it to trigger. Is there some sort of child focus event or something?
Upvotes: 0
Views: 2783
Reputation: 15642
The only other answer (by Spencer) is using a sledgehammer to crack a nut. Unleash the power of CSS. Here's an extract from some code where I get the focused Qwidget
to have a khaki background and the selected item (if applicable, e.g. in QTreeView
) to have a dark kharki background. NB check out about 100 very useful colour names to use in a PyQt5 CSS context.
NB in what follows self
is the QMainWindow
object.
for widget in self.findChildren(QtWidgets.QWidget):
# exclude certain types from getting fancy CSS
if isinstance(widget, QtWidgets.QMenuBar) or isinstance(widget, QtWidgets.QScrollBar):
continue
widget.setStyleSheet("""
QWidget {background: azure;} # this is actually an off-white: see list above
QWidget::focus {background: khaki;} # background turns khaki only on focus!
# ... so obviously you can add some change to the border here too if you want
QWidget::item::focus {background: darkkhaki;}
""")
# NB this next stuff is not relevant to the "how to get a nice focus colouring" question,
# but just to illustrate some of the power and flexibility of CSS
self.ui.menubar.setStyleSheet('QWidget {border-bottom: 1px solid black;}')
# bolding and colour for an isolated element: note that you don't
# have to stipulate "QLabel {...}" unless it makes sense.
self.get_details_panel().ui.breadcrumbs_label.setStyleSheet('font-weight: bold; color: slategrey')
self.get_details_panel().setFrameStyle(QtWidgets.QFrame.Box)
# with this we identify the specific object to stop the style propagating to descendant objects
self.get_details_panel().setStyleSheet('QFrame#"details panel"{border-top: 1px solid black; }')
... NB in the last case the object ("details panel", a QFrame
subclass) has been given an object name, which you can then use in CSS (i.e. in CSS terminology, its "id"):
self.setObjectName('details panel')
Upvotes: 0
Reputation: 2110
I think I came up with a pretty good solution for this after trying a few things out and learning a ton more about eventFilter's. Basically I found that you need to install an event filter in the parent and catch all focus events of the children. It's easier to show an example, this is a bit more complicated then it perhaps needs to be but it illustrates some important points:
import os
import sys
from PyQt4 import QtGui, QtCore
class BasePanel(QtGui.QWidget):
"""This is more or less abstract, subclass it for 'panels' in the main UI"""
def __init__(self, parent=None):
super(BasePanel, self).__init__(parent)
self.frame_layout = QtGui.QVBoxLayout()
self.frame_layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.frame_layout)
self.frame = QtGui.QFrame()
self.frame.setObjectName("base_frame")
self.frame.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Plain)
self.frame.setLineWidth(1)
self.frame_layout.addWidget(self.frame)
self.base_layout = QtGui.QVBoxLayout()
self.frame.setLayout(self.base_layout)
self.focus_in_color = "rgb(50, 255, 150)"
self.focus_out_color = "rgb(100, 100, 100)"
self.frame.setStyleSheet("#base_frame {border: 1px solid %s}" % self.focus_out_color)
self.installEventFilter(self) # this will catch focus events
self.install_filters()
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.FocusIn:
self.frame.setStyleSheet("#base_frame {border: 1px solid %s}" % self.focus_in_color)
elif event.type() == QtCore.QEvent.FocusOut:
self.frame.setStyleSheet("#base_frame {border: 1px solid %s}" % self.focus_out_color)
return False # passes this event to the child, i.e. does not block it from the child widgets
def install_filters(self):
# this will install the focus in/out event filter in all children of the panel
for widget in self.findChildren(QtGui.QWidget):
widget.installEventFilter(self)
class LeftPanel(BasePanel):
def __init__(self, parent=None):
super(LeftPanel, self).__init__(parent)
title = QtGui.QLabel("Left Panel")
title.setAlignment(QtCore.Qt.AlignCenter)
self.base_layout.addWidget(title)
edit = QtGui.QLineEdit()
self.base_layout.addWidget(edit)
class RightPanel(BasePanel):
def __init__(self, parent=None):
super(RightPanel, self).__init__(parent)
title = QtGui.QLabel("Right Panel")
title.setAlignment(QtCore.Qt.AlignCenter)
self.base_layout.addWidget(title)
edit = QtGui.QLineEdit()
self.base_layout.addWidget(edit)
class MainApp(QtGui.QMainWindow):
def __init__(self):
super(MainApp, self).__init__()
main_layout = QtGui.QHBoxLayout()
central_widget = QtGui.QWidget()
central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)
left_panel = LeftPanel()
main_layout.addWidget(left_panel)
right_panel = RightPanel()
main_layout.addWidget(right_panel)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = MainApp()
ex.show()
sys.exit(app.exec_())
Upvotes: 1