using System; using System.Collections.Generic; using System.Linq; using GLib; using Gtk; using DateTime = GLib.DateTime; namespace EstusShots.Gtk.Controls { public class SelectionChangedEventArgs : EventArgs { public SelectionChangedEventArgs(object selection) { Selection = selection; } public object Selection { get; } } public delegate void SelectionChangedEventHandler(object o, SelectionChangedEventArgs args); public class BindableListControl { /// /// Initialize a new BindableListView with an existing TreeView Widget /// /// The columns of the grid /// Unique key field in the item sources type /// An instance of an existing TreeView Widget public BindableListControl(List columns, string keyField, TreeView treeView = null) { TreeView = treeView ?? new TreeView(); Columns = columns; KeyField = keyField; InitTreeViewColumns(); InitListStore(); TreeView.Model = ListStore; Items = new List(); TreeView.Selection.Changed += TreeView_SelectionChanged; } /// The GTK ListStore that is managed by this . public ListStore ListStore { get; internal set; } /// The GTK TreeView control that is managed by this . public TreeView TreeView { get; } /// Property of the element type that is used as a unique identifier for accessing elements. public string KeyField { get; } /// The collection of all elements, that should be shown in the list view. public List Items { get; set; } /// The currently selected item in the view. public T SelectedItem { get; set; } /// All columns that are displayed in the list. public List Columns { get; } /// /// Event will be invoked when the selected item in the has changed. /// public event SelectionChangedEventHandler OnSelectionChanged; /// /// Set elements from the property in the . /// /// public void DataBind() { ListStore.Clear(); Items.ForEach(BindItem); } private void BindItem(T item) { var row = new List(); foreach (var column in Columns) { var prop = item.GetType().GetProperty(column.PropertyName); if (prop == null) throw new TypeLoadException( $"Property '{column.PropertyName}' does not exist on Type '{item.GetType()}'"); var val = prop.GetValue(item); if (column.Format != null) val = column.Format(val); row.Add(val.ToString()); } // The key value must be the first value in the row var key = item.GetType().GetProperty(KeyField)?.GetValue(item); row.Insert(0, key); try { ListStore.AppendValues(row.ToArray()); } catch (Exception e) { Console.WriteLine(e); } } private void TreeView_SelectionChanged(object sender, EventArgs e) { if (!(sender is TreeSelection selection)) return; selection.GetSelected(out var model, out var iter); var key = model.GetValue(iter, 0); var item = Items.FirstOrDefault(x => { var prop = x.GetType().GetProperty(KeyField); var value = prop?.GetValue(x); return value != null && value.Equals(key); }); if (item == null) { Console.WriteLine($"No item for key '{key}' found in data store"); return; } SelectedItem = item; OnSelectionChanged?.Invoke(this, new SelectionChangedEventArgs(SelectedItem)); } private void InitTreeViewColumns() { foreach (var dataColumn in Columns) { // Offset by one, because the first column in the data store is fixed to the key value of the row var index = Columns.IndexOf(dataColumn) + 1; var column = new TreeViewColumn( dataColumn.Title, new CellRendererText(), "text", index) { Resizable = true, Reorderable = true }; TreeView.AppendColumn(column); } } private void InitListStore() { var types = Columns .Select(x => { var propType = typeof(T).GetProperty(x.PropertyName)?.PropertyType; var gType = (GType) propType; if (gType.ToString() == "GtkSharpValue") gType = MapType(propType); return gType; }); var data = new DateTime(); // The first column in the data store is always the key field. var columns = new List {(GType) typeof(T).GetProperty(KeyField)?.PropertyType}; columns.AddRange(types); ListStore = new ListStore(columns.ToArray()); } private static GType MapType(Type type) { return type.Name switch { _ => GType.String }; } } }