Your first Eto UI

As you may be new to Rhino, Eto and/or programming, this guide will focus entirely on a small fun eto app to get you started.

As we have so many exciting things to learn in Eto, it might be good to make a small To Do list.

Your First UI

Your first Window

Let’s start simple and show a window. In this case, we’re going to use a Dialog.

using Eto.Forms;
var dialog = new Dialog()
{
Width = 200,
Height = 200
};
dialog.ShowModal();
Your First Dialog

Creating Content

Creating a window in Eto isn’t too complex in C# or python. Only a few lines of code and a we’re already making progress! The window is a bit empty though, lets add some content. We’re going to use a GridView.

using Eto.Forms;
var gridView = new GridView()
{
Columns = {
new GridColumn()
{
HeaderText = "±"
},
new GridColumn()
{
HeaderText = "Item"
},
},
};
var dialog = new Dialog()
{
Width = 200,
Height = 200,
Content = gridView,
};
Your First Content

Adding Data

We’ve added a GridView, our first Eto control along with some columns. The UI looks a little sparse still. It would be nice to have some items in the UI.

using System.Collections.Generic;
using Eto.Forms;
internal class ToDoItem
{
public string Text { get; set; }
public ToDoItem(string text)
{
Text = text;
}
}
var items = new List<ToDoItem>()
{
new ToDoItem("Buy Groceries"),
new ToDoItem("Feed Cat"),
new ToDoItem("Drink Water"),
};
var gridView = new GridView()
{
Columns = {
new GridColumn()
{
HeaderText = "±"
},
new GridColumn()
{
HeaderText = "Items",
Editable = true,
DataCell = new TextBoxCell("Text")
},
},
DataStore = items, // <-- Don't miss this!
};
var dialog = new Dialog()
{
Width = 200,
Height = 200,
Content = gridView,
};
dialog.ShowModal();
Your First Data

Custom Cells

The UI is looking great! It’s starting to look like a list UI now, we have items in our list. It’s just not very functional. Let’s add some functionality.

The code is getting big now, we’re going to focus on sections now that the skeleton is there.

var buttonCell = new CustomCell()
{
CreateCell = (cc) =>
{
var button = new Button() { Text = "-" };
return button;
}
};
var gridView = new GridView()
{
Columns = {
new GridColumn()
{
HeaderText = "±",
Width = 24,
DataCell = buttonCell,
Your First Data

Adding Functionality

The UI still doesn’t do anything, but it’s starting to look like it does. Let’s fix that and make it functional.

var buttonCell = new CustomCell()
{
CreateCell = (cc) => {
var button = new Button();
button.Text = "-";
button.Click += (s, e) => {
try
{
int row = cc.Row;
items.RemoveAt(row);
} catch {}
};
return new Panel() { Content = button, Padding = 2 };
},
};

Notify the UI

That’s odd. Why doesn’t the - button do anything? That’s because the data behind our UI isn’t informing our GridView of the updates. Luckily the solution is pretty simple.

var items = new List<ToDoItem>() // Old
var items = new ObservableCollection<ToDoItem>() // New

Finishing Up

ObservableCollections notify the UI of changes in the collection. It is advisable to NEVER replace the collection with a new collection, but instead clear and refill.

internal class ToDoItem
{
public string Text { get; set; }
public bool AddItem { get; set; } = false; // <-- Add a new property
public ToDoItem(string text)
{
Text = text;
}
}
var items = new ObservableCollection<ToDoItem>()
{
new ToDoItem("Buy Groceries"),
new ToDoItem("Feed Cat"),
new ToDoItem("Drink Water"),
new ToDoItem("..") { AddItem = true }, // <-- Add a new item
};
var buttonCell = new CustomCell()
{
CreateCell = (cc) => {
if (cc.Item is not ToDoItem element) return new Panel();
int row = cc.Row;
var button = new Button();
if (element.AddItem) // <-- AddItems only add
{
button.Text = "+";
button.Click += (s, e) => {
try
{
items.Insert(items.Count - 1, new ToDoItem("...") { AddItem = true });
} catch {}
};
}
else // <-- Otherwise they remove
{
button.Text = "-";
button.Click += (s, e) => {
try
{
if (items[row].AddItem) return;
items.RemoveAt(row);
} catch {}
};
}
return button;
},
};

Putting it all together

Above we finish getting the UI working and now have a functioning UI. It’s not perfect though, and the code can be a bit tricky to read, often this is good for brevity and tutorials.

Below is all of the code with a few extra bits to make the UI a bit nicer, they’re not strictly necessary, but they should make it easier to read, edit and extend.


using System;
using System.Linq;
using System.Collections.ObjectModel;
using Eto;
using Eto.Forms;
using Rhino.UI;
internal class ToDoItem
{
public bool AddItem { get; set; } = false;
public string Text { get; set; }
public ToDoItem(string text)
{
Text = text;
}
}
internal class ToDoModel : ViewModel
{
public ObservableCollection<ToDoItem> Items { get; } = new();
}
internal class ToDoList : Dialog
{
private ToDoModel Model => DataContext as ToDoModel;
public ToDoList()
{
Width = 300;
Height = 200;
DataContext = new ToDoModel();
Model.Items.Add(new ToDoItem("Buy Groceries"));
Model.Items.Add(new ToDoItem("Feed Cat"));
Model.Items.Add(new ToDoItem("Drink Water"));
Model.Items.Add(new ToDoItem("") { AddItem = true });
InitLayout();
}
private void InitLayout()
{
var buttonCell = new CustomCell()
{
CreateCell = (cc) => {
int row = cc.Row;
var element = Model.Items.ElementAtOrDefault(row);
if (element is null) return new Panel();
var button = new Button();
if (element.AddItem)
{
button.Text = "-";
button.Click += (s, e) => {
try
{
Model.Items.Insert(Model.Items.Count - 1, new ToDoItem("..."));
} catch {}
};
}
else
{
button.Text = "-";
button.Click += (s, e) => {
try
{
if (Model.Items[row].AddItem) return;
Model.Items.RemoveAt(row);
} catch {}
};
}
return new Panel() { Content = button, Padding = 2 };
},
};
var gv = new GridView()
{
Border = BorderType.None,
CanDeleteItem = (e) => true,
GridLines = GridLines.None,
RowHeight = 24,
Columns = {
new GridColumn()
{
HeaderText = "±",
HeaderToolTip = "Click + to add, - to remove",
DataCell = buttonCell,
Width = 24,
Expand = false,
},
new GridColumn()
{
HeaderText = "Item",
Editable = true,
Expand = true,
HeaderToolTip = "To Do Item",
DataCell = new TextBoxCell(nameof(ToDoItem.Text))
{
AutoSelectMode = AutoSelectMode.OnFocus,
TextAlignment = TextAlignment.Left,
},
},
},
DataStore = Model.Items,
};
Content = gv;
}
}
var list = new ToDoList();
list.ShowModal(RhinoEtoApp.MainWindowForDocument(__rhino_doc__));