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.
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.
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.
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.
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.
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.
Python Gtk.Button
Gtk.Button is used to trigger a callback function that is
called when the button is pressed.
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.
Python Gtk.CheckButton
Gtk.CheckButton is a widget that has two states: on and off.
The on state is visualized by a check mark.
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.
Key pressed event
In GTK 4, input events are handled by event controllers. The next example
reacts to key presses with a Gtk.EventControllerKey.
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.
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.
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.
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.
Python Gtk.Grid
Gtk.Grid arranges its children in rows and columns.
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.
Drawing
Drawing is done on a Gtk.DrawingArea widget with the Cairo
library.
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.
Menu
In the next example, we create an application with a menubar.
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.
CSS theme
We can style our applications with 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.
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.
Source
In this article we have created basic GUI applications with Python Gtk.
Author
List all Python tutorials.