Browse Source

Extracted filtering functionality into DynamicGridFilterButtonComponent, meaning it's based on composition, not inheritance, which is better.
Added support for multiple selected filters.

Kenric Nugteren 1 year ago
parent
commit
1aaba9d7a1

+ 9 - 4
InABox.Core/Expressions.cs

@@ -11,6 +11,11 @@ namespace InABox.Core
 
         // Allows setting of object properties via cached expressions
 
+        public static Func<T, TValue> Getter<T, TValue>(Expression<Func<T, TValue>> expression)
+        {
+            return expression.Compile();
+        }
+
         public static Func<T, object> Getter<T>(string propname)
         {
             Func<T, object>? result = null;
@@ -145,7 +150,7 @@ namespace InABox.Core
             return false;
         }
 
-        public static Action<T, object> Setter<T>(string propname)
+        public static Action<T, object?> Setter<T>(string propname)
         {
             var param = Expression.Parameter(typeof(T), "o");
 
@@ -173,14 +178,14 @@ namespace InABox.Core
                 expression = Expression.Empty();
             }
 
-            var lambda = Expression.Lambda<Action<T, object>>(expression, param, valueParameter);
+            var lambda = Expression.Lambda<Action<T, object?>>(expression, param, valueParameter);
             var compiled = lambda.Compile();
             return compiled;
         }
 
-        public static Action<object, object> Setter(Type objectType, string propname, Type type = null)
+        public static Action<object, object> Setter(Type objectType, string propname, Type? type = null)
         {
-            Action<object, object> compiled = null;
+            Action<object, object>? compiled = null;
             try
             {
                 var objectParameter = Expression.Parameter(typeof(object), "o");

+ 10 - 2
inabox.wpf/DynamicGrid/BaseDynamicGrid.cs

@@ -163,9 +163,17 @@ namespace InABox.DynamicGrid
 
         public abstract void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);
 
-        public abstract Button AddButton(string caption, BitmapImage? image, string? tooltip, Func<Button, CoreRow[], bool> action,
+        public abstract Button AddButton(string? caption, BitmapImage? image, string? tooltip, Func<Button, CoreRow[], bool> action,
             DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
-        
+
+        public Button AddButton(string? caption, BitmapImage? image, Func<Button, CoreRow[], bool> action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
+        {
+            var result = AddButton(caption, image, null, action, position);
+            return result;
+        }
+
+        public abstract void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null);
+
         public virtual double RowHeight { get; set; }
         public virtual double HeaderHeight { get; set; }
         public new virtual double FontSize { get; set; }

+ 8 - 179
inabox.wpf/DynamicGrid/DynamicDataGrid.cs

@@ -29,29 +29,13 @@ namespace InABox.DynamicGrid
 
     public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new()
     {
-        public delegate bool FilterSelectedHandler(CoreFilterDefinition filter);
-
-        public event FilterSelectedHandler OnFilterSelected;
 
         public delegate void OnReloadEventHandler(object sender, Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sortby);
 
         private readonly int ChunkSize = 500;
-        private Button MergeBtn;
-        private Button FilterBtn;
-
-        private bool _showFilterList;
-        public bool ShowFilterList
-        {
-            get => _showFilterList;
-            set
-            {
-                _showFilterList = value;
-                if (FilterBtn != null) // FilterBtn can be null when ShowFilterList is set in DynamicGrid constructor
-                    FilterBtn.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
-            }
-        }
+        private Button MergeBtn = null!; //Late-initialised
 
-        protected Tuple<string, Filter<TEntity>>? SelectedFilter;
+        protected DynamicGridFilterButtonComponent<TEntity> FilterComponent;
 
         private Column<TEntity>[] FilterColumns;
 
@@ -90,16 +74,13 @@ namespace InABox.DynamicGrid
 
         protected override void Init()
         {
-            FilterBtn = AddButton("", Wpf.Resources.filter.AsBitmapImage(), DoFilter);
-            FilterBtn.Margin = new Thickness(0, 2, 7, 0);
-            FilterBtn.Padding = new Thickness(0);
+            FilterComponent = new(this, new GlobalConfiguration<CoreFilterDefinitions>(GetTag()));
+            FilterComponent.OnFilterRefresh += () => Refresh(false, true);
 
             MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge);
         }
         protected override void DoReconfigure(FluentList<DynamicGridOption> options)
         {
-            FilterBtn.Visibility = ShowFilterList ? Visibility.Visible : Visibility.Collapsed;
-
             options.BeginUpdate();
             if (Security.CanEdit<TEntity>())
                 options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
@@ -153,7 +134,8 @@ namespace InABox.DynamicGrid
             base.OptionsChanged();
             if (MergeBtn != null)
                 MergeBtn.Visibility = Visibility.Collapsed;
-            ShowFilterList = HasOption(DynamicGridOption.FilterRows);
+
+            FilterComponent.ShowFilterList = HasOption(DynamicGridOption.FilterRows);
         }
 
         protected override void SelectItems(CoreRow[]? rows)
@@ -163,64 +145,13 @@ namespace InABox.DynamicGrid
                 ? Visibility.Visible
                 : Visibility.Collapsed;
         }
-
-        /*private Filter<T>? CreateFilter<T>(Dictionary<string, object> criteria) where T : Entity
-        {
-            Filter<T>? filter = null;
-            foreach (var key in criteria.Keys)
-            {
-                var exp = CoreUtils.GetPropertyExpression<T>(key);
-                var flt = new Filter<T>(exp).IsEqualTo(criteria[key]);
-                if (filter != null)
-                    filter.Ands.Add(flt);
-                else
-                    filter = flt;
-            }
-
-            return filter;
-        }
-
-        private Columns<T> CreateColumns<T>(List<string> columnnames) where T : Entity
-        {
-            var expressions = new List<Expression<Func<T, object?>>>();
-            foreach (var columnname in columnnames)
-                try
-                {
-                    var expression = CoreUtils.GetPropertyExpression<T>(columnname);
-                    expressions.Add(expression);
-                }
-                catch (Exception e)
-                {
-                    Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-                }
-
-            if (!columnnames.Contains("ID"))
-                expressions.Add(CoreUtils.GetPropertyExpression<T>("ID"));
-
-            //if (!columnnames.Contains("Attributes"))
-            //    expressions.Add(CoreUtils.GetPropertyExpression<T>("Attributes"));
-
-            return new Columns<T>(expressions.ToArray());
-        }
-
-        private SortOrder<T>? CreateSortOrder<T>(string columnname) where T : Entity
-        {
-            if (string.IsNullOrWhiteSpace(columnname))
-                return null;
-
-            var expression = CoreUtils.GetPropertyExpression<T>(columnname);
-            return new SortOrder<T>(expression);
-        }*/
-
+        
         public event OnReloadEventHandler? OnReload;
 
         protected override void Reload(Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort,
             Action<CoreTable?, Exception?> action)
         {
-            //if (sort == null)
-            //    sort = new SortOrder<TEntity>(x => x.Sort);
-            if (SelectedFilter != null)
-                criteria.Add(SelectedFilter.Item2);
+            criteria.Add(FilterComponent.GetFilter());
 
             OnReload?.Invoke(this, criteria, columns, ref sort);
             new Client<TEntity>().Query(criteria.Combine(), columns, sort, action);
@@ -695,94 +626,6 @@ namespace InABox.DynamicGrid
             return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
         }
 
-        private bool DoFilter(Button button, CoreRow[] rows)
-        {
-            var menu = new ContextMenu();
-            var none = new MenuItem { Header = "Clear Filters", Tag = null };
-            none.Click += Filter_Click;
-            menu.Items.Add(none);
-
-            var tag = GetTag();
-            var filters = new GlobalConfiguration<CoreFilterDefinitions>(tag).Load();
-            foreach (var filter in filters)
-            {
-                var item = new MenuItem { Header = filter.Name, Tag = filter };
-                if (SelectedFilter?.Item1 == filter.Name)
-                {
-                    item.IsChecked = true;
-                }
-                item.Click += Filter_Click;
-                menu.Items.Add(item);
-            }
-
-            if (Security.IsAllowed<CanCustomiseFilters>())
-            {
-                menu.Items.Add(new Separator());
-                var manage = new MenuItem { Header = "Manage Filters" };
-                manage.Click += (s, e) =>
-                {
-                    var window = new DynamicGridFilterEditor(filters, typeof(TEntity));
-                    if (window.ShowDialog() == true)
-                    {
-                        new GlobalConfiguration<CoreFilterDefinitions>(tag).Save(filters);
-                    }
-                };
-                menu.Items.Add(manage);
-            }
-
-            menu.IsOpen = true;
-
-            return false;
-        }
-
-        protected virtual void FilterSelected(CoreFilterDefinition filter)
-        {
-            
-        }
-        
-        private void DoFilterSelected(CoreFilterDefinition filter)
-        {
-            FilterSelected(filter);
-            OnFilterSelected?.Invoke(filter);
-        }
-
-        public void SelectFilter(CoreFilterDefinition? filter, bool refresh)
-        {
-            SelectedFilter = filter != null
-                ? new(filter.Name, Serialization.Deserialize<Filter<TEntity>>(filter.Filter))
-                : null;
-            Bitmap image = SelectedFilter != null
-                ? Wpf.Resources.filter_set
-                : Wpf.Resources.filter;
-            String text = SelectedFilter?.Item1 ?? "";
-            UpdateButton(FilterBtn, image.AsBitmapImage(), text);
-            if (refresh)
-                Refresh(false, true);
-        }
-
-        private void Filter_Click(object sender, RoutedEventArgs e)
-        {
-            var tag = (sender as MenuItem)?.Tag;
-            string text = "";
-            Bitmap image;
-            if (tag is CoreFilterDefinition filter)
-            {
-                SelectedFilter = new(filter.Name, Serialization.Deserialize<Filter<TEntity>>(filter.Filter));
-                image = Wpf.Resources.filter_set;
-                text = filter.Name;
-                DoFilterSelected(filter);
-            }
-            else
-            {
-                SelectedFilter = null;
-                image = Wpf.Resources.filter;
-                text = "";
-                DoFilterSelected(null);
-            }
-            UpdateButton(FilterBtn, image.AsBitmapImage(), text);
-            Refresh(false, true);
-        }
-
         private bool DoMerge(Button arg1, CoreRow[] arg2)
         {
             if (arg2 == null || arg2.Length <= 1)
@@ -973,20 +816,6 @@ namespace InABox.DynamicGrid
         {
             return new Client<TEntity>().Query(null, new Columns<TEntity>(fields));
         }
-
-        public void UpdateFilterButton(Bitmap image, string text = "")
-        {
-            UpdateButton(FilterBtn, image.AsBitmapImage(), text);
-        }
-
-        public void UpdateFilterButton(string text)
-        {
-            UpdateFilterButton(String.IsNullOrWhiteSpace(text) 
-                ? Wpf.Resources.filter_set
-                : Wpf.Resources.filter,
-                text
-            );
-        }
         
     }
 }

+ 1 - 7
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -3300,7 +3300,7 @@ namespace InABox.DynamicGrid
             return button;
         }
 
-        protected void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null)
+        public override void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null)
         {
             var stackPnl = new StackPanel();
             stackPnl.Orientation = Orientation.Horizontal;
@@ -3368,12 +3368,6 @@ namespace InABox.DynamicGrid
             return button;
         }
 
-        public Button AddButton(string? caption, BitmapImage? image, Func<Button, CoreRow[], bool> action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
-        {
-            var result = AddButton(caption, image, null, action, position);
-            return result;
-        }
-
         private void Button_Click(object sender, RoutedEventArgs e)
         {
             var button = (Button)sender;

+ 325 - 0
inabox.wpf/DynamicGrid/DynamicGridFilterButtonComponent.cs

@@ -0,0 +1,325 @@
+using InABox.Configuration;
+using InABox.Core;
+using InABox.WPF;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace InABox.DynamicGrid;
+
+public class DynamicGridSelectedFilterSettings
+{
+    public List<CoreFilterDefinition> SelectedFilters { get; set; }
+
+    public bool MultipleFilters { get; set; }
+
+    public DynamicGridSelectedFilterSettings()
+    {
+        SelectedFilters = new List<CoreFilterDefinition>();
+        MultipleFilters = false;
+    }
+
+    public DynamicGridSelectedFilterSettings(List<CoreFilterDefinition> selectedFilters, bool multipleFilters)
+    {
+        SelectedFilters = selectedFilters;
+        MultipleFilters = multipleFilters || selectedFilters.Count > 1;
+    }
+}
+
+public class DynamicGridFilterButtonComponent<T>
+    where T : BaseObject, new()
+{
+
+    private Button FilterBtn = null!; // Late-initialised
+
+    private bool _multipleFilters = false;
+
+    private bool MultipleFilters
+    {
+        get => _multipleFilters;
+        set
+        {
+            if (_multipleFilters != value)
+            {
+                _multipleFilters = value;
+                if (!_multipleFilters && SelectedFilters.Count > 1)
+                {
+                    ClearFilters(true);
+                }
+            }
+        }
+    }
+
+    private bool _showFilterList;
+    public bool ShowFilterList
+    {
+        get => _showFilterList;
+        set
+        {
+            _showFilterList = value;
+            if (FilterBtn != null)
+                FilterBtn.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
+        }
+    }
+
+    private string? _buttonText;
+    public string? ButtonText
+    {
+        get => _buttonText;
+        set
+        {
+            if (!string.Equals(_buttonText, value))
+            {
+                _buttonText = value;
+                UpdateButton();
+            }
+        }
+    }
+
+    private class FilterItem
+    {
+        public string Name { get; set; }
+
+        public Filter<T> Filter { get; }
+
+        public CoreFilterDefinition Definition { get; set; }
+
+        public FilterItem(string name, Filter<T> filter, CoreFilterDefinition definition)
+        {
+            Name = name;
+            Filter = filter;
+            Definition = definition;
+        }
+    }
+
+    private List<FilterItem> SelectedFilters = new();
+
+    private IDynamicGrid Grid;
+
+    private IConfiguration<CoreFilterDefinitions> Configuration;
+
+    public delegate void FilterSelectedHandler(DynamicGridSelectedFilterSettings filters);
+
+    public event FilterSelectedHandler OnFiltersSelected;
+
+    public delegate void FilterRefreshHandler();
+
+    public event FilterRefreshHandler OnFilterRefresh;
+
+    public DynamicGridFilterButtonComponent(IDynamicGrid grid, IConfiguration<CoreFilterDefinitions> configuration)
+    {
+        Grid = grid;
+        Configuration = configuration;
+
+        FilterBtn = Grid.AddButton("", Wpf.Resources.filter.AsBitmapImage(), DoFilter);
+        FilterBtn.Margin = new Thickness(0, 2, 7, 0);
+        FilterBtn.Padding = new Thickness(0);
+    }
+
+    public void SetSettings(DynamicGridSelectedFilterSettings settings, bool refresh)
+    {
+        _multipleFilters = settings.MultipleFilters;
+        SelectFilters(settings.SelectedFilters, refresh);
+    }
+
+    #region Menu
+
+    private ContextMenu? Menu;
+
+    private void RebuildMenu(ContextMenu menu)
+    {
+        menu.Items.Clear();
+        menu.AddItem("Clear Filters", null, ClearFilter_Click);
+        var multipleFilters = menu.AddCheckItem("Use Multiple Filters", MultipleFilters_Click, MultipleFilters);
+        multipleFilters.StaysOpenOnClick = true;
+
+        var filters = Configuration.Load();
+        foreach (var filter in filters)
+        {
+            var item = menu.AddCheckItem(filter.Name, filter, Filter_Toggle);
+            item.StaysOpenOnClick = true;
+            if (SelectedFilters.Any(x => x.Name.Equals(filter.Name)))
+            {
+                item.IsChecked = true;
+            }
+        }
+
+        if (Security.IsAllowed<CanCustomiseFilters>())
+        {
+            menu.AddSeparator();
+            menu.AddItem("Manage Filters", null, () =>
+            {
+                var window = new DynamicGridFilterEditor(filters, typeof(T));
+                if (window.ShowDialog() == true)
+                {
+                    Configuration.Save(filters);
+                }
+            });
+        }
+    }
+
+    private bool DoFilter(Button button, CoreRow[] rows)
+    {
+        Menu = new ContextMenu();
+
+        RebuildMenu(Menu);
+
+        Menu.IsOpen = true;
+        Menu.Closed += Menu_Closed;
+
+        return false;
+    }
+
+    private void Menu_Closed(object sender, RoutedEventArgs e)
+    {
+        if (Menu == sender)
+        {
+            Menu = null;
+        }
+    }
+
+    private void ClearFilter_Click()
+    {
+        ClearFilters(true);
+    }
+
+    private void MultipleFilters_Click(bool isChecked)
+    {
+        MultipleFilters = isChecked;
+        if (Menu is not null)
+        {
+            RebuildMenu(Menu);
+        }
+        SaveSettings();
+    }
+
+    private void Filter_Toggle(CoreFilterDefinition tag, bool isChecked)
+    {
+        if (MultipleFilters)
+        {
+            if (isChecked)
+            {
+                SelectFilter(tag, true, true);
+            }
+            else
+            {
+                DeselectFilter(tag, true);
+            }
+        }
+        else
+        {
+            SelectFilter(tag, false, true);
+            if (Menu is not null)
+            {
+                Menu.IsOpen = false;
+            }
+        }
+    }
+
+    #endregion
+
+    public void ClearFilters(bool refresh)
+    {
+        if (SelectedFilters.Count > 0)
+        {
+            SelectedFilters.Clear();
+            UpdateFilters(refresh);
+        }
+    }
+
+    public void DeselectFilter(CoreFilterDefinition filter, bool refresh)
+    {
+        var removed = SelectedFilters.RemoveAll(x => x.Name.Equals(filter.Name));
+        if (removed > 0)
+        {
+            UpdateFilters(refresh);
+        }
+    }
+
+    /// <summary>
+    /// Select a new filter. If <paramref name="additional"/> is <see langword="true"/>, adds it to the list of selected filters;
+    /// otherwise, it sets this as the only filter.
+    /// </summary>
+    /// <param name="filter"></param>
+    /// <param name="additional"></param>
+    /// <param name="refresh"></param>
+    public void SelectFilter(CoreFilterDefinition? filter, bool additional, bool refresh)
+    {
+        var originalCount = SelectedFilters.Count;
+        var alreadyHas = filter is null || SelectedFilters.Any(x => x.Name.Equals(filter.Name));
+
+        if (!additional)
+        {
+            SelectedFilters.Clear();
+        }
+        if (filter is not null && !SelectedFilters.Any(x => x.Name.Equals(filter.Name)))
+        {
+            SelectedFilters.Add(new(filter.Name, Serialization.Deserialize<Filter<T>>(filter.Filter), filter));
+        }
+
+        if (SelectedFilters.Count != originalCount || !alreadyHas)
+        {
+            UpdateFilters(refresh);
+        }
+    }
+
+    public void SelectFilters(IEnumerable<CoreFilterDefinition> filters, bool refresh)
+    {
+        SelectedFilters.Clear();
+        foreach (var filter in filters)
+        {
+            SelectedFilters.Add(new(filter.Name, Serialization.Deserialize<Filter<T>>(filter.Filter), filter));
+        }
+        if (SelectedFilters.Count > 1 && !MultipleFilters)
+        {
+            MultipleFilters = true;
+        }
+        UpdateFilters(refresh);
+    }
+
+    private void UpdateFilters(bool refresh)
+    {
+        UpdateButton();
+        SaveSettings();
+        if (refresh)
+        {
+            OnFilterRefresh?.Invoke();
+        }
+    }
+
+    private void UpdateButton()
+    {
+        var image = SelectedFilters.Count > 0
+            ? Wpf.Resources.filter_set
+            : Wpf.Resources.filter;
+        var text = string.Join(", ", SelectedFilters.Select(x => x.Name));
+        if (!ButtonText.IsNullOrWhiteSpace())
+        {
+            if (text.IsNullOrWhiteSpace())
+            {
+                text = ButtonText;
+            }
+            else
+            {
+                text = $"{ButtonText}: {text}";
+            }
+        }
+        Grid.UpdateButton(FilterBtn, image.AsBitmapImage(), text);
+    }
+
+    private void SaveSettings()
+    {
+        OnFiltersSelected?.Invoke(new DynamicGridSelectedFilterSettings(SelectedFilters.Select(x => x.Definition).ToList(), MultipleFilters));
+    }
+
+    public Filter<T>? GetFilter()
+    {
+        var filters = new Filters<T>();
+        filters.AddRange(SelectedFilters.Select(x => x.Filter));
+        return filters.Combine();
+    }
+}

+ 5 - 1
inabox.wpf/DynamicGrid/IDynamicGrid.cs

@@ -66,7 +66,11 @@ namespace InABox.DynamicGrid
 
         void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);
 
-        Button AddButton(string caption, BitmapImage? image, string? tooltip, Func<Button, CoreRow[], bool> action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
+        Button AddButton(string? caption, BitmapImage? image, string? tooltip, Func<Button, CoreRow[], bool> action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
+
+        Button AddButton(string? caption, BitmapImage? image, Func<Button, CoreRow[], bool> action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
+
+        void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null);
 
         event OnDefineFilter OnDefineFilter;
 

+ 19 - 2
inabox.wpf/WPFUtils.cs

@@ -163,8 +163,23 @@ namespace InABox.WPF
             return item;
         }
 
+        public delegate void CheckToggleAction(bool isChecked);
         public delegate void CheckToggleAction<T>(T tag, bool isChecked);
 
+        private static MenuItem DoAddCheckItem(ItemsControl menu, string caption, CheckToggleAction click, bool isChecked, bool enabled, int index = -1)
+        {
+            var item = new MenuItem { Header = caption, IsEnabled = enabled, IsCheckable = true, IsChecked = isChecked };
+
+            item.Click += (o, e) =>
+            {
+                click(item.IsChecked);
+            };
+
+            ItemsControlInsert(menu, item, index);
+            return item;
+        }
+
+
         private static MenuItem DoAddCheckItem<T>(ItemsControl menu, string caption, T tag, CheckToggleAction<T> click, bool isChecked, bool enabled, int index = -1)
         {
             var item = new MenuItem { Header = caption, IsEnabled = enabled, IsCheckable = true, IsChecked = isChecked };
@@ -187,7 +202,7 @@ namespace InABox.WPF
 
             return separator;
         }
-        private static Separator DoAddSeparatorIfNeeded(ItemsControl menu, int index)
+        private static Separator? DoAddSeparatorIfNeeded(ItemsControl menu, int index)
         {
             if (menu.Items.Count == 0) return null;
 
@@ -231,8 +246,10 @@ namespace InABox.WPF
             => DoAddMenuItem(menu, caption, image, tag, click, enabled, index);
         public static MenuItem AddItem<T>(this MenuItem menu, string caption, Bitmap? image, T tag, Action<T> click, bool enabled = true, int index = -1)
             => DoAddMenuItem(menu, caption, image, tag, click, enabled, index);
+        public static MenuItem AddCheckItem(this ContextMenu menu, string caption, CheckToggleAction click, bool isChecked = false, bool enabled = true, int index = -1)
+            => DoAddCheckItem(menu, caption, click, isChecked, enabled, index);
         public static MenuItem AddCheckItem<T>(this ContextMenu menu, string caption, T tag, CheckToggleAction<T> click, bool isChecked = false, bool enabled = true, int index = -1)
-            => DoAddCheckItem<T>(menu, caption, tag, click, isChecked, enabled, index);
+            => DoAddCheckItem(menu, caption, tag, click, isChecked, enabled, index);
 
         #endregion
     }