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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="DataStores" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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<Season> 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<DataColumn>
|
||||
{
|
||||
new DataColumn(nameof(Season.DisplayName)){Title = "Name"}
|
||||
};
|
||||
SeasonsView = new BindableListView<Season>(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");
|
||||
@@ -62,6 +80,7 @@ namespace EstusShots.Gtk
|
||||
return;
|
||||
}
|
||||
|
||||
ReloadSeasons();
|
||||
Info($"Created new Season");
|
||||
}
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Application.Quit();
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<signal name="show" handler="SeasonsViewOnShown" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
|
||||
Reference in New Issue
Block a user