For all of you following along YEAH its about damned time ! 🙂
I agree
I’ve been proceeding slowly to make it so you can follow and not try to cram 10000 items in one post (we ARE trying to keep it manageable)
First Lets remove the last bits of “cruft” left in our getting starting project
Since we’re not actually using a normal macOS document application we can delete the ViewController.cs and ViewController.designer.cs
Simply right click on ViewController.cs and select delete.
You’ll get a dialog asking if you want to delete the view controller and its “code behind children”
Select Delete and both will be removed. Your should still be able to run your application.
Now on to the fun stuff !
First lets add a menu bar so we have a normal Quit menu Item in it 😛
Start by adding a new class (almost everything in our C# world is an actual class)
We end up with an empty class that we need to edit to set up properly.
We’re using an actual macOS NSMenu here – so we need to add the AppKit using clause so the IDE & compiler know about this in our class. And we’ll make our AppMenu class a subclass of NSMenu
We’ll want to add & retain references to certain menu items – note this doesnt mean you cant handle dynamic menus. Just that there are some, like the Quit menu item, that we dont need to deal with dynamically. So we can add a property to our class to hold this reference when we create it
public class AppMenu : NSMenu
{
public static MenuItem QuitMenuItem;
And we’ll want to alter the Constructor (the method named the same as the class) so it takes one parameter – the menus TITLE
public class AppMenu : NSMenu
{
public static MenuItem QuitMenuItem;
public AppMenu(string title) : base(title)
{ }
The AppMenu method is new so requires some explanation. The first portion public AppMenu(string title)
is just like any other method you’ve ever seen. It has a scope public, a name AppMenu, and a list of parameters string title. But then there is : base(title) which in C# is shorthand for “call the super class constructor method that takes a string parameter”
So just by declaring the method this way you have created the constructor and call the super class constructor. But so far it doesnt do anything – the empty {} is the give away.
When an app start we need to create the “Application Menu” and add whatever items to it we want. On macOS the OS may add some of its own.
So we’ll create that menu and make it have a Quit item in it.
public AppMenu(string title) : base(title)
{
// Application top level Menu
var appMenu = new NSMenuItem(); // create the top item for Application
appMenu.Submenu = new NSMenu(); // add a sub menu
QuitMenuItem = new MenuItem("Quit", "q"); // create the QUIT item
// create and set the ARRAY of items to a NEW ARRAY containing
// our newly created QuitMenuItem
appMenu.Submenu.Items = new[]
{
QuitMenuItem
};
// THIS is like ME or SELF
// here this means the app menu itself
this.Items = new[] { appMenu };
}
This will create the menu – but doesnt “attach” it to our application yet. So if you run right now you wont see this code get run.
You can test this by setting a breakpoint in the code. There are several ways to achieve this. By default pressing CMD+\ will toggle a break point on the line containing the insertion point. Or you can click in the break point gutter
When you run you will see that break point is NEVER reached.
So lets fix that.
Open up the AppDelegate.cs
When an applications starts up, just like in Xojo, you will see the constructor get called, followed by the app “Opening”. In this case we’re going to set up the menu, and “menu handlers” in the Opening event (its not really an event in this framework but thats what it would be in Xojo)
So – how to know what the method we need to implement might be named ?
We could guess. That might take a long time.
I could tell you but I might not be around all the time 🙂
Or we can just go look
In AppDelegate right click on “Application” in the line
public class App : Application
and select “Goto definition”
There s a lot of stuff in here. But what we want so see is which methods are declared PUBLIC VIRTUAL. Why ? thee are ones that are defined to be implemented in a subclass. They are quite literally meant to be overridden.
And there are several of interest to us right now
protected virtual void Opened ()
protected virtual void Opening ()
protected virtual void Closing ()
There are others but these are the ones we care about right now
Opening is called right after the application instance is created. This is usually before anything else is seen.
Opened is called when the application has successfully started.
Closing is called before the app closes.
So since we want to attach out menu when the app is about to show its first window we’ll implement Opening. Copy the declaration and paste it into AppDelegate.cs after the existing App method. And yes type in a {} following it to indicate the method has no code yet.
One thing we need to do is alter the signature of the method to lets the compiler know that our application subclass has overridden the super class Opening event. To do that we need to change the VIRTUAL to OVERRIDE in the method signature.
To add the menu to our application its one line of code
NSApplication.SharedApplication.MainMenu = new AppMenu("Test");
Note there are STILL no attached menu handlers ; but if you run and select the application menu you should see there IS a Quit menu item there
For the Quit menu item we’re going to add that one to the Application – unlike others which might need to be attached to a specific window or control.
To do this we write whatever method we want to be called when the quit menu item is selected. And then we tell the application what menu item should call this method when we select it.
First lets write the method to call when the menu item is selected.
We can, quite literally, name it just about ANYTHING we want. But, picking good names is useful so we’ll create a method in the AppDelegate.cs named QuitMenuitem_Action which I hope is indicative of what this will do 😛
This method doesnt return anything, so its return type should be void.
It DOES get called with two parameters – one, the sender, which is of type object, and the event arguments, of type EventArgs.
So our method signature should look like
void QuitMenuItem_Action(object sender, EventArgs e)
followed by a pair of empty { }
And the ONLY code that needs to be in here is a call to, you guessed it, Quit
void QuitMenuItem_Action(object sender, EventArgs e)
{
Quit();
}
To make it so the App menu item, Quit, runs this code when the item is selected we ned to attach the two. It makes sense to do this in the same spot we created the menu.
If you want you can look in the definition of Application again.
There you will see that Application has a dictionary of menu handlers.
The key is the menu item, and the value is the method to call.
So we can set the Application.MenuHandlers using code like
MenuHandlers = new Dictionary<MenuItem, EventHandler<MenuEventArgs>>()
{
{ AppMenu.QuitMenuItem, QuitMenuItem_Action}
};
This few lines is VERY dense but we can make sense of it
MenuHandlers is a property on Application we’re going to set to
a new Dictionary
But what’s all the rest of <MenuItem, EventHandler<MenuEventArgs>> ?
The difference here, unlike Xojo, is that we can EXPLICITLY TYPE what the key and value MUST be. Xojo’s dictionary only uses variants so strong typing of dictionaries isn’t possible. C# directly supports strong typing of the keys & values.
So this Dictionary<MenuItem, EventHandler<MenuEventArgs>> says we are creating a dictionary that uses MenuItem for keys and the values are EventHandler that get MenuEventArgs as their parameters. Absolutely unambiguous what can go in there.
And the following
{ AppMenu.QuitMenuItem, QuitMenuItem_Action}
adds one item to that dictionary with the Quit menu item as the key and our brand new method we just wrote as the menu item handler.
Now if you run and select Quit from the application menu your app should now quit.
Simple stuff to do in Xojo – and it is here as well but the explanation of how this all works takes time & images 🙂
And now as promised
ADDING CONTROLS !!!!!!
We can close the AppDeletage now as we dont need to make any more changes there for now.
Adding controls & event handlers is all done in the MainWindow.cs we created before. Open that now.
Just like with Application where we perused the framework class to see how things worked we’ll go look at the Window class. This class is MUCH more complex than the last we looked at. But again we are looking for protected virtual methods that are ones we can override to add functionality to our Window subclass.
The one we will override is
protected virtual IEnumerable<Control> SetupControls ()
{
return new Control[0];
}
The implementation in place creates an empty array of controls. Copy the declaration and we’ll paste that into our Window subclass.
Again we need to change virtual to override so the compiler knows we’re stating this method overrides the one in the baes class.
In our Window we will usually, but not always, want to declare a variable to hold a reference to a control. If we want to refer to that control in other code, event handlers, etc it’s easier to keep a reference to the control than it is to dynamically find it on the layout all the time.
So we’ll add a property to our subclass
TextField textfield;
I’d put this declaration right after the declaration of the subclass
Note that we CAN also alter the visibility of this by prefacing it with private ( or any of the many others that exist in C# https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/accessibility-levels) Public, protected and private are all very similar to how Xojo uses them.
The code in SetupControls then has to create the control with its initial set up and add it to the window.
Creating a new control is about what you would expect. You use the NEW keyword and the classname. You can browse the framework to see what constructors are available. Some controls have several. Most however have a nice easy one that takes 4 coordinates for the left, top, width and height. We’ll use that one :
textfield = new TextField( 10, 23, 80, 23);
And we can also fill the field with some text
textfield.Text = "not edit no select";
And now we just have to return the right kind of item.
IEnumerable is an INTERFACE – exactly like in Xojo.
And it just so happens that an array implements that interface !
So we can return a new array of controls like
return new Control[] { textfield };
Now if you run you will find you can edit the text in the textfield.
Cut copy and paste wont work just yet as there is no EDIT menu and the OS relies on that existing to make the textfield work as expected.
I’ll leave adding that as an exercise 🙂
Tinker around and see what other control you can work with and test things out.
Note theres lot there but not all are 100% complete
The project so far