Standard controls in Xamarin.Mac
This article covers working with the standard AppKit controls such as buttons, labels, text fields, check boxes, and segmented controls in a Xamarin.Mac application. It describes adding them to an interface with Interface Builder and interacting with them in code.
When working with C# and .NET in a Xamarin.Mac application, you have access to the same AppKit Controls that a developer working in Objective-C and Xcode does. Because Xamarin.Mac integrates directly with Xcode, you can use Xcode's Interface Builder to create and maintain your Appkit Controls (or optionally create them directly in C# code).
AppKit Controls are the UI Elements that are used to create the User Interface of your Xamarin.Mac application. They consist of elements such as Buttons, Labels, Text Fields, Check Boxes and Segmented Controls and cause instant actions or visible results when a user manipulates them.
In this article, we'll cover the basics of working with AppKit Controls in a Xamarin.Mac application. It is highly suggested that you work through the Hello, Mac article first, specifically the Introduction to Xcode and Interface Builder and Outlets and Actions sections, as it covers key concepts and techniques that we'll be using in this article.
You may want to take a look at the Exposing C# classes / methods to Objective-C section of the Xamarin.Mac Internals document as well, it explains the Register
and Export
commands used to wire-up your C# classes to Objective-C objects and UI Elements.
Introduction to Controls and Views
macOS (formerly known as Mac OS X) provides a standard set of User Interface controls via the AppKit Framework. They consist of elements such as Buttons, Labels, Text Fields, Check Boxes and Segmented Controls and cause instant actions or visible results when a user manipulates them.
All of the AppKit Controls have a standard, built-in appearance that will be appropriate for most uses, some specify an alternate appearance for use in a window frame area or in a Vibrance Effect context, such as in a Sidebar area or in a Notification Center widget.
Apple suggest the following guidelines when working with AppKit Controls:
- Avoid mixing control sizes in the same view.
- In general, avoid resizing controls vertically.
- Use the system font and the proper text size within a control.
- Use the proper spacing between controls.
For more information, pleas see the About Controls and Views section of Apple's OS X Human Interface Guidelines.
Using Controls in a Window Frame
There are a subset of AppKit Controls that include a display style that allows them to be include in a Window's Frame area. For an example, see the Mail app's toolbar:
- Round Textured Button - A
NSButton
with a style ofNSTexturedRoundedBezelStyle
. - Textured Rounded Segmented Control - A
NSSegmentedControl
with a style ofNSSegmentStyleTexturedRounded
. - Textured Rounded Segmented Control - A
NSSegmentedControl
with a style ofNSSegmentStyleSeparated
. - Round Textured Pop-Up Menu - A
NSPopUpButton
with a style ofNSTexturedRoundedBezelStyle
. - Round Textured Drop-Down Menu - A
NSPopUpButton
with a style ofNSTexturedRoundedBezelStyle
. - Search Bar - A
NSSearchField
.
Apple suggest the following guidelines when working with AppKit Controls in a Window Frame:
- Don’t use Window Frame specific control styles in the Window Body.
- Don’t use Window Body controls or styles in the Window Frame.
For more information, pleas see the About Controls and Views section of Apple's OS X Human Interface Guidelines.
Creating a User Interface in Interface Builder
When you create a new Xamarin.Mac Cocoa application, you get a standard blank, window by default. This windows is defined in a .storyboard
file automatically included in the project. To edit your windows design, in the Solution Explorer, double click the Main.storyboard
file:
This will open the window design in Xcode's Interface Builder:
To create your User Interface, you'll drag UI Elements (AppKit Controls) from the Library Inspector to the Interface Editor in Interface Builder. In the example below, a Vertical Split View control has been drug from the Library Inspector and placed on the Window in the Interface Editor:
For more information on creating a User Interface in Interface Builder, please see our Introduction to Xcode and Interface Builder documentation.
Sizing and Positioning
Once a control has been included in the User Interface, use the Constraint editor to set its location and size by entering values manually and control how the control is automatically positioned and sized when the parent Window or View is resized:
Use the Red I-Beams around the outside of the Autoresizing box to stick a control to a given (x,y) location. For example:
Specifies that the selected control (in the Hierarchy View & Interface Editor) will be stuck to the top and right location of the Window or View as it is resized or moved.
Other elements of the editor control properties such as Height and Width:
You can also control the alignment of elements with constraints using the Alignment Editor:
Important
Unlike iOS where (0,0) is the upper left hand corner of the screen, in macOS (0,0) is the lower left hand corner. This is because macOS uses a mathematical coordinate system with the number values increasing in value upward and to the right. You need to take this into consideration when placing AppKit controls on a User Interface.
Setting a Custom Class
There are times when working with AppKit Controls that you will need to subclass and existing control and create you own custom version of that class. For example, defining a custom version of the Source List:
using System;
using AppKit;
using Foundation;
namespace AppKit
{
[Register("SourceListView")]
public class SourceListView : NSOutlineView
{
#region Computed Properties
public SourceListDataSource Data {
get {return (SourceListDataSource)this.DataSource; }
}
#endregion
#region Constructors
public SourceListView ()
{
}
public SourceListView (IntPtr handle) : base(handle)
{
}
public SourceListView (NSCoder coder) : base(coder)
{
}
public SourceListView (NSObjectFlag t) : base(t)
{
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
}
#endregion
#region Public Methods
public void Initialize() {
// Initialize this instance
this.DataSource = new SourceListDataSource (this);
this.Delegate = new SourceListDelegate (this);
}
public void AddItem(SourceListItem item) {
if (Data != null) {
Data.Items.Add (item);
}
}
#endregion
#region Events
public delegate void ItemSelectedDelegate(SourceListItem item);
public event ItemSelectedDelegate ItemSelected;
internal void RaiseItemSelected(SourceListItem item) {
// Inform caller
if (this.ItemSelected != null) {
this.ItemSelected (item);
}
}
#endregion
}
}
Where the [Register("SourceListView")]
instruction exposes the SourceListView
class to Objective-C so that is can be used in Interface Builder. For more information, please see the Exposing C# classes / methods to Objective-C section of the Xamarin.Mac Internals document, it explains the Register
and Export
commands used to wire-up your C# classes to Objective-C objects and UI Elements.
With the above code in place, you can drag an AppKit Control, of the base type that you are extending, onto the design surface (in the example below, a Source List), switch to the Identity Inspector and set the Custom Class to the name that you exposed to Objective-C (example SourceListView
):
Exposing Outlets and Actions
Before an AppKit Control can be accessed in C# code, it needs to be exposed as either an Outlet or and Action. To do this select the given control in either the Interface Hierarchy or the Interface Editor and switch to the Assistant View (ensure that you have the .h
of your Window selected for editing):
Control-drag from the AppKit control onto the give .h
file to start creating an Outlet or Action:
Select the type of exposure to create and give the Outlet or Action a Name:
For more information on working with Outlets and Actions, please see the Outlets and Actions section of our Introduction to Xcode and Interface Builder documentation.
Synchronizing Changes with Xcode
When you switch back to Visual Studio for Mac from Xcode, any changes that you have made in Xcode will automatically be synchronized with your Xamarin.Mac project.
If you select the SplitViewController.designer.cs
in the Solution Explorer you'll be able to see how your Outlet and Action have been wired up in our C# code:
Notice how the definition in the SplitViewController.designer.cs
file:
[Outlet]
AppKit.NSSplitViewItem LeftController { get; set; }
[Outlet]
AppKit.NSSplitViewItem RightController { get; set; }
[Outlet]
AppKit.NSSplitView SplitView { get; set; }
Line up with the definition in the MainWindow.h
file in Xcode:
@interface SplitViewController : NSSplitViewController {
NSSplitViewItem *_LeftController;
NSSplitViewItem *_RightController;
NSSplitView *_SplitView;
}
@property (nonatomic, retain) IBOutlet NSSplitViewItem *LeftController;
@property (nonatomic, retain) IBOutlet NSSplitViewItem *RightController;
@property (nonatomic, retain) IBOutlet NSSplitView *SplitView;
As you can see, Visual Studio for Mac listens for changes to the .h
file, and then automatically synchronizes those changes in the respective .designer.cs
file to expose them to your application. You may also notice that SplitViewController.designer.cs
is a partial class, so that Visual Studio for Mac doesn't have to modify SplitViewController.cs
which would overwrite any changes that we have made to the class.
You normally will never need to open the SplitViewController.designer.cs
yourself, it was presented here for educational purposes only.
Important
In most situations, Visual Studio for Mac will automatically see any changes made in Xcode and sync them to your Xamarin.Mac project. In the off occurrence that synchronization doesn't automatically happen, switch back to Xcode and them back to Visual Studio for Mac again. This will normally kick off a synchronization cycle.
Working with Buttons
AppKit provides several types of button that can be used in your User Interface Design. For more information, please see the Buttons section of Apple's OS X Human Interface Guidelines.
If a button has been exposed via an Outlet, the following code will respond to it being pressed:
ButtonOutlet.Activated += (sender, e) => {
FeedbackLabel.StringValue = "Button Outlet Pressed";
};
For buttons that have been exposed via Actions, a public partial
method will automatically be created for you with the name that you chose in Xcode. To respond to the Action, complete the partial method in the class that the Action was defined on. For example:
partial void ButtonAction (Foundation.NSObject sender) {
// Do something in response to the Action
FeedbackLabel.StringValue = "Button Action Pressed";
}
For buttons that have a state (like On and Off), the state can be checked or set with the State
property against the NSCellStateValue
enum. For example:
DisclosureButton.Activated += (sender, e) => {
LorumIpsum.Hidden = (DisclosureButton.State == NSCellStateValue.On);
};
Where NSCellStateValue
can be:
- On - The button is pushed or the control is selected (such as a check in a Check Box).
- Off - The button is not pushed or the control is not selected.
- Mixed - A mixture of On and Off states.
Mark a Button as Default and Set Key Equivalent
For any button that you have added to a user interface design, you can mark that button as the Default button that will be activated when the user presses the Return/Enter key on the keyboard. In macOS, this button will receive a blue background color by default.
To set a button as default, select it in Xcode's Interface Builder. Next, in the Attribute Inspector, select the Key Equivalent field and press the Return/Enter key:
Equally, you can assign any key sequence that can be used to activate the button using the keyboard instead of the mouse. For example, by pressing the Command-C keys in the image above.
When the app is run and the Window with the Button is Key and Focused, if the user presses Command-C, the Action for the button will be activated (as-if the user had clicked on the button).
Working with Checkboxes and Radio Buttons
AppKit provides several types of Checkboxes and Radio Button Groups that can be used in your User Interface Design. For more information, please see the Buttons section of Apple's OS X Human Interface Guidelines.
Checkboxes and Radio Buttons (exposed via Outlets) have a state (like On and Off), the state can be checked or set with the State
property against the NSCellStateValue
enum. For example:
AdjustTime.Activated += (sender, e) => {
FeedbackLabel.StringValue = string.Format("Adjust Time: {0}",AdjustTime.State == NSCellStateValue.On);
};
Where NSCellStateValue
can be:
- On - The button is pushed or the control is selected (such as a check in a Check Box).
- Off - The button is not pushed or the control is not selected.
- Mixed - A mixture of On and Off states.
To select a button in a Radio Button Group, expose the Radio Button to select as an Outlet and set its State
property. For Example:
partial void SelectCar (Foundation.NSObject sender) {
TransportationCar.State = NSCellStateValue.On;
FeedbackLabel.StringValue = "Car Selected";
}
To get a collection of radio buttons to act as a group and automatically handle the selected state, create a new Action and attach every button in the group to it:
Next, assign a unique Tag
to each radio button in the Attribute Inspector:
Save your changes and return to Visual Studio for Mac, add the code to handle the Action that all of the radio buttons are attached to:
partial void NumberChanged(Foundation.NSObject sender)
{
var check = sender as NSButton;
Console.WriteLine("Changed to {0}", check.Tag);
}
You can use the Tag
property to see which radio button was selected.
Working with Menu Controls
AppKit provides several types of Menu Controls that can be used in your User Interface Design. For more information, please see the Menu Controls section of Apple's OS X Human Interface Guidelines.
Providing Menu Control Data
The Menu Controls available to macOS can be set to populate the dropdown list either from an internal list (that can be pre-defined in Interface Builder or populated via code) or by providing your own custom, external data source.
Working with Internal Data
In addition to defining items in Interface Builder, Menu Controls (such as NSComboBox
), provide a complete set of methods that allow you to Add, Edit or Delete the items from the internal list that they maintain:
Add
- Adds a new item to the end of the list.GetItem
- Returns the item at the given index.Insert
- Inserts a new item in the list at the given location.IndexOf
- Returns the index of the given item.Remove
- Removes the given item from the list.RemoveAll
- Removes all items from the list.RemoveAt
- Removes the item at the given index.Count
- Returns the number of items in the list.
Important
If you are using an Extern Data Source (UsesDataSource = true
), calling any of the above methods will throw an exception.
Working with an External Data Source
Instead of using the built-in Internal Data to provide the rows for your Menu Control, you can optionally use an External Data Source and provide your own backing store for the items (such as a SQLite database).
To work with an External Data Source, you'll create an instance of the Menu Control's Data Source (NSComboBoxDataSource
for example) and override several methods to provide the necessary data:
ItemCount
- Returns the number of items in the list.ObjectValueForItem
- Returns the value of the item for a given index.IndexOfItem
- Returns the index for the give item value.CompletedString
- Returns the first matching item value for the partially typed item value. This method is only called if Autocomplete has been enabled (Completes = true
).
Please see the Databases and ComboBoxes section of the Working with Databases document for more details.
Adjusting the List's Appearance
The following methods are available to adjust the Menu Control's appearance:
HasVerticalScroller
- Iftrue
, the control will display a vertical scrollbar.VisibleItems
- Adjust the number of items displayed when the control is opened. The default value is five (5).IntercellSpacing
- Adjust the amount of space around a given item by providing aNSSize
where theWidth
specifies the left and right margins and theHeight
specifies the space before and after an item.ItemHeight
- Specifies the height of each item in the list.
For Drop-Down types of NSPopupButtons
, the first Menu Item provides the title for the control. For Example:
To change the title, expose this item as an Outlet and use code like the following:
DropDownSelected.Title = "Item 1";
Manipulating the Selected Items
The following methods and properties allow you to manipulate the selected items in the Menu Control's list:
SelectItem
- Selects the item at the given index.Select
- Select the given item value.DeselectItem
- Deselects the item at the given index.SelectedIndex
- Returns the index of the currently selected item.SelectedValue
- Returns the value of the currently selected item.
Use the ScrollItemAtIndexToTop
to present the item at the given index at the top of the list and the ScrollItemAtIndexToVisible
to scroll to list until the item at the given index is visible.
Responding to Events
Menu Controls provide the following events to respond to user interaction:
SelectionChanged
- Is called when the user has selected a value from the list.SelectionIsChanging
- Is called before the new user selected item becomes the active selection.WillPopup
- Is called before the dropdown list of items is displayed.WillDismiss
- Is called before the dropdown list of items is closed.
For NSComboBox
controls, they include all of the same events as the NSTextField
, such as the Changed
event that is called whenever the user edits the value of the text in the Combo Box.
Optionally, you can respond the a Internal Data Menu Items defined in Interface Builder being selected by attaching the item to an Action and use code like the following to respond to Action being triggered by the user:
partial void ItemOne (Foundation.NSObject sender) {
DropDownSelected.Title = "Item 1";
FeedbackLabel.StringValue = "Item One Selected";
}
For more information on working with Menus and Menu Controls, please see our Menus and Pop-up Button and Pull-Down Lists documentation.
Working with Selection Controls
AppKit provides several types of Selection Controls that can be used in your User Interface Design. For more information, please see the Selection Controls section of Apple's OS X Human Interface Guidelines.
There are two ways to track when a Selection Control has user interaction, by exposing it as an Action. For example:
partial void SegmentButtonPressed (Foundation.NSObject sender) {
FeedbackLabel.StringValue = string.Format("Button {0} Pressed",SegmentButtons.SelectedSegment);
}
Or by attaching a Delegate to the Activated
event. For example:
TickedSlider.Activated += (sender, e) => {
FeedbackLabel.StringValue = string.Format("Stepper Value: {0:###}",TickedSlider.IntValue);
};
To set or read the value of a Selection Control, use the IntValue
property. For example:
FeedbackLabel.StringValue = string.Format("Stepper Value: {0:###}",TickedSlider.IntValue);
The specialty controls (such as Color Well and Image Well) have specific properties for their value types. For Example:
ColorWell.Color = NSColor.Red;
ImageWell.Image = NSImage.ImageNamed ("tag.png");
The NSDatePicker
has the following properties for working directly with Date and Time:
- DateValue - The current date and time value as a
NSDate
. - Local - The user's location as a
NSLocal
. - TimeInterval - The time value as a
Double
. - TimeZone - The user's time zone as a
NSTimeZone
.
Working with Indicator Controls
AppKit provides several types of Indicator Controls that can be used in your User Interface Design. For more information, please see the Indicator Controls section of Apple's OS X Human Interface Guidelines.
There are two ways to track when a Indicator Control has user interaction, either by exposing it as an Action or an Outlet and attaching a Delegate to the Activated
event. For example:
LevelIndicator.Activated += (sender, e) => {
FeedbackLabel.StringValue = string.Format("Level: {0:###}",LevelIndicator.DoubleValue);
};
To read or set the value of the Indicator Control, use the DoubleValue
property. For example:
FeedbackLabel.StringValue = string.Format("Rating: {0:###}",Rating.DoubleValue);
The Indeterminate and Asynchronous Progress Indicators should be animated when displayed. Use the StartAnimation
method to start the animation when they are displayed. For example:
Indeterminate.StartAnimation (this);
AsyncProgress.StartAnimation (this);
Calling the StopAnimation
method will stop the animation.
Working with Text Controls
AppKit provides several types of Text Controls that can be used in your User Interface Design. For more information, please see the Text Controls section of Apple's OS X Human Interface Guidelines.
For Text Fields (NSTextField
), the following events can be used to track user interaction:
- Changed - Is fired any time the user changes the value of the field. For example, on every character typed.
- EditingBegan - Is fired when the user selects the field for editing.
- EditingEnded - When the user presses the Enter key in the field or leaves the field.
Use the StringValue
property to read or set the field's value. For example:
FeedbackLabel.StringValue = string.Format("User ID: {0}",UserField.StringValue);
For fields that display or edit numerical values, you can use the IntValue
property. For example:
FeedbackLabel.StringValue = string.Format("Number: {0}",NumberField.IntValue);
An NSTextView
provides a full featured text edit and display area with built-in formatting. Like a NSTextField
, use the StringValue
property to read or set the area's value.
Working with Content Views
AppKit provides several types of Content Views that can be used in your User Interface Design. For more information, please see the Content Views section of Apple's OS X Human Interface Guidelines.
Popovers
A popover is a transient UI element that provides functionality that is directly related to a specific a control or an onscreen area. A popover floats above the window that contains the control or area that it’s related to, and its border includes an arrow to indicate the point from which it emerged.
To create a popover, do the following:
Open the
.storyboard
file of the window that you want to add a popover to by double-clicking it in the Solution ExplorerDrag a View Controller from the Library Inspector onto the Interface Editor:
Define the size and the layout of the Custom View:
Control-click and drag from the source of the popup onto the View Controller:
Select Popover from the popup menu:
Save your changes and return to Visual Studio for Mac to sync with Xcode.
Tab Views
Tab Views consists of a Tab List (which looks similar to a Segmented Control) combined with a set of views that are called Panes. When the user selects a new Tab, the Pane that is attached to it will be displayed. Each Pane contains its own set of controls.
When working with a Tab View in Xcode's Interface Builder, use the Attribute Inspector to set the number of Tabs:
Select each Tab in the Interface Hierarchy to set its Title and add UI Elements to its Pane:
Data Binding AppKit Controls
By using Key-Value Coding and Data Binding techniques in your Xamarin.Mac application, you can greatly decrease the amount of code that you have to write and maintain to populate and work with UI elements. You also have the benefit of further decoupling your backing data (Data Model) from your front end User Interface (Model-View-Controller), leading to easier to maintain, more flexible application design.
Key-Value Coding (KVC) is a mechanism for accessing an object’s properties indirectly, using keys (specially formatted strings) to identify properties instead of accessing them through instance variables or accessor methods (get/set
). By implementing Key-Value Coding compliant accessors in your Xamarin.Mac application, you gain access to other macOS features such as Key-Value Observing (KVO), Data Binding, Core Data, Cocoa bindings, and scriptability.
For more information, please see the Simple Data Binding section of our Data Binding and Key-Value Coding documentation.
Summary
This article has taken a detailed look at working with the standard AppKit controls such as Buttons, Labels, Text Fields, Check Boxes and Segmented Controls in a Xamarin.Mac application. It covered adding them to a User Interface Design in Xcode's Interface Builder, exposing them to code through Outlets and Actions and working with AppKit Controls in C# Code.