Source Lists Contents Overview

Source Lists
Using Source Lists in a Xamarin.Mac application
Contents
This article will cover the following topics in detail:
Introduction to Source Lists
Working with Source Lists
Creating and Maintaining Source Lists in Xcode
Populating the Source List
Overview
When working with C# and .NET in a Xamarin.Mac application, you have access to the same Source Lists
that a developer working in 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 Source Lists (or optionally create
them directly in C# code).
A Source List is a special type of Outline View used to show the source of an action, like the side bar in
Finder or iTunes.
In this article, we'll cover the basics of working with Source Lists 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 wireup your C# classes to Objective-C objects and UI Elements.
Introduction to Source Lists
As stated above, a Source List is a special type of Outline View used to show the source of an action, like
the side bar in Finder or iTunes. A Source List is a type of Table that allows the user expand or collapse
rows of hierarchical data. Unlike a Table View, items in an Source List are not in a flat list, they are
organized in a hierarchy, like files and folders on a hard drive. If an item in an Source List contains other
items, it can be expanded or collapsed by the user.
The Source List is a specially styled Outline View (NSOutlineView), which itself is a subclass of the
Table View (NSTableView) and as such, inherits much of its behavior from its parent class. As a result,
many operations supported by a Outline View, are also supported by an Source List. A Xamarin.Mac
application has control of these features, and can configure the Source List's parameters (either in code or
Interface Builder) to allow or disallow certain operations.
A Source List does not store it's own data, instead it relies on a Data Source
(NSOutlineViewDataSource) to provide both the rows and columns required, on a as-needed basis.
A Source List's behavior can be customized by providing a subclass of the Outline View Delegate
(NSOutlineViewDelegate) to support Outline type to select functionality, item selection and editing,
custom tracking, and custom views for individual items.
Since a Source List shares much of it's behavior and functionality with a Table View and an Outline View,
you might want to go through our Table Views and Outline Views documentation before continuing with this
article.
Working with Source Lists
A Source List is a special type of Outline View used to show the source of an action, like the side bar in
Finder or iTunes. Unlike Outline Views, before we define our Source List in Interface Builder, let's create
the backing classes in Xamarin.Mac.
First, let's create a new SourceListItem class to hold the data for our Source List. In the Solution
Explorer, right-click the Project and select Add > New File... Select General > Empty Class, enter
SourceListItem for the Name and click the New button:
Make the SourceListItem.cs file look like the following:
using System;
using System.Collections;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace MacOutlines
{
public class SourceListItem: NSObject, IEnumerator, IEnumerable
{
#region Private Properties
private string _title;
private NSImage _icon;
private string _tag;
private List<SourceListItem> _items = new List<SourceListItem> ();
#endregion
#region Computed Properties
public string Title {
get { return _title; }
set { _title = value; }
}
public NSImage Icon {
get { return _icon; }
set { _icon = value; }
}
public string Tag {
get { return _tag; }
set { _tag=value; }
}
#endregion
#region Indexer
public SourceListItem this[int index]
{
get
{
return _items[index];
}
set
{
_items[index] = value;
}
}
public int Count {
get { return _items.Count; }
}
public bool HasChildren {
get { return (Count > 0); }
}
#endregion
#region Enumerable Routines
private int _position = -1;
public IEnumerator GetEnumerator()
{
_position = -1;
return (IEnumerator)this;
}
public bool MoveNext()
{
_position++;
return (_position < _items.Count);
}
public void Reset()
{_position = -1;}
public object Current
{
get
{
try
{
return _items[_position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
#endregion
#region Constructors
public SourceListItem ()
{
}
public SourceListItem (string title)
{
// Initialize
this._title = title;
}
public SourceListItem (string title, string icon)
{
// Initialize
this._title = title;
this._icon = NSImage.ImageNamed (icon);
}
public SourceListItem (string title, string icon, ClickedDelegate
clicked)
{
// Initialize
this._title = title;
this._icon = NSImage.ImageNamed (icon);
this.Clicked = clicked;
}
public SourceListItem (string title, NSImage icon)
{
// Initialize
this._title = title;
this._icon = icon;
}
public SourceListItem (string title, NSImage icon, ClickedDelegate
clicked)
{
// Initialize
this._title = title;
this._icon = icon;
this.Clicked = clicked;
}
public SourceListItem (string title, NSImage icon, string tag)
{
// Initialize
this._title = title;
this._icon = icon;
this._tag = tag;
}
public SourceListItem (string title, NSImage icon, string tag,
ClickedDelegate clicked)
{
// Initialize
this._title = title;
this._icon = icon;
this._tag = tag;
this.Clicked = clicked;
}
#endregion
#region Public Methods
public void AddItem(SourceListItem item) {
_items.Add (item);
}
public void AddItem(string title) {
_items.Add (new SourceListItem (title));
}
public void AddItem(string title, string icon) {
_items.Add (new SourceListItem (title, icon));
}
public void AddItem(string title, string icon, ClickedDelegate clicked)
{
_items.Add (new SourceListItem (title, icon, clicked));
}
public void AddItem(string title, NSImage icon) {
_items.Add (new SourceListItem (title, icon));
}
public void AddItem(string title, NSImage icon, ClickedDelegate
clicked) {
_items.Add (new SourceListItem (title, icon, clicked));
}
public void AddItem(string title, NSImage icon, string tag) {
_items.Add (new SourceListItem (title, icon, tag));
}
public void AddItem(string title, NSImage icon, string tag,
ClickedDelegate clicked) {
_items.Add (new SourceListItem (title, icon, tag, clicked));
}
public void Insert(int n, SourceListItem item) {
_items.Insert (n, item);
}
public void RemoveItem(SourceListItem item) {
_items.Remove (item);
}
public void RemoveItem(int n) {
_items.RemoveAt (n);
}
public void Clear() {
_items.Clear ();
}
#endregion
#region Events
public delegate void ClickedDelegate();
public event ClickedDelegate Clicked;
internal void RaiseClickedEvent() {
// Inform caller
if (this.Clicked != null)
this.Clicked ();
}
#endregion
}
}
In the Solution Explorer, right-click the Project and select Add > New File... Select General > Empty
Class, enter SourceListDataSource for the Name and click the New button. Make the
SourceListDataSource.cs file look like the following:
using System;
using System.Collections;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace MacOutlines
{
public class SourceListDataSource : NSOutlineViewDataSource
{
#region Private Variables
private SourceListView _controller;
#endregion
#region Public Variables
public List<SourceListItem> Items = new List<SourceListItem>();
#endregion
#region Constructors
public SourceListDataSource (SourceListView controller)
{
// Initialize
this._controller = controller;
}
#endregion
#region Override Properties
public override nint GetChildrenCount (NSOutlineView outlineView,
Foundation.NSObject item)
{
if (item == null) {
return Items.Count;
} else {
return ((SourceListItem)item).Count;
}
}
public override bool ItemExpandable (NSOutlineView outlineView,
Foundation.NSObject item)
{
return ((SourceListItem)item).HasChildren;
}
public override NSObject GetChild (NSOutlineView outlineView, nint
childIndex, Foundation.NSObject item)
{
if (item == null) {
return Items [(int)childIndex];
} else {
return ((SourceListItem)item) [(int)childIndex];
}
}
public override NSObject GetObjectValue (NSOutlineView outlineView,
NSTableColumn tableColumn, NSObject item)
{
return new NSString (((SourceListItem)item).Title);
}
#endregion
#region Internal Methods
internal SourceListItem ItemForRow(int row) {
int index = 0;
// Look at each group
foreach (SourceListItem item in Items) {
// Is the row inside this group?
if (row >= index && row <= (index + item.Count)) {
return item [row - index - 1];
}
// Move index
index += item.Count + 1;
}
// Not found
return null;
}
#endregion
}
}
This will provide the data for our Source List.
In the Solution Explorer, right-click the Project and select Add > New File... Select General > Empty
Class, enter SourceListDelegate for the Name and click the New button. Make the
SourceListDelegate.cs file look like the following:
using System;
using AppKit;
using Foundation;
namespace MacOutlines
{
public class SourceListDelegate : NSOutlineViewDelegate
{
#region Private variables
private SourceListView _controller;
#endregion
#region Constructors
public SourceListDelegate (SourceListView controller)
{
// Initialize
this._controller = controller;
}
#endregion
#region Override Methods
public override bool ShouldEditTableColumn (NSOutlineView outlineView,
NSTableColumn tableColumn, Foundation.NSObject item)
{
return false;
}
public override NSCell GetCell (NSOutlineView outlineView,
NSTableColumn tableColumn, Foundation.NSObject item)
{
nint row = outlineView.RowForItem (item);
return tableColumn.DataCellForRow (row);
}
public override bool IsGroupItem (NSOutlineView outlineView,
Foundation.NSObject item)
{
return ((SourceListItem)item).HasChildren;
}
public override NSView ViewForTableColumn (NSOutlineView outlineView,
NSTableColumn tableColumn, Foundation.NSObject item)
{
NSTableCellView view = null;
// Is this a group item?
if (((SourceListItem)item).HasChildren) {
view = (NSTableCellView)outlineView.MakeView ("HeaderCell",
this);
} else {
view = (NSTableCellView)outlineView.MakeView ("DataCell",
this);
view.ImageView.Image = ((SourceListItem)item).Icon;
}
// Initialize view
view.TextField.StringValue = ((SourceListItem)item).Title;
// Return new view
return view;
}
public override bool ShouldSelectItem (NSOutlineView outlineView,
Foundation.NSObject item)
{
return (outlineView.GetParent (item) != null);
}
public override void SelectionDidChange (NSNotification notification)
{
NSIndexSet selectedIndexes = _controller.SelectedRows;
// More than one item selected?
if (selectedIndexes.Count > 1) {
// Not handling this case
} else {
// Grab the item
var item = _controller.Data.ItemForRow
((int)selectedIndexes.FirstIndex);
// Was an item found?
if (item != null) {
// Fire the clicked event for the item
item.RaiseClickedEvent ();
// Inform caller of selection
_controller.RaiseItemSelected (item);
}
}
}
#endregion
}
}
This will provide the behavior of our Source List.
Finally, in the Solution Explorer, right-click the Project and select Add > New File... Select General >
Empty Class, enter SourceListView for the Name and click the New button. Make the
SourceListView.cs file look like the following:
using System;
using AppKit;
using Foundation;
namespace MacOutlines
{
[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
}
}
This creates a custom, reusable subclass of NSOutlineView (SourceListView) that we can use to
drive the Source List in any Xamarin.Mac application that we make.
Creating and Maintaining Source Lists in Xcode
Now, let's design our Source List in Interface Builder. Add a new Window and View Controller to our
project. In the Solution Explorer, right-click the Project and select Add > New File... Select Xamarin.Max
> Cocoa Window with Controller, enter RotationWindow for the Name and click the New button.
Second, double-click the RotationWindow.xib file to open it for editing in Interface Builder. Add a new
Window to the design, drag a Source List from the Library Inspector and add it to the new window:
Next, switch to the Identity View, select the Source List, and change it's Class to SourceListView:
Finally, create an Outlet for our Source List called SourceList in the RotationWindow.h file:
Save your changes and return to Xamarin Studio to sync with Xcode.
Populating the Source List
Let's edit the RotationWindow.cs file in Xamarin Studio and make it's AwakeFromNib method look like
the following:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Populate source list
SourceList.Initialize ();
var library = new SourceListItem ("Library");
library.AddItem ("Venues", "house.png", () => {
Console.WriteLine("Venue Selected");
});
library.AddItem ("Singers", "group.png");
library.AddItem ("Genre", "cards.png");
library.AddItem ("Publishers", "box.png");
library.AddItem ("Artist", "person.png");
library.AddItem ("Music", "album.png");
SourceList.AddItem (library);
// Add Rotation
var rotation = new SourceListItem ("Rotation");
rotation.AddItem ("View Rotation", "redo.png");
SourceList.AddItem (rotation);
// Add Kiosks
var kiosks = new SourceListItem ("Kiosks");
kiosks.AddItem ("Sign-in Station 1", "imac");
kiosks.AddItem ("Sign-in Station 2", "ipad");
SourceList.AddItem (kiosks);
// Display side list
SourceList.ReloadData ();
SourceList.ExpandItem (null, true);
}
The Initialize () method need to be called against our Source List's Outlet before any items are
added to it. For each group of items, we create a parent item and then add the sub items to that group item.
Each group is then added to the Source List's collection SourceList.AddItem (...). The last two
lines load the data for the Source List and expands all groups:
// Display side list
SourceList.ReloadData ();
SourceList.ExpandItem (null, true);
Finally, edit the AppDelegate.cs file and make the DidFinishLaunching method look like the
following:
public override void DidFinishLaunching (NSNotification notification)
{
mainWindowController = new MainWindowController ();
mainWindowController.Window.MakeKeyAndOrderFront (this);
var rotation = new RotationWindowController ();
rotation.Window.MakeKeyAndOrderFront (this);
}
If we run our application, the following will be displayed:
Summary
This article has taken a detailed look at working with Source Lists in a Xamarin.Mac application. We saw
how to create and maintain Source Lists in Xcode's Interface Builder and how to work with Source Lists in
C# code.