Qt on Mono with Qyoto

When trying to build graphical interfaces on Linux with Mono, you have lots of choice. If you want maximum portability you can use Windows Forms (which don't look so great on Mono) or Gtk# (way better for multi-platform stuff). Both have a pretty standard look, even if WinForms look native only on Windows and Gtk does so on Gnome, and both work very well for standard desktop apps.

However, when doing more advanced graphics or targeting embedded systems, both fall short: WinForms runs OK on Windows CE (even if it is very limited), while Gtk looks pretty boring and may not be the ideal choice on resource constrained systems. The next multiplatform GUI choices that come to mind are Nokia's Qt or Intel's Clutter. Clutter is still a young project, but both frameworks unfortunately lack some good .NET bindings in essence.

In my case I'm starting to use Qt, through the Qyoto bindings. The latest Ubuntu distro already includes the package, which makes getting started pretty easy — at last if it weren't for the lack of documentation.

Qyoto provides a quite complete mapping of the Qt interface, but unfortunately mantains a lot of the original C++ “smell”: many enumerations are given as simple integers, events (the signal/slot system in Qt) don't rely on .NET events but require you to bind methods to signals by their untyped name. Less of a problem, but still not very pretty: in many points Qyoto doesn't respect the common .NET naming conventions and some Qt methods should really be mapped to properties.

Anyway, these minor criticisms notwithstanding, Qyoto provides a simple and complete way to use Qt's power from C#. Let's take a look to a simple example application.

Getting started

A simple hello world example in Qyoto looks like this:

class Program {
	public static void Main(string[] args) {
		var app = new QApplication(args);
		var w = new TestWidget();
		QApplication.Exec();
	}

}

class TestWidget : QWidget {
	public TestWidget() {
		SetWindowTitle("Hello world");
		var button = new QPushButton("Click me!", this);
		QApplication.Connect(button, SIGNAL("clicked()"), this, SLOT("Clicked()"));
		this.ShowNormal();
	}
	
	[Q_SLOT]
	private void Clicked(){
		Console.WriteLine("Clicked!");
	}
}

As you can see, there's quite a lot of “unusual” syntax that is taken directly from the C++ libraries. The first thing you need to do is to create a QApplication object that initializes Qt. By calling the static Exec() you enter the main application loop and all QWidgets you create will be shown and will keep the loop alive until you close them.

Our sample Qyoto widget!

A QWidget is both the generic base class for all Qt GUI elements and a displayable window. You can simply add other controls (like the QPushButton) by using the constructor overload that takes the control's parent as a parameter.

The most exotic part is the event handling: you need to reference the control's events by name and through a special macro-like method (in this case SIGNAL("clicked()")). The event is then hooked to a “slot” method of any QObject instance (in the code above the click event is linked to the Clicked() method on the same class instance). The method must also be marked with the special [Q_SLOT] attribute before you can use it as an event handler.

If you need some more source code to give a look to, you can check out the Synapse project: a now defunct open-source instant messenger built on Mono and Qyoto.

Missing methods

For some strange reason, the Qyoto libraries that ship with the current Ubuntu (10.04) miss some of the methods that, according to the documentation, should be available. For instance, the QWidget member list mentions a WinId() method that allows you to get the native X11 window handle of the widget. However, the method is nowhere to be found in the actual DLL!

Fortunately you can take a look to the source code and understand how the method is actually implemented.

public uint WinId() {
	return (uint) interceptor.Invoke("winId", "winId() const", typeof(uint));
}

The method can very easily be converted to an extension method in your code:

public static uint WinId(this QWidget widget){
	var invocation = new SmokeInvocation(typeof(QWidget), widget);
	return (uint)invocation.Invoke("winId", "winId() const", typeof(uint));
}

This looks nice and will work even if the next versions of the library start including the strangely missing methods.