I've just pushed a new set of samples to our Elements Samples GitHub repository. The new sample, available for Oxygene (Object Pascal), C# and Swift (GitHub links), shows off some concepts for sharing code in an application with bespoke native User Interface for Mac and Windows.
While purposely very simple, the basic structure of the sample is inspired by how I've handled similar challenges when writing Fire and Water: essentially sharing as much of the actual logic and infrastructure as possible, while still creating a custom UI for both platforms.
This sample focuses on the UI aspect, mainly the Main Window of the app, and how to best structure this code. Of course Fire/Water, as would any real-world app, has large amounts of "data mode/business logic" code that can be shared as well, independently of these patterns.
Let's have a look.
The sample comes as a solution split into three projects, a Mac Cocoa app, a WPF .NET app, and a Shared Project.
All the relevant code is inside the shared project, with the platform-specific projects really just being thin skeletons to let the app be built and started; the Cocoa version has literally no code of its own, and the WPF version has nothing but the App.xaml
, which passes control right into the shared code.
At root level, you see three source files (with some nesting), only two of which really have relevant code.
-
The
Aliases
source file declares a couple of type aliases to provide common names between Cocoa and .NET. It's trivially empty here with just a couple of entries — but for a real life project, you might end up adding a few more (for example, Fire/Water maps classes such asColor
/NSColor
, Fonts,NSViews
/UserControl
and many more). -
The
AppDelegate
file will be familiar to you if you worked with Cocoa before. I've chosen here to adopt it to serve the same purpose on WPF – as the main entry point and center of the app. Once again, in this simple sample its purpose is very trivial – mainly getting the MainWindowController instantiated (and held onto) in the (shared)start()
method seen below:
You'll notice that both AppDelegate
and MainWindowController
(which we'll look at next) have additional files nested below them, named .Cocoa
and .WPF
, respectively. These three files come together to form a single class (using Partial classes, or Extensions in Swift parlance), with the two nested files holding any Cocoa- or WPF-specific code, conditionally compiled via #if
.
This is a purely esthetic design choice. Depending on the amount of platform-specific code, you can also simply put everything in the root file. For the Fire/Water project, the amount of shared vs. specific code varies largely between files, but for consistency reasons, I chose to split all UI classes into three, even if sometimes only a single method (or less) is platform specific. I carried this concept over to this sample as well — in fact, you'll find AppDelegate.WPF.*
is completely empty at this stage (and the Cocoa version only contains the applicationDidFinishLaunching:
entrypoint).
- The
MainWindowController
, finally, represents the main (and only) window of this sample application. For separation of concerns, this is split between the Window itself on one hand (for Cocoa represented only by a code-less.xib
file and for WPF with a.xaml
file with nested code-behind source file), and the controller on the other. All the logic for the window is implememted in the controller and, in this case, 100% shared between platforms:
You see three public properties (var
, in Swift parlance), which are marked to Notify
on change, so they can be bound to the controls via Cocoa or WPF bindings, respectively. You also see a public "action" method called calculateResult
that is connected to the one button in the window (again from the .xib
and the xaml.*
files, respectively).
As the user changes values in the window, the properties get adjusted automatically, and conversely when calculateResult
sets the result
property, the UI updates to reflect the change immediately, as well. All from platform-independent UI code.
The sample will ship with the next build of Elements, and you can also grab it directly from GitHub here:
And it goes without saying that the sample can be used in Fire, Water and Visual Studio.
You can read more about Elements here.