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

Though even my 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/python3
# coding=utf8

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.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 the 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.

Leave a Reply