Add generic TreeView class

This commit is contained in:
2020-02-27 17:15:51 +01:00
parent 64cdaf8e9f
commit 224a5e07df
6 changed files with 188 additions and 74 deletions

View File

@@ -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<T>
{
public ListStore ListStore { get; internal set; }
public TreeView TreeView { get; }
public string KeyField { get; }
public IEnumerable<T> Items { get; set; }
public T SelectedItem { get; set; }
public List<DataColumn> Columns { get; }
public event SelectionChangedEventHandler OnSelectionChanged;
/// <summary>
/// Initialize a new BindableListView with an existing TreeView Widget
/// </summary>
/// <param name="columns">The columns of the grid</param>
/// <param name="keyField">Unique key field in the item sources type</param>
/// <param name="treeView">An instance of an existing TreeView Widget</param>
public BindableListView(List<DataColumn> columns, string keyField, TreeView treeView = null)
{
TreeView = treeView ?? new TreeView();
Columns = columns;
KeyField = keyField;
InitTreeViewColumns();
InitListStore();
TreeView.Model = ListStore;
Items = new List<T>();
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<object>();
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<Type> {typeof(T).GetProperty(KeyField)?.PropertyType};
columns.AddRange(types);
ListStore = new ListStore(columns.ToArray());
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<Season> Data { get; set; }
public SeasonsDataStore(TreeView view)
{
ListStore = new ListStore(GType.Int, GType.String, GType.String);
Data = new List<Season>();
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<TreeViewColumn> BuildColumns()
{
var columns = new List<TreeViewColumn>
{
new TreeViewColumn("Number", new CellRendererText(), "text", 0),
new TreeViewColumn("Game", new CellRendererText(), "text", 1),
new TreeViewColumn("Start", new CellRendererText(), "text", 2)
};
return columns;
}
}
}

View File

@@ -21,4 +21,8 @@
<ProjectReference Include="..\EstusShots.Shared\EstusShots.Shared.csproj" /> <ProjectReference Include="..\EstusShots.Shared\EstusShots.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="DataStores" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,10 +1,11 @@
using System; using System;
using System.Net; using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using EstusShots.Client; using EstusShots.Client;
using EstusShots.Gtk.DataStores; using EstusShots.Gtk.Controls;
using EstusShots.Shared.Models; using EstusShots.Shared.Models;
using Gtk; using Gtk;
using Application = Gtk.Application; using Application = Gtk.Application;
@@ -24,7 +25,7 @@ namespace EstusShots.Gtk
[UI] private readonly Button _newSeasonButton = null; [UI] private readonly Button _newSeasonButton = null;
[UI] private readonly Label _infoLabel = null; [UI] private readonly Label _infoLabel = null;
private SeasonsDataStore SeasonsView { get; set; } private BindableListView<Season> SeasonsView { get; set; }
public MainWindow() : this(new Builder("MainWindow.glade")) { } public MainWindow() : this(new Builder("MainWindow.glade")) { }
@@ -37,17 +38,34 @@ namespace EstusShots.Gtk
_loadButton.Clicked += LoadButtonClicked; _loadButton.Clicked += LoadButtonClicked;
_newSeasonButton.Clicked += NewSeasonButtonOnClicked; _newSeasonButton.Clicked += NewSeasonButtonOnClicked;
SeasonsView = new SeasonsDataStore(_seasonsView); var seasonsColumns = new List<DataColumn>
{
new DataColumn(nameof(Season.DisplayName)){Title = "Name"}
};
SeasonsView = new BindableListView<Season>(seasonsColumns, nameof(Season.SeasonId) ,_seasonsView);
SeasonsView.OnSelectionChanged += SeasonsViewOnOnSelectionChanged;
Info("Application Started"); 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) 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 var season = new Season
{ {
Game = "Test Game", Game = "Test Game",
Number = 1, Number = nextNum,
Start = DateTime.Now Start = DateTime.Now
}; };
var content = new StringContent(JsonSerializer.Serialize(season), Encoding.UTF8, "application/json"); 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}"; _infoLabel.Text = $"Error while creating Season: {response.ReasonPhrase}";
return; return;
} }
ReloadSeasons();
Info($"Created new Season"); Info($"Created new Season");
} }
catch (Exception ex) catch (Exception ex)
@@ -70,23 +89,19 @@ namespace EstusShots.Gtk
Console.WriteLine(ex.Message); 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) private void LoadButtonClicked(object sender, EventArgs a)
{ {
var seasons = Client.GetSeasons().Result; ReloadSeasons();
SeasonsView.Data = seasons;
SeasonsView.DataBind();
Info("List Refreshed"); Info("List Refreshed");
} }
private void ReloadSeasons()
{
var seasons = Client.GetSeasons().Result;
SeasonsView.Items = seasons;
SeasonsView.DataBind();
}
private void Window_DeleteEvent(object sender, DeleteEventArgs a) private void Window_DeleteEvent(object sender, DeleteEventArgs a)
{ {

View File

@@ -29,7 +29,6 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="enable_grid_lines">horizontal</property> <property name="enable_grid_lines">horizontal</property>
<signal name="show" handler="SeasonsViewOnShown" swapped="no"/>
<child internal-child="selection"> <child internal-child="selection">
<object class="GtkTreeSelection"/> <object class="GtkTreeSelection"/>
</child> </child>