From 27f1eb7b8a5db1faed753703bcb912821e0eee80 Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Thu, 29 Jun 2017 15:01:35 +0200
Subject: [PATCH] Implemented basic filtering for combo box items
---
.../AcaciaZPushPlugin.csproj | 1 +
.../Controls/KAbstractComboBox.cs | 30 ++++-
.../AcaciaZPushPlugin/Controls/KComboBox.cs | 117 +++++++++++++++---
.../AcaciaZPushPlugin/Controls/KDataSource.cs | 79 ++++++++++++
.../AcaciaZPushPlugin/UI/GABLookupControl.cs | 2 +-
5 files changed, 203 insertions(+), 26 deletions(-)
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KDataSource.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index d0e5f88..e95a424 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -229,6 +229,7 @@
Component
+
UserControl
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs
index 098c547..96a95d0 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs
@@ -175,10 +175,12 @@ namespace Acacia.Controls
public Control Control
{
- get
- {
- return ((ToolStripControlHost)Items[0]).Control;
- }
+ get { return ControlHost.Control; }
+ }
+
+ public ToolStripControlHost ControlHost
+ {
+ get { return (ToolStripControlHost)Items[0]; }
}
public DropDown(KAbstractComboBox owner, Control control)
@@ -303,6 +305,17 @@ namespace Acacia.Controls
private void ShowDropDown()
{
+ UpdateDropDownLayout();
+
+ // Show the drop down below the current control
+ _dropDown.Show(this.PointToScreen(new Point(0, Height - 1)));
+ }
+
+ protected void UpdateDropDownLayout()
+ {
+ if (_dropDown == null)
+ return;
+
// Calculate the dimensions of the drop-down
int maxHeight = GetDropDownHeightMax();
int minHeight = GetDropDownHeightMin();
@@ -313,8 +326,13 @@ namespace Acacia.Controls
DropControl.MaximumSize = DropControl.MinimumSize = new Size(width, height);
- // Show the drop down below the current control
- _dropDown.Show(this.PointToScreen(new Point(0, Height - 1)));
+ _dropDown.Control.Bounds = _dropDown.ControlHost.Bounds;
+ System.Diagnostics.Trace.WriteLine(string.Format(
+ "Layout: {0}, host: {1}, control: {2}",
+ height,
+ _dropDown.ControlHost.Bounds,
+ _dropDown.Control.Bounds
+ ));
}
protected abstract int GetDropDownHeightMax();
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KComboBox.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KComboBox.cs
index 38146fe..3f14873 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KComboBox.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KComboBox.cs
@@ -45,13 +45,17 @@ namespace Acacia.Controls
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
- SelectedIndex = _committedIndex;
+ ResetSelectedIndex();
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
- SelectedIndex = _committedIndex;
+ }
+
+ private void ResetSelectedIndex()
+ {
+ SelectedIndex = _committedIndex >= Items.Count ? -1 : _committedIndex;
}
protected override void OnMouseDown(MouseEventArgs e)
@@ -90,12 +94,18 @@ namespace Acacia.Controls
{
// Don't notify until committed
}
+
+ public void ItemsChanged(int selectIndex)
+ {
+ _committedIndex = SelectedIndex = selectIndex;
+
+ }
}
- #endregion
-
private readonly DropList _list;
+ #endregion
+
#region Items properties
[DefaultValue(true)]
@@ -108,13 +118,6 @@ namespace Acacia.Controls
[Category("Behavior")]
public int ItemHeight { get { return _list.ItemHeight; } set { _list.ItemHeight = value; } }
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [Editor("System.Windows.Forms.Design.ListControlStringCollectionEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
- [Localizable(true)]
- [MergableProperty(false)]
- [Category("Behavior")]
- public ListBox.ObjectCollection Items { get { return _list.Items; } }
-
[DefaultValue(8)]
[Localizable(true)]
[Category("Behavior")]
@@ -122,6 +125,8 @@ namespace Acacia.Controls
#endregion
+ private DisplayItem _selectedItem;
+
public KComboBox()
{
MaxDropDownItems = 8;
@@ -136,11 +141,13 @@ namespace Acacia.Controls
{
if (_list.SelectedIndex >= 0)
{
- Text = _list.SelectedItem.ToString();
+ _selectedItem = (DisplayItem)_list.SelectedItem;
+ Text = _selectedItem.ToString();
}
else
{
Text = "";
+ _selectedItem = null;
}
}
@@ -154,24 +161,96 @@ namespace Acacia.Controls
_list.EndUpdate();
}
- public object DataSource
+ ///
+ /// Wrapper for list items to use custom string formatting
+ ///
+ private class DisplayItem
{
- get
+ private readonly KComboBox _owner;
+ private readonly object _item;
+
+ public DisplayItem(KComboBox owner, object item)
{
- return _list.DataSource;
+ this._owner = owner;
+ this._item = item;
}
+ public override string ToString()
+ {
+ return _owner.DataSource.GetItemText(_item);
+ }
+
+ public override bool Equals(object obj)
+ {
+ bool result = obj is DisplayItem && ((DisplayItem)obj)._item == _item;
+ return result;
+ }
+
+ public override int GetHashCode()
+ {
+ return ToString().GetHashCode();
+ }
+ }
+
+ private KDataSourceRaw _dataSource;
+ public KDataSourceRaw DataSource
+ {
+ get { return _dataSource; }
set
{
- _list.BindingContext = new BindingContext();
- _list.DataSource = value;
- _list.SelectedIndex = -1;
+ if (_dataSource != value)
+ {
+ _dataSource = value;
+ UpdateItems();
+ }
+ }
+ }
+
+ private void UpdateItems()
+ {
+ int oldCount = _list.Items.Count;
+ _list.BeginUpdate();
+ try
+ {
+ _list.Items.Clear();
+ int selected = -1;
+ foreach (object item in _dataSource.FilteredItems)
+ {
+ DisplayItem displayItem = new DisplayItem(this, item);
+ if (displayItem.Equals(_selectedItem))
+ selected = _list.Items.Count;
+ _list.Items.Add(displayItem);
+ }
+ System.Diagnostics.Trace.WriteLine(string.Format("FILTER: {0}", _list.Items.Count, selected));
+
+ // Select the current item only if new number of items is smaller. This means we don't keep selection
+ // when the user is removing text, only when they are typing more.
+ _list.ItemsChanged(_list.Items.Count < oldCount ? selected : -1);
+ }
+ finally
+ {
+ _list.EndUpdate();
+ }
+ UpdateDropDownLayout();
+ }
+
+ protected override void OnTextChanged(EventArgs e)
+ {
+ base.OnTextChanged(e);
+
+ // Update the filter
+ if (DataSource != null)
+ {
+ DataSource.Filter = new KDataFilter(Text);
+ UpdateItems();
+
+ DroppedDown = true;
}
}
protected override int GetDropDownHeightMax()
{
- return Util.Bound(Items.Count, 1, MaxDropDownItems) * ItemHeight + _list.Margin.Vertical;
+ return Util.Bound(_list.Items.Count, 1, MaxDropDownItems) * ItemHeight + _list.Margin.Vertical;
}
protected override int GetDropDownHeightMin()
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KDataSource.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KDataSource.cs
new file mode 100644
index 0000000..779738d
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KDataSource.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Controls
+{
+ public class KDataFilter
+ {
+ public readonly string FilterText;
+
+ public KDataFilter(string filterText)
+ {
+ this.FilterText = filterText;
+ }
+ }
+
+ public interface KDataSourceRaw
+ {
+ System.Collections.IEnumerable Items { get; }
+ System.Collections.IEnumerable FilteredItems { get; }
+ KDataFilter Filter { get; set; }
+ string GetItemText(object item);
+ }
+
+ abstract public class KDataSource : KDataSourceRaw
+ {
+ ///
+ /// Returns all the items
+ ///
+ abstract public IEnumerable Items
+ {
+ get;
+ }
+
+ public IEnumerable FilteredItems
+ {
+ get
+ {
+ if (string.IsNullOrWhiteSpace(Filter?.FilterText))
+ return Items;
+
+ return ApplyFilter();
+ }
+ }
+
+ private IEnumerable ApplyFilter()
+ {
+ foreach (T item in Items)
+ {
+ if (MatchesFilter(item))
+ yield return item;
+ }
+ }
+
+ virtual protected bool MatchesFilter(T item)
+ {
+ return GetItemText(item).StartsWith(Filter.FilterText);
+ }
+
+ abstract protected string GetItemText(T item);
+
+ public string GetItemText(object item)
+ {
+ return GetItemText((T)item);
+ }
+
+ public KDataFilter Filter
+ {
+ get;
+ set;
+ }
+
+ IEnumerable KDataSourceRaw.Items { get{return Items;}}
+ IEnumerable KDataSourceRaw.FilteredItems { get { return FilteredItems; } }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/GABLookupControl.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/GABLookupControl.cs
index 0821ad2..6e2fbca 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/GABLookupControl.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/GABLookupControl.cs
@@ -216,7 +216,7 @@ namespace Acacia.UI
// Setting the datasource will trigger a select if there is a match
BeginUpdate();
- DataSource = users;
+ //DataSource = users;
//SetItemsCore(users);
if (dropDown && text.Length != 0)
DroppedDown = true;