using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; 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 System.Windows.Threading; using InABox.Clients; using InABox.Core; using InABox.Scripting; using InABox.WPF; using Syncfusion.Data; using Syncfusion.UI.Xaml.Grid; using Syncfusion.UI.Xaml.Grid.Cells; using Syncfusion.UI.Xaml.Grid.Helpers; using Syncfusion.Windows.Controls.Cells; using Syncfusion.Windows.Controls.Grid; using Syncfusion.Windows.Shared; using Syncfusion.Windows.Tools.Controls; using Syncfusion.XPS; using Brush = System.Windows.Media.Brush; using Color = System.Drawing.Color; using DataColumn = System.Data.DataColumn; using DataRow = System.Data.DataRow; using FilterElement = Syncfusion.UI.Xaml.Grid.FilterElement; using FontStyle = System.Windows.FontStyle; using Geometry = System.Windows.Media.Geometry; using GridCellToolTipOpeningEventArgs = Syncfusion.UI.Xaml.Grid.GridCellToolTipOpeningEventArgs; using GridFilterEventArgs = Syncfusion.UI.Xaml.Grid.GridFilterEventArgs; using GridSelectionMode = Syncfusion.UI.Xaml.Grid.GridSelectionMode; using Image = System.Windows.Controls.Image; using Path = System.Windows.Shapes.Path; using Point = System.Windows.Point; using RowColumnIndex = Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex; using SolidColorBrush = System.Windows.Media.SolidColorBrush; using String = System.String; using Transform = System.Windows.Media.Transform; using VirtualizingCellsControl = Syncfusion.UI.Xaml.Grid.VirtualizingCellsControl; namespace InABox.DynamicGrid { public class TimeSpanToStringConverter : IValueConverter { public TimeSpanToStringConverter(string format) { Format = format; } public string Format { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is TimeSpan time) { var result = string.IsNullOrWhiteSpace(Format) || string.Equals(Format, "hh:mm") ? Math.Truncate(time.TotalHours).ToString("#00") + ":" + time.Minutes.ToString("D2") : string.Format("{0:" + Format.Replace(":", "\\:") + "}", time); return result; } return ""; } public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } public class TimeSpanAggregate : ISummaryAggregate { public int Count { get; private set; } public TimeSpan Sum { get; private set; } public Action CalculateAggregateFunc() { return CalculateAggregate; } private void CalculateAggregate(IEnumerable items, string property, PropertyDescriptor args) { if (items is IEnumerable rows) { if (string.Equals(args.Name, "Count")) { Count = rows.Count(); } else if (string.Equals(args.Name, "Sum")) { Sum = new TimeSpan(); foreach (var row in rows) if (row[property] is TimeSpan) Sum += (TimeSpan)row[property]; } } else { Logger.Send(LogType.Error, "", $"Attempting to caluculate aggregate on invalid data type '{items.GetType()}'."); } } } public class TimeSpanAggregateRenderer : GridTableSummaryCellRenderer { public override void OnUpdateEditBinding(DataColumnBase dataColumn, GridTableSummaryCell element, object dataContext) { if (DataGrid == null) return; var record = dataContext as SummaryRecordEntry; if (record == null || record.SummaryRow == null) return; foreach (var summaryColumn in record.SummaryRow.SummaryColumns) { if (!summaryColumn.MappingName.Equals(dataColumn.GridColumn.MappingName)) continue; var tsAgg = DataGrid != null ? SummaryCreator.GetSummaryAggregate(summaryColumn, DataGrid.View) as TimeSpanAggregate : null; if (tsAgg != null) { var format = summaryColumn.Format.Replace("{", "").Replace("}", "").Split(':'); var cmd = format.FirstOrDefault(); var fmt = format.Length > 1 ? string.Join(":", format.Skip(1)) : ""; if (string.Equals(cmd, "Sum")) element.Content = string.IsNullOrWhiteSpace(fmt) || string.Equals(fmt, "hh':'mm") ? Math.Truncate(tsAgg.Sum.TotalHours).ToString("#00") + ":" + tsAgg.Sum.Minutes.ToString("D2") : string.Format("{0:" + fmt.Replace(":", "\\:") + "}", tsAgg.Sum); else if (string.Equals(summaryColumn.Format, "Count")) element.Content = string.Format(fmt, tsAgg.Count); } else { base.OnUpdateEditBinding(dataColumn, element, dataContext); } } } } public class DynamicGridRowStyle : DynamicGridStyle { public DynamicGridRowStyle() : base(null) { } public DynamicGridRowStyle(IDynamicGridStyle source) : base(source) { } public override DependencyProperty FontSizeProperty => Control.FontSizeProperty; public override DependencyProperty FontStyleProperty => Control.FontStyleProperty; public override DependencyProperty FontWeightProperty => Control.FontWeightProperty; public override DependencyProperty BackgroundProperty => Control.BackgroundProperty; public override DependencyProperty ForegroundProperty => Control.ForegroundProperty; } public class DynamicGridCellStyle : DynamicGridStyle { public DynamicGridCellStyle() : base(null) { } public DynamicGridCellStyle(IDynamicGridStyle source) : base(source) { } public override DependencyProperty FontSizeProperty => Control.FontSizeProperty; public override DependencyProperty FontStyleProperty => Control.FontStyleProperty; public override DependencyProperty FontWeightProperty => Control.FontWeightProperty; public override DependencyProperty BackgroundProperty => Control.BackgroundProperty; public override DependencyProperty ForegroundProperty => Control.ForegroundProperty; } public class GridSelectionControllerExt : GridSelectionController { public GridSelectionControllerExt(SfDataGrid datagrid) : base(datagrid) { } protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle) { base.ProcessSelectedItemChanged(handle); if (handle.NewValue != null) { //this.DataGrid.ScrollInView(this.CurrentCellManager.CurrentRowColumnIndex); //int rowIndex = this.CurrentCellManager.CurrentRowColumnIndex.RowIndex; var columnIndex = CurrentCellManager.CurrentRowColumnIndex.ColumnIndex; var scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex; DataGrid.ScrollInView(new RowColumnIndex(scrollRowIndex, columnIndex)); } } } public class DynamicGridSummaryStyleSelector : StyleSelector { private readonly IDynamicGrid _grid; public DynamicGridSummaryStyleSelector(IDynamicGrid grid) { _grid = grid; } public override Style SelectStyle(object item, DependencyObject container) { var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex; var col = vcol > -1 && vcol < _grid.VisibleColumns.Count ? _grid.VisibleColumns[vcol] : null; var style = new Style(typeof(GridTableSummaryCell)); style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black))); style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty, col != null ? col.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right)); style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray))); style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0))); style.Setters.Add(new Setter(Control.FontSizeProperty, 12D)); style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold)); return style; } } // Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly // This should show nothing for false, and a tick in a box for true public class BoolToImageConverter : IValueConverter { private static readonly BitmapImage tick = Wpf.Resources.Bullet_Tick.AsBitmapImage(); public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value is bool boolean && boolean ? tick : null; } public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } public class StringToColorImageConverter : IValueConverter { private readonly int _height = 50; private readonly int _width = 25; private readonly Dictionary cache = new(); public StringToColorImageConverter(int width, int height) { _width = width; _height = height; } public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { var str = value?.ToString(); if (str is null) return null; var colorcode = str.TrimStart('#'); if (!cache.ContainsKey(colorcode)) { var col = ImageUtils.StringToColor(colorcode); var bmp = ImageUtils.BitmapFromColor(col, _width, _height, Color.Black); cache[colorcode] = bmp.AsBitmapImage(); } var result = cache[colorcode]; return result; } public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } public class StringArrayConverter : IValueConverter { object? IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string[] strArray) { return string.Join("\n", strArray); } Logger.Send(LogType.Error, "", $"Attempt to convert an object which is not a string array: {value}."); return null; } object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } } [Serializable] class DynamicGridDragFormat { private string entity; public DataTable Table { get; set; } public Type Entity { get => CoreUtils.GetEntity(entity); set => entity = value.EntityName(); } public DynamicGridDragFormat(DataTable table, Type entity) { Table = table; Entity = entity; } } public abstract class DynamicGrid : BaseDynamicGrid where T : BaseObject, new() { private readonly Dictionary _filterpredicates = new(); private UIElement? _header; private readonly Button Add; private bool bChanged; public bool bRefreshing; private readonly Label ClipboardSpacer; private readonly ContextMenu ColumnsMenu; private readonly Button Copy; private readonly Label Count; private readonly Button Cut; private readonly SfDataGrid DataGrid; private readonly Button Delete; private readonly DockPanel Docker; private readonly DynamicRowMovementColumn? down; private readonly Button Edit; private readonly Label EditSpacer; private readonly Button Export; private readonly Label ExportSpacer; private readonly Button DuplicateBtn; private readonly GridRowSizingOptions gridRowResizingOptions = new() { CanIncludeHiddenColumns = false, AutoFitMode = AutoFitMode.SmartFit }; private readonly Button Help; private readonly Button Import; private T? inplaceeditor; private readonly Grid Layout; private readonly Label Loading; private DoubleAnimation LoadingFader = new DoubleAnimation(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true }; protected Dictionary Lookups = new(); //private readonly Button MultiEdit; private readonly Button Paste; private readonly Button Print; private readonly Label PrintSpacer; private readonly StackPanel LeftButtonStack; private readonly StackPanel RightButtonStack; private readonly DynamicRowMovementColumn? up; private DataTable DataGridItems => (DataGrid.ItemsSource as DataTable)!; #region Events public event SelectItemHandler? OnSelectItem; public event OnCellDoubleClick? OnCellDoubleClick; public event OnGridChanged? OnChanged; public void DoChanged() => OnChanged?.Invoke(this); public event EditorValueChangedHandler? OnEditorValueChanged; public override event OnCustomiseEditor? OnCustomiseEditor; public override event OnCustomiseColumns? OnCustomiseColumns; public override event OnFilterRecord? OnFilterRecord; public override event OnDoubleClick? OnDoubleClick; public override event EntitySaveEvent? OnBeforeSave; public override event EntitySaveEvent? OnAfterSave; public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items); public event EditorLoaded OnEditorLoaded; #endregion private DynamicGridCellStyleConverter CellBackgroundConverter; private DynamicGridCellStyleConverter CellForegroundConverter; private DynamicGridCellStyleConverter CellFontSizeConverter; private DynamicGridCellStyleConverter CellFontStyleConverter; private DynamicGridCellStyleConverter CellFontWeightConverter; protected virtual Brush? GetCellBackground(CoreRow row, String columnname) => null; protected virtual Brush? GetCellForeground(CoreRow row, String columnname) => null; protected virtual double? GetCellFontSize(CoreRow row, String columnname) => null; protected virtual FontStyle? GetCellFontStyle(CoreRow row, String columnname) => null; protected virtual FontWeight? GetCellFontWeight(CoreRow row, String columnname) => null; public DynamicGrid() : base() { IsReady = false; Data = new CoreTable(); ColumnsMenu = new ContextMenu(); var SelectColumns = new MenuItem { Header = "Select Columns" }; SelectColumns.Click += SelectColumnsClick; ColumnsMenu.Items.Add(SelectColumns); LoadColumnsMenu(ColumnsMenu); MasterColumns = new DynamicGridColumns(); MasterColumns.ExtractColumns(typeof(T)); foreach (var column in LookupFactory.RequiredColumns().ColumnNames()) { AddHiddenColumn(column); } ActionColumns = new DynamicActionColumns(); if (IsSequenced) { up = new DynamicRowMovementColumn(DynamicRowMovement.Up, SwapRows); ActionColumns.Add(up); down = new DynamicRowMovementColumn(DynamicRowMovement.Down, SwapRows); ActionColumns.Add(down); HiddenColumns.Add(x => (x as ISequenceable)!.Sequence); } CellBackgroundConverter = new DynamicGridCellStyleConverter(this, GetCellBackground); CellForegroundConverter = new DynamicGridCellStyleConverter(this, GetCellForeground); CellFontSizeConverter = new DynamicGridCellStyleConverter(this, GetCellFontSize); CellFontStyleConverter = new DynamicGridCellStyleConverter(this, GetCellFontStyle); CellFontWeightConverter = new DynamicGridCellStyleConverter(this, GetCellFontWeight); VisibleColumns = new DynamicGridColumns(); DataGrid = new SfDataGrid(); DataGrid.VerticalAlignment = VerticalAlignment.Stretch; DataGrid.HorizontalAlignment = HorizontalAlignment.Stretch; DataGrid.HeaderContextMenu = ColumnsMenu; DataGrid.CellTapped += DataGrid_CellTapped; DataGrid.CellDoubleTapped += DataGrid_CellDoubleTapped; DataGrid.SelectionMode = GridSelectionMode.Extended; DataGrid.SelectionUnit = GridSelectionUnit.Row; DataGrid.CanMaintainScrollPosition = true; DataGrid.NavigationMode = NavigationMode.Row; DataGrid.AllowEditing = false; DataGrid.EditTrigger = EditTrigger.OnTap; DataGrid.CurrentCellBeginEdit += DataGrid_CurrentCellBeginEdit; DataGrid.CurrentCellEndEdit += DataGrid_CurrentCellEndEdit; DataGrid.CurrentCellValueChanged += DataGrid_CurrentCellValueChanged; DataGrid.CurrentCellDropDownSelectionChanged += DataGrid_CurrentCellDropDownSelectionChanged; DataGrid.CurrentCellRequestNavigate += DataGrid_CurrentCellRequestNavigate; DataGrid.PreviewKeyUp += DataGrid_PreviewKeyUp; DataGrid.CurrentCellActivated += DataGrid_CurrentCellActivated; DataGrid.BorderBrush = new SolidColorBrush(Colors.Gray); DataGrid.BorderThickness = new Thickness(0.75F); DataGrid.Background = new SolidColorBrush(Colors.DimGray); DataGrid.AutoGenerateColumns = false; DataGrid.ColumnSizer = GridLengthUnitType.AutoLastColumnFill; DataGrid.SelectionForegroundBrush = BaseDynamicGrid.SelectionForeground; DataGrid.RowSelectionBrush = BaseDynamicGrid.SelectionBackground; DataGrid.CurrentCellBorderThickness = new Thickness(0); DataGrid.AllowFiltering = false; DataGrid.EnableDataVirtualization = true; DataGrid.RowHeight = 30; DataGrid.QueryRowHeight += DataGrid_QueryRowHeight; DataGrid.HeaderRowHeight = 30; DataGrid.MouseLeftButtonUp += DataGrid_MouseLeftButtonUp; DataGrid.MouseRightButtonUp += DataGrid_MouseRightButtonUp; DataGrid.KeyUp += DataGrid_KeyUp; DataGrid.PreviewGotKeyboardFocus += DataGrid_PreviewGotKeyboardFocus; //DataGrid.SelectionController = new GridSelectionControllerExt(DataGrid); DataGrid.FilterChanged += DataGrid_FilterChanged; DataGrid.FilterItemsPopulating += DataGrid_FilterItemsPopulating; var fltstyle = new Style(typeof(GridFilterControl)); fltstyle.Setters.Add(new Setter(GridFilterControl.FilterModeProperty, FilterMode.Both)); fltstyle.Setters.Add(new Setter(GridFilterControl.SortOptionVisibilityProperty, Visibility.Collapsed)); DataGrid.FilterPopupStyle = fltstyle; DataGrid.RowStyleSelector = RowStyleSelector; DataGrid.TableSummaryCellStyleSelector = new DynamicGridSummaryStyleSelector(this); //DataGrid.MouseMove += DataGrid_MouseMove; DataGrid.CellToolTipOpening += DataGrid_CellToolTipOpening; //var headstyle = new Style(typeof(GridHeaderCellControl)); //headstyle.Setters.Add(new Setter(GridHeaderCellControl.BackgroundProperty, new SolidColorBrush(Colors.WhiteSmoke))); //headstyle.Setters.Add(new Setter(GridHeaderCellControl.ForegroundProperty, new SolidColorBrush(Colors.Green))); //headstyle.Setters.Add(new Setter(GridHeaderCellControl.FontSizeProperty, 12.0F)); //DataGrid.HeaderStyle = headstyle; DataGrid.SizeChanged += DataGrid_SizeChanged; DataGrid.SetValue(Grid.RowProperty, 1); 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(typeof(T).Name.Split('.').Last().SplitCamelCase().Replace(" ", "_")); 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; 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 }; Cut = CreateButton(Wpf.Resources.cut.AsBitmapImage(Color.White)); Cut.Margin = new Thickness(0, 2, 2, 0); Cut.Click += Cut_Click; Copy = CreateButton(Wpf.Resources.copy.AsBitmapImage(Color.White)); Copy.Margin = new Thickness(0, 2, 2, 0); Copy.Click += Copy_Click; Paste = CreateButton(Wpf.Resources.paste.AsBitmapImage(Color.White)); Paste.Margin = new Thickness(0, 2, 2, 0); Paste.Click += Paste_Click; ClipboardSpacer = new Label { Width = 5 }; Export = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Export"); Export.Margin = new Thickness(0, 2, 2, 0); Export.Click += Export_Click; Import = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Import"); Import.Margin = new Thickness(0, 2, 2, 0); Import.Click += Import_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); //Stack.Children.Add(MultiEdit); LeftButtonStack.Children.Add(EditSpacer); LeftButtonStack.Children.Add(Print); LeftButtonStack.Children.Add(PrintSpacer); LeftButtonStack.Children.Add(Cut); LeftButtonStack.Children.Add(Copy); LeftButtonStack.Children.Add(Paste); LeftButtonStack.Children.Add(ClipboardSpacer); LeftButtonStack.Children.Add(Export); LeftButtonStack.Children.Add(Import); 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), DoDuplicate); 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) }); Layout.Children.Add(DataGrid); Layout.Children.Add(Loading); Layout.Children.Add(Docker); //Scroll.ApplyTemplate(); Content = Layout; Options.Clear(); } public bool IsReady { get; private 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 data in the grid. This is until is called. /// public CoreTable? MasterData { get; set; } public DynamicActionColumns ActionColumns { get; } private bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable))); public override double RowHeight { get => DataGrid.RowHeight; set => DataGrid.RowHeight = value; } public override double HeaderHeight { get => DataGrid.HeaderRowHeight; set => DataGrid.HeaderRowHeight = value; } protected override void OptionsChanged(object sender, EventArgs args) { ColumnsMenu.Visibility = Options.Contains(DynamicGridOption.SelectColumns) ? Visibility.Visible : Visibility.Hidden; Help.Visibility = Options.Contains(DynamicGridOption.ShowHelp) ? Visibility.Visible : Visibility.Collapsed; Add.Visibility = Options.Contains(DynamicGridOption.AddRows) ? Visibility.Visible : Visibility.Collapsed; Edit.Visibility = Options.Contains(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed; EditSpacer.Visibility = Options.Contains(DynamicGridOption.AddRows) || Options.Contains(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed; Print.Visibility = Options.Contains(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed; PrintSpacer.Visibility = Options.Contains(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed; Cut.Visibility = IsSequenced ? Visibility.Visible : Visibility.Collapsed; Copy.Visibility = IsSequenced ? Visibility.Visible : Visibility.Collapsed; Paste.Visibility = IsSequenced ? Visibility.Visible : Visibility.Collapsed; ClipboardSpacer.Visibility = IsSequenced ? Visibility.Visible : Visibility.Collapsed; Export.Visibility = Options.Contains(DynamicGridOption.ExportData) ? Visibility.Visible : Visibility.Collapsed; Import.Visibility = Options.Contains(DynamicGridOption.ImportData) ? Visibility.Visible : Visibility.Collapsed; ExportSpacer.Visibility = Options.Contains(DynamicGridOption.ExportData) || Options.Contains(DynamicGridOption.ImportData) ? Visibility.Visible : Visibility.Collapsed; DataGrid.NavigationMode = Options.Contains(DynamicGridOption.DirectEdit) ? NavigationMode.Cell : NavigationMode.Row; DataGrid.AllowEditing = Options.Contains(DynamicGridOption.DirectEdit); Count.Visibility = Options.Contains(DynamicGridOption.RecordCount) ? Visibility.Visible : Visibility.Collapsed; Delete.Visibility = Options.Contains(DynamicGridOption.DeleteRows) ? Visibility.Visible : Visibility.Collapsed; DataGrid.AllowFiltering = Options.Contains(DynamicGridOption.FilterRows); DataGrid.FilterRowPosition = Options.Contains(DynamicGridOption.FilterRows) ? FilterRowPosition.FixedTop : FilterRowPosition.None; if (Options.Contains(DynamicGridOption.DragSource)) { DataGrid.AllowDraggingRows = true; DataGrid.RowDragDropController.DragStart += RowDragDropController_DragStart; } else if (DataGrid.AllowDraggingRows) { DataGrid.AllowDraggingRows = false; DataGrid.RowDragDropController.DragStart -= RowDragDropController_DragStart; } if (Options.Contains(DynamicGridOption.DragTarget) && !DataGrid.AllowDrop) { DataGrid.Drop += DataGrid_Drop; DataGrid.AllowDrop = true; } else if (DataGrid.AllowDrop) { DataGrid.Drop -= DataGrid_Drop; DataGrid.AllowDrop = false; } DataGrid.SelectionMode = Options.Contains(DynamicGridOption.MultiSelect) ? GridSelectionMode.Extended : GridSelectionMode.Single; if (up != null) up.Position = Options.Contains(DynamicGridOption.EditRows) ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden; if (down != null) down.Position = Options.Contains(DynamicGridOption.EditRows) ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden; if (DuplicateBtn != null) DuplicateBtn.Visibility = Visibility.Collapsed; } protected override DynamicGridRowStyleSelector GetRowStyleSelector() { return new DynamicGridRowStyleSelector(); } protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style) { var result = base.GetRowStyle(row, style); 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; } return result; } private void DataGrid_CurrentCellActivated(object? sender, CurrentCellActivatedEventArgs e) { if (!Options.Contains(DynamicGridOption.DirectEdit)) return; if ((DataGrid.SelectionController.CurrentCellManager.CurrentCell?.IsEditing != true) && e.ActivationTrigger == ActivationTrigger.Keyboard) DataGrid.SelectionController.CurrentCellManager.BeginEdit(); } private void DataGrid_PreviewKeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.OemPeriod) { var editor = e.OriginalSource as TimeSpanEdit; if (editor != null && editor.SelectionStart < 2) editor.SelectionStart = 3; } else if (e.Key == Key.Tab) { } //throw new NotImplementedException(); } private void DataGrid_CurrentCellRequestNavigate(object? sender, CurrentCellRequestNavigateEventArgs e) { //throw new NotImplementedException(); } private void DataGrid_FilterChanged(object? o, GridFilterEventArgs e) { var col = DataGrid.Columns.IndexOf(e.Column); if (ColumnList[col] is DynamicActionColumn column) { if (e.FilterPredicates != null) { var filter = e.FilterPredicates.Select(x => x.FilterValue.ToString()!).ToArray(); bool include = e.FilterPredicates.Any(x => x.FilterType == FilterType.Equals); column.SelectedFilters = include ? filter : column.Filters.Except(filter).ToArray(); } else column.SelectedFilters = Array.Empty(); DataGrid.ClearFilter(e.Column); //e.FilterPredicates?.Clear(); //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.NotEquals, FilterValue = "" }); //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.Equals, FilterValue = "" }); Refresh(false, false); e.Handled = true; } if (e.FilterPredicates == null) { if (_filterpredicates.ContainsKey(e.Column.MappingName)) _filterpredicates.Remove(e.Column.MappingName); } else { _filterpredicates[e.Column.MappingName] = Serialization.Serialize(e.FilterPredicates, true); } UpdateRecordCount(); } private void DataGrid_FilterItemsPopulating(object? sender, GridFilterItemsPopulatingEventArgs e) { var col = DataGrid.Columns.IndexOf(e.Column); var column = ColumnList[col] as DynamicActionColumn; if (column != null) e.ItemsSource = column.Filters.Select(x => new FilterElement { DisplayText = x, ActualValue = x, IsSelected = column.SelectedFilters == null || column.SelectedFilters.Contains(x) }); } private CoreRow? GetRowFromIndex(int rowIndex) { var row = rowIndex - (Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1); if (row < 0) return null; row = DataGridItems.Rows.IndexOf((DataGrid.View.Records[row].Data as DataRowView)!.Row); if (row < 0) return null; return Data.Rows[row]; } private void DataGrid_CellToolTipOpening(object? sender, GridCellToolTipOpeningEventArgs e) { if (ColumnList[e.RowColumnIndex.ColumnIndex] is not DynamicActionColumn col) return; var toolTip = col.ToolTip; if (toolTip is null) return; var row = GetRowFromIndex(e.RowColumnIndex.RowIndex); if (row is null) return; e.ToolTip.Template = TemplateGenerator.CreateControlTemplate( typeof(ToolTip), () => toolTip.Invoke(col, row) ); } //private void DataGrid_MouseMove(object sender, MouseEventArgs e) //{ // var visualcontainer = DataGrid.GetVisualContainer(); // var point = e.GetPosition(visualcontainer); // var rowColumnIndex = visualcontainer.PointToCellRowColumnIndex(point); // var recordIndex = DataGrid.ResolveToRecordIndex(rowColumnIndex.RowIndex); // if (recordIndex < 0) // return; // if (!rowColumnIndex.IsEmpty) // { // if (DataGrid.View.TopLevelGroup != null) // { // // Get the current row record while grouping // var record = DataGrid.View.TopLevelGroup.DisplayElements[recordIndex]; // if (record.GetType() == typeof(RecordEntry)) // { // var data = (record as RecordEntry).Data as CoreRow; // } // } // else // { // //For getting the record, need to resolve the corresponding record index from row index                      // var record1 = DataGrid.View.Records[DataGrid.ResolveToRecordIndex(rowColumnIndex.RowIndex)].Data; // } // //Gets the column from ColumnsCollection by resolving the corresponding column index from  GridVisibleColumnIndex                       // var gridColumn = DataGrid.Columns[DataGrid.ResolveToGridVisibleColumnIndex(rowColumnIndex.ColumnIndex)]; // if (gridColumn is GridImageColumn) // { // } // } //} protected virtual void LoadColumnsMenu(ContextMenu menu) { } private void DataGrid_CurrentCellBeginEdit(object? sender, CurrentCellBeginEditEventArgs e) { var headerrows = Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1; if (e.RowColumnIndex.RowIndex < headerrows) return; if (inplaceeditor == null) inplaceeditor = LoadItem(Data.Rows[e.RowColumnIndex.RowIndex - headerrows]); var column = DataGrid.Columns[e.RowColumnIndex.ColumnIndex] as GridComboBoxColumn; if (column != null && column.ItemsSource == null) { var colname = column.MappingName; var colno = DataGridItems.Columns.IndexOf(colname); var property = Data.Columns[colno].ColumnName; var prop = CoreUtils.GetProperty(typeof(T), property); var editor = prop.GetEditor(); if (editor is ILookupEditor) { if (!Lookups.ContainsKey(property)) Lookups[property] = ((ILookupEditor)editor).Values(property); var combo = column; combo.ItemsSource = Lookups[property].ToDictionary(Lookups[property].Columns[0].ColumnName, "Display"); combo.SelectedValuePath = "Key"; combo.DisplayMemberPath = "Value"; } } bChanged = false; } private void DataGrid_CurrentCellValueChanged(object? sender, CurrentCellValueChangedEventArgs e) { var headerrows = Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1; if (e.RowColumnIndex.RowIndex < headerrows) return; if (e.Column is GridCheckBoxColumn) inplaceeditor = LoadItem(Data.Rows[e.RowColumnIndex.RowIndex - headerrows]); if (inplaceeditor is not null) UpdateData(inplaceeditor, e.RowColumnIndex.ColumnIndex); if (e.Column is GridCheckBoxColumn) inplaceeditor = null; if (inplaceeditor is not null) bChanged = true; } private void DataGrid_CurrentCellDropDownSelectionChanged(object? sender, CurrentCellDropDownSelectionChangedEventArgs e) { var headerrows = Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1; if (e.RowColumnIndex.RowIndex < headerrows) return; inplaceeditor ??= LoadItem(Data.Rows[e.RowColumnIndex.RowIndex - headerrows]); if (inplaceeditor != null) bChanged = true; } protected void UpdateCell(int row, string colname, object value) { var datacolname = colname.Replace(".", "_"); var table = DataGridItems; var colno = table.Columns.IndexOf(datacolname); var corecol = Data.Columns[colno].ColumnName; var corerow = Data.Rows[row]; corerow[corecol] = value; var datarow = table.Rows[row]; datarow[datacolname] = value; } private void DataGrid_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e) { var headerrows = Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1; if (e.RowColumnIndex.RowIndex < headerrows) return; if (inplaceeditor is not null && bChanged) UpdateData(inplaceeditor, e.RowColumnIndex.ColumnIndex); bChanged = false; inplaceeditor = null; DataGridItems.AcceptChanges(); } private void UpdateData(T obj, int columnindex) { var table = DataGridItems; var iRow = SelectedRows.First().Index; //e.RowColumnIndex.RowIndex - (Options.Contains(DynamicGridOptions.FilterRows) ? 2 : 1); if (iRow > table.Rows.Count) return; var row = table.Rows[iRow]; var colname = DataGrid.Columns[columnindex].MappingName; var value = row[colname]; var colno = table.Columns.IndexOf(colname); var corecol = Data.Columns[colno].ColumnName; var corerow = Data.Rows[iRow]; corerow[corecol] = value; Dictionary changes = new Dictionary(); if (DataGrid.Columns[colname] is GridComboBoxColumn combo) { var prefix = String.Join(".", corecol.Split(".").Reverse().Skip(1).Reverse()); var field = corecol.Split(".").Last(); var lookups = (combo.ItemsSource as DataView)?.Table; if (lookups != null) { var lookuprow = lookups.AsEnumerable().FirstOrDefault(x => x[field] == value); if (lookuprow != null) { foreach (DataColumn lookupcol in lookups.Columns) { var prop = String.IsNullOrWhiteSpace(prefix) ? lookupcol.ColumnName : String.Join(".", new String[] { prefix, lookupcol.ColumnName }); DynamicGridUtils.UpdateEditorValue(new BaseObject[] { inplaceeditor }, prop, lookuprow[lookupcol], changes); //CoreUtils.SetPropertyValue(obj, prop, lookuprow[lookupcol]); } } } } else DynamicGridUtils.UpdateEditorValue(new BaseObject[] { inplaceeditor }, corecol, value, changes); //CoreUtils.SetPropertyValue(obj, corecol, value); SaveItem(obj); foreach (var key in changes.Keys) UpdateCell(iRow, key, changes[key]); //Data.LoadRow(corerow, obj); foreach (var column in Data.Columns.Where(x => !string.Equals(corecol, x.ColumnName))) { var scol = column.ColumnName.Replace('.', '_'); row[scol] = corerow[column.ColumnName] ?? DBNull.Value; } for (var i = 0; i < ActionColumns.Count; i++) row[string.Format("ActionColumn{0}", i)] = ActionColumns[i].Data(corerow); } private void DataGrid_QueryRowHeight(object? sender, QueryRowHeightEventArgs e) { if (e.RowIndex > 0) { e.Height = DataGrid.RowHeight; if (DataGrid.GridColumnSizer.GetAutoRowHeight(e.RowIndex, gridRowResizingOptions, out var autoHeight)) if (autoHeight > DataGrid.RowHeight) e.Height = autoHeight; e.Handled = true; } } private void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e) { if (IsReady && !bRefreshing) ResizeColumns(DataGrid, e.NewSize.Width - 2, e.NewSize.Height - 2); } #region Row Selections protected CoreRow[] GetVisibleRows() { var items = DataGrid.ItemsSource; var result = new List(); var table = DataGridItems; var rows = DataGrid.View.Records.Select(x => (x.Data as DataRowView)!).ToList(); foreach (var row in rows) { var iRow = table.Rows.IndexOf(row.Row); result.Add(Data.Rows[iRow]); } //foreach (var item in DataGrid.SelectedItems) //{ // if (item is CoreRow) // { // //result.Add(item as CoreRow); // } // else // { // var datarow = item as System.Data.DataRowView; // int row = datarow.Row.Table.Rows.IndexOf(datarow.Row); // result.Add(Data.Rows[row]); // } //} return result.ToArray(); } private CoreRow[] GetSelectedRows() { //Logger.Send(LogType.Information, ClientFactory.UserID, String.Format("{0}: GetSelectedRows({1})", this.GetType().EntityName(), DataGrid.SelectedItems.Count)); var result = new List(); foreach (var item in DataGrid.SelectedItems) if (item is CoreRow) { //result.Add(item as CoreRow); } else { var datarow = item as DataRowView; if (datarow != null) { var row = datarow.Row.Table.Rows.IndexOf(datarow.Row); result.Add(Data.Rows[row]); } } return result.ToArray(); } private void SetSelectedRows(CoreRow[] rows) { //Logger.Send(LogType.Information, ClientFactory.UserID, String.Format("{0}: SetSelectedRows({1})", this.GetType().EntityName(), rows.Length)); // CoreTableAdapter adapter = (CoreTableAdapter)DataGrid.ItemsSource; DataGrid.SelectedItems.Clear(); var bFirst = true; foreach (var row in rows.Where(x => x.Index > -1)) { //DataTable table = (DataTable)DataGrid.ItemsSource; if (bFirst || Options.Contains(DynamicGridOption.MultiSelect)) DataGrid.SelectedItems.Add(DataGrid.GetRecordAtRowIndex(row.Index + (Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1))); bFirst = false; } } public override CoreRow[] SelectedRows { get => GetSelectedRows(); set => SetSelectedRows(value); } protected virtual void SelectItems(CoreRow[]? rows) { if (IsReady) OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows)); DuplicateBtn.Visibility = typeof(T).IsAssignableTo(typeof(IDuplicatable)) && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed; } private bool bFilterVisible; private bool bSwallowKey; private void DataGrid_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { var bOld = bFilterVisible; if (e.NewFocus is GridFilterControl) bFilterVisible = true; else if (e.NewFocus is ScrollViewer || e.NewFocus is SfDataGrid) bFilterVisible = false; if (bOld && !bFilterVisible) { //Logger.Send(LogType.Information, "", String.Format("{0}: PreviewGotKeyboardFocus -> {1}", this.GetType().EntityName(), e.NewFocus.GetType().EntityName())); SelectItems(SelectedRows); bSwallowKey = true; } } private void DataGrid_KeyUp(object sender, KeyEventArgs e) { if (!bFilterVisible && !bSwallowKey && DataGrid.SelectedIndex > -1) //Logger.Send(LogType.Information, "", String.Format("{0}: KeyUp -> {1}", this.GetType().EntityName(), SelectedRows.Length)); SelectItems(SelectedRows); bSwallowKey = false; if (IsSequenced) { 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(); } } } private DispatcherTimer? clicktimer; private void StartTimer() { if (clicktimer is null) { clicktimer = new DispatcherTimer(); clicktimer.Interval = TimeSpan.FromMilliseconds(200); clicktimer.Tick += (o, e) => { clicktimer.IsEnabled = false; SelectItems(SelectedRows); }; } clicktimer.IsEnabled = true; } private void StopTimer() { if (clicktimer is not null) clicktimer.IsEnabled = false; } private void DataGrid_MouseRightButtonUp(object sender, MouseButtonEventArgs e) { if (!IsEnabled) return; var visualContainer = DataGrid.GetVisualContainer(); var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer)); var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex); if ((columnindex < 0) || (columnindex >= ColumnList.Count)) return; var column = ColumnList[columnindex] as DynamicActionColumn; var rowindex = rowcolumnindex.RowIndex - (Options.Contains(DynamicGridOption.FilterRows) ? 2 : 1); if (rowindex < 0) return; var row = Data.Rows[rowindex]; var menu = column?.ContextMenu?.Invoke(SelectedRows); if (menu != null && menu.Items.Count > 0) menu.IsOpen = true; } private void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (!IsEnabled) return; // Header Click Here! if (DataGrid.SelectedIndex == -1) { var visualContainer = DataGrid.GetVisualContainer(); var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer)); var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex); if (columnindex > -1 && columnindex < ColumnList.Count) { var bRefresh = false; var dac = ColumnList[columnindex] as DynamicActionColumn; if (dac != null) if (dac.Action?.Invoke(null) == true) bRefresh = true; if (bRefresh) Dispatcher.Invoke(() => { Refresh(false, true); }); } } else if (!bFilterVisible) { StartTimer(); } bSwallowKey = false; } private void DataGrid_CellTapped(object? sender, GridCellTappedEventArgs e) { if (!IsEnabled) return; var dac = ColumnList[e.RowColumnIndex.ColumnIndex] as DynamicActionColumn; if (dac != null) { var bRefresh = false; { foreach (var row in SelectedRows) if (dac.Action?.Invoke(row) == true) bRefresh = true; } if (bRefresh) Task.Run(() => { Dispatcher.Invoke(() => { Refresh(true, true); }); }); } else { StartTimer(); } } protected virtual void DoDoubleClick(object sender) { if (Options.Contains(DynamicGridOption.DirectEdit)) return; SelectItems(SelectedRows); var args = new HandledEventArgs(false); OnDoubleClick?.Invoke(sender, args); if (args.Handled) return; if (Options.Contains(DynamicGridOption.EditRows)) DoEdit(); } private void DataGrid_CellDoubleTapped(object? sender, GridCellDoubleTappedEventArgs e) { StopTimer(); if (OnCellDoubleClick is not null && ColumnList[e.RowColumnIndex.ColumnIndex] is DynamicGridColumn column) { var row = GetRowFromIndex(e.RowColumnIndex.RowIndex); var args = new DynamicGridCellClickEventArgs(row, column); OnCellDoubleClick?.Invoke(this, args); if (args.Handled) return; } if (e.Record != null) DoDoubleClick(this); } #endregion #region Column Handling private readonly List ColumnList = new(); protected virtual DynamicGridColumns LoadColumns() { var result = new DynamicGridColumns(); var cols = Options.Contains(DynamicGridOption.DirectEdit) ? new Columns().Default(ColumnType.IncludeForeignKeys, ColumnType.ExcludeID) : new Columns().Default(ColumnType.IncludeLinked, ColumnType.ExcludeID); result.AddRange(MasterColumns.Where(x => cols.Items.Any(c => c.Property.Equals(x.ColumnName))) .OrderBy(x => CoreUtils.GetPropertySequence(typeof(T), x.ColumnName))); return result; } private bool SwapRows(int row1, int row2) { CoreRow[] rows = Data.Rows.Where(x => x.Index.Equals(row1) || x.Index.Equals(row2)).ToArray(); var items = LoadItems(rows); var first = (items.First() as ISequenceable)!; var last = (items.Last() as ISequenceable)!; var iBuf1 = first.Sequence; var iBuf2 = last.Sequence; first.Sequence = iBuf2; last.Sequence = iBuf1; SaveItems(items); return true; } protected virtual void SaveColumns(DynamicGridColumns columns) { } public override int DesiredWidth() { var result = 0; for (var i = 0; i < ColumnList.Count; i++) { var col = ColumnList[i]; if (col is DynamicActionColumn) { result += (int)RowHeight; } else if (col is DynamicGridColumn) { var dgc = (DynamicGridColumn)col; result += dgc.Width > 0 ? dgc.Width : 300; } } return result; } private void ResizeColumns(SfDataGrid grid, double width, double height) { if (Data == null || width <= 0) return; var fAvailWidth = width; //if (Data.Rows.Count * (DataGrid.RowHeight + 1) + DataGrid.HeaderRowHeight > height + 0.5F) if (height < DataGrid.AutoScroller.VScrollBar.Maximum) fAvailWidth -= (SystemParameters.VerticalScrollBarWidth + 0.75); double fCurWidth = 0.0F; var NumAutoCols = 0; var colWidths = new Dictionary(); for (var i = 0; i < ColumnList.Count; i++) { var col = ColumnList[i]; if (col is DynamicActionColumn dac) { colWidths[i] = dac.Width == 0 ? RowHeight : dac.Width; fCurWidth += colWidths[i]; } else if (col is DynamicGridColumn dgc) { colWidths[i] = dgc.Width; if (dgc.Width != 0) fCurWidth += Math.Max(0.0F, dgc.Width); else NumAutoCols++; } } if (NumAutoCols > 0) { var fAutoWidth = (fAvailWidth - fCurWidth) / NumAutoCols; if (fAutoWidth < 100) fAutoWidth = 100; for (var i = 0; i < ColumnList.Count; i++) if (colWidths[i] == 0) colWidths[i] = fAutoWidth; } foreach (var index in colWidths.Keys) DataGrid.Columns[index].Width = Math.Max(0.0F, colWidths[index]); var vc = DataGrid.GetVisualContainer(); vc.RowHeightManager.Reset(); vc.InvalidateMeasureInfo(); if (vc.ScrollOwner != null) vc.ScrollOwner.HorizontalScrollBarVisibility = vc.ExtentWidth <= fAvailWidth ? ScrollBarVisibility.Hidden : ScrollBarVisibility.Visible; } private void LoadActionColumns(DynamicActionColumnPosition position) { for (var i = 0; i < ActionColumns.Count; i++) { var column = ActionColumns[i]; if (column.Position == position) { //String sColName = String.Format("ActionColumn{0}{1}", i, position == DynamicActionColumnPosition.Start ? "L" : "R"); var sColName = string.Format("ActionColumn{0}", i); gridRowResizingOptions.ExcludeColumns.Add(sColName); if (column is DynamicImageColumn imgcol) { var newcol = new GridImageColumn(); newcol.MappingName = sColName; //newcol.Stretch = Stretch.Uniform; newcol.Width = column.Width == 0 ? DataGrid.RowHeight : column.Width; newcol.Padding = new Thickness(4); newcol.ImageHeight = DataGrid.RowHeight - 8; newcol.ImageWidth = DataGrid.RowHeight - 8; newcol.ColumnSizer = GridLengthUnitType.None; newcol.HeaderText = column.HeaderText; newcol.AllowSorting = false; ApplyFilterStyle(newcol, true, true); newcol.ShowToolTip = column.ToolTip != null; var style = new Style(); style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); style.Setters.Add(new Setter(IsEnabledProperty, false)); newcol.FilterRowCellStyle = style; var headstyle = new Style(typeof(GridHeaderCellControl)); headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black))); headstyle.Setters.Add(new Setter(FontSizeProperty, 12D)); if (!string.IsNullOrWhiteSpace(column.HeaderText)) { //headstyle.Setters.Add(new Setter(LayoutTransformProperty, new RotateTransform(270.0F))); headstyle.Setters.Add(new Setter(BorderThicknessProperty, new Thickness(0.0, 0.0, 0, 0))); headstyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, 0, 0.75, 0.75))); if (imgcol.VerticalHeader) headstyle.Setters.Add(new Setter(TemplateProperty, Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate)); } else { var image = imgcol.Image?.Invoke(null); if (image != null) { var template = new ControlTemplate(typeof(GridHeaderCellControl)); var border = new FrameworkElementFactory(typeof(Border)); border.SetValue(Border.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)); border.SetValue(Border.PaddingProperty, new Thickness(4)); border.SetValue(MarginProperty, new Thickness(0, 0, 1, 1)); var img = new FrameworkElementFactory(typeof(Image)); img.SetValue(Image.SourceProperty, image); border.AppendChild(img); template.VisualTree = border; headstyle.Setters.Add(new Setter(TemplateProperty, template)); } } newcol.HeaderStyle = headstyle; DataGrid.Columns.Add(newcol); ColumnList.Add(column); } else if (column is DynamicTextColumn txtCol) { var newcol = new GridTextColumn(); gridRowResizingOptions.ExcludeColumns.Add(sColName); newcol.TextWrapping = TextWrapping.NoWrap; newcol.TextAlignment = txtCol.Alignment == Alignment.NotSet ? TextAlignment.Left : txtCol.Alignment == Alignment.BottomLeft || txtCol.Alignment == Alignment.MiddleLeft || txtCol.Alignment == Alignment.TopLeft ? TextAlignment.Left : txtCol.Alignment == Alignment.BottomCenter || txtCol.Alignment == Alignment.MiddleCenter || txtCol.Alignment == Alignment.TopCenter ? TextAlignment.Center : TextAlignment.Right; newcol.AllowEditing = false; newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged; newcol.MappingName = sColName; newcol.Width = column.Width == 0 ? DataGrid.RowHeight : column.Width; newcol.ColumnSizer = GridLengthUnitType.None; newcol.HeaderText = column.HeaderText; newcol.AllowFiltering = column.Filters != null && column.Filters.Any(); newcol.AllowSorting = false; newcol.FilterRowOptionsVisibility = Visibility.Collapsed; newcol.ShowToolTip = column.ToolTip != null; var style = new Style(); style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); style.Setters.Add(new Setter(IsEnabledProperty, false)); newcol.FilterRowCellStyle = style; var headstyle = new Style(typeof(GridHeaderCellControl)); headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black))); headstyle.Setters.Add(new Setter(FontSizeProperty, 12D)); headstyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, -0.75, 0, 0.75))); headstyle.Setters.Add(new Setter(BorderThicknessProperty, new Thickness(0.75))); if (txtCol.VerticalHeader) { headstyle.Setters.Add(new Setter(HorizontalContentAlignmentProperty, HorizontalAlignment.Left)); headstyle.Setters.Add(new Setter(TemplateProperty, Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate)); } newcol.HeaderStyle = headstyle; DataGrid.Columns.Add(newcol); ColumnList.Add(column); } } } } private bool CanSort() { return !IsSequenced; } private void ReloadColumns() { ConfigureColumns(MasterColumns /*, false */); VisibleColumns = LoadColumns(); ConfigureColumns(VisibleColumns /*, true */); DataGrid.Columns.Suspend(); ColumnList.Clear(); DataGrid.Columns.Clear(); DataGrid.TableSummaryRows.Clear(); var Summaries = new ObservableCollection(); gridRowResizingOptions.ExcludeColumns = new List(); LoadActionColumns(DynamicActionColumnPosition.Start); foreach (var column in VisibleColumns) { var filtering = true; IProperty? prop; try { prop = DatabaseSchema.Property(typeof(T), column.ColumnName); } catch (Exception e) { Logger.Send(LogType.Error, ClientFactory.UserID, string.Format("Error constructing Column [{0}] : {1}\n{2}", column.ColumnName, e.Message, e.StackTrace)); prop = null; } if (prop != null) { var scolname = column.ColumnName.Replace('.', '_'); GridColumn newcol; if (prop.PropertyType.IsNumeric()) { var digits = 0; var prefix = "N"; if (column.Editor is CurrencyEditor currencyEditor) { var curcol = new GridCurrencyColumn(); if (!prop.PropertyType.IsOrdinal()) { var format = string.IsNullOrWhiteSpace(column.Format) ? "" : column.Format.Replace("C", ""); if (!int.TryParse(format, out digits)) digits = currencyEditor.Digits; } curcol.CurrencyDecimalDigits = digits; curcol.CurrencyGroupSeparator = ","; curcol.CurrencyGroupSizes = new Int32Collection(new[] { 3, 3, 3, 3, 3, 3 }); prefix = "C"; newcol = curcol; } else { var numcol = new GridNumericColumn(); if (!prop.PropertyType.IsOrdinal()) { var format = string.IsNullOrWhiteSpace(column.Format) ? "" : column.Format.Replace("F", ""); if (!int.TryParse(format, out digits)) digits = column.Editor is DoubleEditor doubleEditor ? doubleEditor.Digits : 0; numcol.NumberDecimalDigits = digits; numcol.NumberGroupSeparator = ","; numcol.NumberGroupSizes = new Int32Collection(new[] { 3, 3, 3, 3, 3, 3 }); } else { numcol.NumberGroupSeparator = ""; numcol.NumberDecimalDigits = 0; } prefix = "N"; newcol = numcol; } if (prop.Editor.Summary != Summary.None) { var summary = new GridSummaryColumn { Name = scolname, Format = "{" + (prop.Editor.Summary == Summary.Sum ? "Sum" : "Count") + ":" + string.Format("{0}{1}", prefix, digits) + "}", MappingName = scolname, SummaryType = prop.Editor.Summary == Summary.Sum ? prop.PropertyType.IsOrdinal() ? SummaryType.Int32Aggregate : SummaryType.DoubleAggregate : SummaryType.CountAggregate }; Summaries.Add(summary); } } else if (prop.PropertyType == typeof(DateTime)) { newcol = new GridDateTimeColumn { CustomPattern = column.Format, Pattern = !string.IsNullOrWhiteSpace(column.Format) ? DateTimePattern.CustomPattern : DateTimePattern.ShortDate }; } else if (prop.PropertyType == typeof(TimeSpan)) { newcol = new GridTimeSpanColumn { DisplayBinding = new Binding { Path = new PropertyPath(scolname), Converter = new TimeSpanToStringConverter(column.Format) }, Format = column.Format, //.Replace(":", "':'"), ShowArrowButtons = false }; filtering = false; if (prop.Editor != null && prop.Editor.Summary != Summary.None) { var summary = new GridSummaryColumn { Name = scolname, Format = "{" + (prop.Editor.Summary == Summary.Sum ? "Sum" + (string.IsNullOrWhiteSpace(column.Format) ? "" : ":" + column.Format.Replace(":", "':'")) : prop.Editor.Summary == Summary.Count ? "Count" : "") + "}", MappingName = scolname, SummaryType = prop.Editor.Summary == Summary.Sum ? SummaryType.Custom : SummaryType.CountAggregate, CustomAggregate = new TimeSpanAggregate() }; Summaries.Add(summary); } } else if (prop.PropertyType == typeof(bool)) { if (Options.Contains(DynamicGridOption.DirectEdit)) { var checkcol = new GridCheckBoxColumn { ValueBinding = new Binding { Path = new PropertyPath(scolname) }, IsThreeState = false }; newcol = checkcol; } else { var imgcol = new GridImageColumn { ValueBinding = new Binding { Path = new PropertyPath(scolname), Converter = new BoolToImageConverter() }, ImageHeight = DataGrid.RowHeight - 8, ImageWidth = DataGrid.RowHeight - 8, Padding = new Thickness(4) }; newcol = imgcol; } gridRowResizingOptions.ExcludeColumns.Add(scolname); filtering = false; } else if (prop.Editor is ColorEditor) { var imgcol = new GridImageColumn { ValueBinding = new Binding { Path = new PropertyPath(scolname), Converter = new StringToColorImageConverter(column.Width - 8, (int)DataGrid.RowHeight - 8) }, ImageHeight = DataGrid.RowHeight - 8, ImageWidth = column.Width - 8, Padding = new Thickness(4) }; gridRowResizingOptions.ExcludeColumns.Add(scolname); newcol = imgcol; filtering = false; } else if (prop.Editor is ILookupEditor lookupEditor) { var lookupcol = new GridComboBoxColumn(); //var lookups = editor.Values(column.ColumnName).ToDictionary(column.ColumnName, "Display"); //lookupcol.SelectedValuePath = "Key"; //lookupcol.DisplayMemberPath = "Value"; //lookupcol.ItemsSource = lookups; var table = lookupEditor.Values(column.ColumnName).ToDataTable(); lookupcol.SelectedValuePath = table.Columns[0].ColumnName; lookupcol.DisplayMemberPath = "Display"; lookupcol.ItemsSource = table.DefaultView; newcol = lookupcol; } else { var textcol = new GridTextColumn(); if (!(prop.Editor is MemoEditor)) gridRowResizingOptions.ExcludeColumns.Add(scolname); textcol.TextWrapping = TextWrapping.NoWrap; newcol = textcol; if (prop.PropertyType == typeof(string[])) newcol.DisplayBinding = new Binding { Path = new PropertyPath(scolname), Converter = new StringArrayConverter() }; textcol.AllowEditing = Options.Contains(DynamicGridOption.DirectEdit); textcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged; } DataGrid.SummaryCalculationUnit = SummaryCalculationUnit.AllRows; DataGrid.LiveDataUpdateMode = LiveDataUpdateMode.AllowSummaryUpdate; newcol.MappingName = scolname; newcol.Width = column.Width; // != 0 ? column.Width : double.NaN; newcol.ColumnSizer = GridLengthUnitType.None; //column.Width != 0 ? GridLengthUnitType.None : GridLengthUnitType.AutoWithLastColumnFill; newcol.HeaderText = string.IsNullOrWhiteSpace(column.Caption) ? column.ColumnName : column.Caption; newcol.TextAlignment = column.Alignment == Alignment.NotSet ? prop.PropertyType.IsNumeric() ? TextAlignment.Right : TextAlignment.Left : column.Alignment == Alignment.BottomLeft || column.Alignment == Alignment.MiddleLeft || column.Alignment == Alignment.TopLeft ? TextAlignment.Left : column.Alignment == Alignment.BottomCenter || column.Alignment == Alignment.MiddleCenter || column.Alignment == Alignment.TopCenter ? TextAlignment.Center : TextAlignment.Right; newcol.HorizontalHeaderContentAlignment = newcol.TextAlignment == TextAlignment.Left ? HorizontalAlignment.Left : newcol.TextAlignment == TextAlignment.Center ? HorizontalAlignment.Center : HorizontalAlignment.Right; ApplyFilterStyle(newcol, filtering, false); var headstyle = new Style(typeof(GridHeaderCellControl)); headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black))); headstyle.Setters.Add(new Setter(FontSizeProperty, 12D)); newcol.HeaderStyle = headstyle; var cellstyle = new Style(); if (Options.Contains(DynamicGridOption.DirectEdit)) { if (prop.Editor is null || prop.Editor.Editable != Editable.Enabled) { cellstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.WhiteSmoke))); newcol.AllowEditing = false; } else { cellstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.LightYellow))); newcol.AllowEditing = true; } cellstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black))); newcol.CellStyle = cellstyle; } else { cellstyle.Setters.Add(new Setter(BackgroundProperty, new Binding() { Path = new PropertyPath("."), Converter = CellBackgroundConverter, ConverterParameter = column.ColumnName })); cellstyle.Setters.Add(new Setter(ForegroundProperty, new Binding() { Converter = CellForegroundConverter, ConverterParameter = column.ColumnName })); cellstyle.Setters.Add(new Setter(FontSizeProperty, new Binding() { Converter = CellFontSizeConverter, ConverterParameter = column.ColumnName })); cellstyle.Setters.Add(new Setter(FontStyleProperty, new Binding() { Converter = CellFontStyleConverter, ConverterParameter = column.ColumnName })); cellstyle.Setters.Add(new Setter(FontWeightProperty, new Binding() { Converter = CellFontWeightConverter, ConverterParameter = column.ColumnName })); newcol.CellStyle = cellstyle; } DataGrid.Columns.Add(newcol); ColumnList.Add(column); } } LoadActionColumns(DynamicActionColumnPosition.End); if (Summaries.Any()) { DataGrid.CellRenderers.Remove("TableSummary"); DataGrid.CellRenderers.Add("TableSummary", new TimeSpanAggregateRenderer()); DataGrid.TableSummaryRows.Add(new GridTableSummaryRow { ShowSummaryInRow = false, Position = TableSummaryRowPosition.Bottom, SummaryColumns = Summaries }); } DataGrid.Columns.Resume(); DataGrid.RefreshColumns(); foreach (var key in _filterpredicates.Keys.ToArray()) if (DataGrid.Columns.Any(x => string.Equals(x.MappingName, key))) { var predicates = Serialization.Deserialize>(_filterpredicates[key]); foreach (var predicate in predicates) { DataGrid.Columns[key].FilterPredicates.Add(predicate); DataGrid.Columns[key].FilteredFrom = FilteredFrom.FilterRow; } } else { _filterpredicates.Remove(key); } ResizeColumns(DataGrid, DataGrid.ActualWidth - 2, DataGrid.ActualHeight - 2); } private void ApplyFilterStyle(GridColumn column, bool filtering, bool isactioncolumn) { var filterstyle = new Style(); if (filtering) { filterstyle.Setters.Add(new Setter(BackgroundProperty, BaseDynamicGrid.FilterBackground)); column.ImmediateUpdateColumnFilter = true; column.ColumnFilter = ColumnFilter.Value; column.FilterRowCondition = FilterRowCondition.Contains; column.FilterRowOptionsVisibility = Visibility.Collapsed; column.AllowBlankFilters = true; column.AllowSorting = isactioncolumn ? false : CanSort(); } else { filterstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro))); filterstyle.Setters.Add(new Setter(IsEnabledProperty, false)); column.ColumnFilter = ColumnFilter.Value; column.AllowFiltering = false; column.AllowSorting = false; column.FilterRowEditorType = "TextBox"; column.FilterRowOptionsVisibility = Visibility.Collapsed; } column.FilterRowCellStyle = filterstyle; } #endregion #region Refresh / Reload protected abstract void Reload(Filters criteria, Columns columns, ref SortOrder? sort, Action action); protected virtual bool FilterRecord(CoreRow row) { var bOK = true; foreach (var column in ActionColumns.Where(x => x.FilterRecord != null && x.SelectedFilters != null && x.SelectedFilters.Any())) bOK = bOK && column.FilterRecord.Invoke(row, column.SelectedFilters); if (bOK && OnFilterRecord is not null) bOK = OnFilterRecord(row); return bOK; } public override void Refresh(bool reloadcolumns, bool reloaddata) { if (bRefreshing) return; if (!DoBeforeRefresh()) return; DataGrid.SelectionForegroundBrush = BaseDynamicGrid.SelectionForeground; DataGrid.RowSelectionBrush = BaseDynamicGrid.SelectionBackground; var cursor = UseWaitCursor ? new WaitCursor() : null; Loading.Visibility = Visibility.Visible; Loading.BeginAnimation(Label.OpacityProperty, LoadingFader); bRefreshing = true; // Yo, please don't remove this. // The issue was when we were dynamically adding ActionColumns, and if we had to remove and then re-add them, we were getting massive performance hits // for no reason. I think perhaps the image columns were trying to refer to data that didn't exist anymore when calling DataGrid.Columns.Refresh(), // and thus some mega problems (perhaps even exceptions within Syncfusion) were occurring, and this seems to fix it. // I don't pretend to know why it works; this is probably the strangest problem I've ever come across. if (reloadcolumns) DataGrid.ItemsSource = null; if (reloadcolumns) ReloadColumns(); if (reloaddata) { _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 , (table, exception) => { if (exception != null) { Dispatcher.Invoke(() => { MessageBox.Show(String.Format("Error: {0}", exception.Message)); }); } else if (table is not null) { MasterData = table; Dispatcher.Invoke(() => { ProcessData(reloadcolumns, reloaddata); DoAfterRefresh(); bRefreshing = false; IsReady = true; }); } } ); } else { ProcessData(reloadcolumns, reloaddata); DoAfterRefresh(); bRefreshing = false; IsReady = true; Loading.BeginAnimation(Label.OpacityProperty, null); Loading.Visibility = Visibility.Collapsed; } if (cursor != null) { cursor.Dispose(); cursor = null; } bRefreshing = false; } protected override 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 override void OnAfterRefresh() { } protected void DoAfterRefresh() { OnAfterRefresh(); NotifyAfterRefresh(new AfterRefreshEventArgs()); } public Columns DataColumns() { var columns = new Columns(); foreach (var column in VisibleColumns) columns.Add(column.ColumnName); foreach (var column in HiddenColumns) columns.Add(column); return columns; } private void ProcessData(bool reloadcolumns, bool reloaddata) { Data.Columns.Clear(); Data.Setters.Clear(); if (MasterData != null) foreach (var column in MasterData.Columns) Data.Columns.Add(column); LoadData(); } protected readonly Dictionary _recordmap = new(); public override 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 override 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.LoadRow(masterrow, row); Refresh(false, false); } public void AddRow(T data) { if (MasterData is null) return; var masterrow = MasterData.NewRow(); MasterData.LoadRow(masterrow, data); MasterData.Rows.Add(masterrow); Refresh(false, false); } public void DeleteRow(CoreRow row) { if (MasterData is null) return; var masterrow = _recordmap[row]; MasterData.Rows.Remove(masterrow); Refresh(false, false); } private void FilterRows(CoreTable from, CoreTable into, Dictionary? recordMap = null, Func? filter = null) { into.Rows.Clear(); recordMap?.Clear(); foreach (var row in from.Rows.ToArray()) 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; //else if (Data.Columns[i].DataType == typeof(String[])) // value = String.Join("\n", value as String[]); newrow.Values.Add(value); } //newrow.Values.AddRange(row.Values); //if ((OnFilterRecord == null) || (OnFilterRecord(row))) into.Rows.Add(newrow); recordMap?.TryAdd(newrow, row); } } private void LoadData() { ResetClipBuffer(); if (MasterData is null) return; FilterRows(MasterData, Data, _recordmap); InvalidateGrid(); //ScrollBar.Value = _CurrentRow <= 0 ? 0 : _CurrentRow; SelectedRows = Array.Empty(); } //IncrementalList _data = null; public void InvalidateRow(CoreRow row) { var rowdata = new List(row.Values); foreach (var ac in ActionColumns) rowdata.Add(ac.Data(row)); var datarow = DataGridItems.Rows[row.Index]; for (var i = 0; i < rowdata.Count; i++) datarow[i] = rowdata[i] ?? DBNull.Value; //datarow.ItemArray = rowdata.ToArray(); } private void InvalidateGrid() { var defaults = new List(); var result = new DataTable(); foreach (var column in Data.Columns) { var colname = column.ColumnName.Replace('.', '_'); if (!result.Columns.Contains(colname)) { result.Columns.Add(colname, column.DataType); if (!Options.Contains(DynamicGridOption.DirectEdit)) defaults.Add(column.DataType.GetDefault()); } } for (var i = 0; i < ActionColumns.Count; i++) result.Columns.Add(string.Format("ActionColumn{0}", i), ActionColumns[i] is DynamicImageColumn ? typeof(BitmapImage) : typeof(String) ); foreach (var row in Data.Rows) { var newrow = result.NewRow(); CoreRowToDataRow(newrow, row, defaults); result.Rows.Add(newrow); } if (RowStyleSelector != null) RowStyleSelector.Data = Data; //int rowIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.RowIndex; //int columnIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.ColumnIndex; //int scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex; DataGrid.ItemsSource = result; //this.DataGrid.ScrollInView(new Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex(scrollRowIndex, columnIndex)); ResizeColumns(DataGrid, DataGrid.ActualWidth - 1, DataGrid.ActualHeight); UpdateRecordCount(); Loading.BeginAnimation(Label.OpacityProperty, null); Loading.Visibility = Visibility.Collapsed; } private void UpdateRecordCount() { var count = DataGrid.View != null ? DataGrid.View.Records.Count : Data.Rows.Count; Count.Content = string.Format("{0} Records", count); //Count.Visibility = _Options.Contains(DynamicGridOptions.RecordCount) && (count > 0) ? Visibility.Visible : Visibility.Collapsed; } public IList FilteredRows() { var result = new List(); var table = DataGridItems; var rows = DataGrid.View.Records.Select(x => (x.Data as DataRowView)!).ToList(); foreach (var row in rows) { var iRow = table.Rows.IndexOf(row.Row); result.Add(Data.Rows[iRow]); } return result; } // Doesn't appear to be used - removed 19/12/2022 /*private object?[] CreateRowValues(CoreRow row, List defaults) { var rowdata = new List(row.Values); foreach (var ac in ActionColumns) rowdata.Add(ac.Image.Invoke(row)); var result = ProcessRow(rowdata, defaults); return result.ToArray(); }*/ private void CoreRowToDataRow(DataRow newrow, CoreRow row, List defaults) { var rowdata = new List(row.Values); foreach (var ac in ActionColumns) rowdata.Add(ac.Data(row)); try { var data = ProcessRow(rowdata, defaults).ToArray(); newrow.ItemArray = data; } catch (Exception) { throw; } } private static IEnumerable ProcessRow(List values, List defaults) { if (defaults == null || !defaults.Any()) return values; var result = new List(); for (var i = 0; i < values.Count; i++) { var value = values[i]; var defaultvalue = i < defaults.Count ? defaults[i] : null; result.Add(value == null || (value.Equals(defaultvalue) && !value.GetType().IsEnum) ? null : value); } return result; } //private void LoadMoreItems(uint count, int from) //{ // var rows = Data.Rows.Skip(from).AsQueryable().Take(50); // _data.LoadItems(rows.Select(x => x.ToObject())); // //var list = _orders.Skip(baseIndex).Take(50).ToList(); // //IncrementalItemsSource.LoadItems(list); //} public override void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains) { if (string.IsNullOrWhiteSpace(value)) return; DataGrid.Columns[column].FilterPredicates.Add(new FilterPredicate { FilterType = filtertype, FilterValue = value }); DataGrid.Columns[column].FilteredFrom = FilteredFrom.FilterRow; } #endregion #region Item Manipulation #region Load/Save/Delete protected virtual T[] LoadItems(CoreRow[] rows) { var result = new List(); foreach (var row in rows) { var index = Data.Rows.IndexOf(row); result.Add(LoadItem(row)); } return result.ToArray(); } protected abstract T LoadItem(CoreRow row); public abstract void SaveItem(T item); public virtual void SaveItems(T[] items) { foreach (var item in items) SaveItem(item); } protected virtual bool CanDeleteItems(params CoreRow[] rows) { return true; } protected abstract void DeleteItems(params CoreRow[] rows); protected virtual void DoDelete() { var rows = SelectedRows.ToArray(); if (rows.Any()) if (CanDeleteItems(rows)) if (MessageBox.Show("Are you sure you wish to delete the selected records?", "Confirm Delete", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { DeleteItems(rows); SelectedRows = Array.Empty(); OnChanged?.Invoke(this); Refresh(false, true); SelectItems(null); } } private void Delete_Click(object sender, RoutedEventArgs e) { DoDelete(); } #endregion #region Edit protected virtual void DoEdit() { if (!SelectedRows.Any()) return; var sel = SelectedRows.ToArray(); if (AddEditClick(SelectedRows)) { InvalidateGrid(); SelectedRows = sel; SelectItems(SelectedRows); } } private void Edit_Click(object sender, RoutedEventArgs e) { DoEdit(); } /*private void MultiEdit_Click(object sender, RoutedEventArgs e) { using (new WaitCursor()) { var criteria = new Filters(); var columns = new Columns(); columns.Add("ID"); var iprops = DatabaseSchema.Properties(typeof(T)).Where(x => x.Editor is not NullEditor); foreach (var iprop in iprops) columns.Add(iprop.Name); var sort = LookupFactory.DefineSort(); Reload( criteria, columns, ref sort, (table, exception) => { if(table is not null) { Dispatcher.Invoke(() => { DirectEdit(table); }); } else if(exception is not null) { Logger.Send(LogType.Error, "", $"Error in MultiEdit: {CoreUtils.FormatException(exception)}"); MessageBox.Show(exception.Message); } } ); } }*/ /*public override bool DirectEdit(CoreTable data) { var window = new DynamicEditWindow(); window.OnCreateItem += () => CreateItem(); window.OnCustomiseColumns += (o, c) => { ConfigureColumns(MasterColumns); if (OnCustomiseColumns != null) return OnCustomiseColumns(this, MasterColumns); return MasterColumns; }; window.OnGetEditor += c => { var result = GetEditor(this, c)?.CloneEditor(); if (result == null) return null; OnCustomiseEditor?.Invoke(window, null, c, result); return result; }; window.OnGetSequence += c => { decimal result = 0.0M; var customprop = DatabaseSchema.Property(typeof(T), c.ColumnName); if (customprop != null && customprop is CustomProperty) { result = customprop.Sequence; } else { var bits = c.ColumnName.Split('.'); for (var i = 0; i < bits.Length; i++) { var sProp = string.Join(".", bits.Take(bits.Length - i)); PropertyInfo? prop; try { prop = CoreUtils.GetProperty(typeof(T), sProp); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); prop = null; } if (prop != null) { result = prop.GetSequence() + result / 1000.0M; } else { var cprop = DatabaseSchema.Property(typeof(T), sProp); if (cprop != null) result = cprop.Sequence; else result /= 1000.0M; } } } return result; }; window.Load(data); if (window.ShowDialog() == true) { SaveItems(window.Updates); return true; } return false; }*/ protected virtual void DoAdd(bool OpenEditorOnDirectEdit = false) { //CoreRow row = (SelectedRow > -1) && (SelectedRow < Data.Rows.Count) ? Data.Rows[this.SelectedRow] : null; if (Options.Contains(DynamicGridOption.DirectEdit) && !OpenEditorOnDirectEdit) { if (!CanCreateItems()) return; var item = CreateItem(); SaveItem(item); var datarow = Data.NewRow(); ObjectToRow(item, datarow); Data.Rows.Add(datarow); var masterrow = MasterData.NewRow(); ObjectToRow(item, masterrow); MasterData.Rows.Add(masterrow); _recordmap[datarow] = masterrow; InvalidateGrid(); SelectedRows = new[] { datarow }; OnChanged?.Invoke(this); } else if (AddEditClick(null)) { Refresh(false, true); OnChanged?.Invoke(this); } } private void Add_Click(object sender, RoutedEventArgs e) { DoAdd(); } 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); 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 }); } ); } protected virtual void BeforeLoad(IDynamicEditorForm form) { form.BeforeLoad(); } public override 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); ConfigureColumns(columns); }; editor.OnDefineEditor = (o, c) => { var result = GetEditor(o, c); if (result != null) result = result.CloneEditor(); return result; }; editor.OnFormCustomiseEditor += (o, i, c, e) => OnCustomiseEditor?.Invoke(o, (T[])i, c, e); editor.OnDefineFilter = (type) => { return DefineFilter(type, 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, n) => AfterEditorValueChanged(g, items, n); editor.OnReconfigureEditors = g => ReconfigureEditors(g, items); editor.OnValidateData += (o, i) => ValidateData(items); editor.OnSelectPage += SelectPage; editor.OnGetDocument = LoadDocument; editor.OnFindDocument = FindDocument; editor.OnSaveDocument = SaveDocument; 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); editor.Items = items; AfterLoad(editor, items); } protected virtual void DoAfterSave(IDynamicEditorForm editor, T[] items) { OnAfterSave?.Invoke(editor, items); } protected virtual void DoBeforeSave(IDynamicEditorForm editor, T[] items) { OnBeforeSave?.Invoke(editor, items); } public override bool EditItems(T[] items, Func? PageDataHandler = null, bool PreloadPages = false) { DynamicEditorForm editor; using (var cursor = new WaitCursor()) { editor = new DynamicEditorForm(); 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, String columnnname) { var changes = new Dictionary(); OnAfterEditorValueChanged(grid, items, columnnname, changes); return changes; } protected virtual void OnAfterEditorValueChanged(DynamicEditorGrid grid, T[] items, String columnnname, Dictionary changes) { } protected virtual void ReconfigureEditors(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 string[]? ValidateData(T[] items) { var errors = new List(); DoValidate(items, errors); OnValidate?.Invoke(this, items, errors); return errors.Any() ? errors.ToArray() : 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> _lookupcache = new(); protected virtual void DefineLookups(ILookupEditorControl sender, T[] items) { if (sender.EditorDefinition is not ILookupEditor editor) return; var colname = sender.ColumnName; //Logger.Send(LogType.Information, typeof(T).Name, "Into Define Lookups: " + colname); Task.Run(() => { try { var values = editor.Values(colname, items); Dispatcher.Invoke( () => { try { //Logger.Send(LogType.Information, typeof(T).Name, "Dispatching Results" + colname); sender.LoadLookups(values); } catch (Exception e2) { Logger.Send(LogType.Information, typeof(T).Name, "Exception (2) in LoadLookups: " + e2.Message + "\n" + e2.StackTrace); } } ); } catch (Exception e) { Logger.Send(LogType.Information, typeof(T).Name, "Exception (1) in LoadLookups: " + e.Message + "\n" + e.StackTrace); } }); } /// /// Retrieves an editor to display for the given column of . /// /// The object being edited. /// The column of the editor. /// A new editor, or if no editor defined and no sensible default exists. protected virtual BaseEditor? GetEditor(object item, DynamicGridColumn column) { return column.Editor ?? CoreUtils.GetProperty(item.GetType(), column.ColumnName).GetEditor(); } protected IFilter? DefineFilter(Type type, T[] items) { return LookupFactory.DefineFilter(items, type); } protected virtual void SetEditorValue(object item, string name, object value) { try { CoreUtils.SetPropertyValue(item, name, value); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } protected virtual object? GetEditorValue(object item, string name) { return CoreUtils.GetPropertyValue(item, name); } protected virtual Document? LoadDocument(Guid id) { return null; } protected virtual Document? FindDocument(string filename) { return null; } protected virtual void SaveDocument(Document document) { } protected virtual bool CanCreateItems() { return true; } private bool AddEditClick(CoreRow[]? rows) { if (!IsEnabled || bRefreshing) return false; if (rows == null || !rows.Any()) { if (!CanCreateItems()) return false; var item = CreateItem(); // Yea, and this won't work, because we're actually usually showing the description of a linked item, // Yea, and this won't work, because we're actually usually showing the description of a linked item, // not the id of the link, and we need to set the ID to have it work properly :-( //foreach (String key in VisualFilters.Keys) // CoreUtils.SetPropertyValue(item, key, VisualFilters[key]); if (EditItems(new[] { item })) { //_CurrentRow = Data.Rows.Count; var row = Data.NewRow(); ObjectToRow(item, row); Data.Rows.Add(row); InvalidateGrid(); SelectedRows = new[] { row }; OnChanged?.Invoke(this); return true; } return false; } var items = Array.Empty(); using (new WaitCursor()) { Stopwatch sw = new Stopwatch(); sw.Start(); items = LoadItems(rows); //Logger.Send(LogType.Information, "DG:LoadItems", String.Format("Loaded Items: {0}ms", sw.ElapsedMilliseconds)); sw.Stop(); } if (items.Any()) { var sel = SelectedRows; if (EditItems(items)) { for (var i = 0; i < items.Length; i++) ObjectToRow(items[i], rows[i]); InvalidateGrid(); SelectedRows = sel; OnChanged?.Invoke(this); return true; } return false; } return false; } #endregion #region Duplicate protected virtual IEnumerable LoadDuplicatorItems(CoreRow[] rows) { return LoadItems(rows); } private bool DoDuplicate(Button button, CoreRow[] rows) { if (!rows.Any()) { MessageBox.Show("Please select at least one record to duplicate!"); return false; } /*var ids = ExtractValues(x => x.ID, Selection.Selected).ToArray(); if (!ids.Any()) { MessageBox.Show("Please select at least one record to duplicate!"); return false; }*/ var duplicator = (new T() as IDuplicatable)?.GetDuplicator(); if (duplicator is null) { MessageBox.Show($"Cannot duplicate {typeof(T)}"); return false; } duplicator.Duplicate(LoadDuplicatorItems(rows));// new Filter(x => x.ID).InList(ids)); return true; } #endregion protected virtual void ShowHelp(string slug) { Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + slug) { UseShellExecute = true }); } protected void ReloadForms(IDynamicEditorForm editor, TTargetType item, Expression> sourcekey, Guid sourceid) where TTargetType : Entity, new() where TTargetForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new() where TSourceForm : Entity, IRemotable, IPersistent, IDigitalForm, new() { var type = typeof(IDynamicOneToManyGrid<,>).MakeGenericType(typeof(TTargetType), typeof(TTargetForm)); var page = editor.Pages?.FirstOrDefault(x => x.GetType().GetInterfaces().Contains(type)) as IDynamicOneToManyGrid; if (page != null && item != null) { if (!page.Ready) page.Load(item, null); CoreTable table; if (sourceid == Guid.Empty) { table = new CoreTable(); table.LoadColumns(typeof(TSourceForm)); } else { table = new Client().Query( new Filter(sourcekey).IsEqualTo(sourceid).And(x => x.Form.AppliesTo) .IsEqualTo(typeof(TTargetType).EntityName().Split('.').Last()) ); } var newforms = new List(); foreach (var row in table.Rows) { var sourceform = row.ToObject(); var targetform = new TTargetForm(); targetform.Form.ID = sourceform.Form.ID; targetform.Form.Synchronise(sourceform.Form); newforms.Add(targetform); } page.Items.Clear(); page.LoadItems(newforms.ToArray()); } } #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; if (!IsSequenced) return; using (new WaitCursor()) { var updates = ClipBuffer.Item2.Select(x => x.ToObject()).ToList(); if (BeforePaste(updates, ClipBuffer.Item1)) { var currow = SelectedRows.FirstOrDefault() ?? Data.Rows.LastOrDefault(); var sequence = currow != null ? currow.Get(c => ((ISequenceable)c).Sequence) : 0; var postrows = Data.Rows.Where(r => !ClipBuffer.Item2.Contains(r) && r.Get(x => x.Sequence) >= sequence); updates.AddRange(LoadItems(postrows.ToArray())); foreach (var update in updates) { sequence++; ((ISequenceable)update).Sequence = sequence; } } if (updates.Any()) { SaveItems(updates.ToArray()); Refresh(false, true); } } } protected virtual bool BeforePaste(IEnumerable items, ClipAction action) { return true; } private void Cut_Click(object sender, RoutedEventArgs e) { CutToClipBuffer(); } private void Copy_Click(object sender, RoutedEventArgs e) { CopyToClipBuffer(); } private void Paste_Click(object sender, RoutedEventArgs e) { PasteFromClipBuffer(); } #endregion protected virtual void ObjectToRow(T obj, CoreRow row) { Data.LoadRow(row, obj); } #region Import / Export protected virtual CoreTable LoadImportKeys(String[] fields) { var result = new CoreTable(); result.LoadColumns(new Columns(fields)); return result; } protected virtual Guid GetImportID() { return Guid.Empty; } protected virtual bool CustomiseImportItem(T item) { if (IsSequenced) ((ISequenceable)item).Sequence = CoreUtils.GenerateSequence(); return true; } protected virtual string CustomiseImportFileName(string filename) { return filename; } protected virtual void DoImport() { var list = new DynamicImportList( typeof(T), GetImportID() ); list.OnImportItem += o => { return CustomiseImportItem((T)o); }; list.OnCustomiseImport += (o, args) => { args.FileName = CustomiseImportFileName(args.FileName); }; list.OnSave += (sender, entity) => SaveItem(entity as T); list.OnLoad += (sender, type, fields, id) => LoadImportKeys(fields); list.ShowDialog(); Refresh(false, true); } private void Import_Click(object sender, RoutedEventArgs e) { DoImport(); } protected virtual void CustomiseExportColumns(List columnnames) { } protected virtual string CustomiseExportFileName(string filename) { return filename; } protected virtual void CustomiseExportFilters(Filters filters, CoreRow[] visiblerows) { } protected virtual void ApplyExportFilter(CoreTable table, object data) { } private static bool FilterByPredicate(CoreRow row, string column, FilterPredicate predicate) { var value = row[column]; var vStr = value?.ToString() ?? ""; var pValue = predicate.FilterValue; var pStr = pValue?.ToString() ?? ""; return predicate.FilterType switch { FilterType.Contains => vStr.Contains(pStr), FilterType.EndsWith => vStr.EndsWith(pStr), FilterType.Equals => vStr.Equals(pStr), FilterType.GreaterThan => vStr.CompareTo(pStr) > 0, FilterType.GreaterThanOrEqual => vStr.CompareTo(pStr) >= 0, FilterType.LessThan => vStr.CompareTo(pStr) < 0, FilterType.LessThanOrEqual => vStr.CompareTo(pStr) <= 0, FilterType.NotContains => !vStr.Contains(pStr), FilterType.NotEndsWith => !vStr.EndsWith(pStr), FilterType.NotEquals => !vStr.Equals(pStr), FilterType.NotStartsWith => !vStr.StartsWith(pStr), FilterType.StartsWith => vStr.StartsWith(pStr), _ => true, }; } private List> GetFilterPredicates() { var list = new List>(); foreach (var column in DataGrid.Columns) { var colIndex = DataGrid.Columns.IndexOf(column); var col = ColumnList[colIndex]; if (col is DynamicGridColumn gridColumn) { foreach (var predicate in column.FilterPredicates) { list.Add(new(gridColumn.ColumnName, predicate)); } } } return list; } protected virtual void DoExport() { var columnnames = VisibleColumns.Select(x => x.ColumnName).ToList(); CustomiseExportColumns(columnnames); var form = new DynamicExportForm(typeof(T), columnnames); if (form.ShowDialog() != true) return; var filters = new Filters(); filters.Add(DefineFilter()); var predicates = GetFilterPredicates(); var visiblerows = GetVisibleRows(); CustomiseExportFilters(filters, visiblerows); var columns = new Columns(form.Fields); var otherColumns = form.GetChildFields() .Select(x => new Tuple( x.Key, (Activator.CreateInstance(typeof(Columns<>).MakeGenericType(x.Key), new object[] { x.Value }) as IColumns)!)) .Where(x => x.Item2.ColumnNames().Any()).ToList(); var reloadColumns = new Columns(); foreach (var column in columns.ColumnNames()) { reloadColumns.Add(column); } foreach (var column in HiddenColumns) { reloadColumns.Add(column); } foreach (var (column, _) in predicates) { reloadColumns.Add(column); } var sort = LookupFactory.DefineSort(); Reload(filters, reloadColumns, ref sort, (data, err) => Dispatcher.Invoke(() => { if (data is not null) { var newData = new CoreTable(); foreach (var column in columns.Items) newData.Columns.Add(new CoreColumn { ColumnName = column.Property, DataType = column.Type }); FilterRows(data, newData, filter: (row) => { foreach (var (column, predicate) in predicates) { if (!FilterByPredicate(row, column, predicate)) { return false; } } return true; }); var list = new List>() { new(typeof(T), newData) }; list.AddRange(LoadExportTables(filters, otherColumns)); DoExportTables(list); } else if (err is not null) { Logger.Send(LogType.Error, "", $"Error in export: {CoreUtils.FormatException(err)}"); MessageBox.Show(err.Message); } })); } private void Export_Click(object sender, RoutedEventArgs e) { DoExport(); } /// /// Loads the child tables for an export, based on the filter of the parent table. /// /// /// If not overriden, defaults to creating empty tables with no records. /// /// Filter for the parent table. /// A list of the child table types, with columns to load for each /// A list of tables, in the same order as they came in protected virtual IEnumerable> LoadExportTables(Filters filter, IEnumerable> tableColumns) { return tableColumns.Select(x => { var table = new CoreTable(); table.LoadColumns(x.Item2); return new Tuple(x.Item1, table); }); } private void DoExportTables(List> data) { var filename = CustomiseExportFileName(typeof(T).EntityName().Split('.').Last()); ExcelExporter.DoExport(data, filename); } #endregion public void ScrollIntoView(CoreRow row) { DataGrid.ScrollInView(new RowColumnIndex(row.Index + 1, 0)); } #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; } protected 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 (Export.Visibility != Visibility.Collapsed) return true; return false; } public override Button AddButton(string caption, BitmapImage? image, string? tooltip, Func 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, Func 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 = (Func)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 private void SelectColumnsClick(object sender, RoutedEventArgs e) { var editor = new DynamicGridColumnsEditor(typeof(T)); editor.DirectEdit = Options.Contains(DynamicGridOption.DirectEdit); editor.Columns.AddRange(VisibleColumns); if (editor.ShowDialog().Equals(true)) { VisibleColumns.Clear(); VisibleColumns.AddRange(editor.Columns); SaveColumns(VisibleColumns); //OnSaveColumns?.Invoke(this, editor.Columns); Refresh(true, true); } } #endregion #region Drag + Drop private static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? ""; protected virtual void OnDragEnd(Type entity, CoreTable table) { } private void DataGrid_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DragFormat)) { var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat; if (data is not null) { var table = new CoreTable(); foreach (var column in data.Table.Columns) { if (column is DataColumn dataColumn) { table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName, DataType = dataColumn.DataType }); } } foreach (var row in data.Table.Rows) { if (row is DataRow dataRow) { var coreRow = table.NewRow(); coreRow.LoadValues(dataRow.ItemArray); table.Rows.Add(coreRow); } } OnDragEnd(data.Entity, table); } } } protected void DragTable(Type entity, CoreTable table) { var data = new DataObject(); data.SetData(DragFormat, new DynamicGridDragFormat(table.ToDataTable(), entity)); DragDrop.DoDragDrop(this, data, DragDropEffects.All); } protected virtual void OnRowsDragStart(CoreRow[] rows) { var table = new CoreTable(); table.LoadColumns(Data.Columns); table.LoadRows(rows); DragTable(typeof(T), table); } private void RowDragDropController_DragStart(object? sender, GridRowDragStartEventArgs e) { var rows = new List(); foreach (var record in e.DraggingRecords) { var rowIndex = DataGrid.ResolveToRowIndex(record); rows.Add(GetRowFromIndex(rowIndex)); } var rowArr = rows.ToArray(); OnRowsDragStart(rowArr); e.Handled = true; } #endregion /* Removed as appears unused; removed as of 19/12/2022 #region CellRendering private void PopulateDynamicActionCell(DynamicActionColumn column, int rowIndex, int columnIndex, GridStyleInfo style) { style.CellType = "ImageCell"; var bi = column.Image?.Invoke(rowIndex < 0 ? null : Data.Rows[rowIndex]); if (bi != null) { style.CellValue = bi; style.BorderMargins = new CellMarginsInfo(4.0F); } } //bool rowstylehelperinitialised = false; //protected virtual void ProcessCellStyle(CoreRow row, int column, GridStyleInfo style) //{ // if (!rowstylehelperinitialised) // { // Script stylescript = new Client