Add generic TreeView class
This commit is contained in:
135
EstusShots.Gtk/Controls/BindableListView.cs
Normal file
135
EstusShots.Gtk/Controls/BindableListView.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
EstusShots.Gtk/Controls/DataColumn.cs
Normal file
16
EstusShots.Gtk/Controls/DataColumn.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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");
|
||||||
@@ -62,6 +80,7 @@ namespace EstusShots.Gtk
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReloadSeasons();
|
||||||
Info($"Created new Season");
|
Info($"Created new Season");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -71,23 +90,19 @@ namespace EstusShots.Gtk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
Application.Quit();
|
Application.Quit();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user