Widget toolkits

I knew GTK+ 3 intimately, and needed to understand its native peers. As a result, I’ve made this comparison page. I’ll mix languages and pseudo-languages as appropriate.

Cross-comparison

Same {thing, goal, concept}, different {name, means, implementation}.

GTK+ Win32 Cocoa

widget

window ← control

view ← control

accelerators

accelerators

key equivalents

fully automatic widget placement and sizing, or GtkFixed for DIY

fully manual widget placement and sizing (as a rule, the minimum size required cannot even be queried, except for a few *_GETIDEALSIZE)

both ways + NSView.autoresizingMask and constraints somewhere in between

top-left origin

top-left origin

bottom-left origin

Cairo, GSK (starting with v4)

GDI

Quartz

Pango markup (display-only)

RTF (through Rich Edit controls)

RTF, RTFD, DOC

PangoLayout (rendering-oriented)

—

NSAttributedString

Common properties and actions

GtkWidget:visible

WS_VISIBLE ← ShowWindow()

NSView.isHidden
NSWindow.isVisible ← NSWindow.order*(…)

GtkWidget:is-sensitive

WS_DISABLED

NSControl.isEnabled

GtkWidget:focusable

WS_TABSTOP

NSResponder.acceptsFirstResponder
(it is a ‘key view’)

GtkWidget:has-focus

GetFocus() == window

NSWindow.firstResponder? == view

GtkWidget.grab_focus()

SetFocus(window)

Focused children aren’t remembered, you must implement that yourself in WM_ACTIVATE/WM_SETFOCUS.

NSWindow.makeFirstResponder(view)

override GtkWidget::focus

adjust z-order

adjust NSView.nextKeyView

GtkWidget.queue_draw{,area}()

InvalidateRect()

NSView.needsDisplay = true
NSView.setNeedsDisplay(_:)

GtkWindow.present_with_time()

SetActiveWindow()

NSWindow.makeKeyAndOrderFront(_:)
NSApplication.activate(
ignoringOtherApps:)

Receiving events

Add a handler using g_signal_connect(), override the corresponding virtual method in a subclass, or implement an interface.

Some events may need enabling via GtkWidget.add_events(), and some require adding event controllers.

Handle messages sent to the top-level/parent window, or to the control itself, through subclassing.

Notifications from ‘common controls’ are usually wrapped in WM_NOTIFY or WM_COMMAND messages.

Edit and Rich Edit controls may need EM_SETEVENTMASK.

Win32 has no ‘window server’ and can synthesise events on-demand, thus there’s no need to enable WM_MOUSEMOVE.

One or more of:

  • Assign a ‘delegate’ or ‘dataSource’ object implementing the corresponding protocol.

  • Set ‘target’ and ‘action’ or ‘doubleAction’ properties of classes such as NSControl.

  • Override methods in a subclass, e.g., NSView.draw(_:).

  • Use key-value observing.

  • Use Cocoa bindings.

  • Use NSNotificationCenter.

  • Add NSGestureRecognizers.

  • Directly pass a callback block.

Additionally, you may need to enable acceptsMouseMovedEvents in NSWindow.

Widgets

GtkLabel

WC_STATIC

NSTextField

GtkEntry

WC_EDIT

NSTextField

GtkTextView

WC_EDIT, RICHEDIT_CLASS, MSFTEDIT_CLASS

NSTextView

GtkButton, GtkToggleButton, GtkCheckButton, GtkRadioButton

WC_BUTTON

NSButton

GtkSwitch

—

NSSwitch

GtkComboBox{,Text}

WC_COMBOBOX

NSComboBox, NSPopUpButton

Containers

GtkPaned

—

NSSplitView

GtkBox

—

NSStackView

Utilities

GtkMessageDialog

MessageBox()

NSAlert
NSRunAlertPanel()

GtkFileChooserDialog

Get{Open,Save}FileName(),
IFile{Open,Save}Dialog

NS{Open,Save}Panel

Miscellaneous

Gtk.show_uri_on_window()

ShellExecute{,Ex}()

NSWorkspace.open(…)

Gdk{,Display,Window}.beep(),
GtkWidget.error_bell()

MessageBeep()

NSBeep()

GtkClipboard

{Get,Set}ClipboardData(), …

NSPasteboard

GtkTarget{List,Entry}

numeric clipboard formats

UTIs

Gtk.GLArea

wgl*()

NSOpenGLView

GObject.set_data()

GWL{,P}_USERDATA

objc_setAssociatedObject()

Application support

GtkApplication

MDI? hPrevInstance

NSApplication

GSettings → gsettings, dconf-editor

registry → reg.exe, regedit.exe

NSUserDefaults → defaults

XDG Base Directory Specification, embedded blobs

embedded resources

application bundle contents

gettext, LTR/RTL icon lookup

per-locale resources

per-locale resources, ibtool

Event loops

GMainLoop, entered by Gtk.main() or various *_run() functions

Explicit {Get,Peek}Message() loops, possibly wrapped in MsgWaitForMultipleObjects()

Implicitly entered by DialogBox(), Get{Open,Save}FileName(), etc.

Message dispatch is summarised in The Old New Thing, chapter 15.

NSApplication/{NS,CF}RunLoop,
combined with the Grand Central Dispatch library

g_timeout_add{,_full}()

SetWaitableTimer(),
SetTimer() is a bit problematic

NSTimer,
NSObject.perform(…),
CFRunLoopAddTimer(),
dispatch_after()

g_idle_add{,_full}()

SetEvent() + order of handles

NSObject.perform{,Selector}(…),
CFRunLoopPerformBlock(),
dispatch_async()

Runtime introspection

integrated: GTK Inspector

external: Microsoft Spy++, WinSpy++, Accessibility Insights, …

external: Xcode View Debugging, Accessibility Inspector

Source code availability

open source

Windows XP/2003 source code leaks

Low-level libraries are open source (e.g., CF, CFNetwork, libdispatch). For the higher levels, the most you’ll find are GNUstep reimplementations.

Roots

GTK 0.99.0 (1997)

Windows 1.0 (1985)

NeXTSTEP 0.8 (1988)

Overall attributes

Conceptually simple

Easy to use

Comprehensive

Implements the CADT model

Simplistic, ad-hoc, deep legacy

Horrendously complicated

Note that the GTK+ stack essentially succeeds in misusing Win32 and Cocoa APIs to treat the respective operating systems as X.org-based Unices—​its original goal of replacing Motif in GIMP has shaped its design that way, even though much of it is being rewritten away.

Win32 has been officially renamed to Windows API, and is being displaced by a mix of overcomplicated bullshit that’s very hard to make any sense of. Meanwhile, later parts of the interface, such as XPS printing, are often only accessible through COM, and difficult to use.

AppKit is being displaced by SwiftUI, which is nice in theory but completely useless in practice, so you have to break the abstraction and try to mix it with AppKit and/or UIKit anyway.

C enhancement pills

Both GTK+ and Cocoa use their own object frameworks that are bolted on top of C, though GTK+ is overall much more regular, and perhaps more flexible. Win32 code mainly uses C++ and COM for this role, while core system objects are managed with a mess of specialised C-style functions.

GTK+/GObject/GLib Cocoa/Foundation

C, Vala

Objective C, Swift

manual reference counting with ‘floating’ references

optional automatic reference counting

Manually managed code can use autorelease pools.

signals

Action signals complete the similitude.

messages, callback blocks

KVO and NSNotificationCenter finish the picture.

GObject::notify

addObserver(_:forKeyPath:options:context:)
(from NSObject/NSKeyValueObserving)

Key paths are actually a query language.

g_object_bind_property{,_full}()

bind(_:to:withKeyPath:options:)
(from NSObject/NSKeyValueBindingCreation)

This forms an entirely separate namespace in Cocoa, documented separately from properties.

Data structures

GObject

NSObject

GValue, (GVariant)

NSValue, (NSNumber)

GString

NSMutableString, (NSString)

GBytes

NSData

GByteArray

NSMutableData

GArray, GPtrArray

NSMutableArray, NSArray

GHashTable

NSMutableDictionary, NSDictionary
NSMutableSet, NSSet

GDateTime, GDate

NSDate

Miscellaneous

GError

NSError (or NSString **, or Objective C exceptions)

Additionally, GNOME code may use GLib’s macros for partially automated reference counting that only compile under GCC and Clang, and Apple has extended C itself with blocks—these only work with Clang compilers.

Comments

Use e-mail, webchat, or the form below. I'll also pick up on new HN, Lobsters, and Reddit posts.