GTK is one of the GUI toolkits for building Linux desktop apps, notable for its modern visual look, and with some advanced developer experience features, like Inspector.
But one of the things that is still old-fashioned is its support for declarative UI. GTK allows developer to describe the UI separately from application code, but the language is XML which is too verbose, comparing to QT's QML or Slint. Fortunately, there is an new language, Blueprint, to describe UI for GTK app, which brings the same taste of QML, Slint to the table. But Blueprint is still young, not integrated to GTK yet (the files written in Blueprint must be compiled to Gtk.Builder XML) and the documentation is not rich enough.
This post shows you how to use Dropdown or other list widgets (like ListView, GridView) in Blueprint. The example use case and code is drawn from my application, CoBang.
List widgets follow Model-View-Controller pattern, to dislay a list of data with dynamic length. The widget is not used alone, but with other non-display components from GTK library:
- A model (
Gio.ListStore) to hold the original data (list of item). Each item can hold more data that what is shown in the UI, because this extra data is used for processing, serving the application business logic. - An item factory (
BuilderListItemFactory) to produce the UI for each item from the data. - Usually a selection model (
SingleSelection) which stays between the model and the list widget, to provide information about which item is selected. - An optional wrapping model (
FilterListModel) to create a subset of data, in case you want to filter, sorting what are shown to users.
The simplest widget of this category is Dropdown to get started with. In CoBang, this widget is to show the available webcams.

First, we need to define the model, in form of Gio.ListStore. It can be declared in the Blueprint file.
Gio.ListStore webcam_store {
item-type: typeof<$WebcamDeviceInfo>;
}
Note that, the declaration of this object must be outside the widget tree. If you look at CoBang's scanner-page.blp file, you will see the position of this ListStore like this:
Gio.ListStore webcam_store {
item-type: typeof<$WebcamDeviceInfo>;
}
template $ScannerPage: Adw.Bin { }
The widget tree is the template $ScannerPage: Adw.Bin object. If you put the ListStore inside this tree, Blueprint compiler will not compile the file.
We also need to specify the data type of each element stored by the model, via item-type. The scalar Python types like bool, int, str can be used here, but usually our application needs more complex type. We will define these types in Python code instead of Blueprint file. GTK Builder and Blueprint don't support to define custom types in their files, it is reasonable because we will populate the data for these types in application (Python) code, and defining these types in application code will help autocomplete, static type checker work.
For the use case of CoBang, the custom type is to hold some info around a webcam, and the type is defined like this:
class WebcamDeviceInfo(GObject.GObject):
__gtype_name__ = 'WebcamDeviceInfo'
# pipewiresrc / v4l2src. The type should be DeviceSourceType but PyGobject doesn't support Enum yet.
source_type = GObject.Property(type=str, default='v4l2src')
# The device path, e.g. /dev/video0 or /dev/video1
# or PipeWire serial number.
path = GObject.Property(type=str)
name = GObject.Property(type=str)
__gsignals__ = {
'changed': (GObject.SignalFlags.RUN_LAST, None, ()),
}
def __init__(self, source_type: DeviceSourceType, path: str, name: str):
super().__init__()
self.source_type = source_type
self.path = path
self.name = name
Looking back in the Blueprint file, we can see that we refer to the WebcamDeviceInfo class with $ prefix:
item-type: typeof<$WebcamDeviceInfo>;
because this class is defined outside Blueprint file.
For how to define custom GObject type, you can follow this tutorial.
That's the model part. Now the view part, where we use DropDown widget to display a list of webcam as choices:
DropDown webcam_dropdown {
margin-bottom: 4;
model: SingleSelection {
model: webcam_store;
};
factory: BuilderListItemFactory {
template ListItem {
child: Label {
label: bind template.item as <$WebcamDeviceInfo>.name;
ellipsize: middle;
};
}
};
notify::selected => $on_webcam_device_selected();
}
We connect the DropDown with the ListStore via the model property. Note that we don't connect the two directly, but via an intermediate SingleSelection, it is because we need to retrieve the information: which item has been selected.
Then the complex part, we need to tell GTK how to display each item in ListStore, via the factory property. The ListStore holds a dynamic numbers of item, item data is not known at the time we write code, so we can not use a fix widget to display, we need to use something like a function, receiving input (item from ListStore) and producing output (widget). That's why this property is named factory and its value is some classes named as ...ItemFactory. We want to use Blueprint code as much as possbile, so we will use BuilderListItemFactory for this task. The widget production "function" will be in form of a UI template. For the case of webcam selector, we just need to display each item as a Label widget, so the UI template just need to contain one Label:
factory: BuilderListItemFactory {
template ListItem {
child: Label {
label: bind template.item as <$WebcamDeviceInfo>.name;
ellipsize: middle;
};
}
};
We define how to compute label text from each entry in the data store by using bind with a special, fixed name source, template.item and casting operator to specify from which field of the data (<$WebcamDeviceInfo>.name).
The DropDown doesn't have a dedicated signal for when an item is selected, you can just use a common method in GTK 4, that is to watch the change of a property of the widget. For DropDown, it has selected and selected-item, which are of interest, and the corresponding signals are notify::selected, notify::selected-item. The rest may be familiar to you.
We have done learning how use DropDown in GTK 4 with as less application (Python) code as possible. In the next post, I will show you a more complex example, the ListView combining with filtering data, by a flag or by search.