The Mono framework is a great tool for developing cross-platform applications. Using Mono, a developer can target Windows, Linux and OSX all at once using the same C# language and .NET-compatible framework. But arguably the biggest problem with cross-platform toolkits and frameworks is the GUI – people like seeing windows apps that use the familiar Windows Forms GUI toolkit, and people like seeing Mac apps that use Cocoa. With MonoMac, you don’t necessarily have to choose. As a developer, you can build your Mac gui using Cocoa and your Windows GUI using Windows Forms, and reuse all the rest of your application code for both platforms. But integrating Cocoa with C# and .NET isn’t the most straightforward thing. Here are the basics. First, create a new MonoMac project. I’m using MonoDevelop on Mac. I’ve already installed the MonoMac plugin. You can do so by using the Add-In Manager (MonoDevelop menu>Add-In Manager). From the Add-In Manager, expand “Mac Development,” select the “MonoMac development” entry, and install. You’ll need to restart MonoDevelop.
With that out of the way, click “Start New Solution..” from the MonoDevelop home screen. Expand “C#” and select “MonoMac.” Click “MonoMac Project” and name it. Click “Forward” and leave “GTK# Support” unchecked, we won’t need it.
You’ll be greeted with a few files already in your new project. There’s a MainWindow.cs, a MainWindowController.cs, a Main.cs, an AppDelegate.cs, and finally a MainMenu and MainWindow.xib. Those are very interesting – a .xib is where InterfaceBuilder saves pre-instantiated cocoa GUI items. You’ll be working a lot with these. Rather than explain all of these, let’s get on with the process.
The next step is to double-click MainWindow.xib. This will open the file in Xcode, where we’ll customize it for our app. You DO have Xcode installed, don’t you? If not, get it from the App Store now.
With MainWindow.xib opened in Xcode, drag a button and a label from the object library in the bottom right to the window in the main view.
Show the Assistant Editor by clicking the middle button above “Editor” in the toolbar. Open “MainWindowController.h” by clicking the “Related Files” button at the top left of the pane, and selecting the file from “Recent Items.” If it’s not in that list, you can find it using one of the other menus at the top of the pane.
The next step involves right-click-dragging from the designer pane to the open code. What we need to do is add “Outlets” and “Actions” to the .h file we have open. These hook code to design, and more importantly, MonoMac takes the definitions we’re about to add and hooks to them on the C# side automatically so we can interact with the GUI.
The code can be added by hand, but the easiest way (in my opinion) to do it is to use the IDE. Let’s start by right clicking (it’s probably easier to control-click, the same thing in Mac land) the button we added earlier and dragging to the area of the code right below the “@interface” block.
A window pops up to let us tell Xcode what we mean to do: add an Outlet or Action and name it. For now, select “Outlet” and name it “guiButton.” Right now, we’re telling the code that we’ve got a button called guiButton to interact with, and it’s the one in the GUI that we just right-clicked on. Do this again with the label, but call it “guiLabel.”
The code should now read something like
#import <Cocoa/Cocoa.h> #import <Foundation/Foundation.h> @interface MainWindowController : NSWindowController { NSButton *guiButton; NSTextField *guiLabel; } @property (assign) IBOutlet NSButton *guiButton; @property (assign) IBOutlet NSTextField *guiLabel; @end
Now we want to add an “Action” – the thing that the button DOES. Right-click-drag from the button to the code again, just as before, but this time select “Action” as the connection. Name it “buttonClicked.” Perhaps believably, the code should now read
#import <Cocoa/Cocoa.h> #import <Foundation/Foundation.h> @interface MainWindowController : NSWindowController { NSButton *guiButton; NSTextField *guiLabel; } @property (assign) IBOutlet NSButton *guiButton; @property (assign) IBOutlet NSTextField *guiLabel; - (IBAction)buttonClicked:(id)sender; @end
Now we’ve got everything we need from Xcode. Save and close. Head back over to MonoDevelop. You might need to close all the open files to reload the project, or just restart MonoDevelop. Expand MainWindow.cs in the Solution Explorer, and open MainWindow.designer.cs Note it’s got a bunch of new stuff in it:
// WARNING // // This file has been generated automatically by MonoDevelop to store outlets and // actions made in the Xcode designer. If it is removed, they will be lost. // Manual changes to this file may not be handled correctly. // using MonoMac.Foundation; namespace NewMonoMacProject { [Register ("MainWindowController")] partial class MainWindowController { [Outlet] MonoMac.AppKit.NSButton guiButton { get; set; } [Outlet] MonoMac.AppKit.NSTextField guiLabel { get; set; } [Action ("buttonClicked:")] partial void buttonClicked (MonoMac.Foundation.NSObject sender); void ReleaseDesignerOutlets () { if (guiButton != null) { guiButton.Dispose (); guiButton = null; } if (guiLabel != null) { guiLabel.Dispose (); guiLabel = null; } } } [Register ("MainWindow")] partial class MainWindow { void ReleaseDesignerOutlets () { } } }
We see outlets corresponding to each of our GUI elements, as well as an unimplemented function definition for “buttonClicked” – how convenient! If we click “start debugging” now, the application works just fine – it launches and shows us the gui we designed. But nothing does anything. That’s not very helpful, so let’s implement the buttonClicked function. We can do it right in the file above where it is now, simply be removing the “partial” keyword, and implementing our functionality in brackets below the definition. But as MonoMac notes at the top, manual modifications to this file aren’t very nicely handled, so let’s implement it elsewhere. Notice that at the top of the file, it says we’re implementing partial class MainWindowController so it stands to reason that MainWindowController.cs is where we want to implement custom code. Open that file. Below #endregion write the implementation of buttonClicked. Modify the code so it looks like the following:
using System; using System.Collections.Generic; using System.Linq; using MonoMac.Foundation; using MonoMac.AppKit; namespace NewMonoMacProject { public partial class MainWindowController : MonoMac.AppKit.NSWindowController { #region Constructors // Called when created from unmanaged code public MainWindowController (IntPtr handle) : base (handle) { Initialize (); } // Called when created directly from a XIB file [Export ("initWithCoder:")] public MainWindowController (NSCoder coder) : base (coder) { Initialize (); } // Call to load from the XIB/NIB file public MainWindowController () : base ("MainWindow") { Initialize (); } // Shared initialization code void Initialize () { } #endregion // This is where we can add custom code. partial void buttonClicked (MonoMac.Foundation.NSObject sender){ this.guiLabel.StringValue = "You clicked me!"; } //strongly typed window accessor public new MainWindow Window { get { return (MainWindow)base.Window; } } } }
Note if you typed that function out by hand that this.guiLabel and this.guiButton are available to reference because we added them as outlets earlier in Xcode, and MonoMac added them in the partial implementation of this controller class (MainWindowController) inside MainWindow.designer.cs.
Start debugging inside MonoDevelop. When you press the button, the label text changes. Everything worked perfectly!
Hopefully after going through this exercise, the concepts of Outlets and Actions, how to define them, and how they’re implemented on the MonoMac side of things makes sense. Going from here, it should be relatively straightforward how to implement both a Mac GUI and a Windows GUI in your application and link both of them to shared backend code that does all the other things your app is supposed to.
Great tutorial! I was very helpful, thank you very much.
cool! the XCode part is not easy but worth it.
This was extremely helpful. Huge thanks. I’ve been trying to find a good writeup to get started and this was the most clear walkthrough I could find. Thanks.
Excellent introduction. Thank you!
Thank you! Great write up and everything worked as you laid out.