Earlier this year, the Fire IDE for Elements was officially released after a fairly extensive beta. I have previously stuck with Visual Studio for the [relatively little] Elements work I have been doing but problems with my VM solution on a recently acquired MacBook Pro gave me the impetus to spend some quality time with Fire, and I have to say it is very impressive.
As an introduction to Fire I decided to revisit one of my OS X references, the very excellent Cocoa Programming for Mac OS X. This time around though, I decided also to adopt the tutorials in that book not only to ObjectPascal (Oxygene) but also to create multi-platform solutions, using the “Shared Code” project capabilities of Elements.
This ability to share code is not unique to Fire and is also shared [sic] with Visual Studio. This is not the only thing that is shared with Visual Studio, as we shall see.
First Simple Project: A Random Number Generator
The first project is a very simple random number generator. This is the finished OS X UI:
So, how do we get there with Fire ?
First Steps
Installation of Fire on OS X is as straightforward as any other OS X application. When you first launch the IDE you are greeted by a very minimalistic welcome screen from which to login to your Elements account, access some “Getting Started” resources, choose your preferred default language and create or open a project.
Obviously, all three Elements languages are supported: Oxygene (ObjectPascal), C# and Swift. I prefer Oxygene.
After selecting “Start a New Project” we then get to choose the platform for the project, confirm or change the language and choose from an extensive set of templates, appropriate to the platform you have selected:
For this application we shall create a Cocoa Application (OS X) so select the Cocoa platform and the appropriate template. The Cocoa platform is where you will also find templates for iOS, tvOS and watchOS.
The other platforms are .NET and Java (where the Android templates are to be found).
After choosing a name and save location for our new project, the new project is created and the IDE loads that project and solution for us to start work:
I have expanded all the disclosures in the project hierarchy to show all the files in this new project.
This includes our code, references to other projects or libraries and the resources that our project uses. Since this is a Cocoa OS X application, this includes the .xib files that contain our UI layouts.
The code in our templated source will be familiar to any native OS X developer, although expressed in ObjectPascal rather than Objective-C or Swift. For example the AppDelegate.pas file:
namespace RandomNumber; interface uses AppKit, Foundation; type [NSApplicationMain, IBObject] AppDelegate = class(INSApplicationDelegate) private fMainWindowController: MainWindowController; protected public method applicationDidFinishLaunching(aNotification: NSNotification); end; implementation method AppDelegate.applicationDidFinishLaunching(aNotification: NSNotification); begin fMainWindowController := new MainWindowController(); fMainWindowController.showWindow(nil); end; end.
This is standard boiler-plate stuff and we won’t need to touch it for this application.
The template even includes an initial UI for us, providing two .xib files, one containing the application menu (MainMenu.xib) the other an empty window (MainWindowController.xib).
For this exercise we need only focus on the MainWindow files. Again, the controller source code is boiler-plate that we will not need to worry about for this application. However, we do need to modify the MainWindowController.xib, since this contains the UI which we need to design.
However, selecting this file in the project hierarchy we are presented with this:
Unlike the Visual Studio edition of Elements, Fire contains no UI designer tools. Instead, the native platform tools are used. When working with Elements in Visual Studio, this means you have access to the Visual Studio .NET UI designers immediately to hand since Elements is a Visual Studio plug-in itself.
But on OS X, since Fire is not integrated with Xcode some other mechanism is needed. In fact, the mechanism is the same as that used by Elements in Visual Studio.
Xcode can be used to edit certain files “stand-alone”. In the case of xib files for a project however, a certain amount of “scaffolding” is required, to enable Xcode to connect the UI elements in the xib to the code that will eventually run alongside that UI.
When editing your xib files, Fire (or Visual Studio) maintains a skeleton project containing the xib files and just enough code to allow Xcode to function. In Visual Studio this skeleton project is usually kept in synch whenever affected files are changed in your project or via an explicit “Refresh” facility in the IDE, but you must launch Xcode and open the associated project yourself.
In Fire the skeleton is updated every time you click “Edit in Xcode” for a given xib file, which also launches Xcode (if not already running) and loads the project for you.
Obviously this approach also works for xib files associated with iOS and other Cocoa projects as well.
For Android projects there is a similar mechanism in place that uses Android Studio as the UI designer, but in that case both Visual Studio and Fire are able to launch AndroidStudio for you directly (as long as you have the relevant Windows or OS X version of Android Studio installed, obviously).
So, let’s do that and design the UI in Xcode:
Here I’ve again expanded a couple of the disclosures in the Xcode project to show some of the skeleton project files that Fire has created together with the xib files that are actually part of our Fire project itself.
We can see that the Xcode project has a named that is based on (but not exactly the same as) our Fire project name.
I’m not going to go into the details of Xcode Interface Builder since none of that is specific to Fire. So I shall simply say that you now work with Interface Builder just as you would for any Cocoa OS X UI application since that is of course exactly what we are doing!
So here is our finished UI, in Xcode:
Actually, it is not quite finished.
The application at this point will happily compile and run, presenting this UI. But it will not actually do anything. After all, we haven’t as yet written any code to make it do anything. But when we do so, we will need to make some further changes to the UI design, to connect it to our code.
But, first things first. Let’s write that code.
This is the point at which I shall deviate slightly from the tutorial in the Cocoa Programming book. In that tutorial the next step is to create a new controller class to implement the code required by the application and connect that directly to the UI.
For my purposes however, I want to place my code in a re-usable class that I can share with other versions of my app for different platforms. So, the next thing I shall do is add a “Shared Code” project to my solution.
So from the Fire menu I select “New Project…”. This time it doesn’t really matter which Platform I select for the new project. The template I am using for this project is “Shared Project” and this is available in the list of project templates regardless of which platform I have selected:
I also ensure to set “Add to current solution” checked (it is by default) and the result is a new, very empty project in my Fire solution. I called this project RandomNumber.Shared and to make my OS X application use it, I simply drag and drop the shared project onto the References item in that project:
But as it stands this is actually sharing nothing since there is no code in that shared code project. I shall address that now by adding a new class to the shared project. This will be a simple class with two methods. One to seed the random number generator and the other to generate a random number. These methods can be class methods.
Although the coding surface exposed by this class will be the same across each platform, the implementation will of course vary. This is not a shared library, in the sense that it produces a cross platform library of object code, rather each project that references a Shared Code Project will recompile that project when that referencing project is itself compiled. “Shared Code” means exactly what it says.
What this means is that this class will need to contain conditional compilation directives to ensure the appropriate implementation for each supported platform. At this point we shall implement the Cocoa version, but provide the directives to identify where other platform code will eventually be needed:
namespace RandomNumber; interface uses {$if NOUGAT} Foundation {$elseif ECHOES} {$elseif COOPER} {$endif}; type RandomNumber = public class public class method Generate: Integer; class method Seed; end; implementation class method RandomNumber.Generate: Integer; begin {$ifdef NOUGAT} result := Integer(random() mod 100) + 1; {$elseif ECHOES} result := 4; // Temporary: Sufficiently random number {$elseif COOPER} result := 4; // " " " " {$endif} end; class method RandomNumber.Seed; begin {$ifdef NOUGAT} srandom(Cardinal(time(NIL))); {$elseif ECHOES} {$elseif COOPER} {$endif} end; end.
The key thing to note in this code are the conditional compilation symbols defined for us by the compiler for each of the platforms:
Symbol | Platform |
---|---|
NOUGAT | Cocoa |
ECHOES | .NET |
COOPER | Java (incl. Android) |
Other than that, I’m not inclined to get into a debate over the merits of one or another mechanism for generating random numbers under OS X or on the other platforms. The code above is merely the ObjectPascal equivalent of the Objective-C code provided with the tutorial being followed.
For comparison, here are the (relevant sections of) Objective-C code:
- (IBAction)generate:(id)sender { int generated; generated = (int)(random() % 100) + 1; // ... } - (IBAction)seed:(id)sender { srandom((unsigned)time(NULL)); }
The key difference here is the presence of (IBAction) in the declarations of these methods. This is because this code is intended to be connected directly to the user interface of the application in the tutorial.
In Delphi terms, you can think of these as OnClick event handlers, complete with a sender parameter, which you may have noticed.
In my case however, what we have instead created is a re-usable class. A class that can be re-used across different platforms. But we still need to “hook it up” to this particular platform specific application.
To do that we need a controller object which may be connected to the objects and events in that UI and in turn call our re-usable code.
This again is a very simple class, but being platform specific we need to add it to the OS X platform project, not the shared code. We will call it “RandomController” and it looks like this:
namespace RandomNumber; interface uses AppKit, Foundation; type [IBObject] RandomController = public class private [IBOutlet] lblNumber: NSTextField; [IBAction] method seed(Sender: id); [IBAction] method generate(Sender: id); end; implementation method RandomController.seed(Sender: id); begin RandomNumber.Seed; lblNumber.stringValue := 'Generator Seeded'; end; method RandomController.generate(Sender: id); begin lblNumber.intValue := RandomNumber.Generate; end; end.
The thing to note is that we see proper namespacing at work. Since the RandomController class is in the same namespace as the RandomNumber class in the shared code there is no need to add anything to our uses clauses. In Elements we create and use namespaces, not units.
So our methods for seeding the generator and generating random numbers can simply directly call the class methods provided by the RandomNumber class. Rudimentary feedback is reported by setting the text displayed by the label.
Other than that, this looks like a fairly ordinary ObjectPascal class, even one that might implement part of a Delphi UI. All that is except for a couple of very obvious differences.
The first and perhaps least obvious is that everything is private.
In Delphi, the component and control references and all of the event handlers are all declared as published members of a form class. Strictly speaking they are declared without any explicit visibility, but the default visibility specifier is published so that is what they are.
That is, everything to do with the UI of your forms is laid bare. Exposed. Unprotected.
Which is bonkers when you think about it and is largely responsible for a great deal of the poor practice commonly encountered particularly in legacy Delphi applications, with these very exposed members liberally referenced throughout a project which can make it very difficult to disentangle pieces of the UI from each other.
Which isn’t to say that you couldn’t deliberately create the same mess here, but “deliberately” is the key. You cannot as easily simply fall into bad practice.
Of course, in Delphi the use of published visibility on these members is the result of the implementation decisions made in the very earliest days of the form design persistence framework in the VCL. Those declarations in the code need to be connected up to the elements in the form design at runtime, so some information is required about those declarations in the code that can be discovered at runtime.
The required information is of course contained in the RTTI (RunTime Type Information) of the class for the form and RTTI used only to be produced for published members. Hence the necessary use of published member declarations.
So how does a Cocoa UI get “connected” up to the code ?
The secret is in the other notable difference between the above code and a typical Delphi form class. Specifically, the various ‘IB‘ attributes that decorate elements of the class.
IB here stands for “Interface Builder” and these attributes provide the “glue” between the code and Interface Builder in Xcode. [IBOutlet] and [IBAction] are directly analagous to similar elements of Objective-C declarations. [IBObject] however is specific to Elements (Fire or Visual Studio).
The presence of an [IBObject] attribute is the signal to Elements that the decorated class is required to be made available for referencing in Interface Builder. When the skeleton project for Xcode is created, it will contain declarations that correspond to this class and the actions and outlets supported by that class.
In Delphi terms, an action is similar to an event whilst an outlet is similar to an object reference.
You will notice in our class however that we only have an outlet (reference) for the label, but not the buttons.
At the same time, we still have events that appear to correspond to the events from the buttons.
So how does that work !?
When it comes to outlets, or object references, it means we only have to “connect up” those objects that we need to reference. If we had a label that only ever displayed a fixed piece of text and never needed to be referenced in code then it doesn’t need to be declared in the code either.
We only have to connect up those things to a controller that the particular controller needs.
If we return to Interface Builder in Xcode, we shall see.
So we select the MainWindowController.xib and click the “Edit in Xcode” button. This refreshes the skeleton project for Xcode and identifies our RandomController object which is important for the next step.
In the UI designer of XCode, next to the UI itself is a vertical strip of icons. Each icon in this area represents some object relevant to the UI. Some of these are pre-defined and present in every UI. But we can add additional objects here, similar to non-visual components in Delphi.
In this case we want to add an instance of our RandomController.
We do that by selecting NSObject from the object library and dragging a new instance to this area of the UI design:
But we also need to tell Interface Builder what class of object this represents. This is achieved very simply in the Identity inspector (top right) which for an NSObject includes a list of available classes.
Since we added the [IBObject] attribute to our RandomController class, Fire included declarations for that class in the skeleton code that Xcode is working with, and so we can identify this object as an instance of RandomController.
Xcode here thinks we are hooking the object up to the placeholder class that Elements stubbed out in the skeleton project which is how it knows that such a class even exists. But at runtime, RandomController will be the Oxygene class in my project, not the placeholder.
That placeholder also contains declarations for all the things that I now need to connect up in the UI. That is, the outlets and the actions.
To connect the buttons to the corresponding actions I simply Ctrl+Drag from each button in the UI onto the Object instance. When I release the drag operation, Xcode pops up a menu from which I can choose the actions implemented by that object.
I simply choose the action that corresponds to each button. Once I’ve done this I can see these connections by viewing the Connections Inspector for the object:
The final step is to associate the label with the outlet on the same object.
This time I use the Connections Inspector itself. As you can see in the previous screenshot, the selected object (representing our RandomController, remember) currently has nothing connected to the lblNumber outlet.
I connect this up again by Ctrl+Dragging, this time from the empty connection point next to the outlet in the inspector (which turns into a ‘+’ sign as I do) and dropping the connection line onto the control in the UI that I wish it to refer to.
It has to be a control of the correct type of course, just as the actions can only be connected to methods with the right “signature”.
The final result is this:
And that’s it.
I can now save my work in Xcode, return to Fire and compile and run my application.
Next time I shall add a .NET version of the application to my solution.
Before we get to that point however, there is perhaps one piece of information relating to that which I should cover…
What about .NET UI Designers ?
I described how both Fire and Visual Studio allow you to work with Cocoa and Android UI files using the native platform tools for such things, and also mentioned that the Visual Studio version of Elements obviously has access to the Visual Studio designers for .NET projects.
Which just leaves the question: How do you edit the UI files for .NET projects if you are using Fire ?
Well, the same applies: You use the platform native tools, so in this case that means Visual Studio.
So how do you get the files from Fire into Visual Studio in order to be able to work with the designers there ?
The answer is surprisingly simple: Simply open your Fire solution in Visual Studio. That’s it.
Where-as Xcode and Android Studio have their own very specific project and solution file format, the solution and project files in Fire are specifically and directly compatible with Visual Studio’s own project system!
This is easy to read, write, maintain, debug and test. It’s platform-independent and runs fast:
program random;
begin
Randomize;
Writeln(random);
end.
🙂
Absolutely. But the purpose of illustrative examples is to illustrate. The next installment will reveal more. 🙂
And actually, there is a problem with Randomize() and Random() which will also become apparent in the next installment. (It is perhaps a highly theoretical problem rather than strictly practical, but still a potential problem never-the-less).