Creating a simple NSOutlineView datasource with MonoMac - c#

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?

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.

It took me a little while to track this down, but Xamarin has an example of how to do this Here

Related

c# Return string of Customer name

I am new to C# and trying to solve some simple tasks and I find my self stuck in a methode that is supposed to get the name of the next customer from right on the list:
The task:
I am given is a directed graph of Customers, where one Customer has exactly one reference to the next Customer or null if it is the last Customer. An example of such a graph can be seen in the diagram below.
Given such a graph, I need to find the customer int numberFromRight nodes from right in the graph by implementing the IFinder interface.
Here is the code I have been working on
class Program
{
static void Main(string[] args)
{
var currentCustomer = Customers
.Create("Kim")
.Previous("Hans")
.Previous("Ole")
.Previous("Peter");
while (currentCustomer != null)
{
if (currentCustomer.Next != null)
Console.Write(currentCustomer.Person + " -> ");
else
Console.WriteLine(currentCustomer.Person);
currentCustomer = currentCustomer.Next;
}
Console.ReadLine();
}
}
Customer class
public class Customers
{
private Customers(Customers next, string person)
{
Next = next;
Person = person;
}
public Customers Next { get; }
public string Person { get; }
public Customers Previous(string person)
{
return new Customers(this, person);
}
public static Customers Create(string person)
{
return new Customers(null, person);
}
}
IFinder interface
public interface IFinder
{
string FromRight(Customers customers, int numberFromRight);
}
I want to write my answer in this method and in example is in the graph below the result for FromRight(peter, 3) is Ole.:
public class Finder : IFinder
{
public string FromRight(Customers customers, int numberFromRight)
{
return name;
}
}
Simple solution without recursion:
public class Finder : IFinder
{
public string FromRight(Customers customers, int numberFromRight)
{
return Unwind(customers).Reverse().Skip(numberFromRight - 1).FirstOrDefault()?.Person;
}
private static IEnumerable<Customers> Unwind(Customers customers)
{
while (customers != null)
{
yield return customers;
customers = customers.Next;
}
}
}
The task: I am given is a directed graph of Customers, where one Customer has exactly one reference to the next Customer or null if it is the last Customer.
So basically what you have in memory is a linked list. While they have some advantages, they are pretty rarely used outside of tree structures exactly because they are a pain to itterate or random access over.
I want to write my answer in this methode and in example is in the
graph below the result for FromRight(peter, 3) is Ole.:
This is recursion, with the number being the recursion depth. Something like this:
public string FromRight(Customers customers, int numberFromRight)
{
if(numberFromRight <= 0)
return customers.Name;
else
return FromRight(customer.Next, (numberFromRight-1));
}
I might have a off-by-one error in this. But those buggers are everywhere. And if forget the null check of course. But it should get you into the right direction at least.
You haven't really showed us what you have tried so far here, just giving us the assignment.
So here's the theorical answer :
Use recursion with the following :
if the next customer doesn't exist, you're at the end of the list, therefore you can return a value straight ahead.
if the next customer does exist, then the answer is the next customer's answer plus one.

(C#) Access/Modify Objects in a List

New here, I've been learning c# for about a month.
Anyway, I've been searching StackOverflow for a couple of days now and couldn't find a specific answer to my problem...
//Here's my Class
public class Guy
{
public static int ID { get; set; }
public static int LifeExpectancy { get; set; }
public static bool Living { get; set; }
public Guy(int id, int lifeExpectancy, bool living)
{
ID = id;
LifeExpectancy = lifeExpectancy;
Living = living;
}
}
What I'm trying to do is create a specific number of "someGuy" objects to then put them into a public list using this method...
public static List<Guy> Guys = new List<Guy>();
public static void makeSomeGuys(int howManyGuys)
{
for (int i = 0, i <= howManyGuys; i++)
{
int id = i;
int lifeExpectancy = 80;
bool alive = true;
Guys.Add(New Guy(id, lifeExpectancy, alive));
Console.WriteLine("Made a new Guy {0}", id);
}
return;
}
Questions in order of importance:
How do I access a specific object as well as its parameters? (Accessing from the list "Guys".)
How do I access an object from this list in another class? (Not that I absolutely need to, I'm curious)
Can I search for an object in a list by using its parameters? (As opposed to doing something like... humanPopulation[number])
Should I create a new list for objects that have had their parameters modified? (As opposed to leaving it in the original list)
Is it possible to remove items from a list? (Just in general, is that a thing people do? if so, why?)
I really only need the first question answered. The rest of them are just a bonus. Thanks!
First you need to remove the static modifier from the properties of the Guy class, i.e.:
public int ID { get; set; }
public int LifeExpectancy { get; set; }
public bool Living { get; set; }
because static causes the property to be an attribute of the class itself, rather than the instances of the class (the individual 'guys').
To access life expectancy of the first guy (the zeroth):
Console.WriteLine(Guys[0].LifeExpectancy);
To access life expectancy of the fifth guy:
Console.WriteLine(Guys[4].LifeExpectancy);
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
public class Guy
{
private int m_ID;
private int m_LifeExpectancy;
private bool m_Living;
public int ID
{
get { return m_ID; }
set { m_ID = value; }
}
public int LifeExpectancy
{
get { return m_LifeExpectancy; }
set { m_LifeExpectancy = value; }
}
public bool Living
{
get { return m_Living; }
set { m_Living = value; }
}
public Guy(int id, int lifeExpectancy, bool living)
{
ID = id;
LifeExpectancy = lifeExpectancy;
Living = living;
}
}
public class MyFactory
{
public IList<Guy> MakeSomeGuys(int howManyGuys)
{
IList<Guy> localGuys = new List<Guy>();
for (int i = 0; i <= howManyGuys; i++)
{
int id = i;
int lifeExpectancy = 80;
bool alive = true;
localGuys.Add(new Guy(id, lifeExpectancy, alive));
Console.WriteLine("Made a new Guy {0}", id);
}
return localGuys;
}
}
public class program
{
public void Main()
{
MyFactory mf = new MyFactory();
IList<Guy> guys = mf.MakeSomeGuys(5);
//How do I access a specific object as well as its parameters? (Accessing from the list "Guys".)
int GetFirstGuyId = guys.FirstOrDefault().ID; //LEARN LINQ
//How do I access an object from this list in another class? (Not that I absolutely need to, I'm curious)
//you need to learn about object oriented encapsulation for better understanding.
//Can I search for an object in a list by using its parameters? (As opposed to doing something like...humanPopulation[number])
Guy guyById = guys.Where(g => g.ID == 5).FirstOrDefault(); // returns the first match (need to learn lambda expression)
//Should I create a new list for objects that have had their parameters modified? (As opposed to leaving it in the original list)
// you need to learn about passing values by value / reference (by reference you already changing the original!).
//Is it possible to remove items from a list? (Just in general, is that a thing people do? if so, why?)
//yes
guys.Remove(guyById);
}
}
}
You're likely new to C# and OO programming, so I've included some good links in this answer.
Regarding question 1 only:
Firstly, your Guy class properties aren't properly encapsulated. Make sure you properly scope the ID, LifeExpectancy and Living properties like shown in this article.
If you'd like to access a specific item, that is, a Guy with a particular ID, you'd be better off using an associative container like Dictionary.
If you're happy with the List container, you need to use the Find method on Guys as shown in the example at the link. You'll notice the term Predicate in the documentation, this link will elaborate.

Improve efficiency of modifying object properties in a list

I have a list of custom objects that I am working with. I need to find matching objects, and save two attributes to the object, and move on. I can't help but think that my method of working with these objects is sub-optimal. Given I am working with large volumes of data (in this instance a list with ~ 10000 objects, but in other instances significantly larger), I would appreciate any information that might help me optimize the process.
List<WebListingVerification> listings = new List<WebListingVerification>(); //This list is fully populated, and is actually passed into the function.
string sku = reader["vsr_sku"].ToString();
string vendorName = reader["v_name"].ToString();
string vendorSku = reader["vsr_vendor_sku"].ToString();
WebListingVerification listing = listings.Find(x => x.SKU == sku);
if(listing != null)
{
listings.Remove(listing);
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
listings.Add(listing);
}
As you can see above, I first remove the listing, then edit it, and then re-add it. I imagine there is a way to safely edit the object in the list without running Remove / Add which would help a great deal, but I can't seem to find how to do it. I'm not sure if you could do a compound function off of the listings.Find call (listings.Find(x => x.SKU == sku).Vendor = "vendor") but it would be unsafe, as there will be null returns in this circumstance anyways so..
Any help optimizing this would be greatly appreciated.
Edit
Thank you for the comments, I did not understand the fact that the result of the List.Find function call is in fact a pointer to the object in the list, and not a copy of the object. This clears up my issue!
In addition, thank you for the additional answers. I was looking for a simple improvement, predominantly to remove the Add / Remove routines, but the additional answers give me some good ideas on how to write these routines in the future which may net some significant performance improvements. I've been focused on reporting tasks in the past few months, so this example snippet is very similar to probably 100 different routines where I am gathering data from various source databases. Again, I very much appreciate the input.
public class WebListingVerification
{
public string Sku { get; set; }
public string VendorName { get; set; }
public string VendorSku { get; set; }
}
public class ListingManager : IEnumerable <WebListingVerification>
{
private Dictionary<string, WebListingVerification> _webListDictionary;
public ListingManager(IEnumerable <WebListingVerification> existingListings)
{
if (existingListings == null)
_webListDictionary = new Dictionary<string, WebListingVerification>();
else
_webListDictionary = existingListings.ToDictionary(a => a.Sku);
}
public void AddOrUpdate (string sku, string vendorName, string vendorSku)
{
WebListingVerification verification;
if (false == _webListDictionary.TryGetValue (sku, out verification))
_webListDictionary[sku] = verification = new WebListingVerification();
verification.VendorName = vendorName;
verification.VendorSku = vendorSku;
}
public IEnumerator<WebListingVerification> GetEnumerator()
{
foreach (var item in _webListDictionary)
yield return item.Value;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
If your items are unique, might I suggest a HashSet<T>?
HashSet<WebListingVerification> listings = new HashSet<WebListingVerification>();
string sku = reader["vsr_sku"].ToString();
string vendorName = reader["v_name"].ToString();
string vendorSku = reader["vsr_vendor_sku"].ToString();
if(listings.Contains(listing))
{
listings.Remove(listing);
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
listings.Add(listing);
}
You'd have to roll your own IEqualityComparer<T> interface on the WebListingVerification object and match on the SKU, which I assume is unique.
public class WebListingVerification : IEqualityComparer<WeblistingVerification>
{
public string Sku { get; set; }
public bool Equals(WebListingVerification obj, WebListingVerification obj2)
{
if (obj == null && obj2 == null)
return true;
else if (obj == null | obj2 == null)
return false;
else if (obj.Sku == obj2.Sku)
return true;
else
return false;
}
public int GetHashCode(WebListingVerification obj)
{
return Sku.GetHashCode();
}
}
HashSet.Contains() performance is phenomenal on large datasets like this.
To speed up the lookup you could first convert your list into a dictionary. Note though if your update method is a method, you should not do the conversion inside the method, but outside the update loop.
var dictionary = listings.ToDictionary(l => l.SKU);
And get the item from the dictionary with the sku value.
WebListingVerification listing;
if (dictionary.TryGetValue(sku, out listing))
{
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
}
No need to remove and add back the object into the list. Just;
if(listing != null)
{
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
}

Nested ObservableCollection filtering

I have a question regarding the filtering of an ObservableCollection (and its children).
I have the following class:
public class SomeClass
{
public string Description { get; set; }
public string CodeFlag { get; set; }
public double Price { get; set; }
public List<SomeClass> Children { get; set; }
public SomeClass()
{
this.Children = new List<SomeClass>();
}
public SomeClass Search(Func<SomeClass, bool> predicate)
{
// the node is a leaf
if (this.Children == null || this.Children.Count == 0)
{
if (predicate(this))
return this;
else
return null;
}
else // the node is a branch
{
var results = Children.Select(i => i.Search(predicate)).Where(i => i != null).ToList();
if (results.Any())
{
var result = (SomeClass)MemberwiseClone();
result.Children = results;
return result;
}
/*
* this is where I'm struggling
*/
return null;
}
}
}
And in the view model the following properties:
private ObservableCollection<SomeClass> originalDataSource;
public ObservableCollection<SomeClass> TreeViewDataSource { get; set; }
The originalDataSource is set in the constructor whilst the TreeViewDataSource is the collection bound to the TreeView.
I'm certain that there are better ways to accomplish this, (i.e. have just the one collection) but I'm happy with this for now.
Initially, all of the items in the collection are to be shown - I simply show the Description, Code and Price properties for each item, so far so good.
Now, the view model is informed that the current filter has changed so I want to be able to filter as such.
An example could be to show all items where “CodeFlag” is “ABC” or “XYZ”.
If the filter has changed, I set the TreeViewDataSource as such:
this.TreeViewDataSource = _getFilteredList(this.originalDataSource);
private ObservableCollection<SomeClass> _getFilteredList(ObservableCollection<SomeClass> originalList)
{
var filteredItems = new ObservableCollection<SomeClass>();
SomeClass filterResults = null;
switch (this.SelectedFilter)
{
case SomeEnum.SomeFilterOption:
filterResults = originalList[0].Search(x => x.CodeFlag.Equals("ABC") || x.CodeFlag.Equals("XYZ"));
break;
default:
break;
}
filteredItems.Add(filterResults);
return filteredItems;
}
This almost works as expected.
Where it is not working as expected is if an item has children where the filter does NOT apply.
In this scenario, even though the item itself matches the filter, as its children do not, null is returned.
The
/*
* this is where I'm struggling
*/
comment is where I believe I need additional logic.
Please note, the credit for the original Search method goes to #tono-nam
As it's the Weekend and I may be in a different time zone as that of the vast majority of you, please do not be offended if I do not respond straight away!
Have a great weekend!
You don't need an ObservableCollection for the items you're going to show, since the entire collection changes at once. You can simply use e.g. an array, and let the parent class implement INotifyPropertyChanged to notify the fact that the entire collection has changed.
To answer your question about what to return instead of null, use the same logic you use for leaves: return the item if it matches the predicate and null otherwise.
You can simplify your code by reordering the conditions: first get all children that satisfy the predicate, and if there are none (either because there are no children, or because there are children but they don't match - doesn't matter) then treat the collection as a leaf.

Compare generic Collection?

I have a
ObservableCollection<BasicClass> allCollection;
ObservableCollection<BasicClass> selectedCollection;
where
BasicClass
{
public Name {get;set;}
public Age {get;set;}
}
Now I added many BasicClass items to allCollection and only selected BasicClass to selectedCollection
SomeWhere I want to add items in selectedCollection which are not there in allCollection.
I tried this
foreach(var a in allCollection)
{
foreach(var s in selectedCollection)
if(a.Name!=s.Name)
//selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
But the problem is that this code is adding new BasicClass for each and every unmatched name,
but my actuall requirement is, for each Name of allCollection compare all selectedCollection items. If it is not there then add else move for next Item.
LINQ solution could help this? Actually I achieved this by more if and flags but That looks ver hectic.
My traditional solution
foreach(var a in allCollection)
{
bool same = false;
foreach(var s in selectedCollection)
if(a.Name==s.Name)
same=true;
}
if(same==false)
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
And I hate this..
EDIT:
I don't want compare collection to collection.
I want to compare collection1 value to collection2 all values, and if it not there then I want to add
Are you sure you don't just need this?
foreach(var a in allCollection)
{
if (!selectedCollection.Contains(a))
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
EDIT
I've just seen your comment below about matching on name only, so the above is not really what you want:). Try this approach instead:
foreach(var a in allCollection)
{
if (!selectedCollection.Any(s => a.Name == s.Name))
{
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
}
EDIT
As Chris suggested you could also use "Except" to create a collection. I'm not sure this gains much, it may be quicker but it involves writing the comparer code and creates a new temporary collection. However, it is pretty succinct E.g. Once you had the comparaer written you would just need this to add your missing items to the collection:
selectedCollection.Concat(allCollection.Except(selectedCollection));
So basically you need a 'where-not-in'? Linq->Except is the way to go, to filter on BasicClass.name only implement the IEqualityComparer for Except.
I'm not sure I understood your requirements correctly, so i may be missing the point...
Your BasicClass should implement the IEquatable<BasicClass> interface, so that two instances of BasicClass can be compared for equality:
class BasicClass : IEquatable<BasicClass>
{
public Name {get;set;}
public Age {get;set;}
public bool Equals(BasicClass other)
{
if (other == null)
return false;
return string.Equals(this.Name, other.Name);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
Now you can use the Except method to find items that are in allCollection but not in selectedCollection:
BasicClass[] notSelected = allCollection.Except(selectedCollection).ToArray();
foreach(BasicClass item in notSelected)
{
selectedCollection.Add(item);
}
Alternatively, you can implement a IEqualityComparer<BasicClass> and pass it to Except (instead of implementing IEquatable<BasicClass> in BasicClass)
You're right, this is more easily accomplished with Linq:
var itemsToAdd = allCollection.Except(selectedCollection);
foreach (var item in itemsToAdd)
selectedCollection.Add(item);
On the other hand, this is just going to make both lists contain the exact same items. Sure this is what you want?
If BasicItem overrides 'Equals' and 'GetHashCode' based off of Name, then this is all you need. If it doesn't, then you will also need to implement an IEqualityComparer:
//Allows us to compare BasicItems as if Name is the key
class NameComparer: IEqualityComparer<BasicItem>
{
public bool Equals(BasicItem first, BasicItem second)
{
return first.Name == second.Name;
}
public int GetHashCode(BasicItem value)
{
return value.Name.GetHashCode;
}
}
You now pass an instance of this class to Except:
var itemsToAdd = allCollections.Except(selectedCollection, new NameComparer());

Categories