How to use GTK+ 3 in Python to manage your whole application

When you’re developing with GTK+, you might come to a point at which questions come up like:

  • How do I manage multiple Windows?
  • How to open multiple documents in a single instance?

Well, you could do all those management work yourself. Which I tried to do, when I didn’t know about the capabillities of Gtk.Application. Or you could … just let GTK do it for you! I came across this while using Gtk.Builder to create my Application’s GUI, and stumbled across messages like these printed to my console:

(main.py:13901): Gtk-CRITICAL **: gtk_application_get_app_menu: assertion 'GTK_IS_APPLICATION (application)' failed

(main.py:13901): Gtk-CRITICAL **: gtk_application_get_menubar: assertion 'GTK_IS_APPLICATION (application)' failed

Even though Application seemed to run fine. But when assertions fail, it means that something really nasty must be going on behind the scenes. Definitely nothing you should ignore!

Maybe you found this page by searching for those, and are now having a hard time trying to figure out how to get rid of them, and what’s going on. Well, you will soon know.

Most tutorials and documentation about Python and GTK+ show you to use it (in short) like this:

  • Import Gtk from gi.repository
  • Instanciate some window
  • Call Gtk.main()

However, this has some significant drawbacks. And when you read GTK’s documentation thoroughly, you will notice that it tells you, that you should never start or quit a gtk_main loop yourself in any normal situation, but use a Gtk.Application instead. So: What is a Gtk.Application?

Gtk.Application is a Class offered by the Gtk module and a subclass of Gio.Application.

Gtk.Application allows you to fully integrate your applications in the user’s desktop environment. It will do interprocess communication for you. It will use Dbus. It manages windows and instances. It lets you set a global menu bar, like those in Gnome 3 and Mac OS X. It parses your command line options (if you want). And you need to code almost nothing to get all this working!

All you have to do is to instanciate a Gtk.Application object, pass an identifier and some options to it, connect your Gtk.ApplicationWindow to it, and well, run it.

And if you ever wondered about the difference between a Gtk.Window and a Gtk.ApplicationWindow: That’s it! While a GtkWindow can be used the “traditional” way, a Gtk.ApplicationWindow is meant to be connected to a Gtk.Application instance. And that is, why you’re getting the above messages. When displaying a Gtk.ApplicationWindow, Gtk asserts it is connected to a registered Gtk.Application instance. Assertion failed! 😛

So to make those messages go away, you could either use a Gtk.Window instead of a Gtk.ApplicationWindow, or which in my opinion would be the better choice, go ahead and use Gtk.Application.

You can either get a new Gtk.Application the C way by calling Gtk.Application.new(identifier, flags), or, which is much more pythonic, subclass it.

A simple program using Gtk.Application looks like this:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import gi
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
from gi.repository import GObject, Gio, Gdk, Gtk

class MyApplication(Gtk.Application):
    # Main initialization routine
    def __init__(self, application_id, flags):
        Gtk.Application.__init__(self, application_id=application_id, flags=flags)
        self.connect("activate", self.new_window)

    def new_window(self, *args):
        AppWindow(self)
        
class AppWindow(object):
    def __init__(self, application):
        self.Application = application

        # Read GUI from file and retrieve objects from Gtk.Builder
        try:
            GtkBuilder = Gtk.Builder.new_from_file("gui.glade")
            GtkBuilder.connect_signals(self)
        except GObject.GError:
            print("Error reading GUI file")
            raise
                
        # Fire up the main window
        self.MainWindow = GtkBuilder.get_object("MainWindow")
        self.MainWindow.set_application(application)
        self.MainWindow.show()
            
    def close(self, *args):
        self.MainWindow.destroy()

# Starter
def main():
    # Initialize GTK Application
    Application = MyApplication("com.bachsau.example", Gio.ApplicationFlags.FLAGS_NONE)

    # Start GUI
    Application.run()

if __name__ == "__main__": main()

Let’s dig into that:

  • As with any other application, we first need to import the Gtk module, and as we may want use some of the base classes definition, especialy the Gio flags, import them as well.
  • Next we define a “MyApplication” class, which inherits Gtk.Application and is a singleton, that will manage all aspects our application.
  • Then there’s another class called AppWindow, which gets the MyApplication instance passed and manages everything that happpens in a single window.
  • After all, there’s the main function, which is called when the program is run. It does little more than create an instance of MyApplication and run it.

OK, when the MyApplication instance is created, the constructor just passes an application id to the parent class’es constructor, which is usualy a unique name in the form of tld.domain.product. I’m using “com.bachsau.example”, but it can be everything that takes the form of “string1.string2”. It should be unique across the world and is used to register your application with Dbus.

The second argument is one or more of Gio.ApplicationFlags OR’ed together.

Both arguments are keyword-only-arguments and can be ommited when using the python constructor. But you should at least pass an identifier, to make Gtk.Application’s magic work.

The second command connects the “activate” of Gtk.Application to the later defined new_window method. A “signal” is emitted, when Gtk.Application thinks, that something has happened, and results in the connected function beeing called.

The “activate” signal for example, is emmitted when your application is run, and later, whenever it tries to start a new instance of your application. The new instance is the immediately termined (if you don’t set Gio.ApplicationFlags.NON_UNIQUE), right after making your primary instance open a new window. Isn’t that cool? 🙂

You could even connect other signals, like “open”, which is emitted when the user tries to open a file with your application.

Gtk.Application holds a count of all windows open and automatically quits the main loop, when all windows are destroyed. As you can see, the only thing that MyApplication.Window.close() does is, to destroy it’s MainWindow.

But how does Gtk.Application know a window belongs to it? That is where MainWindow.set_application() comes into play, which is called by the MyApplication.Window class’es constructor, right bevore calling MainWindow.show(), which is vital! This is what adds the Gtk.ApplicationWindow to Gtk.Application’s tracking list. This is what fulfills the above mentioned assertion and finally makes the error messages disappear. That also means, you must NOT set the window visible in Glade when using Gtk.Builder. It just would not work, as your Window would show bevore being connected.

Another thing that you need to take care of, is that you always must either connect the “activate” signal or define a methode called “do_activate”. Otherwise Gtk does not know, how to fire up your application, and the activate-function must result in a Gtk.ApplicationWindow being connected, or else, it will just quit afterwards. All other signals are completely optional, but Gtk.Application has a lot of nice functions to offer. Just read about the Gtk.Application and Gio.Application classes.

That’s all. That’s how you do it right™. All you now have to do is call Application.run() (on your instance of cause) from your main function, to let all the magic happen.

6 comments

  1. I’d like to express my thanks for the material about Gtk.Application at the link above. I’m not a professional programmer, but I am a regular user of Python3, C and Harbour for a variety of programming tasks to manage cameras, photographs, and various documents which my wife and I use in our retirement. Linux is a great learning environment.

    But the main reason for this comment is the material about Gtk.Application. I’ve been struggling, probably for years, with the problem of managing multiple Glade windows within a single Python3 application. Until last week my efforts were HORRIBLE! Then I found the link quoted at the top of this email. Fantastic example!

    But my surprise is this….this example seems to be the ONLY EXAMPLE of Gtk.Application ANYWHERE on the internet. Why this should be is a complete mystery to me.

    Anyway…..thanks again. Brilliant. I will use this as my model for complex Python3 applications from now on.

    1. I don’t think it is the only material but maybe one the very few python examples. However it is somewhat outdated by now. You can read a lot more about these thinks at new “How do I…?” tutorials at gnome.org:
      * https://developer.gnome.org/gtk3/stable/gtk-getting-started.html#id-1.2.3.3
      * https://wiki.gnome.org/HowDoI/GtkApplication

      At least my example hasn’t much to do with using multiple windows but with a more general approach to a possible multi-windowed application than just displaying some windows. The problem with builder is, it creates a single instance of every widget it builds. So the easyest way would be to just use multiple builders based on the same file, which you can manage by multiple instances of window controler you can build in python derived from just the general object class, then have those window controllers, each with its own builder interact with your Gtk.Application derived singleton.

      Another approch are builder templates, which you can use to extend your own window classes and also have multiple instances of them. But they are still buggy in python. I did some experiments here:
      * https://github.com/Bachsau/XDG-Editor
      It runs (open any file, it currently doesnt really open anything) but clicking the help button more than once results in memory corruption and crash.

      My more advanced project is Mixtool (https://github.com/Bachsau/Mixtool) wich is a single windowed application but still uses Gtk.Application and several other parts of Glib, including Gresource packages. The example on my website was derived from this application somewhere around Alpha 0.1 which is nearly 200 commits behind its current development state (including unpublished commits). Feel free to browse my current code for some more advanced examples of GObject usage.

      I mostly stick to the C documention of the API here
      * https://developer.gnome.org/gtk3/stable/GtkApplication.html
      and refer to Christoph Reiters symbol mapping tables if I can’t figure out a functions name in python.
      * https://lazka.github.io/pgi-docs/Gtk-3.0/mapping.html
      A google search for something +”lazka” often yields usable results.

      Beware of tutorials, they are often outdated especialy when it comes to init arguments for classes. Most of them are deprecated. The general rule is now, that you can give anything listed as a property in C docs as a keyword argument and it will be set as the inital value of this property.

  2. I think the line:

    Application = MyApplication(“com.bachsau.example”, Gio.ApplicationFlags.NONE)

    must be:

    Application = MyApplication(“com.bachsau.example”, Gio.ApplicationFlags.FLAGS_NONE)

    Otherwise – at least in my environment – an AttributeError is raised.

    1. You’re right. It was a mistake I made while rewriting the code for this posting to more generic defaults. In my project I was using “NON_UNIQUE” before. Fixed it.

    1. I suggest you read these two articles:
      * https://fuhm.net/super-harmful/
      * https://rhettinger.wordpress.com/2011/05/26/super-considered-super/

      In fact it is a matter of decision, what use cases a programmer intends for his class and if super() is useful in that context. super() can be of great use in some tricky situations, but in most cases it is just useless overhead that limits compatibility and flexibility and makes things needlessly complicated. So I think super() has its legitimation but we shouldn’t use it everywhere.

Leave a Reply to BachsauCancel reply