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;