When my QDoubleSpinBox is focused, it gets a blue outline to it (in "Fusion" style):
How do I turn this off?
Doing this with stylesheets only is doable, but has an important drawback: styling complex widgets like a QSpinBox requires to correctly set all sub control properties.
The basic solution is to set the border for the widget:
QSpinBox {
border: 1px inset palette(mid);
border-radius: 2px;
}
Keep in mind that offering proper visible response of the focus is really important; you might not like the "glow" (and color) the Fusion style offers, but nonetheless it should always be visible when a widget has focus or not, even if it has a blinking text cursor. You can do that by specifying a slightly different color with the :focus selector:
QSpinBox:focus {
border: 1px inset palette(dark);
}
Unfortunately, as explained in the beginning, this has an important drawback: as soon as the stylesheet is applied, the widget painting falls back to the basic primitive methods (the spinbox on the right uses the stylesheet above):
Unfortunately, there's almost no direct way to restore the default painting of the arrows, as using the stylesheet prevents that. So, the only solution is to provide the properties for the controls as explained in the examples about customizing QSpinBox.
There is an alternative, though, using QProxyStyle. The trick is to intercept the control in the drawComplexControl() implementation and remove the State_HasFocus flag of the option before calling the default implementation.
In the following example, I also checked the focus before removing the flag in order to provide sufficient visual feedback, and I also removed the State_MouseOver flag which shows the glowing effect when hovering.
class Proxy(QtWidgets.QProxyStyle):
def drawComplexControl(self, cc, opt, qp, widget=None):
if cc == self.CC_SpinBox:
opt = QtWidgets.QStyleOptionSpinBox(opt)
if opt.state & self.State_HasFocus:
opt.palette.setColor(QtGui.QPalette.Window,
opt.palette.color(QtGui.QPalette.Window).darker(100))
else:
opt.palette.setColor(QtGui.QPalette.Window,
opt.palette.color(QtGui.QPalette.Window).lighter(125))
opt.state &= ~(self.State_HasFocus | self.State_MouseOver)
super().drawComplexControl(cc, opt, qp, widget)
# ...
app = QtWidgets.QApplication(sys.argv)
app.setStyle(Proxy())
# ...
Note that the above "color correction" only works for Fusion style and other styles that use the Window palette role for painting the border. For instance, the Windows style doesn't consider it at all, or you might want to use higher values of darker() or lighter() in order to provide better differentiation.
Related
I'm trying to find a way to alter properties of a widget, such as size of a component or various borders and colours, without changing the default style of the widget (in this case, fusion). Still trying to get my head around style sheets in PyQT, could someone explain how to achieve the bigger arrow buttons without altering the style?
Thanks in advance.
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("fusion")
#Trying to change the arrow size here, while maintaining their "fusion" style
app.setStyleSheet("QSpinBox::up-button { width: 32px; }"
"QSpinBox::down-button { width: 32px; }")
windowExample = basicWindow()
windowExample.show()
sys.exit(app.exec_())
I'm using a Proxy Style for my sliders too in order to alter the slider size, hoping theres something similar for a spinbox:
class SliderProxyStyle(QProxyStyle):
def pixelMetric(self, metric, option, widget):
if metric == QStyle.PM_SliderThickness:
return 20
elif metric == QStyle.PM_SliderLength:
return 40
return super().pixelMetric(metric, option, widget)
You can't, not with simple stylesheets, as explained at the bottom of the sub-controls documentation:
With complex widgets such as QComboBox and QScrollBar, if one property or sub-control is customized, all the other properties or sub-controls must be customized as well."
Complex widgets also include all subclasses of QAbstractSpinBox, from which QSpinBox inherits, so if you alter any of the properties (except for background and foreground color), you have to provide everything else, as the basic implementation of the style will be ignored by using the basic QCommonStyle as fallback.
The only viable solution is to use a QProxyStyle and implement subControlRect for SC_SpinBoxUp, SC_SpinBoxDown and SC_SpinBoxEditField:
class Proxy(QtWidgets.QProxyStyle):
def subControlRect(self, control, opt, subControl, widget=None):
rect = super().subControlRect(control, opt, subControl, widget)
if control == self.CC_SpinBox:
if subControl in (self.SC_SpinBoxUp, self.SC_SpinBoxDown):
rect.setLeft(opt.rect.width() - 32)
elif subControl == self.SC_SpinBoxEditField:
rect.setRight(opt.rect.width() - 32)
return rect
# ...
app = QtWidgets.QApplication(sys.argv)
app.setStyle(Proxy())
# ...
I have a grid of QWidget()s in a QScrollArea(). I override their leaveEvent() for a particular reason (more on that later). When moving around with the mouse pointer, the leaveEvent() is triggered just fine. But when I keep the mouse pointer still and scroll through the QScrollArea() with my scrollwheel, the mouse pointer effectively leaves the widget without triggering a leaveEvent().
1. Some context
I'm working on an application to browse through Arduino libraries:
What you see in the screenshot is a QScrollArea() in the middle with a scrollbar on the right and one at the bottom. This QScrollArea() contains a large QFrame() that holds all the widgets in its QGridLayout(). Each widget - usually a QLabel() or QPushButton() - has a lightgrey border. This way, I visualize the grid they're in.
2. Lighting up a whole row
When clicking on a widget, I want the whole row to light up. To achieve this, I override the mousePressEvent() for each of them:
Note: In practice, I just make each widget a subclasses from a custom class
CellWidget(), such that I need this method-override only once.
def mousePressEvent(self, e:QMouseEvent) -> None:
'''
Set 'blue' property True for all neighbors.
'''
for w in self.__neighbors:
w.setProperty("blue", True)
w.style().unpolish(w)
w.style().polish(w)
w.update()
super().mousePressEvent(e)
return
The variable self.__neighbors is a reference each widget keeps to all its neighboring widgets at the left and right side. This way, each widget has access to all widgets from its own row.
As you can see, I set the property "blue" from each widget. This has an effect in the StyleSheet. For example, this is the stylesheet I give to the QLabel() widgets:
# Note: 'self' is the QLabel() widget.
self.setStyleSheet(f"""
QLabel {
background-color: #ffffff;
color: #2e3436;
border-left: 1px solid #eeeeec;
border-top: 1px solid #eeeeec;
border-right: 1px solid #d3d7cf;
border-bottom: 1px solid #d3d7cf;
}
QLabel[blue = true] {
background-color: #d5e1f0;
}
""")
The unpolish() and polish() methods ensure that the stylesheet gets reloaded (I think?) and the update() method invokes a repaint. The procedure works nice. I click on any given widget, and the whole row lights up blue!
3. Turn off the (blue) light
To turn it off, I override the leaveEvent() method:
def leaveEvent(self, e:QEvent) -> None:
'''
Clear 'blue' property for all neighbors.
'''
for w in self.__neighbors:
w.setProperty("blue", False)
w.style().unpolish(w)
w.style().polish(w)
w.update()
super().leaveEvent(e)
return
So the whole row remains lighted up until you leave the widget with your mouse pointer. The leaveEvent() clears the 'blue' property for all neighbors and forces a repaint on them.
4. The problem
But there is a weakness. When I keep my mouse at the same position and I scroll the mousewheel down, the mouse pointer effectively leaves the widget without triggering a leaveEvent(). The absence of such leaveEvent() breaks my solution. While keeping the mouse still and scrolling down, you can click several rows and they all remain blue.
5. System info
I'm working on an application for both Windows and Linux. The application is written in Python 3.8 and uses PyQt5.
Both enterEvent and leaveEvent obviously depend on mouse movements, and since the scrolling does not involve any movement your approach won't work until the user slightly moves the mouse.
I can think about two possible workarounds, but both of them are a bit hacky and not very elegant. In both cases, I'm subclassing the scroll area and override the viewportEvent
Slightly move the mouse
Since those events require a mouse movement, let's move the mouse (and then move it back):
class ScrollArea(QtWidgets.QScrollArea):
def fakeMouseMove(self):
pos = QtGui.QCursor.pos()
# slightly move the mouse
QtGui.QCursor.setPos(pos + QtCore.QPoint(1, 1))
# ensure that the application correctly processes the mouse movement
QtWidgets.QApplication.processEvents()
# restore the previous position
QtGui.QCursor.setPos(pos)
def viewportEvent(self, event):
if event.type() == event.Wheel:
# let the viewport handle the event correctly, *then* move the mouse
QtCore.QTimer.singleShot(0, self.fakeMouseMove)
return super().viewportEvent(event)
Emulate the enter and leave events
This uses widgetAt() and creates fake enter and leave events sent to the relative widgets, which is not very good for performance: widgetAt might be slow, and repolishing the widgets also takes some time.
class ScrollArea(QtWidgets.QScrollArea):
def viewportEvent(self, event):
if event.type() == event.Wheel:
old = QtWidgets.QApplication.widgetAt(event.globalPos())
res = super().viewportEvent(event)
new = QtWidgets.QApplication.widgetAt(event.globalPos())
if new != old:
QtWidgets.QApplication.postEvent(old,
QtCore.QEvent(QtCore.QEvent.Leave))
QtWidgets.QApplication.postEvent(new,
QtCore.QEvent(QtCore.QEvent.Enter))
return res
return super().viewportEvent(event)
Please excuse my english.
I'm trying to change the background color of a GtkButton using a css file but I can't.
I tried a few examples I found on the web, but none work.
I post two examples. One in Python 3.2.3 and the other in C
I'm using Gtk+ 3.6 and Kubuntu 12.10.
This is the code of one of them:
from gi.repository import Gtk, Gdk
class MainWindow(Gtk.Window):
def __init__(self):
super().__init__()
vbox = Gtk.Box(spacing=10,orientation=Gtk.Orientation.VERTICAL)
self.add(vbox)
self.entries = [ Gtk.Entry() for i in range(3) ]
for e in self.entries:
vbox.pack_start(e, True, True, 0)
e.connect("changed", self.on_entry_changed)
e.set_text('123')
button=Gtk.Button(label='ok')
vbox.pack_end(button,True,True,0)
def on_entry_changed(self,entry):
ctx = entry.get_style_context()
if not entry.get_text().isnumeric():
ctx.add_class('invalid')
else:
ctx.remove_class('invalid')
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(screen, cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_USER) # With the others GTK_STYLE_PROVIDER_PRIORITY values get the same result.
window = MainWindow()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
and the style.css
GtkEntry.invalid {
background-color: #ffaaaa;
background: #ffaaaa;
}
GtkButton {
engine: oxygen-gtk; /*tried also with 'none' and without setting engine*/
background-color: green;
background: green;
}
The entries works well... the bg color change. But the Button no, and theres no error messages.
EDIT3: (Deleted previews edits and change some tags)
Summarizing... I tried to change the button color with all the Python, C, and C++ codes I found in the web unsuccessfully. I read all the tutorials I found and the GTK+ 3 reference manual.
All that I know after that is that the problem is about Kubuntu themes: If I change the GTK theme from 'oxygen-gtk' to 'default' (in GTK Configuration), is the only way I found that the code works well, but this is not the idea and the button looks horrible.
So, the questions are:
Why I can't change the background color of the button?
Why I having this problem only with buttons? (Works well with other widgets)
I get answers here and in GTK forums saying that is not a good practice to change button colors, but... What if I want a menu like the one in this image (link) (see red box buttons)? Wich is the best practis for that?
Thanks and greetings!
I know this is quite old, but popped up in the first few google results, so I thought I'd share my experience.
Gtk.Button has an inline Gtk.Label for the button text, that doesn't inherit from the button by default, so you have to explicitly tell it to (or just specify the colour in it):
GtkButton GtkLabel {
color: #fff; /* This changes the text color in the button */
}
As far as the answer from #sciamp, the GTK theme sets an image for the background and the borders as well, so you have to manually remove that with background-image: none; border-image: none; Hope this saves someone the struggle.
This should work (I mean it's working for me!):
GtkButton {
border-image: none;
background-image: none;
background-color: green;
}
This is complicated, but I don't think it can be done, directly.
I believe the core reason is because the button doesn't render the background. All it does is rendera frame around its area, and then render any children inside. Remember that GtkButton is a container, it typically holds a GtkLabel for a textual label but can hold any widgetry.
I've managed to change the background color of textual labels, but then only the much tigher box around the text itself is affected, which is not what you want.
The indirect solution is to subclass the GtkButton to create a variant which actually does render its background. This is, to be sure, pretty rude towards themes and should be avoided.
I am using QLabel widgets to display error messages to the user in the status bar. This is working fine with the following code;
self.statusbar = self.statusBar()
label = QtGui.QLabel("this is a test error message")
stylesheet = """
QLabel {
font-weight: bold;
color: #FF0000;
}
"""
label.setStyleSheet(stylesheet)
self.statusbar.addWidget(label)
The only problem is that the widgets have a border around them that I can not get rid of. This is not functionally a problem as the message is still visible but it does look rather ugly and I'd like to get rid of it. I can not work out where it is coming from. Whether it is something I need to set on the statusbar or the widget. I have tried modifying the stylesheet for both the statusbar and label to add "border: 0px" to no avail. I have tried setting the labels frame to label.setFrameShape(QtGui.QFrame.NoFrame) but that doesnt seem to be it either.
Anyone have any ideas how I can get rid of it?
You do this with Style sheets. You probably have a line like this
Application app(argc, argv);
underneath that, add one like this:
app.setStyleSheet("QStatusBar::item { border: 0px solid black }; ");
and those pesky boxes will be gone.
try using self.statusbar.showMessage('this is a test error message'), as the QStatusBar is not designed for showing labels. If you need more frexibility than this you may consider subclassing QStatusBar and changing its paintEvent function to special-case labels. Either of these approaches will be much easier to maintain than setting stylesheets for each label you want to but on there anyway, but as usual, YMMV.
for more info check out the manual page for QStatusBar
I have a dialog which contains a pygtk.treeview for listing tasks by priority. Each row has the background colour set based on that priority, so for example the highest priority has a light red background.
The row selection color is not so easy to change. I can set it using treeview.modify_base(gtk.STATE_SELECTED, "#C4C4C4"), but no colours work well with the colours used to enhance the concept of priority.
I had the idea to change the selection colour to be a slightly darker version of the colour used as the normal row background, so in the example above, that would be a darker red. I tried calling the function above in response to the treeselection's changed signal, and it works, but with heavy flickering.
Another idea was to change the selection to transparent and put a border around it instead, but as far as I can tell, this isn't possible.
How can I change the selection colour in the way described above without the flickering?
Can I change show the selection by having only a border around the row?
Note: I'm aware that this violates the theme selected by the user. I feel I have a good reason for it. Having the priority indicated by colour makes it instantly recognisable. The selection colour hides this. If you have alternative suggestions, I am open to them, but it needs to retain the ease at which a user can identify the priority.
You can use the method described here – I have tested it briefly and it does the job without flickering. Basically, the trick is to use the "Markup" property of the cell renderer. There's one catch, though: if you want to change the background colour with this method, only the background behind the "actual" text is changed, not the whole row. However, if you want to change the text colour (with <span foreground= ... ), which was actually
my intention, it looks ok.
I have the following CellDataFunc (this is C#, but I hope it's still useful):
private void CellDataFunc(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
Item item = (Item) model.GetValue (iter, 0);
if(cell is CellRendererText) {
int id = (int)column.GetData("colId");
string text = "";
switch(id) {
case 0: text = item.Name; break;
case 1: text = item.Size; break;
case 2: text = item.Time.ToString(); break;
}
//(cell as Gtk.CellRendererText).Text = text;
if(item.Highlight) {
(cell as Gtk.CellRendererText).Markup =
"<span background=\"red\">"+text+"</span>";
} else {
(cell as Gtk.CellRendererText).Markup = text;
}
}
}
You could add a separate pixbuf cell (say, the same size as a small icon) to the far left to indicate selection. Selected rows could fill this with a more "solid" (saturated) version of the colour used for the background. For example. if you use a pink background for high priority, you could use red for the selection indicator. Or you could use an icon.
To implement this with the colour filling method:
Disable the built-in highlighting as per Tobias' suggestion ("make the STATE_SELECTED color identical to STATE_NORMAL").
Create a widget based on gtk.gdk.Pixbuf that allows you to create a solid area of colour, perhaps using the fill method.
Use a CellRendererPixbuf for your "selection" cell.
You can then color or uncolour the "selection cell" upon selection changes to indicate which row is selected, or display an icon (eg. a stock symbol).
Note that I haven't implemented this, it's just an idea. It departs significantly from the usual GTK selection indication, so (obviously) use your judgement as to whether it's useable.
Not sure what you mean by flickering. A border would require subclassing TreeView.
I'd make the STATE_SELECTED color identical to STATE_NORMAL to disable the built-in highlighting. Then set a data_func on each column and change some color on the cell renderer depending on whether it's in the selection or not.
You're probably already doing this for your priority, so just multiply the color with something to highlight a row.
The question is outdated, but it is possible for someone else to be useful...
For totally disabling selection and making hover effect in the tree:
1. Disable system selection in the tree:
_treeView.Selection.Mode = SelectionMode.None;
Handle MotionNotifyEvent event:
[ConnectBefore]
private void TreeViewOnMotionNotifyEvent(object o, MotionNotifyEventArgs args)
{
TreePath treePath;
TreeIter iter;
int x = Convert.ToInt32(args.Event.X);
int y = Convert.ToInt32(args.Event.Y);
_treeView.GetPathAtPos(x, y, out treePath);
_treeView.Model.GetIter(out iter, treePath);
BindObject fieldModel = CellUtil.GetModelValue(_treeView.Model, iter, 1) as BindObject;
HoverLine = _vievModel.BindObjectCollection.IndexOf(fieldModel);
}
In the SetCellDataFunc method:
BindObject fieldModel = CellUtil.GetModelValue(treeModel, iter, 1) as BindObject;
int rowCount = _vievModel.BindObjectCollection.IndexOf(fieldModel);
if (HoverLine == rowCount)
{
cellRenderer.CellBackgroundGdk = ThemeUtility.GdkOddSelectionColor; //Your selection color
}
else
{
cellRenderer.CellBackgroundGdk = ThemeUtility.SystemSelectionColor; //Your system selection color
}
...
Calling both ModifyBase and ModifyText worked for me. Calling just ModifyBase changes the text color to White
Example in GTK C#:
treeViewList.ModifyBase(StateType.Selected, treeViewList.Style.Base(StateType.Normal));
treeViewList.ModifyText(StateType.Selected, treeViewList.Style.Text(StateType.Normal));