Search code examples
c#cocoamononsoutlineviewmonomac

Creating a simple NSOutlineView datasource with MonoMac


I cant seem to figure out how to create a simple NSOutlineView with 2 columns, and a datastructure that is more than 1 level deep (a hierachy).

I've been researching this for days, and all I can find is Objective C examples, which I really can't use for anything.

I understand there are different patterns for doing this, one being the DataSource pattern. I tried creating a class that inherited from NSOutlineViewDataSource, however thats all I got, I have no clue on what I should do next!

Lets say I would like to display the following class in my NSOutlineView:

public class Person
{
    public string Name {get;set;} // First column
    public int Age {get;set} // Second column
    public List<Person> Children {get;set} // Children
}

What would be the most trivial approach to accomplishing this?


Solution

  • Brace yourselves... A level-independant NSOutlineView in MonoMac!

    After hundreds of google searches, and looking through ObjC as well as C# code, I finally figured out how to do it! I will post my solution here, in case someone else needs it.

    This may or may not be the best way to do it, but it works for me.


    Step 1: In Interface Builder, add an NSOutlineView. Add 2 columns to it, and set their Identifier to colName, and colAge.

    Also, while you're at it, add a button to your form.


    Step 2: Create an outlet for the NSOutlineView - I called mine lvMain because I come from a VCL background. Also, create an action for your button (this will be the onClick handler).


    Step 3: Save your XIB file, and return to Mono - it will update your project file. Now, we want to create the model we wish to use for our view.

    For this example, I will use a simple Person object:

    public class Person:NSObject
    {
        public string Name {
            get;
            set;
        }
    
        public int Age {
            get;
            set;
        }
    
        public List<Person> Children {
            get;
            set;
        }
    
        public Person (string name, int age)
        {
            Name = name;
            Age = age;
            Children = new List<Person>();
        }
    }
    

    Nothing overly complicated there.


    Step 4: Create the datasource. For this example, this is what I made:

    public class MyDataSource:NSOutlineViewDataSource
    {
        /// The list of persons (top level)
        public List<Person> Persons {
            get;
            set;
        }
        // Constructor
        public MyDataSource()
        {
            // Create the Persons list
            Persons = new List<Person>();
        }
    
        public override int GetChildrenCount (NSOutlineView outlineView, NSObject item)
        {
            // If the item is not null, return the child count of our item
            if(item != null)
                return (item as Person).Children.Count;
            // Its null, that means its asking for our root element count.
            return Persons.Count();
        }
    
        public override NSObject GetObjectValue (NSOutlineView outlineView, NSTableColumn forTableColumn, NSObject byItem)
        {
            // Is it null? (It really shouldnt be...)
            if (byItem != null) {
                // Jackpot, typecast to our Person object
                var p = ((Person)byItem);
                // Get the table column identifier
                var ident = forTableColumn.Identifier.ToString();
                // We return the appropriate information for each column
                if (ident == "colName") {
                    return (NSString)p.Name;
                }
                if (ident == "colAge") {
                    return (NSString)p.Age.ToString();
                }
            }
            // Oh well.. errors dont have to be THAT depressing..
            return (NSString)"Not enough jQuery";
        }
    
        public override NSObject GetChild (NSOutlineView outlineView, int childIndex, NSObject ofItem)
        {
            // If the item is null, it's asking for a root element. I had serious trouble figuring this out...
            if(ofItem == null)
                return Persons[childIndex];
            // Return the child its asking for.
            return (NSObject)((ofItem as Person).Children[childIndex]);
        }
    
        public override bool ItemExpandable (NSOutlineView outlineView, NSObject item)
        {
            // Straight forward - it wants to know if its expandable.
            if(item == null)
                return false;
            return (item as Person).Children.Count > 0;
        }
    }
    

    Step 5 - The best step: Bind the datasource and add dummy data! We also wanna refresh our view each time we add a new element. This can probably be optimized, but I'm still in the "Oh my god its working" zone, so I currently don't care.

                // Our Click Action
        partial void btnClick (NSObject sender)
        {
            var p = new Person("John Doe",18);
            p.Children.Add(new Person("Jane Doe",10));
            var ds = lvMain.DataSource as MyDataSource;
            ds.Persons.Add(p);
            lvMain.ReloadData();
        }
    
        public override void AwakeFromNib ()
        {
            base.AwakeFromNib ();
            lvMain.DataSource = new MyDataSource();
    
        }
    

    I hope this information can help the troubled souls of the MonoMac newcomers like myself.