using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.WPF; using Expression = System.Linq.Expressions.Expression; namespace InABox.DynamicGrid { public interface IDynamicDataGrid : IDynamicGrid { /// /// The tag the the DynamicGridColumns are stored against. If set to , /// the name of is used as a default. /// string? ColumnsTag { get; set; } IColumns LoadEditorColumns(); } public class DynamicDataGrid : DynamicGrid, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new() { public delegate void OnReloadEventHandler(object sender, Filters criteria, Columns columns, ref SortOrder? sortby); private readonly int ChunkSize = 500; private Button MergeBtn = null!; //Late-initialised protected DynamicGridFilterButtonComponent FilterComponent; protected DynamicGridCustomColumnsComponent ColumnsComponent; private Column[] FilterColumns; public DynamicDataGrid() : base() { var fields = DatabaseSchema.Properties(typeof(TEntity)); foreach (var field in fields) if (!MasterColumns.Any(x => x.ColumnName == field.Name)) MasterColumns.Add(new DynamicGridColumn { ColumnName = field.Name }); var cols = LookupFactory.DefineColumns(); // Minimum Columns for Lookup values foreach (var col in cols.Items) HiddenColumns.Add(CoreUtils.CreateLambdaExpression(col.Property)); // Minimum Columns for Successful Saving // This should be cross-checked with the relevant Store<> // so that clients will (usually) provide sufficient columns for saving var required = LookupFactory.RequiredColumns(); foreach (var col in required.Items) HiddenColumns.Add(CoreUtils.CreateLambdaExpression(col.Property)); //HiddenColumns.Add(x => x.ID); if (typeof(TEntity).GetInterfaces().Contains(typeof(IIssues))) { HiddenColumns.Add(x => (x as IIssues)!.Issues); var coltype = typeof(DynamicIssuesColumn<>).MakeGenericType(typeof(TEntity)); ActionColumns.Add((Activator.CreateInstance(coltype, this) as DynamicActionColumn)!); } SetupFilterColumns(); } protected override void Init() { FilterComponent = new(this, new GlobalConfiguration(GetTag()), new UserConfiguration(GetTag())); FilterComponent.OnFilterRefresh += () => Refresh(false, true); ColumnsComponent = new DynamicGridCustomColumnsComponent(this, GetTag()); MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge); } protected override void DoReconfigure(FluentList options) { options.BeginUpdate(); if (Security.CanEdit()) options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows); if (Security.CanDelete()) options.Add(DynamicGridOption.DeleteRows); if (Security.CanImport() && typeof(TEntity).HasInterface()) options.Add(DynamicGridOption.ImportData); if (Security.CanExport() && typeof(TEntity).HasInterface()) options.Add(DynamicGridOption.ExportData); if (Security.CanMerge()) options.Add(DynamicGridOption.MultiSelect); options.EndUpdate(); } [MemberNotNull(nameof(FilterColumns))] private void SetupFilterColumns() { if (typeof(TEntity).GetCustomAttribute() is AutoEntity auto) { if (auto.Generator is not null) { var columns = auto.Generator.IDColumns; FilterColumns = columns.Select(x => new Column(x.Property)).ToArray(); } else { FilterColumns = Array.Empty>(); } } else { FilterColumns = new[] { new Column(x => x.ID) }; } foreach (var column in FilterColumns) { AddHiddenColumn(column.Property); } } protected override void BeforeLoad(IDynamicEditorForm form, TEntity[] items) { form.ReadOnly = form.ReadOnly || !Security.CanEdit(); base.BeforeLoad(form, items); } private string? _columnsTag; public string? ColumnsTag { get => _columnsTag; set { _columnsTag = value; ColumnsComponent.Tag = GetTag(); } } protected override void OptionsChanged() { base.OptionsChanged(); if (MergeBtn != null) MergeBtn.Visibility = Visibility.Collapsed; FilterComponent.ShowFilterList = HasOption(DynamicGridOption.FilterRows); } protected override void SelectItems(CoreRow[]? rows) { base.SelectItems(rows); MergeBtn.Visibility = HasOption(DynamicGridOption.MultiSelect) && typeof(TEntity).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge() && rows != null && rows.Length > 1 ? Visibility.Visible : Visibility.Collapsed; } public event OnReloadEventHandler? OnReload; protected override void Reload(Filters criteria, Columns columns, ref SortOrder? sort, Action action) { criteria.Add(FilterComponent.GetFilter()); OnReload?.Invoke(this, criteria, columns, ref sort); new Client().Query(criteria.Combine(), columns, sort, action); } private CoreRow[]? SelectedBeforeRefresh; protected override void OnAfterRefresh() { base.OnAfterRefresh(); if (SelectedBeforeRefresh is not null) { var selectedValues = SelectedBeforeRefresh .Select(x => FilterColumns.Select(c => x[c.Property]).ToList()) .ToList(); SelectedBeforeRefresh = null; var selectedRows = Data.Rows.Where(r => { return selectedValues.Any(v => { for (int i = 0; i < v.Count; ++i) { if (v[i]?.Equals(r[FilterColumns[i].Property]) != true) return false; } return true; }); }).ToArray(); SelectedRows = selectedRows; SelectItems(selectedRows); } } public override void Refresh(bool reloadcolumns, bool reloaddata) { SelectedBeforeRefresh = SelectedRows; base.Refresh(reloadcolumns, reloaddata); } IColumns IDynamicDataGrid.LoadEditorColumns() => LoadEditorColumns(); public Columns LoadEditorColumns() { return DynamicGridUtils.LoadEditorColumns(DataColumns()); } private Filter? GetRowFilter(CoreRow row) { var newFilters = new Filters(); foreach (var column in FilterColumns) { newFilters.Add(new Filter(column.Property).IsEqualTo(row[column.Property])); } return newFilters.Combine(); } protected override TEntity[] LoadItems(CoreRow[] rows) { Filter? filter = null; var results = new List(); for (var i = 0; i < rows.Length; i += ChunkSize) { var chunk = rows.Skip(i).Take(ChunkSize); foreach (var row in chunk) { var newFilter = GetRowFilter(row); if (newFilter is not null) { //var id = row.Get(x => x.ID); if (filter is null) filter = newFilter;//new Filter(x => x.ID).IsEqualTo(id); else filter = filter.Or(newFilter);//.Or(x => x.ID).IsEqualTo(id); } } var columns = LoadEditorColumns(); // new Columns().Default(ColumnType.IncludeOptional, ColumnType.IncludeForeignKeys, ColumnType.IncludeUserProperties); var data = Client.Query(filter, columns); results.AddRange(data.ToObjects()); } return results.ToArray(); } protected override TEntity LoadItem(CoreRow row) { var id = row.Get(x => x.ID); var filter = GetRowFilter(row);//new Filter(x => x.ID).IsEqualTo(id); return Client.Query(filter, LoadEditorColumns()).ToObjects().FirstOrDefault() ?? throw new Exception($"No {typeof(TEntity).Name} with ID {id}"); } public override void SaveItem(TEntity item) { Client.Save(item, "Edited by User"); } public override void SaveItems(TEntity[] items) { Client.Save(items, "Edited by User"); } protected override void DeleteItems(params CoreRow[] rows) { var deletes = new List(); foreach (var row in rows) { var delete = new TEntity(); foreach (var column in FilterColumns) { CoreUtils.SetPropertyValue(delete, column.Property, row[column.Property]); } //var delete = /* row.ToObject(); */ new TEntity { ID = row.Get(x => x.ID) }; deletes.Add(delete); } Client.Delete(deletes, "Deleted on User Request"); } private object GetPropertyValue(Expression member, object obj) { var objectMember = Expression.Convert(member, typeof(object)); var getterLambda = Expression.Lambda>(objectMember); var getter = getterLambda.Compile(); return getter(); } protected override void ObjectToRow(TEntity obj, CoreRow row) { foreach (var column in Data.Columns) { var prop = DatabaseSchema.Property(obj.GetType(), column.ColumnName); if (prop is StandardProperty) row.Set(column.ColumnName, CoreUtils.GetPropertyValue(obj, prop.Name)); else if (prop is CustomProperty) row.Set(column.ColumnName, obj.UserProperties[column.ColumnName]); } //base.ObjectToRow(obj, row); } //private void Auditgrid_OnReload(object sender, Dictionary criteria, ref String sort) //{ // criteria["DocumentType"] = typeof(TEntity).EntityName(); // criteria["DocumentID"] = (sender as FrameworkElement).Tag; // sort = "TimeStamp"; //} public override void LoadEditorButtons(TEntity item, DynamicEditorButtons buttons) { base.LoadEditorButtons(item, buttons); if (ClientFactory.IsSupported()) buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick); } private void AuditTrailClick(object sender, object item) { var entity = (TEntity)item; var window = new AuditWindow(entity.ID); window.ShowDialog(); } protected override DynamicGridColumns LoadColumns() { return ColumnsComponent.LoadColumns(); } protected override void SaveColumns(DynamicGridColumns columns) { ColumnsComponent.SaveColumns(columns); } protected override void LoadColumnsMenu(ContextMenu menu) { base.LoadColumnsMenu(menu); ColumnsComponent.LoadColumnsMenu(menu); } private string GetTag() { var tag = typeof(TEntity).Name; if (!string.IsNullOrWhiteSpace(ColumnsTag)) tag = string.Format("{0}.{1}", tag, ColumnsTag); return tag; } protected override DynamicGridSettings LoadSettings() { var tag = GetTag(); var user = Task.Run(() => new UserConfiguration(tag).Load()); user.Wait(); //var global = Task.Run(() => new GlobalConfiguration(tag).Load()); //global.Wait(); //Task.WaitAll(user, global); //var columns = user.Result.Any() ? user.Result : global.Result; return user.Result; } protected override void SaveSettings(DynamicGridSettings settings) { var tag = GetTag(); new UserConfiguration(tag).Save(settings); } protected override BaseEditor? GetEditor(object item, DynamicGridColumn column) { var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == column.ColumnName); if (prop != null) return prop.Editor; return base.GetEditor(item, column); } protected override object? GetEditorValue(object item, string name) { if (item is TEntity entity) { var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name); if (prop is CustomProperty) { if (entity.UserProperties.ContainsKey(name)) return entity.UserProperties[name]; return null; } } return base.GetEditorValue(item, name); } protected override void SetEditorValue(object item, string name, object value) { var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name); if (prop is CustomProperty && item is TEntity entity) { entity.UserProperties[name] = value; } else { base.SetEditorValue(item, name, value); } } protected bool Duplicate( CoreRow row, Expression> codefield, Type[] childtypes) { var id = row.Get(x => x.ID); var code = row.Get(codefield) as string; var tasks = new List(); var itemtask = Task.Run(() => { var filter = new Filter(x => x.ID).IsEqualTo(id); var result = new Client().Load(filter).FirstOrDefault() ?? throw new Exception("Entity does not exist!"); return result; }); tasks.Add(itemtask); //itemtask.Wait(); Task>? codetask = null; if (!string.IsNullOrWhiteSpace(code)) { codetask = Task.Run(() => { var columns = new Columns(codefield); //columns.Add(codefield); var filter = new Filter(codefield).BeginsWith(code); var table = new Client().Query(filter, columns); var result = table.Rows.Select(x => x.Get(codefield) as string).ToList(); return result; }); tasks.Add(codetask); } //codetask.Wait(); var children = new Dictionary(); foreach (var childtype in childtypes) { var childtask = Task.Run(() => { var prop = childtype.GetProperties().FirstOrDefault(x => x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) && x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity)); if (prop is not null) { var filter = Core.Filter.Create(childtype); filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID"); filter.Operator = Operator.IsEqualTo; filter.Value = id; var sort = LookupFactory.DefineSort(childtype); var client = ClientFactory.CreateClient(childtype); var table = client.Query(filter, null, sort); foreach (var r in table.Rows) { r["ID"] = Guid.Empty; r[prop.Name + ".ID"] = Guid.Empty; } children[childtype] = table; } else { Logger.Send(LogType.Error, "", $"DynamicDataGrid<{typeof(TEntity)}>.Duplicate(): No parent property found for child type {childtype}"); } }); tasks.Add(childtask); //childtask.Wait(); } //var manytomanys = CoreUtils.TypeList( // AppDomain.CurrentDomain.GetAssemblies(), // x => x.GetInterfaces().Any(intf => intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && intf.GenericTypeArguments.Contains(typeof(T))) //); //Task childtask = Task.Run(() => //{ // var result = new CoreTable(); // result.LoadColumns(typeof(TChild)); // var children = new Client().Load(new Filter(x => linkfield).IsEqualTo(id)); // foreach (var child in children) // { // child.ID = Guid.Empty; // String linkprop = CoreUtils.GetFullPropertyName(linkfield, "."); // CoreUtils.SetPropertyValue(child, linkprop, Guid.Empty); // var newrow = result.NewRow(); // result.LoadRow(newrow, child); // result.Rows.Add(newrow); // } // return result; //}); //tasks.Add(childtask); Task.WaitAll(tasks.ToArray()); var item = itemtask.Result; item.ID = Guid.Empty; if (codetask != null) { var codes = codetask.Result; var i = 1; while (codes.Contains(string.Format("{0} ({1})", code, i))) i++; var codeprop = CoreUtils.GetFullPropertyName(codefield, "."); CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i)); } var grid = new DynamicDataGrid(); return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true); } private bool DoMerge(Button arg1, CoreRow[] arg2) { if (arg2 == null || arg2.Length <= 1) return false; var targetid = arg2.Last().Get(x => x.ID); var target = arg2.Last().ToObject().ToString(); var otherids = arg2.Select(r => r.Get(x => x.ID)).Where(x => x != targetid).ToArray(); string[] others = arg2.Where(r => otherids.Contains(r.Get("ID"))).Select(x => x.ToObject().ToString()!).ToArray(); var rows = arg2.Length; if (MessageBox.Show( string.Format( "This will merge the following items:\n\n- {0}\n\n into:\n\n- {1}\n\nAfter this, the items will be permanently removed.\nAre you sure you wish to do this?", string.Join("\n- ", others), target ), "Merge Items Warning", MessageBoxButton.YesNo, MessageBoxImage.Stop) != MessageBoxResult.Yes ) return false; using (new WaitCursor()) { var types = CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.IsSubclassOf(typeof(Entity)) && !x.Equals(typeof(AuditTrail)) && !x.Equals(typeof(TEntity)) && x.GetCustomAttribute() == null && x.HasInterface() && x.HasInterface() ).ToArray(); foreach (var type in types) { var props = CoreUtils.PropertyList( type, x => x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) && x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(TEntity)) ); foreach (var prop in props) { var propname = string.Format(prop.Name + ".ID"); var filter = Core.Filter.Create(type); filter.Expression = CoreUtils.CreateMemberExpression(type, propname); filter.Operator = Operator.InList; filter.Value = otherids; var columns = Columns.Create(type) .Add("ID") .Add(propname); var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray(); if (updates.Any()) { foreach (var update in updates) CoreUtils.SetPropertyValue(update, propname, targetid); ClientFactory.CreateClient(type).Save(updates, string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last())); } } } var histories = new Client() .Query(new Filter(x => x.EntityID).InList(otherids), new Columns(x => x.EntityID)).Rows .Select(x => x.ToObject()).ToArray(); foreach (var history in histories) history.EntityID = targetid; if (histories.Any()) new Client().Save(histories, ""); var deletes = new List(); foreach (var otherid in otherids) { var delete = new TEntity(); CoreUtils.SetPropertyValue(delete, "ID", otherid); deletes.Add(delete); } ClientFactory.CreateClient(typeof(TEntity)) .Delete(deletes, string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last())); return true; } } protected override IEnumerable LoadDuplicatorItems(CoreRow[] rows) { return rows.Select(x => x.ToObject()); } protected override bool BeforePaste(IEnumerable items, ClipAction action) { if (action == ClipAction.Copy) { foreach (var item in items) item.ID = Guid.Empty; return true; } return base.BeforePaste(items, action); } protected override void CustomiseExportFilters(Filters filters, CoreRow[] visiblerows) { base.CustomiseExportFilters(filters, visiblerows); /*if (IsEntity) { var ids = visiblerows.Select(r => r.Get(c => c.ID)).ToArray(); filters.Add(new Filter(x => x.ID).InList(ids)); } else { Filter? filter = null; foreach (var row in visiblerows) { var rowFilter = GetRowFilter(row); if(rowFilter is not null) { if (filter is null) { filter = rowFilter; } else { filter.Or(rowFilter); } } } filters.Add(filter); }*/ } protected override IEnumerable> LoadExportTables(Filters filter, IEnumerable> tableColumns) { var queries = new Dictionary(); var columns = tableColumns.ToList(); foreach (var table in columns) { var tableType = table.Item1; PropertyInfo? property = null; var m2m = CoreUtils.GetManyToMany(tableType, typeof(TEntity)); IFilter? queryFilter = null; if (m2m != null) { property = CoreUtils.GetManyToManyThisProperty(tableType, typeof(TEntity)); } else { var o2m = CoreUtils.GetOneToMany(tableType, typeof(TEntity)); if (o2m != null) { property = CoreUtils.GetOneToManyProperty(tableType, typeof(TEntity)); } } if (property != null) { var subQuery = new SubQuery(); subQuery.Filter = filter.Combine(); subQuery.Column = new Column(x => x.ID); queryFilter = (Activator.CreateInstance(typeof(Filter<>).MakeGenericType(tableType)) as IFilter)!; queryFilter.Expression = CoreUtils.GetMemberExpression(tableType, property.Name + ".ID"); queryFilter.InQuery(subQuery); queries[tableType.Name] = new QueryDef(tableType) { Filter = queryFilter, Columns = table.Item2 }; } } var results = Client.QueryMultiple(queries); return columns.Select(x => new Tuple(x.Item1, results[x.Item1.Name])); } protected override CoreTable LoadImportKeys(String[] fields) { return new Client().Query(null, new Columns(fields)); } } }