ZetCode

Python Gtk

last modified October 30, 2026 Python 3.15

In this article we show how to create GUI applications in Python using the Gtk module. This tutorial covers GTK 4.

GTK

GTK is a multi-platform toolkit for creating graphical user interfaces. It is written in C. GTK has been designed from the ground up to support a range of languages, including Python, Ruby, and Perl.

The GTK library is also called the GIMP Toolkit. Originally, the library was created while developing the GIMP image manipulation program. Since then, GTK became one of the most popular toolkits under Linux and BSD Unix.

We access the GTK library from Python via GObject introspection. The PyGObject package contains Python bindings and support for GObject, GLib, GTK, and other libraries. It needs the GTK library and the introspection data installed in the system. On Debian-based systems, we install them with apt; on Python versions without a binary wheel, pip builds PyGObject from source, which requires the libgirepository1.0-dev and libcairo2-dev packages.

$ sudo apt install gir1.2-gtk-4.0 libgirepository1.0-dev libcairo2-dev
$ pip install PyGObject

GTK version

In the first example, we print the versions of GTK and PyGObject.

version.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

print(f'GTK {Gtk.MAJOR_VERSION}.{Gtk.MINOR_VERSION}.{Gtk.MICRO_VERSION}')
print(f'PyGObject {gi.__version__}')

We import the Gtk module. The require_version function ensures that the namespace is loaded with the given version; it must be called before the import from gi.repository. The gi.repository is the Python module through which the introspected libraries are imported.

$ python version.py
GTK 4.6.9
PyGObject 3.50.2

Python Gtk simple example

In the first application, we display a window on the screen. Gtk.ApplicationWindow is a toplevel window which can contain other widgets. Windows normally have decorations that are under the control of the windowing system and allow the user to manipulate the window.

simple.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


def on_activate(app):

    win = Gtk.ApplicationWindow(application=app)
    win.set_title('Simple')
    win.present()


app = Gtk.Application(application_id='com.zetcode.Simple')
app.connect('activate', on_activate)
app.run(None)

We create the main application window inside the on_activate function. We set the window's title with set_title and present it to the user with present. The Gtk.Application handles the initialization of GTK and ensures that only one instance of the application with the given application_id runs at a time. To set up the application, we connect to the activate signal, which is emitted when the application is activated. The run method runs the application's main loop until the last window is closed.

Simple example in OOP style

In the next example, we rewrite the example in OOP style.

simple2.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Simple')
        self.set_default_size(350, 250)


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.Simple')
app.connect('activate', on_activate)
app.run(None)

We create a class that inherits from Gtk.ApplicationWindow. We build the UI in the init_ui method. With set_default_size, we set the default size of the application window.

Simple application
Figure: Simple application

Python Gtk.Picture

Gtk.Picture displays an image at its natural size, scaled down if there is not enough space. In GTK 4, Gtk.Image is meant for icons; it displays images at a fixed icon size.

picture.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Picture')

        picture = Gtk.Picture.new_for_filename('landscape.png')
        picture.set_margin_start(10)
        picture.set_margin_end(10)
        picture.set_margin_top(10)
        picture.set_margin_bottom(10)

        self.set_child(picture)
        self.set_default_size(380, 260)


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.Picture')
app.connect('activate', on_activate)
app.run(None)

The example displays a PNG image in the window. Gtk.Picture.new_for_filename creates the widget from a file; if the file cannot be loaded, the picture stays empty. We put some margin around the picture. With set_child, we make the picture the only child of the window.

Gtk.Picture
Figure: Gtk.Picture

Python Gtk.Button

Gtk.Button is used to trigger a callback function that is called when the button is pressed.

quit_button.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Quit button')

        box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        box.set_margin_start(5)
        box.set_margin_top(5)

        btn = Gtk.Button(label='Quit')
        btn.connect('clicked', lambda _: self.close())

        btn.set_halign(Gtk.Align.START)
        box.append(btn)

        self.set_default_size(350, 250)
        self.set_child(box)


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.QuitButton')
app.connect('activate', on_activate)
app.run(None)

In the example, we have a button. When we press the button, the application terminates. We create a vertical Gtk.Box, a simple layout widget that arranges widgets in a row or a column, and put some margin to the left and top of it. When the button is clicked, it emits the clicked signal; with connect, we bind the signal to a lambda that closes the window. We align the button to the left of the box with set_halign and add it to the box with append. Finally, we set the box as the child of the window.

Quit button
Figure: Quit button

Python Gtk.CheckButton

Gtk.CheckButton is a widget that has two states: on and off. The on state is visualized by a check mark.

check_button.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('CheckButton')

        box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        box.set_margin_start(5)
        box.set_margin_top(5)

        cbtn = Gtk.CheckButton.new_with_label('Show title')
        cbtn.set_active(True)
        cbtn.connect('toggled', self.on_toggle)

        box.append(cbtn)

        self.set_child(box)
        self.set_default_size(450, 350)

    def on_toggle(self, wid):

        if wid.get_active():
            self.set_title('CheckButton')
        else:
            self.set_title('')


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.CheckButton')
app.connect('activate', on_activate)
app.run(None)

The example displays a title in the titlebar of the window depending on the state of the Gtk.CheckButton. Since the title is visible at the start, we activate the button with set_active. When we check or uncheck the button, the toggled signal is emitted. In the on_toggle handler, we determine the state of the button with get_active and set the title accordingly.

Gtk.CheckButton
Figure: Gtk.CheckButton

Key pressed event

In GTK 4, input events are handled by event controllers. The next example reacts to key presses with a Gtk.EventControllerKey.

key_pressed.py
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Key press')

        keycont = Gtk.EventControllerKey()
        keycont.connect('key-pressed', self.on_key_pressed)
        self.add_controller(keycont)

        self.set_default_size(350, 250)

    def on_key_pressed(self, controller, keyval, keycode, state):

        print(Gdk.keyval_name(keyval), keycode)

        if keyval in (Gdk.KEY_q, Gdk.KEY_Escape):
            self.close()
            return True

        return False


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.KeyPress')
app.connect('activate', on_activate)
app.run(None)

We create the controller, connect to its key-pressed signal, and add it to the window. The handler receives the controller, the key value, the hardware key code, and the state of the modifier keys. The Gdk.keyval_name function returns the name of the key value. If the user presses the q or the Escape key, the window is closed. Returning True stops further handling of the event.

We run the application and press the a, b, Enter, and Escape keys; the names and the codes of the keys are printed to the console.

$ python key_pressed.py
a 38
b 56
Return 36
Escape 9

Python Gtk.Entry

Gtk.Entry is a single line text entry widget. Many key bindings are supported by default. If the entered text is longer than the allocation of the widget, the widget scrolls so that the cursor position is visible.

entry.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Entry')

        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8)
        hbox.set_margin_start(5)
        hbox.set_margin_top(5)
        hbox.set_valign(Gtk.Align.START)

        self.entry = Gtk.Entry()
        self.entry.set_placeholder_text('Enter text')
        self.entry.connect('changed', self.on_changed)
        hbox.append(self.entry)

        self.label = Gtk.Label.new('...')
        hbox.append(self.label)

        self.set_child(hbox)
        self.set_default_size(450, 350)

    def on_changed(self, entry):
        self.label.set_text(entry.get_text())


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.Entry')
app.connect('activate', on_activate)
app.run(None)

In the example, we have an entry widget and a label. The text entered into the entry is displayed in the label. The placeholder text is shown while the entry is empty. The entry emits the changed signal whenever its text changes, including pasting and deleting. In the handler, we get the text with get_text and set it to the label with set_text.

Gtk.Entry
Figure: Gtk.Entry

Python Gtk.DropDown

Gtk.DropDown lets the user choose an item from a list. It replaces the Gtk.ComboBoxText widget, which is deprecated since GTK 4.10.

dropdown.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('DropDown')

        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8)
        hbox.set_margin_start(5)
        hbox.set_margin_top(5)
        hbox.set_valign(Gtk.Align.START)

        distros = ['Arch', 'Fedora', 'Red Hat', 'Gentoo', 'Xubuntu']

        dropdown = Gtk.DropDown.new_from_strings(distros)
        dropdown.connect('notify::selected-item', self.on_selected)
        hbox.append(dropdown)

        self.label = Gtk.Label.new(distros[0])
        hbox.append(self.label)

        self.set_child(hbox)
        self.set_default_size(450, 350)

    def on_selected(self, dropdown, _pspec):

        item = dropdown.get_selected_item()
        self.label.set_text(item.get_string())


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.DropDown')
app.connect('activate', on_activate)
app.run(None)

The new_from_strings constructor creates a drop-down from a list of strings. When the selection changes, the selected-item property changes and the drop-down emits the notify::selected-item signal. In the handler, we get the selected item, a Gtk.StringObject, and set its string to the label.

Gtk.DropDown
Figure: Gtk.DropDown

Python Gtk.Grid

Gtk.Grid arranges its children in rows and columns.

grid.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Calculator')

        grid = Gtk.Grid()
        grid.set_row_spacing(5)
        grid.set_column_spacing(5)
        grid.set_row_homogeneous(True)
        grid.set_column_homogeneous(True)

        for side in ('start', 'end', 'top', 'bottom'):
            getattr(grid, f'set_margin_{side}')(8)

        self.display = Gtk.Entry()
        self.display.set_alignment(1)
        grid.attach(self.display, 0, 0, 4, 1)

        labels = ['7', '8', '9', '/',
                  '4', '5', '6', '*',
                  '1', '2', '3', '-',
                  '0', '.', '=', '+']

        for i, label in enumerate(labels):
            btn = Gtk.Button(label=label)
            btn.connect('clicked', self.on_clicked)
            grid.attach(btn, i % 4, i // 4 + 1, 1, 1)

        self.set_child(grid)
        self.set_default_size(280, 250)

    def on_clicked(self, btn):

        text = self.display.get_text() + btn.get_label()
        self.display.set_text(text)


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.Calculator')
app.connect('activate', on_activate)
app.run(None)

The example creates the skeleton of a calculator. The attach method places a widget at the given column and row; the last two arguments are the number of columns and rows the widget spans. The entry spans all four columns. Homogeneous rows and columns give all cells the same size. When a button is clicked, its label is appended to the text of the entry.

Gtk.Grid
Figure: Gtk.Grid

Drawing

Drawing is done on a Gtk.DrawingArea widget with the Cairo library.

shapes.py
import math

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Drawing')

        area = Gtk.DrawingArea.new()
        area.set_draw_func(self.on_draw)

        self.set_default_size(450, 300)
        self.set_child(area)

    def on_draw(self, area, ctx, w, h):

        ctx.set_source_rgb(0.6, 0.6, 0.6)

        ctx.rectangle(20, 20, 120, 80)
        ctx.rectangle(180, 20, 80, 80)
        ctx.fill()

        ctx.arc(330, 60, 40, 0, 2 * math.pi)
        ctx.fill()

        ctx.arc(90, 160, 40, math.pi / 4, math.pi)
        ctx.fill()

        ctx.translate(220, 180)

        ctx.scale(1, 0.7)
        ctx.arc(0, 0, 50, 0, 2 * math.pi)
        ctx.fill()


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.Drawing')
app.connect('activate', on_activate)
app.run(None)

The example draws a few basic shapes. We set the drawing function with set_draw_func. The function receives the drawing area, the Cairo context, and the width and height of the area. The set_source_rgb method sets a colour for the context; the values are from 0 to 1. We add two rectangles to the path and paint their interior with fill. With the arc method, we paint a full circle and a circular sector. The translate method moves the origin of the user space, and scale shrinks the y axis, so the circle becomes an ellipse.

Basic shapes
Figure: Basic shapes

Menu

In the next example, we create an application with a menubar.

menu.py
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gio, Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('Simple menu')
        self.set_show_menubar(True)
        self.set_default_size(350, 250)


def on_about(action, param):
    print('Simple menu example')


def on_quit(action, param, app):
    app.quit()


def on_startup(app):

    act_about = Gio.SimpleAction.new('about', None)
    act_about.connect('activate', on_about)
    app.add_action(act_about)

    act_quit = Gio.SimpleAction.new('quit', None)
    act_quit.connect('activate', on_quit, app)
    app.add_action(act_quit)

    app.set_accels_for_action('app.quit', ['<Ctrl>q'])

    file_menu = Gio.Menu.new()
    file_menu.append('About', 'app.about')
    file_menu.append('Quit', 'app.quit')

    menubar = Gio.Menu.new()
    menubar.append_submenu('File', file_menu)

    app.set_menubar(menubar)


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.SimpleMenu')
app.connect('startup', on_startup)
app.connect('activate', on_activate)
app.run(None)

Menus in GTK 4 are built from Gio.Menu models; menu items refer to actions by name. We set up the menubar in the handler of the startup signal, which is emitted once, when the application starts. Two Gio.SimpleAction objects are added to the application, so their names have the app. prefix. With set_accels_for_action, we bind the Ctrl+Q shortcut to the quit action. The append_submenu method adds the File menu to the menubar, and set_show_menubar shows it in the window.

Menu
Figure: Menu

CSS theme

We can style our applications with CSS.

theme.css
window {
    background-color: rgb(59, 56, 56);
}

button {
    color: rgb(233, 32, 27);
}

In the CSS, we set a custom background colour for the window and a custom foreground colour for the button.

css_theme.py
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, Gtk


class AppWindow(Gtk.ApplicationWindow):

    def __init__(self, app):

        super().__init__(application=app)

        self.init_ui()

    def init_ui(self):

        self.set_title('CSS theme')

        box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)

        btn = Gtk.Button(label='Quit')
        btn.connect('clicked', lambda _: self.close())

        btn.set_margin_start(5)
        btn.set_margin_top(5)
        btn.set_halign(Gtk.Align.START)

        box.append(btn)

        self.set_child(box)
        self.set_default_size(350, 250)


def on_startup(app):

    provider = Gtk.CssProvider.new()
    provider.load_from_path('theme.css')

    Gtk.StyleContext.add_provider_for_display(
        Gdk.Display.get_default(), provider,
        Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)


def on_activate(app):

    win = AppWindow(app)
    win.present()


app = Gtk.Application(application_id='com.zetcode.CssTheme')
app.connect('startup', on_startup)
app.connect('activate', on_activate)
app.run(None)

Styles are loaded with Gtk.CssProvider. In the startup handler, we load the CSS from a file and add the provider to the default display, so it applies to all windows of the application. The application priority overrides the rules of the theme.

CSS theme
Figure: CSS theme

Source

PyGObject documentation

GTK 4 API reference

In this article we have created basic GUI applications with Python Gtk.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.