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.
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 synthesize events on-demand, thus there's no need to enable WM_MOUSEMOVE. | One or more of:
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?
| 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,
|
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.
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/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:)
Key paths are actually a query language. |
g_object_bind_property{,_full}() | bind(_:to:withKeyPath:options:)
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) |
Comments
Use e-mail, webchat, or the form below. I'll also pick up on new HN, Lobsters, and Reddit posts.