Add lookup control

This commit is contained in:
2020-03-07 15:46:10 +01:00
parent 027734f862
commit e85f4a1adb
6 changed files with 228 additions and 55 deletions

View File

@@ -21,6 +21,8 @@ namespace EstusShots.Gtk.Controls
public delegate void ItemActivatedEventHandler<T>(T item);
public delegate void RowContextMenuHandler<T>(T selection);
public class BindableListControl<T>
{
/// <summary> The GTK ListStore that is managed by this <see cref="BindableListControl{T}" />. </summary>
@@ -50,6 +52,11 @@ namespace EstusShots.Gtk.Controls
/// Will be invoked when a row in the view has ben acitvated (e.g. double clicked)
/// </summary>
public event ItemActivatedEventHandler<T> ItemActivated;
/// <summary>
/// Will be invoked when a row is right clicked
/// </summary>
public event RowContextMenuHandler<T> RowContextMenuOpened;
/// <summary>
/// Initialize a new BindableListView with an existing TreeView Widget
@@ -68,9 +75,10 @@ namespace EstusShots.Gtk.Controls
Items = new List<T>();
TreeView.RowActivated += TreeViewOnRowActivated;
TreeView.ButtonReleaseEvent += TreeViewOnButtonReleaseEvent;
TreeView.Selection.Changed += TreeViewSelectionOnChanged;
}
/// <summary>
/// Set elements from the <see cref="Items" /> property in the <see cref="ListStore" />.
/// </summary>
@@ -152,6 +160,12 @@ namespace EstusShots.Gtk.Controls
SelectedItem = item;
SelectionChanged?.Invoke(selection, new SelectionChangedEventArgs<T>(SelectedItem));
}
private void TreeViewOnButtonReleaseEvent(object o, ButtonReleaseEventArgs args)
{
if (args.Event.Button != 3) return;
RowContextMenuOpened?.Invoke(SelectedItem);
}
private void InitTreeViewColumns()
{
@@ -175,7 +189,6 @@ namespace EstusShots.Gtk.Controls
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> {(GType) typeof(T).GetProperty(KeyField)?.PropertyType};
columns.AddRange(types);

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="_multiLookupControl">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkComboBox" id="_searchBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="_addButton">
<property name="label" translatable="yes">Add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="_selectionTreeView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="enable_grid_lines">horizontal</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gdk;
using GLib;
using Gtk;
using Key = Gtk.Key;
using Menu = Gtk.Menu;
using MenuItem = Gtk.MenuItem;
using UI = Gtk.Builder.ObjectAttribute;
namespace EstusShots.Gtk.Controls
{
public class LookupSelectionControlOptions<T>
{
public List<DataColumn> Columns { get; set; }
public List<T> SearchSpace { get; set; }
public string KeyProperty { get; set; }
public string DisplayProperty { get; set; }
}
public class LookupSelectionControl<T> : Box
{
[UI] private readonly ComboBox _searchBox = null;
[UI] private readonly TreeView _selectionTreeView = null;
[UI] private readonly Button _addButton = null;
private readonly BindableListControl<T> _selectedItemsListControl;
public string KeyProperty { get; }
public string DisplayProperty { get; }
public List<T> AllItems { get; }
public List<T> SelectedItems { get; set; }
public LookupSelectionControl(LookupSelectionControlOptions<T> options) :
this(options, new Builder("Controls.glade"))
{
}
public LookupSelectionControl(LookupSelectionControlOptions<T> options, Builder builder) :
base(builder.GetObject("_multiLookupControl").Handle)
{
builder.Autoconnect(this);
KeyProperty = options.KeyProperty;
DisplayProperty = options.DisplayProperty;
AllItems = options.SearchSpace;
SelectedItems = new List<T>();
var completionStore = new ListStore(GType.String, GType.String);
BindCompletionList(completionStore);
_searchBox.Model = completionStore;
_searchBox.IdColumn = 0;
_searchBox.EntryTextColumn = 1;
_searchBox.Entry.Completion = new EntryCompletion
{
Model = completionStore,
InlineSelection = true,
InlineCompletion = true,
TextColumn = 1,
PopupCompletion = true
};
// Init the selected items TreeView
_selectedItemsListControl = new BindableListControl<T>(options.Columns, KeyProperty, _selectionTreeView)
{
Items = SelectedItems
};
_selectionTreeView.CanFocus = false;
_selectedItemsListControl.RowContextMenuOpened += selection =>
{
var remove = new MenuItem {Label = "Remove"};
remove.Activated += (sender, eventArgs) =>
{
SelectedItems.Remove(_selectedItemsListControl.SelectedItem);
_selectedItemsListControl.DataBind();
};
var menu = new Menu {remove};
menu.ShowAll();
menu.Popup();
};
_addButton.Clicked += (sender, args) => AddSelectionToTreeView();
_searchBox.Entry.Activated += (sender, args) => AddSelectionToTreeView();
}
private void AddSelectionToTreeView()
{
// FInd item preferably via its ID, try matching the display value as fallback
var item = Guid.TryParse(_searchBox.ActiveId, out var selectedSeason)
? AllItems.FirstOrDefault(x => x.GetPropertyValue(KeyProperty).Equals(selectedSeason))
: AllItems.FirstOrDefault(y => y.GetPropertyValue(DisplayProperty).Equals(_searchBox.Entry.Text));
if (item == null) return;
if (!SelectedItems.Contains(item)) SelectedItems.Add(item);
_selectedItemsListControl.DataBind();
_searchBox.Entry.Text = "";
}
private void BindCompletionList(ListStore store)
{
foreach (var item in AllItems)
{
// value of key
var keyValue = item.GetType().GetProperty(KeyProperty)?.GetValue(item).ToString();
// value for display
var displayValue = item.GetType().GetProperty(DisplayProperty)?.GetValue(item);
store.AppendValues(keyValue, displayValue);
}
}
}
}