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.IO;
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.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;
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(BaseDynamicGrid));
public bool UseWaitCursor
{
get => (bool)GetValue(UseWaitCursorProperty);
set => SetValue(UseWaitCursorProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(object),
typeof(BaseDynamicGrid),
new PropertyMetadata(null, DoItemsSourceChanged)
);
private static void DoItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is BaseDynamicGrid dynamicGrid)
{
dynamicGrid.OnItemSourceChanged(e.NewValue);
}
}
public object? ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public abstract void OnItemSourceChanged(object 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;
///
/// Called when an item is selected in the grid. It is not called if is not .
///
///
/// It is unnecessary to use this if within a grid. Instead, override .
///
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(ImageUtils.SvgToBitmapImage(Wpf.Resources.download));
ExportButton.ToolTip = "Export to File";
ExportButton.Margin = new Thickness(0, 2, 2, 0);
ExportButton.Click += ExportButtonClick;
}
if(this is IImportDynamicGrid)
{
ImportButton = CreateButton(ImageUtils.SvgToBitmapImage(Wpf.Resources.upload));
ImportButton.ToolTip = "Import from File";
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()
{
}
public CoreRow GetVisibleRow(int index) => UIComponent.GetVisibleRow(index);
#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.UIFilterChanged(object sender) => DoFilterChanged();
protected virtual void DoFilterChanged()
{
}
private Dictionary 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();
}
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);
}
}
}
///
/// Represents the unfiltered data in the grid. This is until is called.
///
///
/// This differs from in that has been filtered by ,
/// whereas contains every record loaded from the database.
///
public CoreTable? MasterData { get; set; }
public DynamicGridColumns VisibleColumns { get; protected set; }
public DynamicActionColumns ActionColumns { get; protected set; }
private List ColumnList = new();
IList 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
///
/// Initialise things like custom buttons; called once during construction.
///
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);
}
///
/// Configure custom buttons and options.
///
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;
}
///
/// Call the event, and do any updating which needs to occur when items are selected.
///
///
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; }
///
/// Create a new column header group, and return it for editing.
///
///
public DynamicGridColumnGrouping AddColumnGrouping()
{
var group = new DynamicGridColumnGrouping();
ColumnGroupings.Add(group);
return group;
}
///
/// Gets the current column header group, and if there is none, create a new one.
///
///
public DynamicGridColumnGrouping GetColumnGrouping()
{
if(ColumnGroupings.Count == 0)
{
return AddColumnGrouping();
}
return ColumnGroupings[^1];
}
#endregion
protected virtual DynamicGridColumns LoadColumns()
{
return GenerateColumns();
}
///
/// Provide a set of columns which is the default for this grid.
///
public abstract DynamicGridColumns GenerateColumns();
protected abstract void SaveColumns(DynamicGridColumns columns);
public int DesiredWidth()
{
return UIComponent.DesiredWidth();
}
///
/// Handle to configure column groups.
///
///
/// This is called after , so by the time this is called, both
/// and will be loaded, which means one can reference these in the column groups.
///
/// Note: is cleared before this function is called.
///
protected virtual void ConfigureColumnGroups()
{
}
protected virtual void ConfigureColumns(DynamicGridColumns columns)
{
}
public class ColumnsLoadedEventArgs : EventArgs
{
public List Columns { get; private set; }
public DynamicGridColumnGroupings ColumnGroupings { get; private set; }
public IEnumerable ActionColumns => Columns.OfType();
public IEnumerable DataColumns => Columns.OfType();
public ColumnsLoadedEventArgs(List columns, DynamicGridColumnGroupings columnGroupings)
{
Columns = columns;
ColumnGroupings = columnGroupings;
}
public DynamicGridColumn Add(
Expression> 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 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();
ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.Start));
ColumnList.AddRange(VisibleColumns);
ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.End));
OnColumnsLoaded(ColumnList, ColumnGroupings);
VisibleColumns.Clear();
VisibleColumns.AddRange(ColumnList.OfType());
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 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());
}
///
/// Process the data from according to .
///
///
/// Set to 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 is provided, this will add to the grid the rows
/// according to the range from .
///
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 _recordmap = new();
public void UpdateRow(CoreRow row, Expression> column, TType value, bool refresh = true)
{
row.Set(column, value);
_recordmap[row].Set(column, value);
if (refresh)
InvalidateRow(row);
}
public void UpdateRow(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 updates)
{
var result = new Dictionary();
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);
}
///
/// Filter all given rows into , given that they match and .
/// If is given, also updates the map from to .
///
protected IList FilterRows(
IEnumerable from,
CoreTable into,
Dictionary? recordMap = null,
Func? filter = null)
{
var newRows = new List();
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();
}
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>> 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
///
/// Create a new row.
///
protected abstract void NewRow();
///
/// Edit 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.
///
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();
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) => CustomiseEditor(column, editor);
protected virtual BaseEditor 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? ClipBuffer;
protected void ResetClipBuffer()
{
ClipBuffer = null;
}
protected void SetClipBuffer(ClipAction action, CoreRow[] rows)
{
ClipBuffer = new Tuple(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);
}
///
/// Reorder the given rows, to place them at ; that is, the first row of will
/// be at after this method executes. If is , the items will copied, rather
/// than moved.
///
///
/// To move the rows to the end, should be equal to the number of rows in .
///
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(ImageSource? 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;
button.MinWidth = 30;
UpdateButton(button, image, text, tooltip);
return button;
}
public void UpdateButton(Button button, ImageSource? 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, ImageSource? 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, ImageSource? 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
///
/// Handle a number of rows from a different being dragged into this one.
///
/// The type of entity that that the rows of represent.
/// The data being dragged.
///
protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
{
Logger.Send(LogType.Information,"","OnDragEnd");
}
///
/// Handle all types of items being dragged onto this grid that aren't handled by ,
/// i.e., data which is not a from another
///
///
/// Can be used to handle files, for example.
///
///
///
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
}
///
/// Shows that this can be used to import data.
///
public interface IImportDynamicGrid
{
void DoImport();
}
///
/// Shows that this can be used to export data.
///
public interface IExportDynamicGrid
{
void DoExport();
}
///
/// Shows that this can be used to duplicate data.
///
public interface IDuplicateDynamicGrid
{
bool DoDuplicate(CoreRow[] rows);
}
///
/// Shows that this can show a help menu.
///
public interface IHelpDynamicGrid
{
string HelpSlug();
}