From 224a5e07df95d384c373b0573f8444b51511954b Mon Sep 17 00:00:00 2001 From: luxick Date: Thu, 27 Feb 2020 17:15:51 +0100 Subject: [PATCH] Add generic TreeView class --- EstusShots.Gtk/Controls/BindableListView.cs | 135 ++++++++++++++++++ EstusShots.Gtk/Controls/DataColumn.cs | 16 +++ EstusShots.Gtk/DataStores/SeasonsDataStore.cs | 55 ------- EstusShots.Gtk/EstusShots.Gtk.csproj | 4 + EstusShots.Gtk/MainWindow.cs | 51 ++++--- EstusShots.Gtk/MainWindow.glade | 1 - 6 files changed, 188 insertions(+), 74 deletions(-) create mode 100644 EstusShots.Gtk/Controls/BindableListView.cs create mode 100644 EstusShots.Gtk/Controls/DataColumn.cs delete mode 100644 EstusShots.Gtk/DataStores/SeasonsDataStore.cs diff --git a/EstusShots.Gtk/Controls/BindableListView.cs b/EstusShots.Gtk/Controls/BindableListView.cs new file mode 100644 index 0000000..6baf06f --- /dev/null +++ b/EstusShots.Gtk/Controls/BindableListView.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Gtk; + +namespace EstusShots.Gtk.Controls +{ + public class SelectionChangedEventArgs : EventArgs + { + public object Selection { get; } + + public SelectionChangedEventArgs(object selection) + { + Selection = selection; + } + } + + public delegate void SelectionChangedEventHandler(object o, SelectionChangedEventArgs args); + + public class BindableListView + { + public ListStore ListStore { get; internal set; } + + public TreeView TreeView { get; } + + public string KeyField { get; } + + public IEnumerable Items { get; set; } + + public T SelectedItem { get; set; } + + public List Columns { get; } + + public event SelectionChangedEventHandler OnSelectionChanged; + + /// + /// 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 BindableListView(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; + } + + 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)); + } + + public void DataBind() + { + ListStore.Clear(); + foreach (var item in Items) + { + 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()}'"); + + row.Add(prop.GetValue(item)); + } + + // 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 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); + TreeView.AppendColumn(column); + } + } + + private void InitListStore() + { + var types = Columns + .Select(x => typeof(T).GetProperty(x.PropertyName)?.PropertyType); + + // The first column in the data store is always the key field. + var columns = new List {typeof(T).GetProperty(KeyField)?.PropertyType}; + columns.AddRange(types); + + ListStore = new ListStore(columns.ToArray()); + } + } +} \ No newline at end of file diff --git a/EstusShots.Gtk/Controls/DataColumn.cs b/EstusShots.Gtk/Controls/DataColumn.cs new file mode 100644 index 0000000..375e054 --- /dev/null +++ b/EstusShots.Gtk/Controls/DataColumn.cs @@ -0,0 +1,16 @@ +namespace EstusShots.Gtk.Controls +{ + public class DataColumn + { + public string PropertyName { get; set; } + + public string Title { get; set; } + + public DataColumn() { } + + public DataColumn(string propertyName) + { + PropertyName = propertyName; + } + } +} \ No newline at end of file diff --git a/EstusShots.Gtk/DataStores/SeasonsDataStore.cs b/EstusShots.Gtk/DataStores/SeasonsDataStore.cs deleted file mode 100644 index 5961da9..0000000 --- a/EstusShots.Gtk/DataStores/SeasonsDataStore.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using EstusShots.Shared.Models; -using GLib; -using Gtk; - -namespace EstusShots.Gtk.DataStores -{ - public class SeasonsDataStore - { - public ListStore ListStore { get; private set; } - - public TreeView View { get; set; } - - public List Data { get; set; } - - public SeasonsDataStore(TreeView view) - { - ListStore = new ListStore(GType.Int, GType.String, GType.String); - Data = new List(); - View = view; - var columns = BuildColumns(); - columns.ForEach(column => View.AppendColumn(column)); - View.Model = ListStore; - } - - public void DataBind() - { - ListStore.Clear(); - foreach (var datum in Data) - { - var row = new object[] {datum.Number, datum.Game, datum.Start.ToString(CultureInfo.InvariantCulture)}; - try - { - ListStore.AppendValues(row); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } - private List BuildColumns() - { - var columns = new List - { - new TreeViewColumn("Number", new CellRendererText(), "text", 0), - new TreeViewColumn("Game", new CellRendererText(), "text", 1), - new TreeViewColumn("Start", new CellRendererText(), "text", 2) - }; - return columns; - } - } -} \ No newline at end of file diff --git a/EstusShots.Gtk/EstusShots.Gtk.csproj b/EstusShots.Gtk/EstusShots.Gtk.csproj index 4ddfaa2..0c3735a 100644 --- a/EstusShots.Gtk/EstusShots.Gtk.csproj +++ b/EstusShots.Gtk/EstusShots.Gtk.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/EstusShots.Gtk/MainWindow.cs b/EstusShots.Gtk/MainWindow.cs index 4a7c5da..06837c5 100644 --- a/EstusShots.Gtk/MainWindow.cs +++ b/EstusShots.Gtk/MainWindow.cs @@ -1,10 +1,11 @@ using System; -using System.Net; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; using EstusShots.Client; -using EstusShots.Gtk.DataStores; +using EstusShots.Gtk.Controls; using EstusShots.Shared.Models; using Gtk; using Application = Gtk.Application; @@ -24,7 +25,7 @@ namespace EstusShots.Gtk [UI] private readonly Button _newSeasonButton = null; [UI] private readonly Label _infoLabel = null; - private SeasonsDataStore SeasonsView { get; set; } + private BindableListView SeasonsView { get; set; } public MainWindow() : this(new Builder("MainWindow.glade")) { } @@ -37,17 +38,34 @@ namespace EstusShots.Gtk _loadButton.Clicked += LoadButtonClicked; _newSeasonButton.Clicked += NewSeasonButtonOnClicked; - SeasonsView = new SeasonsDataStore(_seasonsView); + var seasonsColumns = new List + { + new DataColumn(nameof(Season.DisplayName)){Title = "Name"} + }; + SeasonsView = new BindableListView(seasonsColumns, nameof(Season.SeasonId) ,_seasonsView); + SeasonsView.OnSelectionChanged += SeasonsViewOnOnSelectionChanged; Info("Application Started"); } + private void SeasonsViewOnOnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!(e.Selection is Season season)) return; + Info($"Season '{season.DisplayName}' selected"); + } + private void NewSeasonButtonOnClicked(object sender, EventArgs e) { + if (!SeasonsView.Items.Any()) + { + Info("Cannot add Season (Not Loaded)"); + return; + } + var nextNum = SeasonsView.Items.Max(x => x.Number) + 1 ; var season = new Season { Game = "Test Game", - Number = 1, + Number = nextNum, Start = DateTime.Now }; var content = new StringContent(JsonSerializer.Serialize(season), Encoding.UTF8, "application/json"); @@ -61,7 +79,8 @@ namespace EstusShots.Gtk _infoLabel.Text = $"Error while creating Season: {response.ReasonPhrase}"; return; } - + + ReloadSeasons(); Info($"Created new Season"); } catch (Exception ex) @@ -70,23 +89,19 @@ namespace EstusShots.Gtk Console.WriteLine(ex.Message); } } - - private void SeasonsViewOnShown(object sender, EventArgs e) - { - Info("Loading Data"); - // var seasons = Client.GetSeasons().Result; - // SeasonsView.Data = seasons; - // SeasonsView.DataBind(); - // Info("Data Loaded"); - } private void LoadButtonClicked(object sender, EventArgs a) { - var seasons = Client.GetSeasons().Result; - SeasonsView.Data = seasons; - SeasonsView.DataBind(); + ReloadSeasons(); Info("List Refreshed"); } + + private void ReloadSeasons() + { + var seasons = Client.GetSeasons().Result; + SeasonsView.Items = seasons; + SeasonsView.DataBind(); + } private void Window_DeleteEvent(object sender, DeleteEventArgs a) { diff --git a/EstusShots.Gtk/MainWindow.glade b/EstusShots.Gtk/MainWindow.glade index a3fec1e..581e877 100644 --- a/EstusShots.Gtk/MainWindow.glade +++ b/EstusShots.Gtk/MainWindow.glade @@ -29,7 +29,6 @@ True True horizontal -