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.

Contents

Cross-comparison

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

GTK+Win32Cocoa
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 synthesize 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 summarized 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, however 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 specialized C-style functions.

GTK+/GObject/GLibCocoa/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.