Migrate from ComboBoxText to ComboRow(DropDown)

You will need to create a GObject that contains the id and the (localised) name and exposes them as properties:

import gi

from gi.repository import Gio, GObject


class Country(GObject.Object):
    __gtype_name__ = 'Country'

    def __init__(self, country_id, country_name):
        super().__init__()

        self._country_id = country_id
        self._country_name = country_name

    @GObject.Property
    def country_id(self):
        return self._country_id

    @GObject.Property
    def country_name(self):
        return self._country_name

Then you can use the object to populate the Gio.ListStore:

nodes = {
    "au": "Austria",
    "uk": "United Kingdom",
    "us": "United States",
}

model = Gio.ListStore(item_type=Country)
for n in nodes.keys():
    model.append(Country(country_id=n, country_name=nodes[n]))

At this point, you have a GObject that contains the data. In order to map the objects inside the model to a widget inside the drop down, you will need to use a list item factory and Gtk.DropDown.set_factory; the factory gets a reference to the item in the row, and creates a widget that maps to it:

import gi

gi.require_version('Adw', '1')
gi.require_version('Gtk', '4.0')

from gi.repository import Adw, Gio, GObject, Gtk

class ExampleWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app, title="DropDown")
        
        nodes = {
            "au": "Austria",
            "uk": "United Kingdom",
            "us": "United States",
        }

        # Populate the model
        self.model = Gio.ListStore(item_type=Country)
        for n in nodes.keys():
            self.model.append(Country(country_id=n, country_name=nodes[n]))
        
        # Set up the factory
        factory = Gtk.SignalListItemFactory()
        factory.connect("setup", self._on_factory_setup)
        factory.connect("bind", self._on_factory_bind)
        
        self.dd = Gtk.DropDown(model=self.model, factory=factory, hexpand=True)
        self.dd.connect("notify::selected-item", self._on_selected_item_notify)
        
        box = Gtk.Box(spacing=12, hexpand=True, vexpand=True)
        box.props.margin_start = 12
        box.props.margin_end = 12
        box.props.margin_top = 6
        box.props.margin_bottom = 6
        box.append(Gtk.Label(label="Select Country:"))
        box.append(self.dd)
        self.set_child(box)

    # Set up the child of the list item; this can be an arbitrarily
    # complex widget but we use a simple label now
    def _on_factory_setup(self, factory, list_item):
        label = Gtk.Label()
        list_item.set_child(label)

    # Bind the item in the model to the row widget; again, since
    # the object in the model can contain multiple properties, and
    # the list item can contain any arbitrarily complex widget, we
    # can have very complex rows instead of the simple cell renderer
    # layouts in GtkComboBox
    def _on_factory_bind(self, factory, list_item):
        label = list_item.get_child()
        country = list_item.get_item()
        label.set_text(country.country_name)

    # The notify signal is fired when the selection changes
    def _on_selected_item_notify(self, dropdown, _):
        country = dropdown.get_selected_item()
        print(country)


class ExampleApp(Adw.Application):
    def __init__(self):
        super().__init__()
        self.window = None
        
    def do_activate(self):
        if self.window is None:
            self.window = ExampleWindow(self)
        self.window.present()


app = ExampleApp()
app.run([])

IMPORTANT NOTE: There is a caveat when it comes to GtkDropDown (and all the other list widgets in GTK4) and Python: you cannot use the GtkExpression API in your code, as PyGObject does not know how to handle instanced types that do not inherit from GObject, like GtkExpression.

If you want to bind this to GSettings, you can use the selected-item property of the GtkDropDown to update the settings storage.

2 Likes