Dissecting SemanticMerge, Part 4: Cocoa 101 for WinForms/WPF developers

Monday, March 24, 2014 0 Comments

Ever thought about writing a Mac GUI in C#?

You’re probably used to Buttons, Panels, Dialogs and the like, but how do they map to the Mac counterparts?

Well, this is what we try to cover in this post: a Cocoa 101 for WinForms developers.

Background

Mac development is a new world for a Windows developer. New tools, new subsystems, new OS, new languages...

We wanted to develop a Mac native UI for SemanticMerge and then we faced some decisions: stay in C#? Move to Objective-C? Get used to the Interface Builder or ignore it?

Since other teams might be facing a similar situation we thought it would be good to dissect our SemanticMerge GUI and use it to explain some Mac GUI development essentials.

Before entering the Cocoa 101 we had to decide whether we should go for Objective-C or not and whether we should go for a native UI or try something like Qt.

In a previous post we also covered some MonoMac basics and then one interesting topic: should you use the Interface Builder or go hard-core and code the entire GUI?

Welcome to Cocoa

You start and write your first “hello world” in Xamarin Studio but then you’ve to figure out how to really structure your app. And then is when worlds collide.

As Windows developers we were used to Buttons, Forms, Panels, Splitters and all but… that’s not the same thing when you get to Xamarin.Mac. You’ve to get used to the Cocoa and a new set of controls.

As I mentioned first, this is what I’m going to do by dissecting our SemanticMerge application. And for the sake of simplicity, I’ll start with a simple dialog, the start up window:

And let’s now go and dissect it:

The picture shows all the controls we use in this simple dialog and I think it is a good way to get familiar with Cocoa if you come from a Windows background.

Let me share thoughts on the picture above:

  • The Window has a ContentView which is the one you’ll use to place more controls inside. The ContentView is an NSView.
  • The NSBoxes are somehow similar to Panels in WinForms. You can use them to contain other controls.
  • Buttons: all buttons in Cocoa will be of type NSButton, but then you’ll set properties to it depending on the style of the button you want to use.
  • Something similar happens with NSTextField: you can use a TextField as a WinForms TextBox, but you can also configure it to be like a Label. In the picture I defined the ‘edit boxes’ as NSTextFields with “Editable = true” and “Bordered = true”.

Alignment with Constraints

Once you meet a few controls is time to know how to align them. You can always just place them in the Window ContentView with global coordinates, but it won’t help if you need your NSBoxes to grow with the window, or your buttons to keep aligned to the right.

While we were all familiar with Anchors in WinForms, to code UIs in Mac we need to learn the Constraints.

Constraints are the set of rules you use to define where each control is located, aligned and so on.

The ‘language’ to define constrains can be intimidating at first, but it is really easy to learn and very powerful.

I’ll first explain how constraints are defined and later I’ll cover how to code it in C#.

Let’s start with a very simple scenario: just a window with two buttons as the figure shows:

If we don’t set the constraints correctly (step 2) then when the window grows the buttons will stay on the same position, losing the correct relative position “anchored” to the bottom right of the window.

In step 3 the constraints are correctly set and the buttons keep the relative position to the bottom right.

How do we achieve this? With the ‘magic of Mac constraint rules’.

The rule to specify the horizontal alignment is very simple:

  • H: stands for horizontal rule.
  • [cancel(150)] refers to the “cancel” button (later I’ll explain how do you tell the system that the button will be named ‘cancel’ when specifying contraints.
  • -10- means you’re separating the two buttons with a “10 size” separation.
  • [ok(150)] gives a 150 size to the “ok” button.
  • -10- is another separator.
  • | the last vertical bar on a horizontal constraint rule refers to the right border of the window. It is the piece of the constraint saying “keep everything aligned to the right”.

The language is easy to remember and very powerful as you can see.

Since we’re only specifying the “right border” it means everything will be relative to the right.

Notice I didn’t set a “left bar” which means the buttons will be ‘anchored’ to the right.

What if I edit the rule and modify it in the following way:

“H: |-10-[cancel(150)]-10-[ok]-10-|”

I have added a “left border” so basically I’m saying:

  • The “cancel” button should be linked to the left border with a 10 points margin.
  • Then there will be another “10” separator between the buttons.
  • And the “ok” button will grow as much as it needs in order to keep a right margin of 10 with the right border.

The result will be something like:

Now that we know how to set up the horizontal constraints, the vertical ones will be really easy to figure out:

We specify 2 different rules where we set the vertical margin between the buttons and the bottom border of the window, and also the button height (or vertical size).

Now that we “master” the constraints, it is not hard to figure out the layout of the SemanticMerge launch window:

Remark: you ALWAYS have to specify both the vertical and horizontal constraints. Otherwise nothing will be rendered. It happened to us :-)

Defining constraints in C# and MonoMac

Now that the constraint language is crystal clear ;-): how is this set on a MonoMac application?

Look at the following code listing which is able to create a Window like the following:

    public override void AwakeFromNib ()
    {
        base.AwakeFromNib ();

        // button created programatically
        NSButton button = new NSButton();
        button.Title = "Click Me";

        // button added to the window
        NSView contentView = Window.ContentView;
        contentView.AddSubview(button);

        // now we set the constraints

        // nothing works if you do not specify this!!
        button.TranslatesAutoresizingMaskIntoConstraints = false;

        // now let's add the names of the controls
        // to the constraints system so we can
        // reference the controls by name in the
        // constraints rules

        var views = new NSMutableDictionary();
        var constraints = new List();

        views.Add(new NSString("button"), button);

        // and here go the constraints

        constraints.AddRange(
            NSLayoutConstraint.FromVisualFormat(
                "H:[button(150)]-10-|", 0 , null, views));
        constraints.AddRange(
            NSLayoutConstraint.FromVisualFormat(
                "V:[button(23)]-10-|", 0, null, views));

        contentView.AddConstraints(constraints.ToArray());
    }

Setting the constraints in code is straightforward as you see, but simply don’t forget to:

  • Specify horizontal and vertical constraints.
  • Set the “TranslatesAutoresizingMaskIntoConstraints” to false!

Wrapping up

We’ve discussed about whether going native or use multi-platform UI toolkits, then we went through the ‘imperative vs designer based’ UI implementation alternatives and later we introduced some very common Cocoa controls.

Finally we dissected the “launcher dialog” in SemanticMerge to explain how constraints work.

Hope you find this post useful and happy Mac C# hacking.

We develop Plastic SCM, a version control that excels in branching and merging, can deal with huge projects and big binary assets natively, and it comes with GUIs and tools to make everything simpler.

If you want to give it a try, download it from here.

We are also the developers of SemanticMerge, and the gmaster Git client.

0 comments: