Search code examples
pythoncssgtk3glade

Cannot apply CSS to GtkEntry Widget (GTK3 / gi / Python) Not Working


I've been unable to figure out what I'm doing wrong. I'm sure it's something simple but escaping me. Even in the following example I'm unable to get this working. I cannot seem to apply a CSS style to a GtkEntry widget in a single GtkWindow. I'm simply trying to turn the background of the entry box red.

Everything I've reviewed and examples I've seen all look like the below and nobody else seems to be stumped.

Here is my Python:

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk

builder = Gtk.Builder()
builder.add_from_file("entry.glade")

window = builder.get_object('window1')
window.connect('destroy', Gtk.main_quit)

css = "#red { background-image: linear-gradient(red); }"
provider = Gtk.CssProvider()
provider.load_from_data(css)
Gtk.StyleContext().add_provider_for_screen(Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

entry = builder.get_object('entry1')
entry_style_context = entry.get_style_context()
entry_style_context.add_class("red")

window.show_all()

Gtk.main()

And here is the Glade XML:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="3.0"/>
  <!-- interface-naming-policy project-wide -->
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
      <object class="GtkVBox" id="vbox1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkEntry" id="entry1">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="invisible_char">●</property>
            <property name="primary_icon_activatable">False</property>
            <property name="secondary_icon_activatable">False</property>
            <property name="primary_icon_sensitive">True</property>
            <property name="secondary_icon_sensitive">True</property>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

If I set the CSS to .entry { background-image: linear-gradient(red); } it works, but then all GtkEntry boxes would be red (which I don't want).

What in the world am I missing?


Solution

  • Question: Apply CSS to a GtkEntry Widget

    As ptomato in his answer describes, you can't use css = "#red ... if you don't define a name for your widget.

    Define a widgets name, using either:

    • In the glade file: <property name="name">red</property>
    • In the script: entry = builder.get_object('entry1') entry.set_name('red')


    Method 1: Using a widgets name, css = b"#entry ...:

    1. Set the widget name in the glade file to entry
          <child>
            <object class="GtkEntry" id="entry1">
              <property name="name">entry</property>
      
    2. Define your CSS using the widgets name entry:

          css = b"#entry {background-image: linear-gradient(red, orange); color: white;}"
      

      Working example, using a widgets name:

      class App:
          def __init__(self):
              super().__init__()
      
              builder = Gtk.Builder()
              builder.add_from_file("entry.glade")
      
              window = builder.get_object('window1')
              window.connect('destroy', Gtk.main_quit)
      
              css = b"#entry {background-image: linear-gradient(red, orange);}"
              provider = Gtk.CssProvider()
              provider.load_from_data(css)
              Gtk.StyleContext()\
                  .add_provider_for_screen(Gdk.Screen.get_default(),
                                           provider,
                                           Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
      
              window.show_all()
      
      if __name__ == "__main__":
          main = App()
          Gtk.main()
      

    Method 2: Using a widgets style class, css = b".entry.red ...:

    • Gtk.Widget.get_style_context, Gtk.StyleContext.add_class

      1. Define your CSS using nested style class .entry, .red, .blue:
              css = b"""
              .entry {color: white;}
              .entry.red {background-image: linear-gradient(red, orange);}
              .entry.blue {background-image: linear-gradient(blue, lightblue);}
      
      1. Add the named style classes to the widget:
              entry1 = builder.get_object('entry1')
              entry1.get_style_context().add_class("entry")
              entry1.get_style_context().add_class("red")
      
              # the same for `entry2`
              ...
      

      Working example using a widgets style class:

      enter image description here

      class App:
          def __init__(self):
              super().__init__()
      
              builder = Gtk.Builder()
              builder.add_from_file("entry.glade")
      
              window = builder.get_object('window1')
              window.connect('destroy', Gtk.main_quit)
      
              css = b"""
              .entry { color: white;}
              .entry.red { background-image: linear-gradient(red, orange);}
              .entry.blue { background-image: linear-gradient(blue, lightblue);}
              """
              provider = Gtk.CssProvider()
              provider.load_from_data(css)
              Gtk.StyleContext()\
                  .add_provider_for_screen(Gdk.Screen.get_default(),
                                           provider,
                                           Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
      
              entry1 = builder.get_object('entry1')
              entry1.set_text('TEST')
              entry1.get_style_context().add_class("entry")
              entry1.get_style_context().add_class("red")
      
              entry2 = builder.get_object('entry2')
              entry2.set_text('TEST')
              entry2.get_style_context().add_class("entry")
              entry2.get_style_context().add_class("blue")
      
              window.show_all()
      
      if __name__ == "__main__":
          main = App()
          Gtk.main()
      

    Tested with Python: 3.5 - gi.__version__: 3.22.0 - Glade 3.20.0