浏览代码

Merge remote-tracking branch 'origin/kenric' into frank

frankvandenbos 7 月之前
父节点
当前提交
75ce9b54dc

+ 26 - 0
InABox.Core/CoreUtils.cs

@@ -2734,6 +2734,32 @@ namespace InABox.Core
             list.Sort((a, b) => comparison(a).CompareTo(comparison(b)));
         }
 
+        /// <summary>
+        /// Compare the elements in this list to the other list, returning <see langword="true"/> if they have all the same
+        /// elements, regardless of order.
+        /// </summary>
+        public static bool CompareTo<T>(this IList<T> thisList, IList<T> list)
+        {
+            if(thisList.Count != list.Count)
+            {
+                return false;
+            }
+            return thisList.All(x => list.Contains(x));
+        }
+
+        /// <summary>
+        /// Compare the elements in this list to the other list, returning <see langword="true"/> if they have all the same
+        /// elements, regardless of order.
+        /// </summary>
+        public static bool CompareTo<T>(this IList<T> thisList, IList<T> list, IEqualityComparer<T> comparer)
+        {
+            if(thisList.Count != list.Count)
+            {
+                return false;
+            }
+            return thisList.All(x => list.Contains(x, comparer));
+        }
+
         #endregion
 
     }

+ 0 - 1
InABox.Core/Objects/Editors/BaseCodeEditor.cs

@@ -5,7 +5,6 @@
         public BaseCodeEditor()
         {
             Visible = Visible.Optional;
-            Editable = Editable.Hidden;
             Width = 120;
             Alignment = Alignment.MiddleCenter;
             ValidChars = "";

+ 26 - 14
inabox.wpf/DigitalForms/DigitalFormReportGrid.cs

@@ -74,20 +74,24 @@ namespace InABox.DynamicGrid
         {
             Form = (DigitalForm)item;
 
-            CoreTable data;
+            CoreTable? data = null;
             if (PageDataHandler != null)
                 data = PageDataHandler?.Invoke(typeof(ReportTemplate));
-            else if (Form.ID == Guid.Empty)
-            {
-                data = new CoreTable();
-                data.LoadColumns(typeof(ReportTemplate));
-            }
-            else
+
+            if(data is null)
             {
-                data = new Client<ReportTemplate>()
-                    .Query(new Filter<ReportTemplate>(x => x.Section).IsEqualTo(Form.ID.ToString()));
+                if (Form.ID == Guid.Empty)
+                {
+                    data = new CoreTable();
+                    data.LoadColumns(typeof(ReportTemplate));
+                }
+                else
+                {
+                    data = new Client<ReportTemplate>()
+                        .Query(new Filter<ReportTemplate>(x => x.Section).IsEqualTo(Form.ID.ToString()));
+                }
             }
-            OriginalItems = data.Rows.Select(x => x.ToObject<ReportTemplate>()).ToList();
+            OriginalItems = data.ToList<ReportTemplate>();
 
             Items = OriginalItems.ToList();
             Refresh(true, true);
@@ -95,6 +99,17 @@ namespace InABox.DynamicGrid
             Ready = true;
         }
 
+        public void Cancel()
+        {
+            foreach(var item in OriginalItems)
+            {
+                item.CancelChanges();
+            }
+            Items = OriginalItems.ToList();
+            Refresh(false, true);
+        }
+
+
         private DynamicVariableGrid? GetVariableGrid()
             => EditorGrid.Pages?.FirstOrDefault(x => x is DynamicVariableGrid)
                 as DynamicVariableGrid;
@@ -230,9 +245,6 @@ namespace InABox.DynamicGrid
             return new Size(400, 400);
         }
 
-        public int Order()
-        {
-            return int.MinValue;
-        }
+        public int Order { get; set; } = int.MinValue;
     }
 }

+ 52 - 24
inabox.wpf/DynamicGrid/Controls/MultiSelectDialog.cs

@@ -1,5 +1,6 @@
 using System;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Windows;
@@ -27,6 +28,14 @@ namespace InABox.DynamicGrid
         public DynamicGridSelectedFilterSettings Filters { get; set; } = new();
     }
 
+    /// <summary>
+    /// Represents a dialog to select <typeparamref name="T"/>(s), given a filter and a set of columns. It can either do multi-selecting or single-selecting.
+    /// </summary>
+    /// <remarks>
+    /// This is the standard way to do a selection dialog. To access all selected IDs, use <see cref="IDs"/>; to access the data according to the columns
+    /// provided in the constructor, use <see cref="Data"/>; use <see cref="Items"/> if you want to load all the items with the selected IDs with a custom
+    /// set of columns.
+    /// </remarks>
     public class MultiSelectDialog<T> : IMultiSelectDialog where T : Entity, IRemotable, IPersistent, new()
     {
         //private Expression<Func<T, object>>[] _columns = new Expression<Func<T, object>>[] { };
@@ -39,11 +48,6 @@ namespace InABox.DynamicGrid
         private readonly Button OKButton;
         private ThemableWindow? window;
         
-        public MultiSelectDialog() : this(null, null)
-        {
-        }
-
-        //public MultiSelectDialog(Filter<T> filter, Expression<Func<T, object>>[] columns, bool multiselect = true) : base()
         public MultiSelectDialog(Filter<T>? filter, Columns<T>? columns, bool multiselect = true)
         {
             
@@ -161,13 +165,16 @@ namespace InABox.DynamicGrid
             var window = GetWindow();
             if (window.DialogResult == true)
             {
-                if (datagrid?.Data != null && datagrid.SelectedRows.Any())
-                    return datagrid.SelectedRows.Select(r => r.Get<T, Guid>(x => x.ID)).ToArray();
+                return datagrid.SelectedRows.ToArray(r => r.Get<T, Guid>(x => x.ID));
             }
             else if (window.DialogResult == false)
-                return new Guid[] { Guid.Empty };
-
-            return Array.Empty<Guid>();
+            {
+                return [Guid.Empty];
+            }
+            else
+            {
+                return Array.Empty<Guid>();
+            }
         }
 
         public CoreTable Data()
@@ -212,23 +219,16 @@ namespace InABox.DynamicGrid
 
             if (window.DialogResult == true)
             {
-                if (datagrid.Data != null && datagrid.SelectedRows.Any())
+                var ids = datagrid.SelectedRows.ToArray(r => r.Get<T, Guid>(x => x.ID));
+                if (ids.Length > 0)
                 {
-                    Filter<T>? flt = null;
-                    foreach (var row in datagrid.SelectedRows)
-                    {
-                        var id = row.Get<T, Guid>(x => x.ID);
-                        flt = flt == null ? new Filter<T>(x => x.ID).IsEqualTo(id) : flt.Or(x => x.ID).IsEqualTo(id);
-                    }
-
-                    var items = new Client<T>().Query(flt, columns).Rows.Select(r => r.ToObject<T>());
-                    return items.ToArray();
+                    return new Client<T>().Query(new Filter<T>(x => x.ID).InList(ids), columns).ToArray<T>();
                 }
             }
             else if (window.DialogResult == false)
-                return new T[] { new T() };
+                return [new T()];
 
-            return Array.Empty<T>();
+            return [];
         }
 
         Entity[] IMultiSelectDialog.Items(IColumns? columns) => Items(columns as Columns<T>);
@@ -262,12 +262,40 @@ namespace InABox.DynamicGrid
             window.Close();
         }
 
-
-
         private void Grid_OnReload(object sender, Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sortby)
         {
             if (_filter != null)
                 criteria.Add(_filter);
         }
+
+        public static bool SelectItem([NotNullWhen(true)] out T? item, Filter<T>? filter = null, Columns<T>? columns = null, string? title = null)
+        {
+            var dlg = new MultiSelectDialog<T>(filter, columns, multiselect: false);
+            if (dlg.ShowDialogInternal(title, null, null, default))
+            {
+                item = dlg.Data().ToObjects<T>().FirstOrDefault();
+                return item is not null;
+            }
+            else
+            {
+                item = null;
+                return false;
+            }
+        }
+
+        public static bool SelectItems([NotNullWhen(true)] out T[]? items, Filter<T>? filter = null, Columns<T>? columns = null, string? title = null)
+        {
+            var dlg = new MultiSelectDialog<T>(filter, columns, multiselect: true);
+            if (dlg.ShowDialogInternal(title, null, null, default))
+            {
+                items = dlg.Data().ToArray<T>();
+                return true;
+            }
+            else
+            {
+                items = null;
+                return false;
+            }
+        }
     }
 }

+ 1 - 0
inabox.wpf/DynamicGrid/DynamicEditorForm/DynamicEditorForm.xaml.cs

@@ -127,6 +127,7 @@ public partial class DynamicEditorForm : ThemableChromelessWindow, IDynamicEdito
         Form.Setup(type, pages, buttons, pageDataHandler, preloadPages);
     }
     public void SetLayoutType<T>() where T : DynamicEditorGridLayout => Form.SetLayoutType<T>();
+    public void SetLayout(DynamicEditorGridLayout layout) => Form.SetLayout(layout);
 
     // Providing new implementation to avoid using DIalogResult, which breaks if non-modal.
     public new bool? ShowDialog()

+ 18 - 1
inabox.wpf/DynamicGrid/DynamicEditorForm/DynamicEditorGrid.xaml.cs

@@ -352,6 +352,11 @@ public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost
         {
         }
 
+        public void Cancel()
+        {
+            // Do nothing, since the cancelling of the items is handled by the editor form.
+        }
+
         public string Caption() => Header;
 
         public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
@@ -526,7 +531,11 @@ public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost
 
         public Size MinimumSize() => new Size(800, GeneralHeight);
 
-        public int Order() => PageOrder;
+        public int Order
+        {
+            get => PageOrder;
+            set => PageOrder = value;
+        }
     }
 
     #endregion
@@ -564,6 +573,14 @@ public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost
         return page;
     }
 
+    public void SetLayout(DynamicEditorGridLayout layout)
+    {
+        Layout = layout;
+        Layout.OnSelectPage += Layout_SelectPage;
+        Layout.TabStripVisible = _tabStripVisible;
+        Content = Layout;
+    }
+
     public void SetLayoutType<T>() where T : DynamicEditorGridLayout
     {
         LayoutType = typeof(T);

+ 7 - 0
inabox.wpf/DynamicGrid/DynamicEditorForm/EmbeddedDynamicEditorForm.xaml.cs

@@ -363,6 +363,11 @@ namespace InABox.DynamicGrid
             foreach (var item in _items)
                 item.CancelChanges();
 
+            foreach(var page in Editor.Pages)
+            {
+                page.Cancel();
+            }
+
             OnCancel?.Invoke();
             //OKButton.IsEnabled = false;
             //CancelButton.IsEnabled = false;
@@ -389,6 +394,8 @@ namespace InABox.DynamicGrid
 
         public void SetLayoutType(Type t) => Editor.SetLayoutType(t);
 
+        public void SetLayout(DynamicEditorGridLayout layout) => Editor.SetLayout(layout);
+
         private void Editor_OnSelectPage(DynamicEditorGrid sender, BaseObject[] items)
         {
             OnSelectPage?.Invoke(sender, items);

+ 2 - 0
inabox.wpf/DynamicGrid/DynamicEditorForm/IDynamicEditorForm.cs

@@ -47,6 +47,8 @@ namespace InABox.DynamicGrid
 
         void SetLayoutType<T>() where T : DynamicEditorGridLayout;
 
+        void SetLayout(DynamicEditorGridLayout layout);
+
         void UnloadEditorPages(bool saved);
     }
 }

+ 1 - 5
inabox.wpf/DynamicGrid/Grids/DynamicDocumentGrid.cs

@@ -431,12 +431,8 @@ public class DynamicDocumentGrid<TDocument, TEntity, TEntityLink> : DynamicManyT
         options.SelectColumns = false;
         options.DragTarget = true;
     }
-    
 
-    public override int Order()
-    {
-        return int.MaxValue;
-    }
+    public override int Order { get; set; } = int.MaxValue;
 
     protected override void HandleDragOver(object sender, DragEventArgs e)
     {

+ 144 - 136
inabox.wpf/DynamicGrid/Grids/DynamicEnclosedListGrid.cs

@@ -12,182 +12,190 @@ using FastReport.Utils;
 using InABox.Configuration;
 using InABox.Core;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+public interface IDynamicEnclosedListGrid<TOne, TMany> : IDynamicEditorPage
+{
+    TOne Entity { get; set; }
+    List<TMany> Items { get; }
+    void LoadItems(IEnumerable<TMany> items);
+}
+
+public class DynamicEnclosedListGrid<TOne, TMany> : DynamicGrid<TMany>, IDynamicEditorPage, IDynamicEnclosedListGrid<TOne, TMany>
+    where TMany : BaseObject, new() where TOne : Entity, IRemotable, IPersistent, new()
 {
-    public interface IDynamicEnclosedListGrid<TOne, TMany> : IDynamicEditorPage
+    private readonly List<TMany> MasterList = new();
+
+    private readonly PropertyInfo property;
+
+    public PageType PageType => PageType.Other;
+
+    public bool ReadOnly { get; set; }
+
+    protected DynamicGridCustomColumnsComponent<TMany> ColumnsComponent;
+
+    public DynamicEnclosedListGrid(PropertyInfo prop)
     {
-        TOne Entity { get; set; }
-        List<TMany> Items { get; }
-        void LoadItems(IEnumerable<TMany> items);
+        Items = new List<TMany>();
+        property = prop;
+
+        ColumnsComponent = new(this, null);
     }
 
-    public class DynamicEnclosedListGrid<TOne, TMany> : DynamicGrid<TMany>, IDynamicEditorPage, IDynamicEnclosedListGrid<TOne, TMany>
-        where TMany : BaseObject, new() where TOne : Entity, IRemotable, IPersistent, new()
+    protected override void Init()
     {
-        private readonly List<TMany> MasterList = new();
-
-        private readonly PropertyInfo property;
+    }
 
-        public PageType PageType => PageType.Other;
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        options.RecordCount = true;
+        options.SelectColumns = true;
+    }
 
-        public bool ReadOnly { get; set; }
+    public DynamicEditorGrid EditorGrid { get; set; }
 
-        protected DynamicGridCustomColumnsComponent<TMany> ColumnsComponent;
+    public bool Ready { get; set; }
 
-        public DynamicEnclosedListGrid(PropertyInfo prop)
-        {
-            Items = new List<TMany>();
-            property = prop;
+    public string Caption()
+    {
+        var caption = typeof(TMany).GetCustomAttribute(typeof(Caption));
+        if (caption != null)
+            return ((Caption)caption).Text;
+        var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(typeof(TMany).Name);
+        return result;
+    }
 
-            ColumnsComponent = new(this, null);
-        }
+    public virtual int Order { get; set; } = int.MinValue;
 
-        protected override void Init()
-        {
-        }
+    public void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
+    {
+        Entity = (TOne)item;
+        MasterList.Clear();
+        if (property.GetValue(item) is IList list)
+            foreach (var entry in list)
+                MasterList.Add((TMany)entry);
+        Items.AddRange(MasterList);
+        Refresh(true, true);
+        Ready = true;
+    }
 
-        protected override void DoReconfigure(DynamicGridOptions options)
+    public void Cancel()
+    {
+        foreach(var item in MasterList)
         {
-            options.RecordCount = true;
-            options.SelectColumns = true;
+            item.CancelChanges();
         }
 
-        public DynamicEditorGrid EditorGrid { get; set; }
+        Items.Clear();
+        Items.AddRange(MasterList);
+        Refresh(false, true);
+    }
 
-        public bool Ready { get; set; }
+    public void BeforeSave(object item)
+    {
+        var list = Activator.CreateInstance(property.PropertyType) as IList;
+        foreach (var entry in Items)
+            list.Add(entry);
+        property.SetValue(item, list);
+    }
 
-        public string Caption()
-        {
-            var caption = typeof(TMany).GetCustomAttribute(typeof(Caption));
-            if (caption != null)
-                return ((Caption)caption).Text;
-            var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(typeof(TMany).Name);
-            return result;
-        }
+    public void AfterSave(object item)
+    {
+    }
 
-        public virtual int Order()
-        {
-            return int.MinValue;
-        }
+    public Size MinimumSize()
+    {
+        return new Size(400, 400);
+    }
 
-        public void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
-        {
-            Entity = (TOne)item;
-            MasterList.Clear();
-            if (property.GetValue(item) is IList list)
-                foreach (var entry in list)
-                    MasterList.Add((TMany)entry);
-            Items.AddRange(MasterList);
-            Refresh(true, true);
-            Ready = true;
-        }
+    public List<TMany> Items { get; private set; }
 
-        public void BeforeSave(object item)
-        {
-            var list = Activator.CreateInstance(property.PropertyType) as IList;
-            foreach (var entry in Items)
-                list.Add(entry);
-            property.SetValue(item, list);
-        }
+    public TOne Entity { get; set; }
 
-        public void AfterSave(object item)
-        {
-        }
+    public void LoadItems(IEnumerable<TMany> items)
+    {
+        Items.Clear();
+        if (items != null)
+            Items.AddRange(items);
+        Refresh(false, true);
+    }
 
-        public Size MinimumSize()
-        {
-            return new Size(400, 400);
-        }
+    public override DynamicGridColumns GenerateColumns()
+    {
+        var cols = new DynamicGridColumns();
+        cols.AddRange(base.GenerateColumns().Where(x => !x.ColumnName.StartsWith(property.Name + ".")));
+        return cols;
+    }
 
-        public List<TMany> Items { get; private set; }
+    protected override DynamicGridColumns LoadColumns()
+    {
+        return ColumnsComponent.LoadColumns();
+    }
 
-        public TOne Entity { get; set; }
+    protected override void SaveColumns(DynamicGridColumns columns)
+    {
+        ColumnsComponent.SaveColumns(columns);
+    }
 
-        public void LoadItems(IEnumerable<TMany> items)
-        {
-            Items.Clear();
-            if (items != null)
-                Items.AddRange(items);
-            Refresh(false, true);
-        }
+    protected override void LoadColumnsMenu(ContextMenu menu)
+    {
+        base.LoadColumnsMenu(menu);
+        ColumnsComponent.LoadColumnsMenu(menu);
+    }
 
-        public override DynamicGridColumns GenerateColumns()
-        {
-            var cols = new DynamicGridColumns();
-            cols.AddRange(base.GenerateColumns().Where(x => !x.ColumnName.StartsWith(property.Name + ".")));
-            return cols;
-        }
+    public override TMany LoadItem(CoreRow row)
+    {
+        return Items[row.Index];
+    }
 
-        protected override DynamicGridColumns LoadColumns()
-        {
-            return ColumnsComponent.LoadColumns();
-        }
+    public override void SaveItem(TMany item)
+    {
+        if (!Items.Contains(item))
+            Items.Add(item);
 
-        protected override void SaveColumns(DynamicGridColumns columns)
+        if (item is ISequenceable && LookupFactory.DefineSort<TMany>() is SortOrder<TMany> sort)
         {
-            ColumnsComponent.SaveColumns(columns);
+            Items = Items.AsQueryable().SortBy(sort.Expression).ToList();
         }
+    }
 
-        protected override void LoadColumnsMenu(ContextMenu menu)
-        {
-            base.LoadColumnsMenu(menu);
-            ColumnsComponent.LoadColumnsMenu(menu);
-        }
+    public override void DeleteItems(params CoreRow[] rows)
+    {
+        foreach (var row in rows.OrderByDescending(x => x.Index))
+            Items.RemoveAt(row.Index);
+    }
 
-        public override TMany LoadItem(CoreRow row)
-        {
-            return Items[row.Index];
-        }
+    protected override void Reload(
+        Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort, 
+        CancellationToken token, Action<CoreTable?, Exception?> action)
+    {
+        var results = new CoreTable();
+        results.LoadColumns(typeof(TMany));
 
-        public override void SaveItem(TMany item)
+        if (sort != null)
         {
-            if (!Items.Contains(item))
-                Items.Add(item);
-
-            if (item is ISequenceable && LookupFactory.DefineSort<TMany>() is SortOrder<TMany> sort)
+            var exp = IQueryableExtensions.ToLambda<TMany>(sort.Expression);
+            var sorted = sort.Direction == SortDirection.Ascending
+                ? Items.AsQueryable().OrderBy(exp)
+                : Items.AsQueryable().OrderByDescending(exp);
+            foreach (var then in sort.Thens)
             {
-                Items = Items.AsQueryable().SortBy(sort.Expression).ToList();
+                var thexp = IQueryableExtensions.ToLambda<TMany>(then.Expression);
+                sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp);
             }
-        }
 
-        public override void DeleteItems(params CoreRow[] rows)
-        {
-            foreach (var row in rows.OrderByDescending(x => x.Index))
-                Items.RemoveAt(row.Index);
+            results.LoadRows(sorted);
         }
-
-        protected override void Reload(
-            Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort, 
-            CancellationToken token, Action<CoreTable?, Exception?> action)
+        else
         {
-            var results = new CoreTable();
-            results.LoadColumns(typeof(TMany));
-
-            if (sort != null)
-            {
-                var exp = IQueryableExtensions.ToLambda<TMany>(sort.Expression);
-                var sorted = sort.Direction == SortDirection.Ascending
-                    ? Items.AsQueryable().OrderBy(exp)
-                    : Items.AsQueryable().OrderByDescending(exp);
-                foreach (var then in sort.Thens)
-                {
-                    var thexp = IQueryableExtensions.ToLambda<TMany>(then.Expression);
-                    sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp);
-                }
-
-                results.LoadRows(sorted);
-            }
-            else
-            {
-                results.LoadRows(Items);
-            }
+            results.LoadRows(Items);
+        }
 
-            //if (sort != null)
-            //    results.LoadRows(Items.AsQueryable().SortBy(sort.Expression));
-            //else
-            //    results.LoadRows(Items.OrderBy(x => x.Sort));
+        //if (sort != null)
+        //    results.LoadRows(Items.AsQueryable().SortBy(sort.Expression));
+        //else
+        //    results.LoadRows(Items.OrderBy(x => x.Sort));
 
-            action.Invoke(results, null);
-        }
+        action.Invoke(results, null);
     }
 }

+ 3 - 2
inabox.wpf/DynamicGrid/Grids/DynamicGrid.cs

@@ -1507,7 +1507,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                 {
                     DeleteItems(rows);
                     SelectedRows = Array.Empty<CoreRow>();
-                    OnChanged?.Invoke(this, EventArgs.Empty);
+                    DoChanged();
                     Refresh(false, true);
                     SelectItems(null);
                 }
@@ -1576,7 +1576,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
 
         InvalidateGrid();
         SelectedRows = newRows.ToArray();
-        OnChanged?.Invoke(this, EventArgs.Empty);
+        DoChanged();
     }
 
     private void Add_Click(object sender, RoutedEventArgs e)
@@ -2238,6 +2238,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         if (updates.Length != 0)
         {
             SaveItems(updates);
+            DoChanged();
             Refresh(false, true);
         }
     }

+ 11 - 4
inabox.wpf/DynamicGrid/Grids/DynamicManyToManyGrid.cs

@@ -122,10 +122,7 @@ public class DynamicManyToManyGrid<TManyToMany, TThis> : DynamicGrid<TManyToMany
         return result;
     }
 
-    public virtual int Order()
-    {
-        return int.MinValue;
-    }
+    public virtual int Order { get; set; } = int.MinValue;
 
     public bool Ready { get; set; }
 
@@ -364,6 +361,16 @@ public class DynamicManyToManyGrid<TManyToMany, TThis> : DynamicGrid<TManyToMany
         }
     }
 
+    public void Cancel()
+    {
+        foreach(var item in MasterList)
+        {
+            item.CancelChanges();
+        }
+        WorkingList = MasterList.ToList();
+        Refresh(false, true);
+    }
+
     private void RefreshData(CoreTable data)
     {
         MasterList = data.ToArray<TManyToMany>();

+ 11 - 5
inabox.wpf/DynamicGrid/Grids/DynamicOneToManyGrid.cs

@@ -152,12 +152,21 @@ public class DynamicOneToManyGrid<TOne, TMany> : DynamicGrid<TMany>,
             }
         }
 
-        MasterList = data.Rows.Select(x => x.ToObject<TMany>()).ToArray();
+        MasterList = data.ToArray<TMany>();
 
         Items = MasterList.ToList();
         Refresh(false, true);
         Ready = true;
     }
+    public void Cancel()
+    {
+        foreach(var item in MasterList)
+        {
+            item.CancelChanges();
+        }
+        Items = MasterList.ToList();
+        Refresh(false, true);
+    }
 
     public virtual void BeforeSave(object item)
     {
@@ -199,10 +208,7 @@ public class DynamicOneToManyGrid<TOne, TMany> : DynamicGrid<TMany>,
         return result;
     }
 
-    public virtual int Order()
-    {
-        return int.MinValue;
-    }
+    public virtual int Order { get; set; } = int.MinValue;
 
     #endregion
 

+ 1 - 1
inabox.wpf/DynamicGrid/Layouts/DefaultDynamicEditorGridLayout.cs

@@ -40,7 +40,7 @@ namespace InABox.DynamicGrid
         public override void LoadPages(IEnumerable<IDynamicEditorPage> pages)
         {
             Details.Items.Clear();
-            foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order()).ThenBy(x => x.Caption()))
+            foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order).ThenBy(x => x.Caption()))
             {
                 var tab = new DynamicTabItem();
                 tab.Header = page.Caption();

+ 62 - 63
inabox.wpf/DynamicGrid/Layouts/VerticalDynamicEditorGridLayout.xaml.cs

@@ -13,87 +13,86 @@ using System.Windows.Media.Imaging;
 using System.Windows.Navigation;
 using System.Windows.Shapes;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+/// <summary>
+/// Interaction logic for VerticalDynamicEditorGridLayout.xaml
+/// </summary>
+public partial class VerticalDynamicEditorGridLayout : DynamicEditorGridLayout
 {
-    /// <summary>
-    /// Interaction logic for VerticalDynamicEditorGridLayout.xaml
-    /// </summary>
-    public partial class VerticalDynamicEditorGridLayout : DynamicEditorGridLayout
+    
+    public override bool TabStripVisible
     {
-        
-        public override bool TabStripVisible
-        {
-            get { return Editors.TabStripVisible; }
-            set { Editors.TabStripVisible = value; }
-        }
+        get { return Editors.TabStripVisible; }
+        set { Editors.TabStripVisible = value; }
+    }
 
-        private double _totalWidth;
-        public override double TotalWidth => _totalWidth;
+    private double _totalWidth;
+    public override double TotalWidth => _totalWidth;
 
-        private double _editorHeight;
-        private double _pageHeight;
-        public override double TotalHeight => _editorHeight + _pageHeight;
+    private double _editorHeight;
+    private double _pageHeight;
+    public override double TotalHeight => _editorHeight + _pageHeight;
 
-        public VerticalDynamicEditorGridLayout()
-        {
-            InitializeComponent();
-        }
+    public VerticalDynamicEditorGridLayout()
+    {
+        InitializeComponent();
+    }
 
-        public override void LoadPages(IEnumerable<IDynamicEditorPage> pages)
+    public override void LoadPages(IEnumerable<IDynamicEditorPage> pages)
+    {
+        Editors.Items.Clear();
+        OtherPages.Items.Clear();
+        foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order).ThenBy(x => x.Caption()))
         {
-            Editors.Items.Clear();
-            OtherPages.Items.Clear();
-            foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order()).ThenBy(x => x.Caption()))
-            {
-                var tab = new DynamicTabItem();
-                tab.Header = page.Caption();
-                if (page is FrameworkElement element)
-                    element.Margin = new Thickness(0, 2, 0, 0);
-                tab.Content = page;
+            var tab = new DynamicTabItem();
+            tab.Header = page.Caption();
+            if (page is FrameworkElement element)
+                element.Margin = new Thickness(0, 2, 0, 0);
+            tab.Content = page;
 
-                var minSize = page.MinimumSize();
-
-                if(page is DynamicEditorGrid.DynamicEditPage)
-                {
-                    Editors.Items.Add(tab);
-                    _editorHeight = Math.Max(_editorHeight, minSize.Height);
-                    _totalWidth = Math.Max(_totalWidth, minSize.Width);
-                }
-                else
-                {
-                    OtherPages.Items.Add(tab);
-                    _pageHeight = Math.Max(_pageHeight, minSize.Height);
-                }
+            var minSize = page.MinimumSize();
 
+            if(page is DynamicEditorGrid.DynamicEditPage)
+            {
+                Editors.Items.Add(tab);
+                _editorHeight = Math.Max(_editorHeight, minSize.Height);
+                _totalWidth = Math.Max(_totalWidth, minSize.Width);
+            }
+            else
+            {
+                OtherPages.Items.Add(tab);
+                _pageHeight = Math.Max(_pageHeight, minSize.Height);
             }
 
-            Editors.SelectedIndex = 0;
         }
 
-        private bool bChanging;
-        private void Editors_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            if (bChanging) return;
-            if ((e.OriginalSource != Editors && e.OriginalSource != OtherPages) || e.OriginalSource is not DynamicTabControl tabControl) return;
-            if (tabControl.SelectedItem is not DynamicTabItem tab) return;
+        Editors.SelectedIndex = 0;
+    }
 
-            bChanging = true;
-            try
-            {
-                if (tab is not null && tab.Content is IDynamicEditorPage page)
-                {
-                    SelectPage(page);
-                }
-            }
-            finally
+    private bool bChanging;
+    private void Editors_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (bChanging) return;
+        if ((e.OriginalSource != Editors && e.OriginalSource != OtherPages) || e.OriginalSource is not DynamicTabControl tabControl) return;
+        if (tabControl.SelectedItem is not DynamicTabItem tab) return;
+
+        bChanging = true;
+        try
+        {
+            if (tab is not null && tab.Content is IDynamicEditorPage page)
             {
-                bChanging = false;
+                SelectPage(page);
             }
         }
-
-        private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
+        finally
         {
-            EditorRow.MaxHeight = e.NewSize.Height - 50;
+            bChanging = false;
         }
     }
+
+    private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
+    {
+        EditorRow.MaxHeight = e.NewSize.Height - 50;
+    }
 }

+ 13 - 15
inabox.wpf/DynamicGrid/Pages/DynamicEditorPage.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using System.Windows;
 using InABox.Core;
 
@@ -22,6 +24,8 @@ public interface IDynamicEditorPage
 
     bool ReadOnly { get; set; }
 
+    int Order { get; set; }
+
     void Load(object item, Func<Type, CoreTable?>? PageDataHandler);
 
     /// <summary>
@@ -42,26 +46,13 @@ public interface IDynamicEditorPage
     event EventHandler OnChanged;
     void DoChanged();
 
+    void Cancel();
+
     Size MinimumSize();
 
     string Caption();
-
-    int Order();
 }
 
-//public class DynamicEditorPage 
-//{
-//	public String Name { get; set; }
-//	public Control Page { get; set; }
-
-//	public DynamicEditorPage(String name, Control page) : base()
-//	{
-//		Name = name;
-//		Page = page;
-//	}
-
-//}
-
 public class DynamicEditorPages : List<IDynamicEditorPage>
 {
     public DynamicEditorPages() : base()
@@ -74,4 +65,11 @@ public class DynamicEditorPages : List<IDynamicEditorPage>
         foreach (var page in pages)
             Add(page);
     }
+
+    public bool TryGetPage<T>([NotNullWhen(true)] out T? page)
+        where T : IDynamicEditorPage
+    {
+        page = this.OfType<T>().FirstOrDefault();
+        return page is not null;
+    }
 }