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; 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() { private Button MergeBtn = null!; //Late-initialised protected DynamicGridCustomColumnsComponent ColumnsComponent; public DynamicGridFilterButtonComponent FilterComponent; 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) 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 foreach (var col in LookupFactory.RequiredColumns()) 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)!); } } protected override void Init() { FilterComponent = new(this, new GlobalConfiguration(GetTag()), new UserConfiguration(GetTag())); FilterComponent.OnFilterRefresh += () => Refresh(false, true); ColumnsComponent = new DynamicGridCustomColumnsComponent(this, GetTag()); var dataComponent = new DynamicGridClientDataComponent(this); dataComponent.OnReload += DataComponent_OnReload; DataComponent = dataComponent; MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge); } protected override void SelectItems(CoreRow[]? rows) { base.SelectItems(rows); MergeBtn.Visibility = Options.MultiSelect && typeof(T).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge() && rows != null && rows.Length > 1 ? Visibility.Visible : Visibility.Collapsed; } private void DataComponent_OnReload(object sender, Filters criteria, Columns columns, ref SortOrder? sortby) { criteria.Add(FilterComponent.GetFilter()); } protected override void OptionsChanged() { base.OptionsChanged(); FilterComponent.ShowFilterList = Options.FilterRows && !Options.HideDatabaseFilters; if (MergeBtn != null) MergeBtn.Visibility = Visibility.Collapsed; } protected override void DoReconfigure(DynamicGridOptions options) { if (Security.CanEdit()) { options.AddRows = true; options.EditRows = true; } if (Security.CanDelete()) options.DeleteRows = true; if (Security.CanImport() && typeof(TEntity).HasInterface()) options.ImportData = true; if (Security.CanExport() && typeof(TEntity).HasInterface()) options.ExportData = true; if (Security.CanMerge()) options.MultiSelect = true; } 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(); } } 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 = (item as TEntity)!; 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); } #region Duplicate 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 = Columns.None().Add(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); } protected override IEnumerable LoadDuplicatorItems(CoreRow[] rows) { return rows.Select(x => x.ToObject()); } #endregion 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 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 Client.Query(null, Columns.None().Add(fields)); } #region Merge 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.Entities.Where( x => x.IsClass && !x.IsGenericType && x.IsSubclassOf(typeof(Entity)) && !x.Equals(typeof(AuditTrail)) && !x.Equals(typeof(T)) && 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(T)) ); 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.None(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(T).EntityName().Split('.').Last())); } } } var histories = new Client() .Query( new Filter(x => x.EntityID).InList(otherids), Columns.None() .Add(x => x.ID).Add(x => x.EntityID)) .ToArray(); foreach (var history in histories) history.EntityID = targetid; if (histories.Length != 0) new Client().Save(histories, ""); var deletes = new List(); foreach (var otherid in otherids) { var delete = new T(); CoreUtils.SetPropertyValue(delete, "ID", otherid); deletes.Add(delete); } ClientFactory.CreateClient(typeof(T)) .Delete(deletes, string.Format("Merged {0} Records", typeof(T).EntityName().Split('.').Last())); return true; } } #endregion }