How to apply css only to a particular widget?

So far to apply CSS to an entire application, I used the following code (Python):

#Gtk, Gdk are imported properly
provider = Gtk.CssProvider()
context = Gtk.StyleContext()
provider.load_from_path("my_css_file.css")
context.add_provider_for_screen(
            Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

I want to know if what I’m doing is right or if any better solution is available. (Actually, I can’t find a good doc which explains us how to use CSS with widgets, the Theming guide, only explains the syntax and not ‘how-to’, please correct me, if I’m wrong).

Additionally, is it possible to theme only a particular widget like widget.add_css(_css_as_str_), thats the main question. Please help me.

Your code is wrong; the add_provider_for_screen() method is a static one:

provider = Gtk.CssProvider().load_from_path(path_to_css_file);
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, priority)

There is an “how do I” guide on the wiki.

In short: yes, you can use a CSS provider global to the screen, if you wish to define custom style classes. You can also add the CSS provider to the widget itself, by retrieving the Gtk.StyleContext for the widget; the CSS, in this case, will not cascade, so it’s only useful if your widget does not have children, or if you don’t want to apply custom style classes to the widget’s children.

1 Like

Sorry, I don’t get how to theme just a widget (directly apply css string to a widget). Can you please demonstrate?

There is no API to directly theme a widget by passing a string containing CSS to it. At most, you can create a Gtk.CssProvider and apply it to the widget’s Gtk.StyleContext, just like you’d apply it to the screen-default’s style context:

provider = Gtk.CssProvider().load_from_path(path_to_css_file)
widget.get_style_context().add_provider(provider, priority)

As I said, though, this will not cascade, so it’ll only apply to the widget itself and not its children.

1 Like

Test out a GtkApplication with a GMenu and some styling CSS with python3. Ok, might need a little more work but it is a start.

If you change colors with CSS be careful because people are using different themes and the new colors might not work so well with some of the themes. To get a general idea you can test with the Adwaita dark and light themes.

Eric

#!/usr/bin/python3

'''
Test some different themes along with styling with CSS.

GTK_THEME=Adwaita:dark python3 program.py
GTK_THEME=Adwaita:light python3 program.py
'''

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gio, Gtk, Gdk
 
class Application(Gtk.Application):
    def __init__(self):
        Gtk.ApplicationWindow.__init__(self, application_id="test.menu", flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("startup", self.startup_menu)
        self.connect("activate", self.activate_window)
                     
    def startup_menu(self, *args):
        action=Gio.SimpleAction.new("open_file", None)
        action.connect("activate", self.open_file)
        self.add_action(action)

        action=Gio.SimpleAction.new("save_file", None)
        action.connect("activate", self.save_file)
        self.add_action(action)

        menu = Gio.Menu()

        # The "open file" menu item
        open_item = Gio.MenuItem()
        open_item.set_label("Open File")
        open_item.set_detailed_action("app.open_file")

        # The "save file" menu item
        save_item = Gio.MenuItem()
        save_item.set_label("Save File")
        save_item.set_detailed_action("app.save_file")

        # The open and save menu items are part of a "section"; sections are used
        # to tell the component that will render the menu to visually/semantically
        # separate the items
        section_menu = Gio.Menu()
        section_menu.append_item(open_item)
        section_menu.append_item(save_item)

        # Put the section in a sub-menu, without a label
        submenu = Gio.Menu()
        submenu.append_section(None, section_menu)

        # Put the sub-menu in the main menu, with a label
        menu.append_submenu("File", submenu)

        self.set_menubar(menu)

    def activate_window(self, *args):
        window=Gtk.ApplicationWindow(application=self, title="Menu")
        window.set_default_size(200, 200)
        window.set_position(Gtk.WindowPosition.CENTER)

        label1=Gtk.Label("Green Label")
        label1.set_vexpand(True)
        label1.set_hexpand(True)
        label1.set_name("green_label")

        entry1=Gtk.Entry()
        entry1.set_text("entry1")
        #entry1.set_vexpand(True)
        entry1.set_hexpand(True)

        label2=Gtk.Label("Cyan Label")
        label2.set_vexpand(True)
        label2.set_hexpand(True)
        label2.set_name("cyan_label")

        entry2=Gtk.Entry()
        entry2.set_text("entry2")
        entry2.set_vexpand(True)
        entry2.set_hexpand(True)

        grid=Gtk.Grid()
        grid.attach(label1, 0, 0, 1, 1)
        grid.attach(entry1, 0, 1, 1, 1)
        grid.attach(label2, 0, 2, 1, 1)
        grid.attach(entry2, 0, 3, 1, 1)

        window.add(grid)

        style_provider=Gtk.CssProvider()
        css=b'#green_label{background: rgba(0,255,0,1.0);} #cyan_label{background: rgba(0,255,255,1.0);}'
        style_provider.load_from_data(css)
        Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), style_provider,Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        window.show_all()

    def open_file(self, *args):
        print("Open File")

    def save_file(self, *args):
        print("Save File")

if __name__ == "__main__":
    app=Application()
    app.run(None)
1 Like

Sorry for this late doubt, I replaced my code with yours and got this error:
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, 1) TypeError: argument provider: Expected Gtk.StyleProvider, but got bool
Then I checked the docs, provider = Gtk.CssProvider().load_from_path(path_to_css_file) stores the return value of load_from_path and not the Gtk.CssProvider(). So we need to set up the provider first and then load the file, then give it to style context. Am I right?

Gtk.CssProvider.load_from_path() returns a bool value, indeed, so you cannot chain the call. My bad.

The appropriate call would be:

provider = Gtk.CssProvider()
try:
    provider.load_from_path(path_to_css_file)
except GLib.Error as e:
    print(f"Unable to load the CSS file at {path_to_css_file}: {e.message}")
1 Like