|
@@ -0,0 +1,1705 @@
|
|
|
+using InABox.Core;
|
|
|
+using InABox.Wpf;
|
|
|
+using InABox.WPF;
|
|
|
+using Syncfusion.Data;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.ComponentModel;
|
|
|
+using System.Diagnostics;
|
|
|
+using System.Diagnostics.CodeAnalysis;
|
|
|
+using System.Linq;
|
|
|
+using System.Linq.Expressions;
|
|
|
+using System.Text;
|
|
|
+using System.Threading;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using System.Windows;
|
|
|
+using System.Windows.Controls;
|
|
|
+using System.Windows.Input;
|
|
|
+using System.Windows.Media;
|
|
|
+using System.Windows.Media.Animation;
|
|
|
+using System.Windows.Media.Imaging;
|
|
|
+using Color = System.Drawing.Color;
|
|
|
+
|
|
|
+namespace InABox.DynamicGrid;
|
|
|
+
|
|
|
+public abstract class BaseDynamicGrid : ContentControl, IDynamicGridUIComponentParent
|
|
|
+{
|
|
|
+ public static readonly DependencyProperty UseWaitCursorProperty =
|
|
|
+ DependencyProperty.Register(nameof(UseWaitCursor), typeof(bool), typeof(DynamicGrid<>));
|
|
|
+
|
|
|
+ public bool UseWaitCursor
|
|
|
+ {
|
|
|
+ get => (bool)GetValue(UseWaitCursorProperty);
|
|
|
+ set => SetValue(UseWaitCursorProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected enum ClipAction
|
|
|
+ {
|
|
|
+ Cut,
|
|
|
+ Copy
|
|
|
+ }
|
|
|
+
|
|
|
+ private IDynamicGridUIComponent UIComponent;
|
|
|
+
|
|
|
+ private UIElement? _header;
|
|
|
+ private readonly Button Add;
|
|
|
+
|
|
|
+ public bool bRefreshing;
|
|
|
+ bool IDynamicGridUIComponentParent.IsRefreshing => bRefreshing;
|
|
|
+
|
|
|
+ private readonly Label ClipboardSpacer;
|
|
|
+ private readonly Button Copy;
|
|
|
+
|
|
|
+ private readonly Label Count;
|
|
|
+
|
|
|
+ private readonly Border Disabler;
|
|
|
+
|
|
|
+ private readonly DynamicActionColumn? drag;
|
|
|
+
|
|
|
+ private readonly Button Delete;
|
|
|
+ private readonly DockPanel Docker;
|
|
|
+ private readonly Button Edit;
|
|
|
+ private readonly Label EditSpacer;
|
|
|
+ private readonly Button? ExportButton;
|
|
|
+ private readonly Label ExportSpacer;
|
|
|
+ private readonly Button? DuplicateBtn;
|
|
|
+ private readonly Button SwitchViewBtn;
|
|
|
+
|
|
|
+ private readonly Button? Help;
|
|
|
+ private readonly Button? ImportButton;
|
|
|
+
|
|
|
+ private readonly Grid Layout;
|
|
|
+ private readonly Label Loading;
|
|
|
+
|
|
|
+ private readonly DoubleAnimation LoadingFader = new(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true };
|
|
|
+
|
|
|
+ private readonly Button Print;
|
|
|
+ private readonly Label PrintSpacer;
|
|
|
+
|
|
|
+ private readonly StackPanel LeftButtonStack;
|
|
|
+ private readonly StackPanel RightButtonStack;
|
|
|
+
|
|
|
+ protected DynamicGridRowStyleSelector RowStyleSelector;
|
|
|
+
|
|
|
+ protected virtual bool CanDuplicate { get; } = false;
|
|
|
+
|
|
|
+ #region Events
|
|
|
+
|
|
|
+ private event IDynamicGrid.ReconfigureEvent? _onReconfigure;
|
|
|
+ public event IDynamicGrid.ReconfigureEvent? OnReconfigure
|
|
|
+ {
|
|
|
+ add
|
|
|
+ {
|
|
|
+ _onReconfigure += value;
|
|
|
+ Reconfigure();
|
|
|
+ }
|
|
|
+ remove
|
|
|
+ {
|
|
|
+ _onReconfigure -= value;
|
|
|
+ Reconfigure();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public OnGetDynamicGridRowStyle? OnGetRowStyle { get; set; }
|
|
|
+
|
|
|
+ public event OnPrintData? OnPrintData;
|
|
|
+
|
|
|
+ public event BeforeRefreshEventHandler? BeforeRefresh;
|
|
|
+ public event AfterRefreshEventHandler? AfterRefresh;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called when an item is selected in the grid. It is not called if <see cref="IsReady"/> is not <see langword="true"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// It is unnecessary to use this if within a grid. Instead, override <see cref="SelectItems(CoreRow[]?)"/>.
|
|
|
+ /// </remarks>
|
|
|
+ public event SelectItemHandler? OnSelectItem;
|
|
|
+
|
|
|
+ public event OnCellDoubleClick? OnCellDoubleClick;
|
|
|
+
|
|
|
+ public event EventHandler? OnChanged;
|
|
|
+
|
|
|
+ public delegate void BeforeSelectionEvent(CancelEventArgs cancel);
|
|
|
+ public event BeforeSelectionEvent? OnBeforeSelection;
|
|
|
+
|
|
|
+ protected virtual void Changed()
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ public virtual void DoChanged()
|
|
|
+ {
|
|
|
+ Changed();
|
|
|
+ OnChanged?.Invoke(this, EventArgs.Empty);
|
|
|
+ }
|
|
|
+
|
|
|
+ public event OnFilterRecord? OnFilterRecord;
|
|
|
+
|
|
|
+ public event OnDoubleClick? OnDoubleClick;
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ protected DynamicGridSettings Settings { get; set; }
|
|
|
+
|
|
|
+ public BaseDynamicGrid() : base()
|
|
|
+ {
|
|
|
+ UseWaitCursor = true;
|
|
|
+
|
|
|
+ Options = new DynamicGridOptions();
|
|
|
+ Options.OnChanged += () =>
|
|
|
+ {
|
|
|
+ _hasLoadedOptions = true;
|
|
|
+ OptionsChanged();
|
|
|
+ };
|
|
|
+
|
|
|
+ ActionColumns = new DynamicActionColumns();
|
|
|
+ ColumnGroupings = new DynamicGridColumnGroupings();
|
|
|
+
|
|
|
+ RowStyleSelector = GetRowStyleSelector();
|
|
|
+ RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);
|
|
|
+
|
|
|
+ IsReady = false;
|
|
|
+
|
|
|
+ Data = new CoreTable();
|
|
|
+
|
|
|
+ drag = new DynamicImageColumn(InABox.Wpf.Resources.drag.AsBitmapImage()) { Position = DynamicActionColumnPosition.Start };
|
|
|
+
|
|
|
+ VisibleColumns = new DynamicGridColumns();
|
|
|
+
|
|
|
+ PreInit();
|
|
|
+
|
|
|
+ UIComponent = CreateUIComponent();
|
|
|
+
|
|
|
+ Loading = new Label();
|
|
|
+ Loading.Content = "Loading...";
|
|
|
+ Loading.Foreground = new SolidColorBrush(Colors.White);
|
|
|
+ Loading.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ Loading.HorizontalContentAlignment = HorizontalAlignment.Center;
|
|
|
+ Loading.Visibility = Visibility.Collapsed;
|
|
|
+ Loading.SetValue(Panel.ZIndexProperty, 999);
|
|
|
+ Loading.SetValue(Grid.RowProperty, 1);
|
|
|
+ Loading.FontSize = 14.0F;
|
|
|
+ LoadingFader.Completed += (sender, args) =>
|
|
|
+ {
|
|
|
+ if (Loading.Visibility == Visibility.Visible)
|
|
|
+ {
|
|
|
+ //Logger.Send(LogType.Information, this.GetType().EntityName().Split(".").Last(), "Loading Fader Restarting");
|
|
|
+ Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if(this is IHelpDynamicGrid helpGrid)
|
|
|
+ {
|
|
|
+ Help = CreateButton(Wpf.Resources.help.AsBitmapImage(Color.White));
|
|
|
+ Help.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ Help.SetValue(DockPanel.DockProperty, Dock.Right);
|
|
|
+ Help.Click += (o, e) => ShowHelp(helpGrid.HelpSlug());
|
|
|
+ }
|
|
|
+
|
|
|
+ Add = CreateButton(Wpf.Resources.add.AsBitmapImage(Color.White));
|
|
|
+ Add.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ Add.Click += Add_Click;
|
|
|
+
|
|
|
+ Edit = CreateButton(Wpf.Resources.pencil.AsBitmapImage(Color.White));
|
|
|
+ Edit.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ Edit.Click += Edit_Click;
|
|
|
+
|
|
|
+ SwitchViewBtn = CreateButton(Wpf.Resources.alter.AsBitmapImage());
|
|
|
+ SwitchViewBtn.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ SwitchViewBtn.Click += SwitchView_Click;
|
|
|
+
|
|
|
+ EditSpacer = new Label { Width = 5 };
|
|
|
+
|
|
|
+ Print = CreateButton(Wpf.Resources.print.AsBitmapImage(Color.White));
|
|
|
+ Print.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ Print.Click += (o, e) => DoPrint(o);
|
|
|
+
|
|
|
+ PrintSpacer = new Label { Width = 5 };
|
|
|
+
|
|
|
+ Copy = CreateButton(Wpf.Resources.duplicate.AsBitmapImage(Color.White), tooltip: "Duplicate Rows");
|
|
|
+ Copy.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ Copy.Click += Copy_Click;
|
|
|
+
|
|
|
+ ClipboardSpacer = new Label { Width = 5 };
|
|
|
+
|
|
|
+ if(this is IExportDynamicGrid)
|
|
|
+ {
|
|
|
+ ExportButton = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Export");
|
|
|
+ ExportButton.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ ExportButton.Click += ExportButtonClick;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(this is IImportDynamicGrid)
|
|
|
+ {
|
|
|
+ ImportButton = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Import");
|
|
|
+ ImportButton.Margin = new Thickness(0, 2, 2, 0);
|
|
|
+ ImportButton.Click += ImportButton_Click;
|
|
|
+ }
|
|
|
+
|
|
|
+ ExportSpacer = new Label { Width = 5 };
|
|
|
+
|
|
|
+
|
|
|
+ LeftButtonStack = new StackPanel();
|
|
|
+ LeftButtonStack.Orientation = Orientation.Horizontal;
|
|
|
+ LeftButtonStack.SetValue(DockPanel.DockProperty, Dock.Left);
|
|
|
+
|
|
|
+ if(Help is not null)
|
|
|
+ {
|
|
|
+ LeftButtonStack.Children.Add(Help);
|
|
|
+ }
|
|
|
+ LeftButtonStack.Children.Add(Add);
|
|
|
+ LeftButtonStack.Children.Add(Edit);
|
|
|
+ LeftButtonStack.Children.Add(SwitchViewBtn);
|
|
|
+ //Stack.Children.Add(MultiEdit);
|
|
|
+ LeftButtonStack.Children.Add(EditSpacer);
|
|
|
+
|
|
|
+ LeftButtonStack.Children.Add(Print);
|
|
|
+ LeftButtonStack.Children.Add(PrintSpacer);
|
|
|
+
|
|
|
+ LeftButtonStack.Children.Add(Copy);
|
|
|
+ LeftButtonStack.Children.Add(ClipboardSpacer);
|
|
|
+
|
|
|
+ if(ExportButton is not null)
|
|
|
+ {
|
|
|
+ LeftButtonStack.Children.Add(ExportButton);
|
|
|
+ }
|
|
|
+ if(ImportButton is not null)
|
|
|
+ {
|
|
|
+ LeftButtonStack.Children.Add(ImportButton);
|
|
|
+ }
|
|
|
+ if(ExportButton is not null || ImportButton is not null)
|
|
|
+ {
|
|
|
+ LeftButtonStack.Children.Add(ExportSpacer);
|
|
|
+ }
|
|
|
+
|
|
|
+ RightButtonStack = new StackPanel();
|
|
|
+ RightButtonStack.Orientation = Orientation.Horizontal;
|
|
|
+ RightButtonStack.SetValue(DockPanel.DockProperty, Dock.Right);
|
|
|
+
|
|
|
+ Delete = CreateButton(Wpf.Resources.delete.AsBitmapImage(Color.White));
|
|
|
+ Delete.Margin = new Thickness(2, 2, 0, 0);
|
|
|
+ Delete.SetValue(DockPanel.DockProperty, Dock.Right);
|
|
|
+ Delete.Click += Delete_Click;
|
|
|
+
|
|
|
+ if(this is IDuplicateDynamicGrid)
|
|
|
+ {
|
|
|
+ DuplicateBtn = AddButton("Duplicate", Wpf.Resources.paste.AsBitmapImage(Color.White), DuplicateButton_Click);
|
|
|
+ }
|
|
|
+
|
|
|
+ Count = new Label();
|
|
|
+ Count.Height = 30;
|
|
|
+ Count.Margin = new Thickness(0, 2, 0, 0);
|
|
|
+ Count.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ Count.HorizontalContentAlignment = HorizontalAlignment.Center;
|
|
|
+ Count.SetValue(DockPanel.DockProperty, Dock.Left);
|
|
|
+
|
|
|
+ Docker = new DockPanel();
|
|
|
+
|
|
|
+ Docker.SetValue(Grid.RowProperty, 2);
|
|
|
+ Docker.SetValue(Grid.ColumnProperty, 0);
|
|
|
+ Docker.Children.Add(LeftButtonStack);
|
|
|
+ Docker.Children.Add(Delete);
|
|
|
+ Docker.Children.Add(RightButtonStack);
|
|
|
+ Docker.Children.Add(Count);
|
|
|
+
|
|
|
+
|
|
|
+ Layout = new Grid();
|
|
|
+ Layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
|
+ Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
|
|
|
+ Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
|
|
+ Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
|
|
|
+
|
|
|
+ var control = UIComponent.Control;
|
|
|
+ control.SetValue(Grid.RowProperty, 1);
|
|
|
+
|
|
|
+ Layout.Children.Add(control);
|
|
|
+ Layout.Children.Add(Loading);
|
|
|
+ Layout.Children.Add(Docker);
|
|
|
+
|
|
|
+ Disabler = new Border()
|
|
|
+ {
|
|
|
+ BorderBrush = new SolidColorBrush(Colors.Transparent),
|
|
|
+ Background = new SolidColorBrush(Colors.DimGray) { Opacity = 0.2 },
|
|
|
+ Visibility = Visibility.Collapsed,
|
|
|
+ };
|
|
|
+ Disabler.SetValue(Canvas.ZIndexProperty, 99);
|
|
|
+ Disabler.SetValue(Grid.RowSpanProperty, 3);
|
|
|
+
|
|
|
+ Layout.Children.Add(Disabler);
|
|
|
+
|
|
|
+ //Scroll.ApplyTemplate();
|
|
|
+
|
|
|
+ Content = Layout;
|
|
|
+
|
|
|
+ IsEnabledChanged += (sender, args) =>
|
|
|
+ {
|
|
|
+ Disabler.Visibility = Equals(args.NewValue, true)
|
|
|
+ ? Visibility.Collapsed
|
|
|
+ : Visibility.Visible;
|
|
|
+ };
|
|
|
+
|
|
|
+ Settings = LoadSettings();
|
|
|
+
|
|
|
+ Init();
|
|
|
+ Reconfigure();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void PreInit()
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ #region IDynamicGridUIComponentParent
|
|
|
+
|
|
|
+ protected virtual IDynamicGridUIComponent CreateUIComponent()
|
|
|
+ {
|
|
|
+ return new DynamicGridGridUIComponent()
|
|
|
+ {
|
|
|
+ Parent = this
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ protected IDynamicGridUIComponent GetUIComponent() => UIComponent;
|
|
|
+
|
|
|
+ bool IDynamicGridUIComponentParent.CanFilter()
|
|
|
+ {
|
|
|
+ return !Options.ReorderRows || !Options.EditRows;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool IDynamicGridUIComponentParent.CanSort()
|
|
|
+ {
|
|
|
+ return !Options.ReorderRows || !Options.EditRows;
|
|
|
+ }
|
|
|
+
|
|
|
+ DynamicGridRowStyleSelector IDynamicGridUIComponentParent.RowStyleSelector => RowStyleSelector;
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.BeforeSelection(CancelEventArgs cancel)
|
|
|
+ {
|
|
|
+ BeforeSelection(cancel);
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.SelectItems(CoreRow[] rows)
|
|
|
+ {
|
|
|
+ SelectItems(rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.HandleKey(KeyEventArgs e)
|
|
|
+ {
|
|
|
+ if (Options.ReorderRows)
|
|
|
+ {
|
|
|
+ if (e.Key == Key.X && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
|
|
|
+ {
|
|
|
+ CutToClipBuffer();
|
|
|
+ }
|
|
|
+ else if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
|
|
|
+ {
|
|
|
+ CopyToClipBuffer();
|
|
|
+ }
|
|
|
+ else if (e.Key == Key.V && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
|
|
|
+ {
|
|
|
+ PasteFromClipBuffer();
|
|
|
+ }
|
|
|
+ else if (e.Key == Key.Escape)
|
|
|
+ {
|
|
|
+ ResetClipBuffer();
|
|
|
+ InvalidateGrid();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.DoubleClickCell(CoreRow? row, DynamicColumnBase? column)
|
|
|
+ {
|
|
|
+ var args = new DynamicGridCellClickEventArgs(row, column);
|
|
|
+ if (OnCellDoubleClick is not null)
|
|
|
+ {
|
|
|
+
|
|
|
+ OnCellDoubleClick?.Invoke(this, args);
|
|
|
+ if (args.Handled)
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (row is not null)
|
|
|
+ DoDoubleClick(this, args);
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.ExecuteActionColumn(DynamicActionColumn column, CoreRow[]? rows)
|
|
|
+ {
|
|
|
+ var bRefresh = false;
|
|
|
+ if(rows is null)
|
|
|
+ {
|
|
|
+ bRefresh = column.Action?.Invoke(null) ?? false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ foreach (var row in rows)
|
|
|
+ if (column.Action?.Invoke(row) == true)
|
|
|
+ bRefresh = true;
|
|
|
+ }
|
|
|
+ if (bRefresh)
|
|
|
+ Dispatcher.BeginInvoke(() => { Refresh(true, true); });
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.OpenColumnMenu(DynamicColumnBase column)
|
|
|
+ {
|
|
|
+ if(column is DynamicMenuColumn menuColumn)
|
|
|
+ {
|
|
|
+ menuColumn.Action?.Invoke(SelectedRows.FirstOrDefault());
|
|
|
+ }
|
|
|
+ else if(column is DynamicActionColumn actionColumn)
|
|
|
+ {
|
|
|
+ var menu = actionColumn?.ContextMenu?.Invoke(SelectedRows);
|
|
|
+ if (menu != null && menu.Items.Count > 0)
|
|
|
+ {
|
|
|
+ menu.IsOpen = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.UpdateRecordCount(int count)
|
|
|
+ {
|
|
|
+ Count.Content = FormatRecordCount(count);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual string FormatRecordCount(int count) => $"{count} Records";
|
|
|
+
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.LoadColumnsMenu(ContextMenu menu)
|
|
|
+ {
|
|
|
+ menu.AddItem("Select Columns", null, SelectColumnsClick);
|
|
|
+ LoadColumnsMenu(menu);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.DragOver(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ HandleDragOver(sender, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.Drop(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ if (!Options.DragTarget)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if(DynamicGridUtils.TryGetDropData(e, out var entityType, out var table))
|
|
|
+ {
|
|
|
+ OnDragEnd(entityType, table, e);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ HandleDragDrop(sender, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.DragStart(object? sender, CoreRow[] rows)
|
|
|
+ {
|
|
|
+ Logger.Send(LogType.Information, "", "RowDragDropController_DragStart");
|
|
|
+
|
|
|
+ if (!Options.DragSource)
|
|
|
+ return;
|
|
|
+
|
|
|
+ OnRowsDragStart(rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void UIFilterChanged(object sender) => DoFilterChanged();
|
|
|
+
|
|
|
+
|
|
|
+ //void IDynamicGridUIComponentParent<T>.UIFilterChanged(object sender) => DoFilterChanged();
|
|
|
+
|
|
|
+ protected virtual void DoFilterChanged()
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private Dictionary<DynamicColumnBase, IDynamicGridColumnFilter?> ColumnFilters { get; set; } = new();
|
|
|
+
|
|
|
+ IDynamicGridColumnFilter? IBaseDynamicGrid.GetColumnFilter(DynamicColumnBase column) => GetColumnFilter(column);
|
|
|
+
|
|
|
+ protected IDynamicGridColumnFilter? GetColumnFilter(DynamicColumnBase column)
|
|
|
+ {
|
|
|
+ if(!ColumnFilters.TryGetValue(column, out var filter))
|
|
|
+ {
|
|
|
+ filter = GenerateColumnFilter(column);
|
|
|
+ ColumnFilters.Add(column, filter);
|
|
|
+ }
|
|
|
+ return filter;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual IDynamicGridColumnFilter? GenerateColumnFilter(DynamicColumnBase column)
|
|
|
+ {
|
|
|
+ if(column is DynamicGridColumn gc)
|
|
|
+ {
|
|
|
+ if(gc.Editor is DateTimeEditor || gc.Editor is DateEditor)
|
|
|
+ {
|
|
|
+ return new DateTreeDynamicGridColumnFilter(this, column);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return new StandardDynamicGridColumnFilter(this, column);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if(column is DynamicActionColumn ac)
|
|
|
+ {
|
|
|
+ if(ac.GetFilter is not null)
|
|
|
+ {
|
|
|
+ return ac.GetFilter();
|
|
|
+ }
|
|
|
+ else if(ac is DynamicTextColumn textColumn)
|
|
|
+ {
|
|
|
+ return new StandardDynamicGridColumnFilter(this, textColumn);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ protected virtual DynamicGridRowStyleSelector GetRowStyleSelector()
|
|
|
+ {
|
|
|
+ return new SimpleDynamicGridRowStyleSelector<DynamicGridRowStyle>();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
|
|
|
+ {
|
|
|
+ DynamicGridStyle? result = null;
|
|
|
+
|
|
|
+ if (ClipBuffer != null)
|
|
|
+ if (ClipBuffer.Item2.Contains(row))
|
|
|
+ {
|
|
|
+ var bgbrush = style.Background as SolidColorBrush;
|
|
|
+ var bgcolor = bgbrush != null ? bgbrush.Color : Colors.Transparent;
|
|
|
+
|
|
|
+ result = new DynamicGridRowStyle(style);
|
|
|
+
|
|
|
+ result.Background = ClipBuffer.Item1 == ClipAction.Cut
|
|
|
+ ? new SolidColorBrush(bgcolor.MixColors(0.5, Colors.Orchid))
|
|
|
+ : new SolidColorBrush(bgcolor.MixColors(0.5, Colors.LightGreen));
|
|
|
+
|
|
|
+ result.Foreground = new SolidColorBrush(Colors.Gray);
|
|
|
+ result.FontStyle = FontStyles.Italic;
|
|
|
+ }
|
|
|
+ result ??= OnGetRowStyle != null ? OnGetRowStyle(row, style) : style;
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void BeforeSelection(CancelEventArgs cancel)
|
|
|
+ {
|
|
|
+ OnBeforeSelection?.Invoke(cancel);
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsReady { get; protected set; }
|
|
|
+
|
|
|
+ public UIElement? Header
|
|
|
+ {
|
|
|
+ get => _header;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (_header is not null && Layout.Children.Contains(_header))
|
|
|
+ Layout.Children.Remove(_header);
|
|
|
+ _header = value;
|
|
|
+ if (_header is not null)
|
|
|
+ {
|
|
|
+ _header.SetValue(Grid.RowProperty, 0);
|
|
|
+ _header.SetValue(Grid.ColumnProperty, 0);
|
|
|
+ _header.SetValue(Grid.ColumnSpanProperty, 2);
|
|
|
+ Layout.Children.Add(_header);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Represents the unfiltered data in the grid. This is <see langword="null"/> until <see cref="Refresh(bool, bool)"/> is called.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// This differs from <see cref="Data"/> in that <see cref="Data"/> has been filtered by <see cref="FilterRecord(CoreRow)"/>,
|
|
|
+ /// whereas <see cref="MasterData"/> contains every record loaded from the database.
|
|
|
+ /// </remarks>
|
|
|
+ public CoreTable? MasterData { get; set; }
|
|
|
+
|
|
|
+ public DynamicGridColumns VisibleColumns { get; protected set; }
|
|
|
+ public DynamicActionColumns ActionColumns { get; protected set; }
|
|
|
+ private List<DynamicColumnBase> ColumnList = new();
|
|
|
+ IList<DynamicColumnBase> IBaseDynamicGrid.ColumnList => ColumnList;
|
|
|
+
|
|
|
+ public CoreTable Data { get; set; }
|
|
|
+
|
|
|
+ public double RowHeight
|
|
|
+ {
|
|
|
+ get => UIComponent.RowHeight;
|
|
|
+ set => UIComponent.RowHeight = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double HeaderHeight
|
|
|
+ {
|
|
|
+ get => UIComponent.HeaderRowHeight;
|
|
|
+ set => UIComponent.HeaderRowHeight = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Options
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initialise things like custom buttons; called once during construction.
|
|
|
+ /// </summary>
|
|
|
+ protected abstract void Init();
|
|
|
+
|
|
|
+ protected abstract void DoReconfigure(DynamicGridOptions options);
|
|
|
+
|
|
|
+ private bool _hasLoadedOptions = false;
|
|
|
+
|
|
|
+ protected virtual void OptionsChanged()
|
|
|
+ {
|
|
|
+ var reloadColumns = false;
|
|
|
+
|
|
|
+ if(Help is not null)
|
|
|
+ {
|
|
|
+ Help.Visibility = Options.ShowHelp ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ }
|
|
|
+
|
|
|
+ Add.Visibility = Options.AddRows ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ Edit.Visibility = Options.EditRows ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+
|
|
|
+ EditSpacer.Visibility = Options.AddRows || Options.EditRows
|
|
|
+ ? Visibility.Visible
|
|
|
+ : Visibility.Collapsed;
|
|
|
+
|
|
|
+ Print.Visibility = Options.Print ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ PrintSpacer.Visibility = Options.Print ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+
|
|
|
+ Copy.Visibility = Options.ReorderRows ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ ClipboardSpacer.Visibility = Options.ReorderRows ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+
|
|
|
+ if(ExportButton is not null)
|
|
|
+ {
|
|
|
+ ExportButton.Visibility = Options.ExportData ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ }
|
|
|
+ if(ImportButton is not null)
|
|
|
+ {
|
|
|
+ ImportButton.Visibility = Options.ImportData ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ }
|
|
|
+ ExportSpacer.Visibility = Options.ExportData || Options.ImportData
|
|
|
+ ? Visibility.Visible
|
|
|
+ : Visibility.Collapsed;
|
|
|
+
|
|
|
+ SwitchViewBtn.Visibility = Options.DirectEdit
|
|
|
+ ? Options.HideDirectEditButton
|
|
|
+ ? Visibility.Collapsed
|
|
|
+ : Visibility.Visible
|
|
|
+ : Visibility.Collapsed;
|
|
|
+
|
|
|
+ Count.Visibility = Options.RecordCount ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+
|
|
|
+ Delete.Visibility = Options.DeleteRows ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+
|
|
|
+ if (drag is not null)
|
|
|
+ {
|
|
|
+ var hasSequence = drag.Position == DynamicActionColumnPosition.Start;
|
|
|
+ if (Options.ReorderRows)
|
|
|
+ {
|
|
|
+ if (!ActionColumns.Contains(drag))
|
|
|
+ {
|
|
|
+ ActionColumns.Insert(0, drag);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ActionColumns.Remove(drag);
|
|
|
+ }
|
|
|
+ if(hasSequence != Options.ReorderRows)
|
|
|
+ {
|
|
|
+ drag.Position = Options.ReorderRows ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;
|
|
|
+ reloadColumns = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (DuplicateBtn != null)
|
|
|
+ DuplicateBtn.Visibility = Visibility.Collapsed;
|
|
|
+
|
|
|
+ if (UIComponent.OptionsChanged())
|
|
|
+ {
|
|
|
+ reloadColumns = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(reloadColumns && IsReady)
|
|
|
+ {
|
|
|
+ Refresh(true, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsDirectEditMode()
|
|
|
+ {
|
|
|
+ return Options.DirectEdit
|
|
|
+ && (Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.DirectEdit
|
|
|
+ || Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.Default);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void SwitchView_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ Settings.ViewMode = Settings.ViewMode switch
|
|
|
+ {
|
|
|
+ DynamicGridSettings.DynamicGridViewMode.Default => DynamicGridSettings.DynamicGridViewMode.Normal,
|
|
|
+ DynamicGridSettings.DynamicGridViewMode.Normal => DynamicGridSettings.DynamicGridViewMode.DirectEdit,
|
|
|
+ DynamicGridSettings.DynamicGridViewMode.DirectEdit or _ => DynamicGridSettings.DynamicGridViewMode.Normal
|
|
|
+ };
|
|
|
+ SaveSettings(Settings);
|
|
|
+ Reconfigure();
|
|
|
+ }
|
|
|
+ public DynamicGridOptions Options { get; }
|
|
|
+
|
|
|
+ protected void OnReconfigureEvent(DynamicGridOptions options)
|
|
|
+ {
|
|
|
+ _onReconfigure?.Invoke(options);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Configure custom buttons and options.
|
|
|
+ /// </summary>
|
|
|
+ public void Reconfigure(DynamicGridOptions options)
|
|
|
+ {
|
|
|
+ options.BeginUpdate().Clear();
|
|
|
+ DoReconfigure(options);
|
|
|
+ OnReconfigureEvent(options);
|
|
|
+ options.EndUpdate();
|
|
|
+ if (!_hasLoadedOptions)
|
|
|
+ {
|
|
|
+ _hasLoadedOptions = true;
|
|
|
+ OptionsChanged();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Reconfigure()
|
|
|
+ {
|
|
|
+ Reconfigure(Options);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Reconfigure(IDynamicGrid.ReconfigureEvent onReconfigure)
|
|
|
+ {
|
|
|
+ OnReconfigure += onReconfigure;
|
|
|
+ Reconfigure();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ protected virtual DynamicGridSettings LoadSettings()
|
|
|
+ {
|
|
|
+ return new DynamicGridSettings();
|
|
|
+ }
|
|
|
+ protected virtual void SaveSettings(DynamicGridSettings settings)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void LoadColumnsMenu(ContextMenu menu)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void UpdateCell(int row, string colname, object? value)
|
|
|
+ {
|
|
|
+ var coreRow = Data.Rows[row];
|
|
|
+ coreRow[colname] = value;
|
|
|
+ UIComponent.UpdateCell(coreRow, colname, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void UpdateCell(CoreRow row, DynamicColumnBase column)
|
|
|
+ {
|
|
|
+ UIComponent.UpdateCell(row, column);
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Row Selections
|
|
|
+
|
|
|
+ protected CoreRow[] GetVisibleRows()
|
|
|
+ {
|
|
|
+ return UIComponent.GetVisibleRows();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public CoreRow[] SelectedRows
|
|
|
+ {
|
|
|
+ get => UIComponent.SelectedRows;
|
|
|
+ set => UIComponent.SelectedRows = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Call the <see cref="OnSelectItem"/> event, and do any updating which needs to occur when items are selected.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="rows"></param>
|
|
|
+ protected virtual void SelectItems(CoreRow[]? rows)
|
|
|
+ {
|
|
|
+ if (IsReady)
|
|
|
+ OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows));
|
|
|
+
|
|
|
+ if(DuplicateBtn is not null)
|
|
|
+ {
|
|
|
+ DuplicateBtn.Visibility = CanDuplicate && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
|
|
|
+ {
|
|
|
+ if (IsDirectEditMode())
|
|
|
+ return;
|
|
|
+
|
|
|
+ //SelectItems(SelectedRows);
|
|
|
+ var e = new HandledEventArgs(false);
|
|
|
+ OnDoubleClick?.Invoke(sender, e);
|
|
|
+ if (e.Handled)
|
|
|
+ return;
|
|
|
+ if (Options.EditRows)
|
|
|
+ DoEdit();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Column Handling
|
|
|
+
|
|
|
+ #region Column Grouping
|
|
|
+
|
|
|
+ public DynamicGridColumnGroupings ColumnGroupings { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Create a new column header group, and return it for editing.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public DynamicGridColumnGrouping AddColumnGrouping()
|
|
|
+ {
|
|
|
+ var group = new DynamicGridColumnGrouping();
|
|
|
+ ColumnGroupings.Add(group);
|
|
|
+ return group;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the current column header group, and if there is none, create a new one.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public DynamicGridColumnGrouping GetColumnGrouping()
|
|
|
+ {
|
|
|
+ if(ColumnGroupings.Count == 0)
|
|
|
+ {
|
|
|
+ return AddColumnGrouping();
|
|
|
+ }
|
|
|
+ return ColumnGroupings[^1];
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ protected virtual DynamicGridColumns LoadColumns()
|
|
|
+ {
|
|
|
+ return GenerateColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Provide a set of columns which is the default for this grid.
|
|
|
+ /// </summary>
|
|
|
+ public abstract DynamicGridColumns GenerateColumns();
|
|
|
+
|
|
|
+ protected abstract void SaveColumns(DynamicGridColumns columns);
|
|
|
+
|
|
|
+ public int DesiredWidth()
|
|
|
+ {
|
|
|
+ return UIComponent.DesiredWidth();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle to configure column groups.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// This is called after <see cref="LoadColumns"/>, so by the time this is called, both <see cref="VisibleColumns"/>
|
|
|
+ /// and <see cref="ActionColumns"/> will be loaded, which means one can reference these in the column groups.
|
|
|
+ /// <br/>
|
|
|
+ /// <b>Note:</b> <see cref="ColumnGroupings"/> is cleared before this function is called.
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual void ConfigureColumnGroups()
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void ConfigureColumns(DynamicGridColumns columns)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ColumnsLoadedEventArgs : EventArgs
|
|
|
+ {
|
|
|
+ public List<DynamicColumnBase> Columns { get; private set; }
|
|
|
+ public DynamicGridColumnGroupings ColumnGroupings { get; private set; }
|
|
|
+
|
|
|
+ public IEnumerable<DynamicActionColumn> ActionColumns => Columns.OfType<DynamicActionColumn>();
|
|
|
+ public IEnumerable<DynamicGridColumn> DataColumns => Columns.OfType<DynamicGridColumn>();
|
|
|
+
|
|
|
+ public ColumnsLoadedEventArgs(List<DynamicColumnBase> columns, DynamicGridColumnGroupings columnGroupings)
|
|
|
+ {
|
|
|
+ Columns = columns;
|
|
|
+ ColumnGroupings = columnGroupings;
|
|
|
+ }
|
|
|
+
|
|
|
+ public DynamicGridColumn Add<T>(
|
|
|
+ Expression<Func<T, object?>> member,
|
|
|
+ int? width = null,
|
|
|
+ string? caption = null,
|
|
|
+ string? format = null,
|
|
|
+ Alignment? alignment = null)
|
|
|
+ {
|
|
|
+ var col = DynamicGridColumns.CreateColumn(member, width: width, caption: caption, format: format, alignment: alignment);
|
|
|
+ Columns.Add(col);
|
|
|
+ return col;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public delegate void ColumnsLoadedEvent(BaseDynamicGrid sender, ColumnsLoadedEventArgs args);
|
|
|
+
|
|
|
+ public event ColumnsLoadedEvent? ColumnsLoaded;
|
|
|
+
|
|
|
+ protected virtual void OnColumnsLoaded(List<DynamicColumnBase> columns, DynamicGridColumnGroupings groupings)
|
|
|
+ {
|
|
|
+ ColumnsLoaded?.Invoke(this, new ColumnsLoadedEventArgs(columns, groupings));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ReloadColumns()
|
|
|
+ {
|
|
|
+ ColumnFilters.Clear();
|
|
|
+
|
|
|
+ VisibleColumns = LoadColumns();
|
|
|
+ ConfigureColumns(VisibleColumns);
|
|
|
+
|
|
|
+ ColumnGroupings.Clear();
|
|
|
+ ConfigureColumnGroups();
|
|
|
+
|
|
|
+ ColumnList = new List<DynamicColumnBase>();
|
|
|
+ ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.Start));
|
|
|
+ ColumnList.AddRange(VisibleColumns);
|
|
|
+ ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.End));
|
|
|
+
|
|
|
+ OnColumnsLoaded(ColumnList, ColumnGroupings);
|
|
|
+
|
|
|
+ UIComponent.RefreshColumns(ColumnList, ColumnGroupings);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Refresh / Reload
|
|
|
+
|
|
|
+ protected bool IsPaging { get; set; } = false;
|
|
|
+
|
|
|
+ protected virtual bool FilterRecord(CoreRow row)
|
|
|
+ {
|
|
|
+ if (OnFilterRecord is not null)
|
|
|
+ return OnFilterRecord(row);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private class RowRange(int rowIdx, int size)
|
|
|
+ {
|
|
|
+ public int RowIdx { get; set; } = rowIdx;
|
|
|
+
|
|
|
+ public int Size { get; set; } = size;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected abstract void ReloadData(CancellationToken token, Action<CoreTable?, Exception?> action);
|
|
|
+
|
|
|
+ private CancellationTokenSource? RefreshCancellationToken;
|
|
|
+ public virtual void Refresh(bool reloadcolumns, bool reloaddata)
|
|
|
+ {
|
|
|
+ if (bRefreshing)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!DoBeforeRefresh())
|
|
|
+ return;
|
|
|
+
|
|
|
+ UIComponent.BeforeRefresh();
|
|
|
+
|
|
|
+ using var cursor = UseWaitCursor ? new WaitCursor() : null;
|
|
|
+
|
|
|
+ Loading.Visibility = Visibility.Visible;
|
|
|
+ Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
|
|
|
+
|
|
|
+ bRefreshing = true;
|
|
|
+
|
|
|
+ if (reloadcolumns)
|
|
|
+ {
|
|
|
+ ReloadColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reloaddata)
|
|
|
+ {
|
|
|
+ RefreshCancellationToken?.Cancel();
|
|
|
+
|
|
|
+ var tokenSource = new CancellationTokenSource();
|
|
|
+ RefreshCancellationToken = tokenSource;
|
|
|
+ var token = tokenSource.Token;
|
|
|
+
|
|
|
+ ReloadData(token, (table, exception) =>
|
|
|
+ {
|
|
|
+ if (token.IsCancellationRequested) return; // Don't bother even checking exceptions if task was cancelled.
|
|
|
+
|
|
|
+ if (exception != null)
|
|
|
+ {
|
|
|
+ Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else if (table is not null)
|
|
|
+ {
|
|
|
+ if (table.Offset == 0 || MasterData is null)
|
|
|
+ {
|
|
|
+ MasterData = table;
|
|
|
+ Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ ProcessData(null);
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ DoAfterRefresh();
|
|
|
+ bRefreshing = false;
|
|
|
+ IsReady = true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int idx = MasterData.Rows.Count;
|
|
|
+ MasterData.AddPage(table);
|
|
|
+ Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ ProcessData(new(idx, table.Rows.Count));
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ProcessData(null);
|
|
|
+ DoAfterRefresh();
|
|
|
+ bRefreshing = false;
|
|
|
+ IsReady = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Shutdown()
|
|
|
+ {
|
|
|
+ RefreshCancellationToken?.Cancel();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);
|
|
|
+
|
|
|
+ protected void NotifyAfterRefresh(AfterRefreshEventArgs args) => AfterRefresh?.Invoke(this, args);
|
|
|
+
|
|
|
+ protected bool OnBeforeRefresh()
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool DoBeforeRefresh()
|
|
|
+ {
|
|
|
+ var result = OnBeforeRefresh();
|
|
|
+ if (result)
|
|
|
+ {
|
|
|
+ var args = new BeforeRefreshEventArgs() { Cancel = false };
|
|
|
+ NotifyBeforeRefresh(args);
|
|
|
+ result = args.Cancel == false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void OnAfterRefresh()
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void DoAfterRefresh()
|
|
|
+ {
|
|
|
+ OnAfterRefresh();
|
|
|
+ NotifyAfterRefresh(new AfterRefreshEventArgs());
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Process the data from <see cref="MasterData"/> according to <paramref name="range"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Set <paramref name="range"/> to <see langword="null"/> if this is the first page of data to be loaded. This will thus update the grid accordingly,
|
|
|
+ /// clearing all current rows, resetting columns, selection, etc. If the <paramref name="range"/> is provided, this will add to the grid the rows
|
|
|
+ /// according to the range from <see cref="MasterData"/>.
|
|
|
+ /// </remarks>
|
|
|
+ private void ProcessData(RowRange? range)
|
|
|
+ {
|
|
|
+ if(range is null)
|
|
|
+ {
|
|
|
+ Data.Columns.Clear();
|
|
|
+ Data.Setters.Clear();
|
|
|
+ if (MasterData != null)
|
|
|
+ foreach (var column in MasterData.Columns)
|
|
|
+ Data.Columns.Add(column);
|
|
|
+ }
|
|
|
+
|
|
|
+ LoadData(range);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected readonly Dictionary<CoreRow, CoreRow> _recordmap = new();
|
|
|
+
|
|
|
+ public void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true)
|
|
|
+ {
|
|
|
+ row.Set(column, value);
|
|
|
+ _recordmap[row].Set(column, value);
|
|
|
+ if (refresh)
|
|
|
+ InvalidateRow(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true)
|
|
|
+ {
|
|
|
+ row.Set(column, value);
|
|
|
+ _recordmap[row].Set(column, value);
|
|
|
+ if (refresh)
|
|
|
+ InvalidateRow(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.UpdateData(CoreRow row, string changedColumn, Dictionary<CoreColumn, object?> updates)
|
|
|
+ {
|
|
|
+ var result = new Dictionary<string, object?>();
|
|
|
+
|
|
|
+ foreach (var (col, value) in updates)
|
|
|
+ {
|
|
|
+ UpdateRow(row, col.ColumnName, value, refresh: false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddRow(CoreRow row)
|
|
|
+ {
|
|
|
+ if (MasterData is null) return;
|
|
|
+
|
|
|
+ var masterrow = MasterData.NewRow();
|
|
|
+ MasterData.FillRow(masterrow, row);
|
|
|
+ Refresh(false, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DeleteRow(CoreRow row)
|
|
|
+ {
|
|
|
+ if (MasterData is null) return;
|
|
|
+
|
|
|
+ var masterrow = _recordmap[row];
|
|
|
+ MasterData.Rows.Remove(masterrow);
|
|
|
+ Refresh(false, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Filter all given rows into <paramref name="into"/>, given that they match <paramref name="filter"/> and <see cref="FilterRecord(CoreRow)"/>.
|
|
|
+ /// If <paramref name="recordMap"/> is given, also updates the map from <paramref name="from"/> to <paramref name="into"/>.
|
|
|
+ /// </summary>
|
|
|
+ protected IList<CoreRow> FilterRows(
|
|
|
+ IEnumerable<CoreRow> from,
|
|
|
+ CoreTable into,
|
|
|
+ Dictionary<CoreRow, CoreRow>? recordMap = null,
|
|
|
+ Func<CoreRow, bool>? filter = null)
|
|
|
+ {
|
|
|
+ var newRows = new List<CoreRow>();
|
|
|
+ foreach (var row in from)
|
|
|
+ if (FilterRecord(row) && filter?.Invoke(row) != false)
|
|
|
+ {
|
|
|
+ var newrow = into.NewRow();
|
|
|
+ for (var i = 0; i < into.Columns.Count; i++)
|
|
|
+ {
|
|
|
+ var value = i < row.Values.Count ? row.Values[i] : null;
|
|
|
+ if (into.Columns[i].DataType.IsNumeric())
|
|
|
+ value = into.Columns[i].DataType.IsDefault(value) ? null : value;
|
|
|
+ newrow.Values.Add(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ newRows.Add(newrow);
|
|
|
+ into.Rows.Add(newrow);
|
|
|
+ recordMap?.TryAdd(newrow, row);
|
|
|
+ }
|
|
|
+ return newRows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void LoadData(RowRange? range)
|
|
|
+ {
|
|
|
+ if (MasterData is null)
|
|
|
+ return;
|
|
|
+ if(range is null)
|
|
|
+ {
|
|
|
+ ResetClipBuffer();
|
|
|
+ Data.Rows.Clear();
|
|
|
+ _recordmap.Clear();
|
|
|
+
|
|
|
+ FilterRows(MasterData.Rows, Data, _recordmap);
|
|
|
+
|
|
|
+ InvalidateGrid();
|
|
|
+ SelectedRows = Array.Empty<CoreRow>();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var _newRows = FilterRows(Enumerable.Range(range.RowIdx, range.Size).Select(i => MasterData.Rows[i]), Data, _recordmap);
|
|
|
+ UIComponent.AddPage(_newRows);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void InvalidateRow(CoreRow row)
|
|
|
+ {
|
|
|
+ UIComponent.InvalidateRow(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void InvalidateGrid()
|
|
|
+ {
|
|
|
+ if (RowStyleSelector != null)
|
|
|
+ RowStyleSelector.Data = Data;
|
|
|
+
|
|
|
+ UIComponent.RefreshData(Data);
|
|
|
+
|
|
|
+ Loading.BeginAnimation(Label.OpacityProperty, null);
|
|
|
+ Loading.Visibility = Visibility.Collapsed;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
|
|
|
+ {
|
|
|
+ UIComponent.AddVisualFilter(column, value, filtertype);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected List<Tuple<string, Func<CoreRow, bool>>> GetFilterPredicates()
|
|
|
+ {
|
|
|
+ return UIComponent.GetFilterPredicates();
|
|
|
+ }
|
|
|
+
|
|
|
+ public object? GetData(CoreRow row, DynamicColumnBase column)
|
|
|
+ {
|
|
|
+ if(column is DynamicActionColumn ac)
|
|
|
+ {
|
|
|
+ return ac.Data(row);
|
|
|
+ }
|
|
|
+ else if(column is DynamicGridColumn gc)
|
|
|
+ {
|
|
|
+ return row[gc.ColumnName];
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Item Manipulation
|
|
|
+
|
|
|
+ #region Abstract/Virtual Functions
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Create a new row.
|
|
|
+ /// </summary>
|
|
|
+ protected abstract void NewRow();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Edit <paramref name="rows"/> or create a new row and edit it. This should update the rows, and if it creates a new row,
|
|
|
+ /// it should be added to the grid.
|
|
|
+ /// </summary>
|
|
|
+ protected abstract bool EditRows(CoreRow[]? rows);
|
|
|
+
|
|
|
+ public abstract void DeleteRows(params CoreRow[] rows);
|
|
|
+
|
|
|
+ protected virtual bool CanDeleteRows(params CoreRow[] rows)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool DuplicateButton_Click(Button button, CoreRow[] rows)
|
|
|
+ {
|
|
|
+ return DoDuplicate(rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool DoDuplicate(CoreRow[] rows)
|
|
|
+ {
|
|
|
+ return this is IDuplicateDynamicGrid grid && grid.DoDuplicate(rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Load/Save/Delete
|
|
|
+
|
|
|
+ protected virtual void DoDelete()
|
|
|
+ {
|
|
|
+ var rows = SelectedRows.ToArray();
|
|
|
+
|
|
|
+ if (rows.Any())
|
|
|
+ if (CanDeleteRows(rows))
|
|
|
+ if (MessageBox.Show("Are you sure you wish to delete the selected records?", "Confirm Delete", MessageBoxButton.YesNo) ==
|
|
|
+ MessageBoxResult.Yes)
|
|
|
+ {
|
|
|
+ DeleteRows(rows);
|
|
|
+ SelectedRows = Array.Empty<CoreRow>();
|
|
|
+ Refresh(false, true);
|
|
|
+ DoChanged();
|
|
|
+ SelectItems(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Delete_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ DoDelete();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Edit
|
|
|
+
|
|
|
+ protected virtual void DoEdit()
|
|
|
+ {
|
|
|
+ if (SelectedRows.Length == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (AddEditClick(SelectedRows))
|
|
|
+ {
|
|
|
+ SelectItems(SelectedRows);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Edit_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ DoEdit();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void DoAdd(bool openEditorOnDirectEdit = false)
|
|
|
+ {
|
|
|
+ if (IsDirectEditMode() && !openEditorOnDirectEdit)
|
|
|
+ {
|
|
|
+ NewRow();
|
|
|
+ }
|
|
|
+ else if (AddEditClick(null))
|
|
|
+ {
|
|
|
+ Refresh(false, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Add_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (CanCreateRows())
|
|
|
+ DoAdd();
|
|
|
+ }
|
|
|
+
|
|
|
+ BaseEditor IDynamicGridUIComponentParent.CustomiseEditor(DynamicGridColumn column, BaseEditor editor)
|
|
|
+ {
|
|
|
+ return editor.CloneEditor();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual bool CanCreateRows()
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool AddEditClick(CoreRow[]? rows)
|
|
|
+ {
|
|
|
+ if (!IsEnabled || bRefreshing)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (rows == null || rows.Length == 0)
|
|
|
+ {
|
|
|
+ if (!CanCreateRows())
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return EditRows(null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return EditRows(rows);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ protected virtual void DoPrint(object sender)
|
|
|
+ {
|
|
|
+ OnPrintData?.Invoke(sender);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void ShowHelp(string slug)
|
|
|
+ {
|
|
|
+ Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + slug) { UseShellExecute = true });
|
|
|
+ }
|
|
|
+
|
|
|
+ void IDynamicGridUIComponentParent.MoveRows(InABox.Core.CoreRow[] rows, int index) => MoveRows(rows, index);
|
|
|
+
|
|
|
+ #region ClipBuffer
|
|
|
+
|
|
|
+ private Tuple<ClipAction, CoreRow[]>? ClipBuffer;
|
|
|
+
|
|
|
+ protected void ResetClipBuffer()
|
|
|
+ {
|
|
|
+ ClipBuffer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void SetClipBuffer(ClipAction action, CoreRow[] rows)
|
|
|
+ {
|
|
|
+ ClipBuffer = new Tuple<ClipAction, CoreRow[]>(action, rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CutToClipBuffer()
|
|
|
+ {
|
|
|
+ SetClipBuffer(ClipAction.Cut, SelectedRows);
|
|
|
+ InvalidateGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CopyToClipBuffer()
|
|
|
+ {
|
|
|
+ SetClipBuffer(ClipAction.Copy, SelectedRows);
|
|
|
+ InvalidateGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void PasteFromClipBuffer()
|
|
|
+ {
|
|
|
+ if (ClipBuffer == null)
|
|
|
+ return;
|
|
|
+ var row = SelectedRows.FirstOrDefault();
|
|
|
+ MoveRows(ClipBuffer.Item2, row is not null ? (int)row.Index + 1 : Data.Rows.Count, isCopy: ClipBuffer.Item1 == ClipAction.Copy);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Reorder the given rows, to place them at <paramref name="index"/>; that is, the first row of <paramref name="rows"/> will
|
|
|
+ /// be at <paramref name="index"/> after this method executes. If <paramref name="isCopy"/> is <see langword="true"/>, the items will copied, rather
|
|
|
+ /// than moved.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// To move the rows to the end, <paramref name="index"/> should be equal to the number of rows in <see cref="Data"/>.
|
|
|
+ /// </remarks>
|
|
|
+ protected abstract void MoveRows(CoreRow[] rows, int index, bool isCopy = false);
|
|
|
+
|
|
|
+ private void Copy_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ var rows = SelectedRows;
|
|
|
+ if (rows.Length == 0) return;
|
|
|
+ MoveRows(rows, rows[^1].Index + 1, isCopy: true);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Import / Export
|
|
|
+
|
|
|
+ private void DoImport()
|
|
|
+ {
|
|
|
+ if(this is IImportDynamicGrid grid)
|
|
|
+ {
|
|
|
+ grid.DoImport();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ImportButton_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ DoImport();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Import() => DoImport();
|
|
|
+
|
|
|
+ private void DoExport()
|
|
|
+ {
|
|
|
+ if(this is IExportDynamicGrid grid)
|
|
|
+ {
|
|
|
+ grid.DoExport();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ExportButtonClick(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ DoExport();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ public void ScrollIntoView(CoreRow row)
|
|
|
+ {
|
|
|
+ UIComponent.ScrollIntoView(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Custom Buttons
|
|
|
+
|
|
|
+ private Button CreateButton(BitmapImage? image = null, string? text = null, string? tooltip = null)
|
|
|
+ {
|
|
|
+ var button = new Button();
|
|
|
+ button.SetValue(BorderBrushProperty, new SolidColorBrush(Colors.Gray));
|
|
|
+ button.SetValue(BorderThicknessProperty, new Thickness(0.75));
|
|
|
+ button.Height = 30;
|
|
|
+ UpdateButton(button, image, text, tooltip);
|
|
|
+ return button;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null)
|
|
|
+ {
|
|
|
+ var stackPnl = new StackPanel();
|
|
|
+ stackPnl.Orientation = Orientation.Horizontal;
|
|
|
+ //stackPnl.Margin = new Thickness(2);
|
|
|
+
|
|
|
+ if (image != null)
|
|
|
+ {
|
|
|
+ var img = new Image();
|
|
|
+ img.Source = image;
|
|
|
+ img.Margin = new Thickness(2);
|
|
|
+ img.ToolTip = tooltip;
|
|
|
+ stackPnl.Children.Add(img);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!string.IsNullOrEmpty(text))
|
|
|
+ {
|
|
|
+ button.MaxWidth = double.MaxValue;
|
|
|
+ var lbl = new Label();
|
|
|
+ lbl.Content = text;
|
|
|
+ lbl.VerticalAlignment = VerticalAlignment.Stretch;
|
|
|
+ lbl.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ lbl.Margin = new Thickness(2, 0, 5, 0);
|
|
|
+ lbl.ToolTip = ToolTip;
|
|
|
+ stackPnl.Children.Add(lbl);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ button.MaxWidth = 30;
|
|
|
+
|
|
|
+ button.Content = stackPnl;
|
|
|
+ button.ToolTip = tooltip;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool bFirstButtonAdded = true;
|
|
|
+
|
|
|
+ private bool AnyButtonsVisible()
|
|
|
+ {
|
|
|
+ if (Add.Visibility != Visibility.Collapsed)
|
|
|
+ return true;
|
|
|
+ if (Edit.Visibility != Visibility.Collapsed)
|
|
|
+ return true;
|
|
|
+ /*if (MultiEdit.Visibility != Visibility.Collapsed)
|
|
|
+ return true;*/
|
|
|
+ if (ExportButton is not null && ExportButton.Visibility != Visibility.Collapsed)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
|
|
|
+ {
|
|
|
+ var button = CreateButton(image, caption, tooltip);
|
|
|
+ button.Margin = position == DynamicGridButtonPosition.Right
|
|
|
+ ? new Thickness(2, 2, 0, 0)
|
|
|
+ : bFirstButtonAdded && AnyButtonsVisible()
|
|
|
+ ? new Thickness(0, 2, 0, 0)
|
|
|
+ : new Thickness(0, 2, 2, 0);
|
|
|
+ button.Padding = !String.IsNullOrWhiteSpace(caption) ? new Thickness(5, 1, 5, 1) : new Thickness(1);
|
|
|
+ button.Tag = action;
|
|
|
+ button.Click += Button_Click;
|
|
|
+ if (position == DynamicGridButtonPosition.Right)
|
|
|
+ RightButtonStack.Children.Add(button);
|
|
|
+ else
|
|
|
+ LeftButtonStack.Children.Add(button);
|
|
|
+ bFirstButtonAdded = false;
|
|
|
+ return button;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Button AddButton(string? caption, BitmapImage? image, DynamicGridButtonClickEvent 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;
|
|
|
+ var action = (DynamicGridButtonClickEvent)button.Tag;
|
|
|
+
|
|
|
+ //CoreRow row = (CurrentRow > -1) && (CurrentRow < Data.Rows.Count) ? Data.Rows[this.CurrentRow] : null;
|
|
|
+ if (action.Invoke(button, SelectedRows))
|
|
|
+ Refresh(false, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Header Actions
|
|
|
+
|
|
|
+ protected abstract bool SelectColumns([NotNullWhen(true)] out DynamicGridColumns? columns);
|
|
|
+
|
|
|
+ private void SelectColumnsClick()
|
|
|
+ {
|
|
|
+ if (SelectColumns(out var columns))
|
|
|
+ {
|
|
|
+ VisibleColumns.Clear();
|
|
|
+ VisibleColumns.AddRange(columns);
|
|
|
+ SaveColumns(columns);
|
|
|
+ Refresh(true, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Drag + Drop
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle a number of rows from a different <see cref="DynamicGrid{T}"/> being dragged into this one.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="entity">The type of entity that that the rows of <paramref name="table"/> represent.</param>
|
|
|
+ /// <param name="table">The data being dragged.</param>
|
|
|
+ /// <param name="e"></param>
|
|
|
+ protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
|
|
|
+ {
|
|
|
+ Logger.Send(LogType.Information,"","OnDragEnd");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle all types of items being dragged onto this grid that aren't handled by <see cref="OnDragEnd(Type, CoreTable, DragEventArgs)"/>,
|
|
|
+ /// i.e., data which is not a <see cref="CoreTable"/> from another <see cref="DynamicGrid{T}"/>
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Can be used to handle files, for example.
|
|
|
+ /// </remarks>
|
|
|
+ /// <param name="sender"></param>
|
|
|
+ /// <param name="e"></param>
|
|
|
+ protected virtual void HandleDragDrop(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void HandleDragOver(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual DragDropEffects OnRowsDragStart(CoreRow[] rows)
|
|
|
+ {
|
|
|
+ return DragDropEffects.None;
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Shows that this <see cref="BaseDynamicGrid"/> can be used to import data.
|
|
|
+/// </summary>
|
|
|
+public interface IImportDynamicGrid
|
|
|
+{
|
|
|
+ void DoImport();
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Shows that this <see cref="BaseDynamicGrid"/> can be used to export data.
|
|
|
+/// </summary>
|
|
|
+public interface IExportDynamicGrid
|
|
|
+{
|
|
|
+ void DoExport();
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Shows that this <see cref="BaseDynamicGrid"/> can be used to duplicate data.
|
|
|
+/// </summary>
|
|
|
+public interface IDuplicateDynamicGrid
|
|
|
+{
|
|
|
+ bool DoDuplicate(CoreRow[] rows);
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Shows that this <see cref="BaseDynamicGrid"/> can show a help menu.
|
|
|
+/// </summary>
|
|
|
+public interface IHelpDynamicGrid
|
|
|
+{
|
|
|
+ string HelpSlug();
|
|
|
+}
|