using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
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 InABox.Clients;
using InABox.Core;
using InABox.Wpf;
using InABox.WPF;
using Syncfusion.Data;
using Syncfusion.UI.Xaml.Grid;
using Syncfusion.UI.Xaml.Grid.Helpers;
using static InABox.DynamicGrid.IDynamicGrid;
using Color = System.Drawing.Color;
using Columns = InABox.Core.Columns;
using Image = System.Windows.Controls.Image;
using RowColumnIndex = Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex;
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
using String = System.String;
using VerticalAlignment = System.Windows.VerticalAlignment;
using VirtualizingCellsControl = Syncfusion.UI.Xaml.Grid.VirtualizingCellsControl;
using System.Threading;
using System.Diagnostics.CodeAnalysis;
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;
protected readonly Button DuplicateBtn;
private readonly Button SwitchViewBtn;
private readonly Button Help;
private readonly Button ImportButton;
private readonly Grid Layout;
private readonly Label Loading;
private DoubleAnimation LoadingFader = new DoubleAnimation(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);
}
};
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(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 };
ExportButton = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Export");
ExportButton.Margin = new Thickness(0, 2, 2, 0);
ExportButton.Click += ExportButtonClick;
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);
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);
LeftButtonStack.Children.Add(ExportButton);
LeftButtonStack.Children.Add(ImportButton);
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;
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.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 abstract DynamicGridRowStyleSelector GetRowStyleSelector();
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;
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;
ExportButton.Visibility = Options.ExportData ? Visibility.Visible : Visibility.Collapsed;
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));
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);
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);
}
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.
///
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);
}
protected abstract bool DoDuplicate(CoreRow[] 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)
{
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 abstract string HelpSlug();
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);
}
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
protected abstract void DoImport();
private void ImportButton_Click(object sender, RoutedEventArgs e)
{
DoImport();
}
public void Import() => DoImport();
protected abstract void 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.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
///
/// 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
}
public abstract class DynamicGrid : BaseDynamicGrid, IDynamicGridUIComponentParent, IDynamicGrid
where T : BaseObject, new()
{
#region Events
public ValidateEvent? OnValidate { get; set; }
public event OnDefineFilter? OnDefineFilter;
public event OnCreateItem? OnCreateItem;
public event OnAfterCreateItem? OnAfterCreateItem;
public event EditorValueChangedHandler? OnEditorValueChanged;
public event OnCustomiseEditor? OnCustomiseEditor;
public event EntitySaveEvent? OnBeforeSave;
public event EntitySaveEvent? OnAfterSave;
public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items);
public event EditorLoaded? OnEditorLoaded;
public event OnLoadEditorButtons? OnLoadEditorButtons;
#endregion
protected override string HelpSlug()
{
return typeof(T).Name.Split('.').Last().SplitCamelCase().Replace(" ", "_");
}
protected override bool CanDuplicate => typeof(T).IsAssignableTo(typeof(IDuplicatable));
public DynamicGrid() : base()
{
}
protected sealed override void PreInit()
{
MasterColumns = new DynamicGridColumns();
MasterColumns.ExtractColumns(typeof(T));
HiddenColumns = new HiddenColumnsList();
if (typeof(T).IsAssignableTo(typeof(ISequenceable)))
{
HiddenColumns.Add(x => (x as ISequenceable)!.Sequence);
}
}
protected override void Init()
{
}
#region IDynamicGridUIComponentParent
protected override IDynamicGridUIComponent CreateUIComponent()
{
return new DynamicGridGridUIComponent
{
Parent = this
};
}
T IDynamicGrid.LoadItem(CoreRow row) => LoadItem(row);
void IDynamicGridUIComponentParent.EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary changes)
=> EntityChanged(obj, row, changedColumn, changes);
void IDynamicGridUIComponentParent.UpdateData(T obj, CoreRow row, string changedColumn, Dictionary updates)
{
var result = new Dictionary();
foreach (var (col, value) in updates)
{
UpdateRow(row, col.ColumnName, value, refresh: false);
DynamicGridUtils.UpdateEditorValue(new BaseObject[] { obj }, col.ColumnName, value, result);
}
EntityChanged(obj, row, changedColumn, result);
}
#endregion
protected override DynamicGridRowStyleSelector GetRowStyleSelector()
{
return new DynamicGridRowStyleSelector();
}
public DynamicGridColumns MasterColumns { get; protected set; }
public class HiddenColumnsList
{
private List Columns { get; set; } = new();
public IEnumerable ColumnNames => Columns;
public void Add(Expression> column) => Add(CoreUtils.GetFullPropertyName(column, "."));
public void Add(IColumn column) => Add(column.Property);
public void Add(string column)
{
if (!Contains(column))
Columns.Add(column);
}
public bool Contains(string column) => Columns.Contains(column);
}
public void AddHiddenColumn(string column) => HiddenColumns.Add(column);
public HiddenColumnsList HiddenColumns { get; private set; }
private static bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable)));
#region Options
protected override void DoReconfigure(DynamicGridOptions options)
{
options.ReorderRows = IsSequenced;
}
#endregion
private void EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary changes)
{
OnAfterEditorValueChanged(null, [obj], new AfterEditorValueChangedArgs(changedColumn, changes), changes);
SaveItem(obj);
foreach (var (key, value) in changes)
{
row[key] = value;
}
GetUIComponent().UpdateRow(row);
}
#region Column Handling
///
/// Create a set of according to the natural default sizes and formats and captions, based on the contents of .
///
///
///
protected DynamicGridColumns ExtractColumns(Columns columns)
{
var cols = new DynamicGridColumns();
foreach (var col in columns)
{
var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)
cols.Add(mc);
}
return cols;
}
DynamicGridColumns IDynamicGrid.ExtractColumns(IColumns columns)
{
var cols = new DynamicGridColumns();
foreach (var col in columns)
{
var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)
cols.Add(mc);
}
return cols;
}
public override DynamicGridColumns GenerateColumns()
{
var cols = IsDirectEditMode()
? new Columns(ColumnTypeFlags.IncludeVisible | ColumnTypeFlags.IncludeForeignKeys)
: new Columns(ColumnTypeFlags.IncludeVisible | ColumnTypeFlags.IncludeLinked);
var columns = ExtractColumns(cols);
OnGenerateColumns?.Invoke(this, new GenerateColumnsEventArgs(columns));
return columns;
}
public event GenerateColumnsEvent? OnGenerateColumns;
public event SaveColumnsEvent? OnSaveColumns;
public event GetAvailableColumnsEvent? GetAvailableColumns;
protected override void SaveColumns(DynamicGridColumns columns)
{
OnSaveColumns?.Invoke(this, new(columns));
}
#endregion
#region Refresh / Reload
protected abstract void Reload(
Filters criteria, Columns columns, ref SortOrder? sort,
CancellationToken token, Action action);
public Filter? DefineFilter()
{
if (OnDefineFilter is null)
return null;
var result = OnDefineFilter.Invoke(typeof(T)) as Filter;
return result;
}
public IEnumerable ExtractValues(Expression> column, Selection selection)
{
var result = selection == Selection.None
? Enumerable.Empty()
: selection == Selection.Selected
? SelectedRows.Select(r => r.Get(column))
: Data.ExtractValues(column);
return result;
}
protected override void ReloadData(CancellationToken token, Action action)
{
_lookupcache.Clear();
var criteria = new Filters();
var filter = DefineFilter();
if (filter != null)
criteria.Add(filter);
var columns = DataColumns();
var sort = LookupFactory.DefineSort();
if (sort == null && IsSequenced)
sort = new SortOrder("Sequence");
Reload(criteria, columns, ref sort, token, action);
}
public Columns DataColumns()
{
var columns = Columns.None();
foreach (var column in VisibleColumns)
columns.Add(column.ColumnName);
foreach (var column in HiddenColumns.ColumnNames)
columns.Add(new Column(column));
if (!Options.ReadOnly)
{
foreach (var column in LookupFactory.RequiredColumns())
{
columns.Add(column);
}
}
return columns;
}
public void UpdateRow(CoreRow row, T obj, bool invalidateRow = true)
{
ObjectToRow(obj, row);
ObjectToRow(obj, _recordmap[row]);
if (invalidateRow)
{
InvalidateRow(row);
}
}
public void UpdateRows(CoreRow[] rows, T[] objs, bool invalidateRows = true)
{
for(var i = 0; i < objs.Length; ++i)
{
UpdateRow(rows[i], objs[i], invalidateRow: invalidateRows);
}
}
public void AddRow(T data)
{
if (MasterData is null) return;
MasterData.LoadRow(data);
Refresh(false, false);
}
#endregion
#region Item Manipulation
#region Load/Save/Delete
public virtual T[] LoadItems(IList rows)
{
return rows.ToArray(LoadItem);
}
public abstract T LoadItem(CoreRow row);
public abstract void SaveItem(T item);
public virtual void SaveItems(IEnumerable items)
{
foreach (var item in items)
SaveItem(item);
}
protected virtual bool CanDeleteItems(params CoreRow[] rows)
{
return true;
}
public abstract void DeleteItems(params CoreRow[] rows);
protected override bool CanDeleteRows(params CoreRow[] rows) => CanDeleteItems(rows);
public override void DeleteRows(params CoreRow[] rows) => DeleteItems(rows);
#endregion
#region Edit
protected override void NewRow()
{
CreateItems(null);
}
protected void CreateItems(Func>? create)
{
var newRows = new List();
var items = create?.Invoke() ?? CoreUtils.One(CreateItem());
foreach (var item in items)
{
if (!AfterCreate(item))
return;
SaveItem(item);
var datarow = Data.NewRow();
ObjectToRow(item, datarow);
Data.Rows.Add(datarow);
newRows.Add(datarow);
var masterrow = MasterData.NewRow();
ObjectToRow(item, masterrow);
MasterData.Rows.Add(masterrow);
_recordmap[datarow] = masterrow;
}
InvalidateGrid();
SelectedRows = newRows.ToArray();
DoChanged();
}
public virtual DynamicEditorPages LoadEditorPages(T item)
{
DynamicEditorPages pages = new DynamicEditorPages();
DynamicGridUtils.LoadOneToManyPages(typeof(T), pages);
DynamicGridUtils.LoadEnclosedListPages(typeof(T), pages);
DynamicGridUtils.LoadManyToManyPages(typeof(T), pages);
DynamicGridUtils.LoadCustomEditorPages(typeof(T), pages);
pages.RemoveAll(x => !x.Visible);
foreach (var page in pages)
page.Ready = false;
return pages;
}
public virtual void LoadEditorButtons(T item, DynamicEditorButtons buttons)
{
buttons.Clear();
buttons.Add(
"",
Wpf.Resources.help.AsBitmapImage(),
item,
(f, i) =>
{
Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + typeof(T).Name.SplitCamelCase().Replace(" ", "_"))
{ UseShellExecute = true });
}
);
OnLoadEditorButtons?.Invoke(item, buttons);
}
protected virtual void BeforeLoad(IDynamicEditorForm form, T[] items)
{
form.BeforeLoad();
}
void IDynamicGrid.InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func? pageDataHandler, bool preloadPages)
{
InitialiseEditorForm(editor, items.Cast().ToArray(), pageDataHandler, preloadPages);
}
public virtual bool EditItems(object[] items, Func? PageDataHandler = null, bool PreloadPages = false)
{
var values = items.Cast().ToArray();
return EditItems(values, PageDataHandler, PreloadPages);
}
public virtual void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func? pageDataHandler = null, bool preloadPages = false)
{
var pages = items.Length == 1 ? LoadEditorPages(items.First()) : new DynamicEditorPages();
var buttons = new DynamicEditorButtons();
if (items.Length == 1)
LoadEditorButtons(items.First(), buttons);
editor.Setup(items.Any() ? items.First().GetType() : typeof(T), pages, buttons, pageDataHandler, preloadPages);
editor.OnCustomiseColumns = (sender, columns) =>
{
columns.Clear();
columns.AddRange(MasterColumns);
};
editor.OnDefineEditor = (o, c) =>
{
var result = GetEditor(o, c);
if (result != null)
result = result.CloneEditor();
return result;
};
editor.OnFormCustomiseEditor += DoCustomiseEditor;
editor.OnDefineFilter = (type, column) => { return DefineLookupFilter(type, column, items); };
//editor.OnDefineFilter += (o, e) => { return DefineFilter(items, e); };
editor.OnDefineLookups = editor => DefineLookups(editor, items);
editor.OnEditorValueChanged += (s, n, v) => EditorValueChanged(editor, items, n, v);
editor.OnAfterEditorValueChanged += (g, args) => AfterEditorValueChanged(g, items, args);
editor.OnReconfigureEditors = g => DoReconfigureEditors(g, items);
editor.OnValidateData += (o, i) => ValidateData(items);
editor.OnSelectPage += SelectPage;
editor.OnSaveItem = (o, e) =>
{
try
{
using var Wait = new WaitCursor();
DoBeforeSave(editor, items);
if (items.Length == 1)
editor.UnloadEditorPages(false);
foreach (var item in items)
SaveItem(item);
if (items.Length == 1)
editor.UnloadEditorPages(true);
DoAfterSave(editor, items);
}
catch (Exception err)
{
MessageBox.Show(err.Message);
e.Cancel = true;
}
};
BeforeLoad(editor, items);
editor.Items = items;
AfterLoad(editor, items);
}
private void DoCustomiseEditor(IDynamicEditorForm sender, object[] items, DynamicGridColumn column, BaseEditor editor)
{
CustomiseEditor((T[])items, column, editor);
OnCustomiseEditor?.Invoke(sender, (T[])items, column, editor);
}
protected virtual void CustomiseEditor(T[] items, DynamicGridColumn column, BaseEditor editor)
{
}
protected virtual void DoAfterSave(IDynamicEditorForm editor, T[] items)
{
OnAfterSave?.Invoke(editor, items);
}
protected virtual void DoBeforeSave(IDynamicEditorForm editor, T[] items)
{
OnBeforeSave?.Invoke(editor, items);
}
///
/// Edit the with a non-modal editor window, attaching them to the provided.
///
/// List of objects to edit.
/// A callback for when the items are finished being edited.
public virtual void EditItemsNonModal(ISubPanelHost host, T[] items, Action callback, Func? PageDataHandler = null, bool PreloadPages = false)
{
if (!DynamicGridUtils.TryEdit(items, out var editLock))
{
if(editLock.Panel is Window window)
{
Task.Delay(100).ContinueWith(task =>
{
window.WindowState = WindowState.Normal;
window.Activate();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else
{
MessageWindow.ShowMessage("One or more items are already being edited in an open window. You cannot edit the same entity multiple times simultaneously.", "Simultaneous edit.");
}
return;
}
DynamicEditorForm editor;
using (var cursor = new WaitCursor())
{
editor = new DynamicEditorForm();
editor.SetValue(Panel.ZIndexProperty, 999);
editor.Form.DisableOKIfUnchanged = true;
InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);
OnEditorLoaded?.Invoke(editor, items);
}
editLock.Panel = editor;
host.AddSubPanel(editor);
editor.SubPanelClosed += o =>
{
try
{
DynamicGridUtils.FinishEdit(items);
callback(items, editor.Result == true);
}
catch(Exception e)
{
MessageWindow.ShowError("Error occurred while closing editor.", e);
}
};
editor.Show();
}
///
/// Edit the with a modal editor window.
///
/// List of objects to edit.
/// if the items were saved.
public virtual bool EditItems(T[] items, Func? PageDataHandler = null, bool PreloadPages = false)
{
DynamicEditorForm editor;
using (var cursor = new WaitCursor())
{
editor = new DynamicEditorForm()
{
WindowStartupLocation = WindowStartupLocation.CenterScreen
};
editor.SetValue(Panel.ZIndexProperty, 999);
InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);
OnEditorLoaded?.Invoke(editor, items);
}
return editor.ShowDialog() == true;
}
private Dictionary AfterEditorValueChanged(DynamicEditorGrid grid, T[] items, AfterEditorValueChangedArgs args)
{
var changes = new Dictionary();
OnAfterEditorValueChanged(grid, items, args, changes);
return changes;
}
protected virtual void OnAfterEditorValueChanged(DynamicEditorGrid? grid, T[] items, AfterEditorValueChangedArgs args, Dictionary changes)
{
}
protected virtual void DoReconfigureEditors(DynamicEditorGrid grid, T[] items)
{
/*if (items.First() is IDimensioned dimensioned)
{
UpdateEditor(grid, x => x.Dimensions.Quantity, dimensioned.Dimensions.GetUnit().HasQuantity);
UpdateEditor(grid, x => x.Dimensions.Length, dimensioned.Dimensions.GetUnit().HasLength);
UpdateEditor(grid, x => x.Dimensions.Width, dimensioned.Dimensions.GetUnit().HasWidth);
UpdateEditor(grid, x => x.Dimensions.Height, dimensioned.Dimensions.GetUnit().HasHeight);
UpdateEditor(grid, x => x.Dimensions.Weight, dimensioned.Dimensions.GetUnit().HasWeight);
}*/
}
private List? ValidateData(T[] items)
{
var errors = new List();
DoValidate(items, errors);
OnValidate?.Invoke(this, items, errors);
return errors.Count != 0 ? errors : null;
}
protected virtual void DoValidate(T[] items, List errors)
{
}
protected virtual void AfterLoad(IDynamicEditorForm editor, T[] items)
{
editor.AfterLoad();
}
protected virtual void SelectPage(object sender, BaseObject[]? items)
{
}
protected virtual Dictionary EditorValueChanged(IDynamicEditorForm editor, T[] items, string name, object value)
{
var result = DynamicGridUtils.UpdateEditorValue(items, name, value);
if (OnEditorValueChanged != null)
{
var newchanges = OnEditorValueChanged(editor, name, value);
foreach (var key in newchanges.Keys)
result[key] = newchanges[key];
}
return result;
}
private readonly Dictionary, Dictionary