Using MonoTouch.Dialog I add StyledStringElement elements.
There is a background task that retrieves details that need to update the element.Value
Is there a way to force the element to have it's text updated after the element.Value is updated?
Ian
If you want to update this element-by-element then you can use something like:
public class MyStyledStringElement {
public void SetValueAndUpdate (string value)
{
Value = value;
if (GetContainerTableView () != null) {
var root = GetImmediateRootElement ();
root.Reload (this, UITableViewRowAnimation.Fade);
}
}
}
A variation would be to load everything and update once (i.e. iterating on the root.Reload for every Element).
I've added "this.PrepareCell (cell); to the SetValueAndUpdate method and works. But I still thinking in that there is another better option detecting the change of "caption" and calling this.PrepareCell (cell);.
public void UpdateCaption(string caption) {
this.Caption = caption;
Console.WriteLine ("actualizando : " + Caption);
UITableViewCell cell = this.GetActiveCell ();
if (cell != null) {
this.PrepareCell (cell);
cell.SetNeedsLayout ();
}
}
Another approach to update the label would be to derive from StyledStringElement or StringElement and directly refresh the DetailTextLabel within the cell:
class UpdateableStringElement : StringElement
{
public UpdateableStringElement(string name): base (name)
{
}
UILabel DetailText = null;
public void UpdateValue(string text)
{
Value = text;
if (DetailText != null)
DetailText.Text = text;
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = base.GetCell(tv);
DetailText = cell.DetailTextLabel;
return cell;
}
}
Instead of the Value property you can then use the UpdateValue method:
var element = new UpdateableStringElement("demo");
SomeEventOfYours += delegate {
element.UpdateValue(LocalizedValue);
};
Related
Goal: add an image above an UIAlertController's Title label by subclassing UIAlertController and adding new line characters, \n, to the title string to make space for the UIImageView
Desire
Current
As one can see, able to add the image to the UIAlertController successfully but the image is not being spacing/placed above the Title. It appears to be adding to the center of the alert. How to space the image correctly above the UIAlertController title?
Current code:
namespace XamarinFormsApp1.Extensions
{
public class AlertController : UIAlertController
{
private string originalTitle;
private string spaceAdjustedTitle;
private UIImageView imageView = null;
private CoreGraphics.CGSize previousImgViewSize
= CoreGraphics.CGSize.Empty;
public override UIAlertControllerStyle PreferredStyle
{
get
{
return UIAlertControllerStyle.Alert;
}
}
public override string Title
{
get
{
return originalTitle;
}
set
{
if (Title != spaceAdjustedTitle ||
string.IsNullOrEmpty(Title) ||
string.IsNullOrEmpty(spaceAdjustedTitle))
{
originalTitle = value;
}
}
}
public void setTitleImage(UIImage image)
{
if (this.imageView == null)
{
UIImageView imageView = new UIImageView(image);
this.View.AddSubview(imageView);
this.imageView = imageView;
return;
}
imageView.Image = image;
}
public override void ViewDidLayoutSubviews()
{
if (imageView == null)
{
base.ViewDidLayoutSubviews();
return;
}
// Adjust title if image size has changed
if (previousImgViewSize != imageView.Bounds.Size)
{
previousImgViewSize = imageView.Bounds.Size;
adjustTitle(imageView);
}
// Position `imageView`
var linesCount = newLinesCount(imageView);
var padding = Constants.Padding(PreferredStyle);
var x = View.Bounds.Width / 2.0;
var y = padding + linesCount * lineHeight / 2.0;
CoreGraphics.CGPoint cgPoint = imageView.Center;
cgPoint.X = (nfloat)x;
cgPoint.Y = (nfloat)y;
imageView.Center = cgPoint;
base.ViewDidLayoutSubviews();
}
private void adjustTitle(UIImageView imageView)
{
var linesCount = (int)newLinesCount(imageView);
var lines = Enumerable
.Range(1, linesCount)
.Select(i => "\n")
.Aggregate((c, n) => $"{c}{n}");
spaceAdjustedTitle = lines + (originalTitle ?? "");
Title = spaceAdjustedTitle;
}
private double newLinesCount(UIImageView imageView)
{
return Math.Ceiling(
imageView.Bounds.Height / lineHeight);
}
private float lineHeight
{
get
{
UIFontTextStyle style = this.PreferredStyle
== UIAlertControllerStyle.Alert
? UIFontTextStyle.Headline
: UIFontTextStyle.Callout;
return (float)UIFont
.GetPreferredFontForTextStyle(style)
.PointSize;
}
}
struct Constants
{
static float paddingAlert = 22;
static float paddingSheet = 11;
public static float Padding(UIAlertControllerStyle style)
{
return style == UIAlertControllerStyle.Alert
? Constants.paddingAlert
: Constants.paddingSheet;
}
}
}
}
Note: Credit to #stringCode for image and swift solution, see.
UIAlertViewController is not meant to be subclassed.
An extract from the documentation says:
You could still get the UI you desire by using a UIViewController with transparency on the View and a subview with the layout you desire.
You would need to also set these two properties: ModalTransitionStyle and ModalPresentationStyle to UIModalTransitionStyle.CrossDissolve and UIModalPresentationStyle.OverCurrentContext respectively if you want your custom UIAlertController to behaves the same as a UIAlertController
Update:
This is what I meant you could do:
In the Main.Storyboard drop a UIViewController and update the design as you wish. Following the image you posted above I created the UI as seen below:
That's an Image, 2 UILabels for the Title and Message and 3 buttons for the 3 different actions (Default, Destroy, Cancel). All these controls are inside a UIView with White background. For the example I called it ContentView
Adding the 3 button on the UI seems to be the easiest way to work with this and then hide/show them when you are about to present your alert. You could also create the buttons on the fly based on the actions you wanna show. This is up to you.
Create a ViewController Class, I called it NiceAlertController, and assign it to the ViewController in the Storyboard. Also, make sure to create back properties (Outlets) for all the UIControls (Label, Button, Image, etc) so you can access it from the ViewController class.
Here more information about how to work with iOS Storyboard on the designer
Now in your class you will need to add the code to make it work.
In your class to make the view transparent you will need to add this to your ViewDidLoad method:
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.2f);
this.View.Opaque = false;
}
Also, we could mimic the way UIAlertControllers are created and create our method like that one:
public static NiceAlertController Create(string title, string message, UIImage image = null)
{
//New instance of your ViewController UI
var storyboard = UIStoryboard.FromName("Main", NSBundle.MainBundle);
var alertController = storyboard.InstantiateViewController(nameof(NiceAlertController)) as NiceAlertController;
//Using the same transition and presentation style as the UIAlertViewController
alertController.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
alertController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
//This assumes your properties are called Title, Message and Image
alertController.Title = title;
alertController.Message = message;
alertController.Image = image;
return alertController;
}
The 3 properties used above (Title, Message and Image) looks like this:
public new string Title { get; private set; }
public string Message { get; private set; }
public UIImage Image { get; private set; }
Why these properties? because by the time you create the class the Controls on the view are not yet available. They will be available only after the View is loaded. This is why we will need to add other changes like the one below.
Here we are setting the values to the Controls on the UI
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.titleLabel.Text = Title;
this.messageLabel.Text = Message;
//If you don't set an image while Create, it will use the default image you set on the Designer.
if (Image != null)
{
this.imageView.Image = Image;
}
}
Now from any other ViewController you can call this ViewController as you would call an AlertViewController:
private void ShowMyAlertController()
{
var alert = NiceAlertController.Create("Hello", "My nice alert controller");
this.PresentViewController(alert, true, null);
}
And it should look like this:
To handle the Actions (What to do when the buttons are tapped) you could create specific methods like:
public void AddDefaultAction(string title, Action action)
{
//Logic here
}
public void AddCancelAction(string title, Action action)
{
//Logic here
}
public void AddDestructiveAction(string title, Action action)
{
//Logic here
}
Hope this gives you the idea of how to create custom UIViewcontroller and make it look like a UIAlertViewController.
Hope this helps.-
I have a UITableView which contains a prototype cell, with components added onto it in the storyboard, and referenced in my CustomCell.h & .m files, with the CustomCell.cs looking like this:
public partial class CustomCell : UITableViewCell
{
public CustomCell(IntPtr handle) : base(handle)
{
}
public UILabel Sender
{
get
{
return senderLabel;
}
set
{
senderLabel = value;
}
}
public UILabel Subject
{
get
{
return subjectLabel;
}
set
{
subjectLabel = value;
}
}
public UILabel Date
{
get
{
return timeLabel;
}
set
{
timeLabel = value;
}
}
public UILabel Preview
{
get
{
return previewLabel;
}
set
{
previewLabel = value;
}
}
public UILabel Initials
{
get
{
return initialsLabel;
}
set
{
initialsLabel = value;
}
}
}
This custom cell is then used in my table's source file, and its GetCell method, to try and add data to the table:
CustomCell cell = tableView.DequeueReusableCell("MailCell") as CustomCell;
cell.Sender.Text = TableItems[indexPath.Row].Sender;
cell.Subject.Text = TableItems[indexPath.Row].Subject;
cell.Preview.Text = TableItems[indexPath.Row].Preview;
cell.Date.Text = TableItems[indexPath.Row].Time;
cell.Initials.Text = "";
When you run the program and try and add the data to the table, it breaks on the second line of the GetCell method with a NullReferenceException, which implies that their is either no label in the cell, or that there is no cell to edit. My prototype's cell has an identifier of "MailCell", and I have tried making a full constructor in the class, which didn't work. I had previously programmatically added in the components into the table's cell's, and that worked perfectly and therefore I can be sure that my code to add the data to the source works as intended, and is not the problem. What else is there possible to test?
It breaks because it can not find an instance of you custom cell for the very first row.
DequeueReusableCell is looking for an instance if it can not find one it returns null
Change your code to:
CustomCell cell = tableView.DequeueReusableCell("MailCell") as CustomCell ?? new CustomCell();
When we enter value in row 1 the value entered in row 1 is appearing back in row 6 when we scroll to the row 6. Please see the below code and advice.
namespace Kites
{
public class Marks
{
// add any if you need more
public string StudentName { get; set; }
public string MarksScored { get; set; }
}
public class TEXTCHECK
{
public int POS { get; set; }
public string Value { get; set; }
}
public class MarksListViewAdapter : BaseAdapter<Marks>
{
private List<Marks> mstuduentmarks;
private List<TEXTCHECK> abc = new List<TEXTCHECK>();
private Context mcontext;
public MarksListViewAdapter (Context context, List<Marks> stud)
{
mstuduentmarks = stud;
mcontext = context;
}
public override int Count
{
get
{
return mstuduentmarks.Count;
// return mattendence.Count;
}
}
public override long GetItemId (int position)
{
return position;
}
public override Marks this[int position]
{
get
{
return mstuduentmarks [position];
// return mattendence [position];
}
}
class ViewHolder : Java.Lang.Object
{
public EditText comsevin;
public TextView namenmn;
}
public override View GetView (int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
View view = convertView;
if (view == null) // otherwise create a new one
{
view = LayoutInflater.From(mcontext).Inflate(Resource.Layout.listview_Marks, null, false);
holder = new ViewHolder();
holder.comsevin = view.FindViewById<EditText>(Resource.Id.editTextTeacherMarks);
holder.namenmn = view.FindViewById<TextView>(Resource.Id.textStudentNameTeacherMarks);
holder.namenmn.Tag = position;
view.Tag = holder;
}
else
{
holder = (ViewHolder)view.Tag;
}
holder.namenmn.Text = mstuduentmarks[position].StudentName;
int pos = (int)holder.namenmn.Tag;
holder.comsevin.TextChanged += (sender, e) =>
{
abc[pos].Value = holder.comsevin.Text;
};
//TextView txtStudent =
//txtStudent.Text = mstuduentmarks[position].StudentName;
//txtMarks.FocusChange += (object sender, View.FocusChangeEventArgs e) =>
//{
// //txtMarks.RequestFocusFromTouch ();
// mstuduentmarks[position].MarksScored = txtMarks.Text;
//};
holder.comsevin.BeforeTextChanged += (sender, e) =>
{
abc.Add(new TEXTCHECK { POS = position, Value = mstuduentmarks[position].MarksScored });
};
holder.comsevin.AfterTextChanged += (sender, e) =>
{
int a = abc[pos].POS;
mstuduentmarks[pos].MarksScored = abc[pos].Value;
};
//txtMarks.Tag = position;
//txtMarks.TextChanged += TxtMarks_TextChanged;
return view;
}
//void TxtMarks_TextChanged (object sender, Android.Text.TextChangedEventArgs e)
//{
// EditText txtMarks = (EditText)sender;
// //var position = (int)txtMarks.Tag;
//}
}
}
When we enter value in row 1 the value entered in row 1 is appearing back in row 6 when we scroll to the row 6. Please see the below code and advice.
As a rule of thumb, when experiencing lists that don't reflect the dataset (experiencing item repetition for example) in listview / recyclerview it means that you're either using dirty views which were previously used and then uncorrectly Re-Bound, or simply using wrong positions during bind
I see where you are getting it wrong:
if (view == null) // otherwise create a new one
{
view = LayoutInflater.From(mcontext).Inflate(Resource.Layout.listview_Marks, null, false);
holder = new ViewHolder();
holder.comsevin = view.FindViewById<EditText>(Resource.Id.editTextTeacherMarks);
holder.namenmn = view.FindViewById<TextView>(Resource.Id.textStudentNameTeacherMarks);
holder.namenmn.Tag = position;//<------------here!!!
view.Tag = holder;
}
TLDR Don't save positions this way.
Whats happening: this instance of your view is being reused by listView, meaning that sometimes (many times) if (view == null) will be false and this means Tag property will not be updated for row 6 (or any other calls that will use recycled Views) and you are in fact using a dirty value.
You are then trying to use the Tag property as position, but forgetting this tag is already dirty if the view was recycled
int pos = (int)holder.namenmn.Tag;
holder.comsevin.TextChanged += (sender, e) =>
{
abc[pos].Value = holder.comsevin.Text;
};
Since you have access to the position in this method call you should use it directly
take a look at this guide from Java Code geeks even though it's in Java you will be able to see a good implementation of the old ViewHolder/ListView pattern.
Hope this helps
I have subclassed UITableView with the required methods overridden in the subclass to populate the table view. However while NumberOfSections is called, NumberOfRowsInSection is not called.
The code below is from a Xamarin project but this is no different to its equivalent native version with slight differences in method names.
partial class ShootsTable : UITableView, IUISearchResultsUpdating, IUISearchBarDelegate
{
public bool History { get; set; }
public UIViewController Parent { get; set; }
private Bootleg.API.Event[] Events { get; set; }
private List<nint> ExpandedSections { get; }
public ShootsTable (IntPtr handle) : base (handle)
{
ExpandedSections = new List<nint> ();
SetEvents();
}
private void SetEvents()
{
if (History) {
Events = AppDelegate.Api.GetShootHistory ().ToArray();
} else {
Events = AppDelegate.Api.MyEvents.ToArray();
}
}
private void AddRefreshControl()
{
var refreshControl = new UIRefreshControl ();
refreshControl.AttributedTitle = new NSAttributedString ("Pull to refresh");
refreshControl.AddTarget (async delegate(object sender, EventArgs e) {
await AppDelegate.Api.RefreshEvents();
SetEvents();
ReloadData();
refreshControl.EndRefreshing();
}, UIControlEvent.ValueChanged);
AddSubview (refreshControl);
}
private void AddSearchControl()
{
var searchControl = new UISearchController (Parent);
searchControl.SearchResultsUpdater = this;
searchControl.SearchBar.Delegate = this;
TableHeaderView = searchControl.SearchBar;
}
public void UpdateSearchResultsForSearchController (UISearchController searchController)
{
SetEvents ();
Events = Events.Where (e => e.name.Contains (searchController.SearchBar.Text)).ToArray ();
ReloadData ();
}
public override nint NumberOfSections ()
{
if (Events != null && Events.Length > 0)
{
SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine;
return Events.Length;
}
var label = new UILabel (new CGRect (Bounds.X, Bounds.Y, Bounds.Size.Width, Bounds.Size.Height));
label.Text = History ? "You have not contributed to any shoots yet." : "No shoots available.";
label.Lines = 2;
label.TextAlignment = UITextAlignment.Center;
BackgroundView = label;
SeparatorStyle = UITableViewCellSeparatorStyle.None;
return 0;
}
public override nint NumberOfRowsInSection (nint section)
{
var e = Events[section];
if (e.group == null)
{
return 1;
}
return ExpandedSections.Contains (section) ? e.events.Count : 0;
}
[Export ("tableView:heightForHeaderInSection:")]
public System.nfloat GetHeightForHeader (UIKit.UITableView tableView, System.nint section)
{
return Events [section].group == null ? 0 : tableView.SectionHeaderHeight;
}
[Export ("tableView:viewForHeaderInSection:")]
public UIKit.UIView GetViewForHeader (UIKit.UITableView tableView, System.nint section)
{
var e = Events [section];
if (e.group == null)
{
return null;
}
var cell = (ShootGroupCell) tableView.DequeueReusableCell (ShootGroupCell.Key);
cell.Event = e;
cell.AddGestureRecognizer (new UITapGestureRecognizer(() => {
if (ExpandedSections.Contains(section))
{
ExpandedSections.Remove(section);
tableView.ReloadSections(new NSIndexSet((nuint) section), UITableViewRowAnimation.Automatic);
} else {
ExpandedSections.Add(section);
tableView.ReloadSections(new NSIndexSet((nuint) section), UITableViewRowAnimation.Automatic);
}
}));
return cell.ContentView;
}
public override UITableViewCell CellAt (NSIndexPath ns)
{
var e = Events[ns.Section];
e = e.group == null ? e : e.events[ns.Row];
if (History) {
var cell = (ShootCell)DequeueReusableCell (ShootCell.Key);
cell.Event = e;
cell.Parent = Parent;
return cell;
} else {
var cell = (ShootJoinCell) DequeueReusableCell (ShootJoinCell.Key);
cell.Event = e;
return cell;
}
}
[Export ("tableView:heightForRowAtIndexPath:")]
public System.nfloat GetHeightForRow (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath)
{
return Events [indexPath.Section].group == null || ExpandedSections.Contains (indexPath.Section) ? tableView.RowHeight : 0;
}
[Export ("tableView:didSelectRowAtIndexPath:")]
public async void RowSelected (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath)
{
if (!History)
{
var e = Events [indexPath.Section];
if (e.group != null) {
e = e.events [indexPath.Row];
}
MBHUDView.HudWithBody ("Connecting Event...", MBAlertViewHUDType.ActivityIndicator, 0, true);
await AppDelegate.Api.ConnectToEvent (e, false);
MBHUDView.DismissCurrentHUD ();
if (AppDelegate.Api.CurrentEvent.id != e.id)
{
var alert = UIAlertController.Create ("Confirm", "This event requires you to confirm you wish to join.", UIAlertControllerStyle.Alert);
alert.AddAction(UIAlertAction.Create("Join", UIAlertActionStyle.Default, async delegate {
MBHUDView.HudWithBody ("Connecting Event...", MBAlertViewHUDType.ActivityIndicator, 0, true);
await AppDelegate.Api.ConnectToEvent (e, true);
MBHUDView.DismissCurrentHUD ();
e = AppDelegate.Api.CurrentEvent;
DialogHelper.PermissionsDialog (e, delegate {
Parent.PerformSegue("phasesSegue", tableView);
}, null, Parent);
}));
alert.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, null));
Parent.PresentViewController (alert, true, null);
}
else
{
e = AppDelegate.Api.CurrentEvent;
DialogHelper.PermissionsDialog (e, delegate {
Parent.PerformSegue("phasesSegue", tableView);
}, null, Parent);
}
}
}
Now I have been looking at several possible solutions however it seems quite uncommon to sub-class UITableView with the majority of solutions simply being not setting the delegate or data source properly. I have also seen a SO solution to this issue simply being the TableView not being in view and therefore the delegate methods do not need to be called however this is not the case.
I do not need to set delegate methods as I am subclassing UITableView itself and it is sufficient to just override the built in methods. In the storyboard I set the class to my custom class ShootsTable.
Does anyone have any ideas?
Thanks in advance.
This appears to be a bug. Not sure what level (Xamarin or iOS) it is at but as soon as this is changed back to a IUITableViewDataSource implementation it works.
I will report this to Xamarin and see what comes back.
I'm attempting my first Windows Form project, having been entirely web based previously and experiencing some issues. I want to bind a list of objects to a TabControl and have this create the Tabs and then have a databound value accessible from the click event of each tab.
The Object I'm wanting to bind is
public class TreeNodeItem
{
private NTree<string> node;
public TreeNodeItem(NTree<string> node)
{
this.node = node;
}
public string Value
{
get { return this.node.data; }
}
}
The NTree node represents a node in an object that models data in a tree structure. I want to create a tab for each object in the list with the Value property being bound to the Tab Text property. Other posts mention binding to the ItemsSource property of the control, but Visual Studio is not giving me this property.
Any help will be greatly appreciated.
Cheers
Stewart
Okay, I was unaware of that the binding was a must. Although I have never seen something like this being done in a Windows Forms Application, I've decided to create a class that does this for us.
It uses the ObservableCollection<T> to keep track whether an object / property has been changed inside its list.
public class ObservableList<T> : ObservableCollection<T>
{
public ObservableList() : base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(nObservableCollection_CollectionChanged);
}
public event PropertyChangedEventHandler OnPropertyChanged;
void nObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (OnPropertyChanged != null)
{
OnPropertyChanged(new object[] { e.OldItems, e.NewItems }, null); // Call method to let it change the tabpages
}
}
}
Now, we have to create a helper class that helps us keeping track:
public class TabControlBind
{
public TabControlBind(TabControl tabControl)
{
// Create a new TabPageCollection and bind it to our tabcontrol
this._tabPages = new TabControl.TabPageCollection(tabControl);
}
// Fields
private ObservableList<TreeNodeItem> _treeNodeItems;
private TabControl.TabPageCollection _tabPages;
// Properties
public ObservableList<TreeNodeItem> TreeNodeItems
{
get { return _treeNodeItems; }
set
{
if (_treeNodeItems != value)
{
_treeNodeItems = value;
_treeNodeItems.OnPropertyChanged += OnPropretyChanged;
OnPropretyChanged(null, null);
}
}
}
public TabControl.TabPageCollection TabPages
{
get
{
return this._tabPages;
}
}
// Events
private void OnPropretyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender == null) // If list got set
{
// Remove existing tabpages
this._tabPages.Clear();
// Loop through all items inside the ObservableList object and add them to the Tabpage
foreach (TreeNodeItem _treeNodeItem in this._treeNodeItems)
{
TabPage tabPage = new TabPage() { Text = _treeNodeItem.Value, Tag = _treeNodeItems };
this._tabPages.Add(tabPage);
}
}
else if (sender is object[]) // If only one (or multiple) objects have been changed
{
// Get OldItems and NewItems
object[] changedItems = (object[])sender;
// Remove OldItems
if (changedItems[0] != null)
{
foreach (dynamic oldItems in (IList)changedItems[0])
{
foreach (TabPage tab in this._tabPages)
{
if (tab.Text == oldItems.Value)
{
this._tabPages.Remove(tab);
break;
}
}
}
}
// Add OldItems
if (changedItems[1] != null)
{
foreach (dynamic newItems in (IList)changedItems[1])
{
TabPage tabPage = new TabPage() { Text = newItems.Value, Tag = newItems };
this._tabPages.Add(tabPage);
}
}
}
}
}
This is a sample on how to use it:
TabControlBind tabControlBinder;
ObservableList<TreeNodeItem> treeNodeItems;
private void btnAdd_Click(object sender, EventArgs e)
{
// This will automatically update the TabControl
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test3" }));
}
private void frmMain_Load(object sender, EventArgs e)
{
// Create a new list object an add items to it
treeNodeItems = new ObservableList<TreeNodeItem>();
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test" }));
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test2" }));
// Create a new instance of the TabControlBind class, set it to our TabControl
tabControlBinder = new TabControlBind(tabControl);
tabControlBinder.TreeNodeItems = treeNodeItems;
}