Browse Source

Digital forms can now print reports, and can have custom buttons

Kenric Nugteren 1 year ago
parent
commit
dd87f02318

+ 13 - 0
InABox.Core/Classes/Document/Document.cs

@@ -1,4 +1,5 @@
 using System;
+using System.IO;
 
 namespace InABox.Core
 {
@@ -51,5 +52,17 @@ namespace InABox.Core
         {
             return FileName;
         }
+
+        public static Document FromFile(string filename)
+        {
+            var data = File.ReadAllBytes(filename);
+            return new Document
+            {
+                Data = data,
+                FileName = filename,
+                CRC = CoreUtils.CalculateCRC(data),
+                TimeStamp = new FileInfo(filename).LastWriteTime
+            };
+        }
     }
 }

+ 15 - 3
InABox.Core/DigitalForms/DigitalFormReportDataModel.cs

@@ -9,6 +9,8 @@ namespace InABox.Core
     public interface IDigitalFormReportDataModel
     {
         public DigitalFormVariable[]? Variables { get; set; }
+
+        void AddFormData(Guid formInstanceID, DFLoadStorage storage);
     }
 
     /// <summary>
@@ -23,6 +25,8 @@ namespace InABox.Core
 
         private CoreTable? FormDataTable { get; set; } = null;
 
+        private Dictionary<Guid, DFLoadStorage> FormData { get; set; } = new Dictionary<Guid, DFLoadStorage>();
+
         //private DigitalFormVariable[]? _variables = null;
 
         public DigitalFormVariable[]? Variables { get; set; }
@@ -34,6 +38,11 @@ namespace InABox.Core
 
         public override string Name => "Digital Form Reports";
 
+        public void AddFormData(Guid formInstanceID, DFLoadStorage storage)
+        {
+            FormData.Add(formInstanceID, storage);
+        }
+
         protected override void CheckRequiredTables(List<string> requiredtables)
         {
             base.CheckRequiredTables(requiredtables);
@@ -90,10 +99,13 @@ namespace InABox.Core
 
             for(var i = 0; i < jsonLists.Count; ++i)
             {
-                var json = jsonLists[i];
-                var blobJSON = blobLists[i];
+                if (!FormData.TryGetValue(idList[i], out var formData))
+                {
+                    var json = jsonLists[i];
+                    var blobJSON = blobLists[i];
 
-                var formData = DigitalForm.DeserializeFormData(json, blobJSON);
+                    formData = DigitalForm.DeserializeFormData(json, blobJSON);
+                }
 
                 var formRow = FormDataTable!.NewRow();
                 formRow["Parent.ID"] = idList[i];

+ 9 - 15
inabox.dxf/DxfUtils.cs

@@ -210,24 +210,18 @@ namespace InABox.Dxf
             lines.AddRange(segments.Select(x => x as Line));
         }
 
-        public static Bitmap DXFToBitmap(string filename)
+        public static Bitmap? DXFToBitmap(string filename)
         {
-            Bitmap result = null;
-
-            using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
+            using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
+            try
             {
-                try
-                {
-                    result = ProcessImage(stream, Path.GetFileNameWithoutExtension(filename));
-                }
-                catch (Exception e)
-                {
-                    result = null;
-                    OnProcessError?.Invoke(e.Message);
-                }
+                return ProcessImage(stream, Path.GetFileNameWithoutExtension(filename));
+            }
+            catch (Exception e)
+            {
+                OnProcessError?.Invoke(e.Message);
+                return null;
             }
-
-            return result;
         }
     }
 }

+ 31 - 6
inabox.wpf/DigitalForms/Designer/DynamicEditFormWindow.xaml

@@ -1,4 +1,4 @@
-<local:DynamicFormWindow x:Class="InABox.DynamicGrid.DynamicFormEditWindow"
+<Window x:Class="InABox.DynamicGrid.DynamicFormEditWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -6,8 +6,9 @@
         xmlns:local="clr-namespace:InABox.DynamicGrid"
         mc:Ignorable="d"
         Title="FormDesigner" Height="800" Width="1000" Padding="5"
-                         Closing="DynamicFormWindow_Closing">
-    <Grid Margin="5">
+        Closing="DynamicFormWindow_Closing"
+        x:Name="Window">
+    <Grid Margin="5" DataContext="{Binding ElementName=Window}">
 
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*" />
@@ -26,8 +27,32 @@
 
         <DockPanel Grid.Row="1">
 
-            <Button x:Name="CompleteForm" Content="Complete Form" Width="100" Height="35" Margin="5" Click="CompleteForm_Click"  DockPanel.Dock="Right" />
-            <Button x:Name="SaveForm" Content="Save Progress" Width="80" Height="35" Margin="5" Click="SaveForm_Click"  DockPanel.Dock="Left" />
+            <Button x:Name="CompleteForm" Content="Complete Form"
+                    Height="35" Padding="5" Margin="5"
+                    Click="CompleteForm_Click"  DockPanel.Dock="Right" />
+            <Button x:Name="SaveForm" Content="Save Progress"
+                    Height="35" Padding="5" Margin="5"
+                    Click="SaveForm_Click"  DockPanel.Dock="Left" />
+            <Button x:Name="PrintForm" Content="Print"
+                    Height="35" Padding="5" Margin="0,5,5,5" MinWidth="50"
+                    Click="PrintForm_Click"  DockPanel.Dock="Left" />
+
+            <ItemsControl ItemsSource="{Binding CustomButtons}" DockPanel.Dock="Left">
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate DataType="local:DynamicFormEditButton">
+                        <Button Content="{Binding Content}"
+                                Margin="0,5,5,5" Padding="5" Height="35" MinWidth="50"
+                                Tag="{Binding}"
+                                Click="Button_Click"/>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel Orientation="Horizontal"/>
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+            </ItemsControl>
+            
             <Label x:Name="CompletedDate" DockPanel.Dock="Left"
                    HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
 
@@ -38,4 +63,4 @@
 
     </Grid>
 
-</local:DynamicFormWindow>
+</Window>

+ 344 - 257
inabox.wpf/DigitalForms/Designer/DynamicEditFormWindow.xaml.cs

@@ -1,346 +1,433 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 using System.Windows;
+using System.Windows.Controls;
 using InABox.Clients;
 using InABox.Core;
+using InABox.Wpf;
+using InABox.Wpf.Reports;
 using InABox.WPF;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+public class DynamicFormEditButton : INotifyPropertyChanged
 {
-    /// <summary>
-    ///     Interaction logic for FormDesigner.xaml
-    /// </summary>
-    public partial class DynamicFormEditWindow : DynamicFormWindow
+    public delegate void DynamicFormEditButtonDelegate(DynamicFormEditWindow window, DynamicFormEditButton button);
+
+    private object? _content;
+    public object? Content
     {
-        public enum FormResult
+        get => _content;
+        set
         {
-            None,
-            Cancel,
-            Save,
-            Complete
+            _content = value;
+            OnPropertyChanged();
         }
+    }
 
-        public DynamicFormEditWindow()
-        {
-            InitializeComponent();
-            Grid.OnChanged += Grid_OnChanged;
+    public DynamicFormEditButtonDelegate Action { get; set; }
 
-            //Complete.IsEnabled = Security.IsAllowed<CanChangeDigitalFormCompletion>();
-        }
+    public DynamicFormEditButton(object? content, DynamicFormEditButtonDelegate action)
+    {
+        Content = content;
+        Action = action;
+    }
 
-        protected override DynamicFormDesignGrid Grid { get => Preview; }
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-        public FormMode Mode
-        {
-            get => Grid.Mode;
-            private set => Grid.Mode = value;
-        }
+    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
+    {
+        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+    }
+}
 
-        public FormResult Result = FormResult.None;
+/// <summary>
+///     Interaction logic for FormDesigner.xaml
+/// </summary>
+public partial class DynamicFormEditWindow : Window, IDynamicFormWindow
+{
+    public delegate void CustomiseDynamicFormEditWindow(DynamicFormEditWindow window);
 
-        public IDigitalFormDataModel? DataModel
-        {
-            get => Grid.DataModel;
-            set
-            {
-                Grid.DataModel = value;
-                RefreshEnabled();
-            }
-        }
+    public enum FormResult
+    {
+        None,
+        Cancel,
+        Save,
+        Complete
+    }
+
+    public DynamicFormEditWindow()
+    {
+        InitializeComponent();
+        Grid.OnChanged += Grid_OnChanged;
+
+        //Complete.IsEnabled = Security.IsAllowed<CanChangeDigitalFormCompletion>();
+    }
 
-        private bool IsReopening = false;
-        private bool HasChanged = false;
-        private bool HasUnsavedChanges => Grid.IsChanged || HasChanged || DataModel!.Instance.ID == Guid.Empty;
+    public DynamicFormDesignGrid Grid => Preview;
+
+    public FormMode Mode
+    {
+        get => Grid.Mode;
+        private set => Grid.Mode = value;
+    }
 
-        public DFSaveStorage SaveValues() => Grid.SaveValues();
+    public FormResult Result = FormResult.None;
 
-        public void LoadValues(DFLoadStorage storage)
+    public IDigitalFormDataModel? DataModel
+    {
+        get => Grid.DataModel;
+        set
         {
-            Grid.LoadValues(storage);
+            Grid.DataModel = value;
             RefreshEnabled();
         }
+    }
+
+    public ObservableCollection<DynamicFormEditButton> CustomButtons { get; set; } = new ObservableCollection<DynamicFormEditButton>();
+
+    private bool IsReopening = false;
+    private bool HasChanged = false;
+    private bool HasUnsavedChanges => Grid.IsChanged || HasChanged || DataModel!.Instance.ID == Guid.Empty;
 
-        private void RefreshEnabled()
+    private DFLayoutType _type;
+    public DFLayoutType Type
+    {
+        get => _type;
+        set
         {
-            var formInstance = DataModel!.Instance;
-            var completed = formInstance.FormCompleted;
+            _type = value;
+            Width = _type == DFLayoutType.Mobile ? 600 : 1000;
+            Height = 800;
+        }
+    }
 
-            CompletedDate.Content = !completed.IsEmpty() ? $"Completed {completed:d MMM yyyy} at {completed:hh:mm tt}" : "Not completed yet";
+    public DFSaveStorage SaveValues() => Grid.SaveValues();
 
-            if (completed.IsEmpty()
-                && DFUtils.CanEditForm(formInstance.GetType(), formInstance, DataModel.Entity))
-            {
-                Mode = FormMode.Filling;
-            }
-            else
-            {
-                Mode = Security.IsAllowed<CanEditCompletedForms>()
-                    ? FormMode.Editing
-                    : FormMode.ReadOnly;
-            }
+    public void LoadValues(DFLoadStorage storage)
+    {
+        Grid.LoadValues(storage);
+        RefreshEnabled();
+    }
 
-            if (Mode == FormMode.Editing || Mode == FormMode.Filling || Mode == FormMode.Preview)
-            {
-                SaveForm.IsEnabled = HasUnsavedChanges;
-            }
-            else
-            {
-                SaveForm.IsEnabled = false;
-            }
+    private void RefreshEnabled()
+    {
+        var formInstance = DataModel!.Instance;
+        var completed = formInstance.FormCompleted;
 
-            if (!completed.IsEmpty() && Security.IsAllowed<CanChangeDigitalFormCompletion>())
-            {
-                CompleteForm.Content = "Re-open form";
-                CompleteForm.IsEnabled = true;
-                IsReopening = true;
-            }
-            else
-            {
-                CompleteForm.Content = "Complete form";
-                CompleteForm.IsEnabled = Mode == FormMode.Filling && DataModel?.Instance.FormCompleted == DateTime.MinValue;
-                IsReopening = false;
-            }
-        }
+        CompletedDate.Content = !completed.IsEmpty() ? $"Completed {completed:d MMM yyyy} at {completed:hh:mm tt}" : "Not completed yet";
 
-        private void Grid_OnChanged(DynamicFormDesignGrid sender, string fieldName)
+        if (completed.IsEmpty()
+            && DFUtils.CanEditForm(formInstance.GetType(), formInstance, DataModel.Entity))
         {
-            if (Mode == FormMode.Editing || Mode == FormMode.Filling || Mode == FormMode.Preview)
-            {
-                SaveForm.IsEnabled = true;
-            }
+            Mode = FormMode.Filling;
+        }
+        else
+        {
+            Mode = Security.IsAllowed<CanEditCompletedForms>()
+                ? FormMode.Editing
+                : FormMode.ReadOnly;
         }
 
-        protected override void OnClosing(CancelEventArgs e)
+        if (Mode == FormMode.Editing || Mode == FormMode.Filling || Mode == FormMode.Preview)
         {
-            if (DialogResult == null)
-            {
-                Result = FormResult.Cancel;
-                DialogResult = false;
-            }
-            base.OnClosing(e);
+            SaveForm.IsEnabled = HasUnsavedChanges;
+        }
+        else
+        {
+            SaveForm.IsEnabled = false;
         }
 
-        private void Complete()
+        if (!completed.IsEmpty() && Security.IsAllowed<CanChangeDigitalFormCompletion>())
         {
-            if (!Grid.Validate(out var messages))
-            {
-                MessageBox.Show(string.Join('\n', messages));
-                return;
-            }
-            if (MessageBox.Show("Are you sure you want to complete this form?", "Confirm Completion", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
-            {
-                Result = FormResult.Complete;
-                DialogResult = true;
-            }
+            CompleteForm.Content = "Re-open form";
+            CompleteForm.IsEnabled = true;
+            IsReopening = true;
         }
-        private void Reopen()
+        else
         {
-            HasChanged = true;
-            DataModel!.Instance.FormCompleted = DateTime.MinValue;
-            DataModel!.Instance.FormCompletedBy.Clear();
-            RefreshEnabled();
+            CompleteForm.Content = "Complete form";
+            CompleteForm.IsEnabled = Mode == FormMode.Filling && DataModel?.Instance.FormCompleted == DateTime.MinValue;
+            IsReopening = false;
         }
+    }
 
-        private void CompleteForm_Click(object sender, RoutedEventArgs e)
+    private void Grid_OnChanged(DynamicFormDesignGrid sender, string fieldName)
+    {
+        if (Mode == FormMode.Editing || Mode == FormMode.Filling || Mode == FormMode.Preview)
         {
-            if (IsReopening)
-                Reopen();
-            else
-                Complete();
+            SaveForm.IsEnabled = true;
         }
+    }
 
-        private void SaveForm_Click(object sender, RoutedEventArgs e)
+    protected override void OnClosing(CancelEventArgs e)
+    {
+        if (DialogResult == null)
         {
-            if (DataModel?.Instance.FormCompleted.IsEmpty() == false && !Grid.Validate(out var messages))
-            {
-                MessageBox.Show(string.Join('\n', messages));
-                return;
-            }
-            if (MessageBox.Show("Are you sure you want to save this form?", "Confirm Save", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
-            {
-                Result = FormResult.Save;
-                DialogResult = true;
-            }
+            Result = FormResult.Cancel;
+            DialogResult = false;
         }
+        base.OnClosing(e);
+    }
 
-        public static Columns<TForm> FormColumns<TForm>() where TForm : IDigitalFormInstance
+    private void Complete()
+    {
+        if (!Grid.Validate(out var messages))
         {
-            return (FormColumns(typeof(TForm)) as Columns<TForm>)!;
+            MessageBox.Show(string.Join('\n', messages));
+            return;
         }
-        public static IColumns FormColumns(Type TForm)
+        if (MessageBox.Show("Are you sure you want to complete this form?", "Confirm Completion", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
         {
-            return Columns.Create<IDigitalFormInstance>(TForm)
-                .Add<IDigitalFormInstance>(x => x.ID)
-                .Add<IDigitalFormInstance>(x=>x.Number)
-                .Add<IDigitalFormInstance>(x => x.FormCompleted)
-                .Add<IDigitalFormInstance>(x => x.FormData)
-                .Add<IDigitalFormInstance>(x => x.BlobData)
-                .Add<IDigitalFormInstance>(x => x.Form.ID)
-                .Add<IDigitalFormInstance>(x => x.Form.Description)
-                .Add("Parent.ID");
+            Result = FormResult.Complete;
+            DialogResult = true;
         }
+    }
+    private void Reopen()
+    {
+        HasChanged = true;
+        DataModel!.Instance.FormCompleted = DateTime.MinValue;
+        DataModel!.Instance.FormCompletedBy.Clear();
+        RefreshEnabled();
+    }
 
-        public static bool EditDigitalForm(
-            IDigitalFormInstance formInstance,
-            [NotNullWhen(true)] out IDigitalFormDataModel? dataModel,
-            Entity? parent = null)
-        {
+    private void CompleteForm_Click(object sender, RoutedEventArgs e)
+    {
+        if (IsReopening)
+            Reopen();
+        else
+            Complete();
+    }
 
-            dataModel = null;
+    private void Button_Click(object sender, RoutedEventArgs e)
+    {
+        if (sender is not Button btn || btn.Tag is not DynamicFormEditButton button) return;
 
-            DigitalFormLayout layout = null;
-            DigitalFormVariable[] variables = null;
-            DFLoadStorage? values = null;
-            String error = "";
+        button.Action(this, button);
+    }
 
-            Progress.ShowModal("Loading Form", (progress) =>
-            {
+    private DataModel? GetReportDataModel()
+    {
+        var dataModel = DataModel;
+        if (dataModel is null) return null;
 
-                var formid = formInstance.Form.ID;
+        var formType = dataModel.Instance.GetType();
 
-                values = DigitalForm.DeserializeFormData(formInstance);
+        var filter = Filter.Create<Entity>(formType, x => x.ID).IsEqualTo(dataModel.Instance.ID);
 
-                var results = Client.QueryMultiple(
-                    new KeyedQueryDef<DigitalFormVariable>(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formid)),
-                    new KeyedQueryDef<DigitalFormLayout>(
-                        new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(formid)
-                            .And(x => x.Active).IsEqualTo(true)
-                            .And(x => x.Layout).IsNotEqualTo("")));
+        return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)!
+            .MakeGenericType(formType), new object?[] { filter, dataModel.Instance.Form.ID }) as DataModel)!;
+    }
 
-                variables = results[nameof(DigitalFormVariable)].Rows.Select(x => x.ToObject<DigitalFormVariable>()).ToArray();
+    private void PrintForm_Click(object sender, RoutedEventArgs e)
+    {
+        var model = GetReportDataModel();
+        var dataModel = DataModel;
+        if (model is null || dataModel is null) return;
 
-                var desktopLayout = results[nameof(DigitalFormLayout)]
-                    .Rows.FirstOrDefault(x => x.Get<DigitalFormLayout, DFLayoutType>(x => x.Type) == DFLayoutType.Desktop)
-                    ?.ToObject<DigitalFormLayout>();
+        (model as IDigitalFormReportDataModel)!.AddFormData(dataModel.Instance.ID, SaveValues().ToLoadStorage());
 
-                layout = desktopLayout ?? results[nameof(DigitalFormLayout)].ToObjects<DigitalFormLayout>().FirstOrDefault();
-                if (layout != null)
-                {
-                    if (parent is null)
-                    {
-                        var parentlink = CoreUtils.HasProperty(formInstance.GetType(), "Parent")
-                            ? CoreUtils.GetPropertyValue(formInstance, "Parent") as IEntityLink
-                            : null;
-                        var parenttype = parentlink?.GetType().BaseType?.GetGenericArguments().FirstOrDefault();
-
-                        if (parenttype != null && parentlink != null)
-                        {
-                            var parentid = parentlink.ID;
-                            var filter = Filter.Create(parenttype);
-                            filter.Expression = CoreUtils.GetMemberExpression(parenttype, "ID");
-                            filter.Operator = Operator.IsEqualTo;
-                            filter.Value = parentid;
-
-                            var client = (Activator.CreateInstance(typeof(Client<>).MakeGenericType(parenttype)) as Client)!;
-                            parent = client.Query(filter, null, null).Rows.FirstOrDefault()?.ToObject(parenttype) as Entity;
-                        }
-
-                        if (parent == null)
-                        {
-                            Logger.Send(LogType.Error, "",
-                                $"Form parent is null; Form Type: {formInstance.GetType()}; Parent Type: {parenttype}; Form ID: {formInstance.ID}");
-                            error = "An error occurred while loading the form: Form Entity is null";
-                        }
-                    }
-                }
-                else
-                    error = "No layout found for form!";
-            });
+        var menu = new ContextMenu();
 
-            if (!String.IsNullOrWhiteSpace(error))
-            {
-                MessageBox.Show(error);
-                return false;
-            }
+        ReportUtils.PopulateMenu(menu, dataModel.Instance.Form.ID.ToString(), model, false);
+        if(menu.Items.Count == 0)
+        {
+            menu.AddItem("No reports", null, null, enabled: false);
+        }
 
-            var form = new DynamicFormEditWindow
-            {
-                Type = layout.Type,
-                Title = string.Format("Viewing {0}", formInstance.Form.Description)
-                //Completed = !formInstance.FormCompleted.IsEmpty()
-            };
+        menu.IsOpen = true;
+    }
 
-            form.LoadLayout(layout, variables);
+    private void SaveForm_Click(object sender, RoutedEventArgs e)
+    {
+        if (DataModel?.Instance.FormCompleted.IsEmpty() == false && !Grid.Validate(out var messages))
+        {
+            MessageBox.Show(string.Join('\n', messages));
+            return;
+        }
+        if (MessageBox.Show("Are you sure you want to save this form?", "Confirm Save", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+        {
+            Result = FormResult.Save;
+            DialogResult = true;
+        }
+    }
 
-            try
-            {
-                dataModel = formInstance.CreateDataModel(parent);
-                dataModel.Variables = variables;
-                dataModel.OnModelSaved += (model) =>
-                {
-                    DFUtils.OnSave(formInstance.GetType(), formInstance, parent);
-                };
-                form.DataModel = dataModel;
-            }
-            catch (Exception e)
-            {
-                Logger.Send(LogType.Error, ClientFactory.UserID, $"Error during Edit Form / CreateDataModel: {CoreUtils.FormatException(e)}");
-            }
+    public static Columns<TForm> FormColumns<TForm>() where TForm : IDigitalFormInstance
+    {
+        return (FormColumns(typeof(TForm)) as Columns<TForm>)!;
+    }
+    public static IColumns FormColumns(Type TForm)
+    {
+        return Columns.Create<IDigitalFormInstance>(TForm)
+            .Add<IDigitalFormInstance>(x => x.ID)
+            .Add<IDigitalFormInstance>(x=>x.Number)
+            .Add<IDigitalFormInstance>(x => x.FormCompleted)
+            .Add<IDigitalFormInstance>(x => x.FormData)
+            .Add<IDigitalFormInstance>(x => x.BlobData)
+            .Add<IDigitalFormInstance>(x => x.Form.ID)
+            .Add<IDigitalFormInstance>(x => x.Form.Description)
+            .Add("Parent.ID");
+    }
 
-            form.Initialize();
+    public static bool EditDigitalForm(
+        IDigitalFormInstance formInstance,
+        [NotNullWhen(true)] out IDigitalFormDataModel? dataModel,
+        Entity? parent = null,
+        CustomiseDynamicFormEditWindow? customise = null)
+    {
 
-            if (values is not null)
-                form.LoadValues(values);
+        dataModel = null;
 
-            if (form.ShowDialog() == true)
+        DigitalFormLayout layout = null;
+        DigitalFormVariable[] variables = null;
+        DFLoadStorage? values = null;
+        String error = "";
+
+        Progress.ShowModal("Loading Form", (progress) =>
+        {
+            var formid = formInstance.Form.ID;
+
+            values = DigitalForm.DeserializeFormData(formInstance);
+
+            var results = Client.QueryMultiple(
+                new KeyedQueryDef<DigitalFormVariable>(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formid)),
+                new KeyedQueryDef<DigitalFormLayout>(
+                    new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(formid)
+                        .And(x => x.Active).IsEqualTo(true)
+                        .And(x => x.Layout).IsNotEqualTo("")));
+
+            variables = results[nameof(DigitalFormVariable)].Rows.Select(x => x.ToObject<DigitalFormVariable>()).ToArray();
+
+            var desktopLayout = results[nameof(DigitalFormLayout)]
+                .Rows.FirstOrDefault(x => x.Get<DigitalFormLayout, DFLayoutType>(x => x.Type) == DFLayoutType.Desktop)
+                ?.ToObject<DigitalFormLayout>();
+
+            layout = desktopLayout ?? results[nameof(DigitalFormLayout)].ToObjects<DigitalFormLayout>().FirstOrDefault();
+            if (layout != null)
             {
-                if (form.Result == FormResult.Complete)
+                if (parent is null)
                 {
-                    formInstance.FormCompleted = DateTime.Now;
-                    formInstance.FormCompletedBy.ID = ClientFactory.UserGuid;
-                    formInstance.FormCompletedBy.UserID = ClientFactory.UserID;
-                    /*formInstance.FormCompleted = form.Completed
-                        ? formInstance.FormCompleted.IsEmpty()
-                            ? DateTime.Now
-                            : formInstance.FormCompleted
-                        : DateTime.MinValue;
-
-                    formInstance.FormCompletedBy.ID = form.Completed
-                        ? !formInstance.FormCompletedBy.IsValid()
-                            ? ClientFactory.UserGuid
-                            : formInstance.FormCompletedBy.ID
-                        : Guid.Empty;
-
-                    formInstance.FormCompletedBy.UserID = form.Completed
-                        ? string.IsNullOrWhiteSpace(formInstance.FormCompletedBy.UserID)
-                            ? ClientFactory.UserID
-                            : formInstance.FormCompletedBy.UserID
-                        : "";*/
-                }
+                    var parentlink = CoreUtils.HasProperty(formInstance.GetType(), "Parent")
+                        ? CoreUtils.GetPropertyValue(formInstance, "Parent") as IEntityLink
+                        : null;
+                    var parenttype = parentlink?.GetType().BaseType?.GetGenericArguments().FirstOrDefault();
 
-                DigitalForm.SerializeFormData(formInstance, variables, form.SaveValues());
+                    if (parenttype != null && parentlink != null)
+                    {
+                        var parentid = parentlink.ID;
+                        var filter = Filter.Create(parenttype);
+                        filter.Expression = CoreUtils.GetMemberExpression(parenttype, "ID");
+                        filter.Operator = Operator.IsEqualTo;
+                        filter.Value = parentid;
+
+                        var client = (Activator.CreateInstance(typeof(Client<>).MakeGenericType(parenttype)) as Client)!;
+                        parent = client.Query(filter, null, null).Rows.FirstOrDefault()?.ToObject(parenttype) as Entity;
+                    }
+
+                    if (parent == null)
+                    {
+                        Logger.Send(LogType.Error, "",
+                            $"Form parent is null; Form Type: {formInstance.GetType()}; Parent Type: {parenttype}; Form ID: {formInstance.ID}");
+                        error = "An error occurred while loading the form: Form Entity is null";
+                    }
+                }
             }
+            else
+                error = "No layout found for form!";
+        });
 
-            return form.Result == FormResult.Save || form.Result == FormResult.Complete;
+        if (!String.IsNullOrWhiteSpace(error))
+        {
+            MessageBox.Show(error);
+            return false;
         }
 
-        public static bool EditDigitalForm<TForm>(Guid formID, [NotNullWhen(true)] out IDigitalFormDataModel? dataModel)
-            where TForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
+        var form = new DynamicFormEditWindow
+        {
+            Type = layout.Type,
+            Title = string.Format("Viewing {0}", formInstance.Form.Description)
+        };
+        form.LoadLayout(layout, variables);
+
+        try
         {
-            var form = new Client<TForm>()
-                .Query(
-                    new Filter<TForm>(x => x.ID).IsEqualTo(formID),
-                    FormColumns<TForm>())
-                .Rows.FirstOrDefault()?.ToObject<TForm>();
-            if (form is null)
+            dataModel = formInstance.CreateDataModel(parent!);
+            dataModel.Variables = variables;
+            dataModel.OnModelSaved += (model) =>
             {
-                throw new Exception($"{typeof(TForm)} {formID} does not exist");
+                DFUtils.OnSave(formInstance.GetType(), formInstance, parent!);
+            };
+            form.DataModel = dataModel;
+        }
+        catch (Exception e)
+        {
+            Logger.Send(LogType.Error, ClientFactory.UserID, $"Error during Edit Form / CreateDataModel: {CoreUtils.FormatException(e)}");
+        }
+
+        form.Initialize();
+
+        if (values is not null)
+            form.LoadValues(values);
+
+        customise?.Invoke(form);
+
+        if (form.ShowDialog() == true)
+        {
+            if (form.Result == FormResult.Complete)
+            {
+                formInstance.FormCompleted = DateTime.Now;
+                formInstance.FormCompletedBy.ID = ClientFactory.UserGuid;
+                formInstance.FormCompletedBy.UserID = ClientFactory.UserID;
+                /*formInstance.FormCompleted = form.Completed
+                    ? formInstance.FormCompleted.IsEmpty()
+                        ? DateTime.Now
+                        : formInstance.FormCompleted
+                    : DateTime.MinValue;
+
+                formInstance.FormCompletedBy.ID = form.Completed
+                    ? !formInstance.FormCompletedBy.IsValid()
+                        ? ClientFactory.UserGuid
+                        : formInstance.FormCompletedBy.ID
+                    : Guid.Empty;
+
+                formInstance.FormCompletedBy.UserID = form.Completed
+                    ? string.IsNullOrWhiteSpace(formInstance.FormCompletedBy.UserID)
+                        ? ClientFactory.UserID
+                        : formInstance.FormCompletedBy.UserID
+                    : "";*/
             }
-            return EditDigitalForm(form, out dataModel);
+
+            DigitalForm.SerializeFormData(formInstance, variables, form.SaveValues());
         }
 
-        private void DynamicFormWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+        return form.Result == FormResult.Save || form.Result == FormResult.Complete;
+    }
+
+    public static bool EditDigitalForm<TForm>(Guid formID, [NotNullWhen(true)] out IDigitalFormDataModel? dataModel)
+        where TForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
+    {
+        var form = (new Client<TForm>()
+            .Query(
+                new Filter<TForm>(x => x.ID).IsEqualTo(formID),
+                FormColumns<TForm>())
+            .Rows.FirstOrDefault()?.ToObject<TForm>())
+            ?? throw new Exception($"{typeof(TForm)} {formID} does not exist");
+        return EditDigitalForm(form, out dataModel);
+    }
+
+    private void DynamicFormWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+    {
+        if (DialogResult != true && HasUnsavedChanges)
         {
-            if (DialogResult != true && HasUnsavedChanges)
+            if (MessageBox.Show("This form has unsaved changes. Do you wish to discard them?", "Discard Changes?", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
             {
-                if (MessageBox.Show("This form has unsaved changes. Do you wish to discard them?", "Discard Changes?", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-                {
-                    e.Cancel = true;
-                }
+                e.Cancel = true;
             }
         }
     }

+ 2 - 2
inabox.wpf/DigitalForms/Designer/DynamicFormDesignWindow.xaml

@@ -1,4 +1,4 @@
-<local:DynamicFormWindow x:Class="InABox.DynamicGrid.DynamicFormDesignWindow"
+<Window x:Class="InABox.DynamicGrid.DynamicFormDesignWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -37,4 +37,4 @@
 
     </Grid>
 
-</local:DynamicFormWindow>
+</Window>

+ 60 - 49
inabox.wpf/DigitalForms/Designer/DynamicFormDesignWindow.xaml.cs

@@ -14,70 +14,81 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Shapes;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+/// <summary>
+/// Interaction logic for DynamicFormDesignWindow.xaml
+/// </summary>
+public partial class DynamicFormDesignWindow : Window, IDynamicFormWindow
 {
-    /// <summary>
-    /// Interaction logic for DynamicFormDesignWindow.xaml
-    /// </summary>
-    public partial class DynamicFormDesignWindow : DynamicFormWindow
-    {
 
-        public DynamicFormDesignWindow() : base()
-        {
-            InitializeComponent();
+    public DynamicFormDesignWindow() : base()
+    {
+        InitializeComponent();
 
-            Preview.Mode = FormMode.Designing;
-        }
+        Preview.Mode = FormMode.Designing;
+    }
 
-        protected override DynamicFormDesignGrid Grid { get => Preview; }
+    public DynamicFormDesignGrid Grid => Preview;
 
-        public bool Designing
+    private DFLayoutType _type;
+    public DFLayoutType Type
+    {
+        get => _type;
+        set
         {
-            get => Grid.Mode == FormMode.Designing;
-            set
-            {
-                Grid.Mode = value
-                    ? FormMode.Designing
-                    : FormMode.Preview;
-                SwitchView.Content = value ? "Preview" : "Design";
-            }
+            _type = value;
+            Width = _type == DFLayoutType.Mobile ? 600 : 1000;
+            Height = 800;
         }
+    }
 
-        public event DynamicFormDesignGrid.CreateVariableHandler OnCreateVariable
+    public bool Designing
+    {
+        get => Grid.Mode == FormMode.Designing;
+        set
         {
-            add => Grid.OnCreateVariable += value;
-            remove => Grid.OnCreateVariable -= value;
+            Grid.Mode = value
+                ? FormMode.Designing
+                : FormMode.Preview;
+            SwitchView.Content = value ? "Preview" : "Design";
         }
+    }
 
-        public event DynamicFormDesignGrid.EditVariableHandler OnEditVariable
-        {
-            add => Grid.OnEditVariable += value;
-            remove => Grid.OnEditVariable -= value;
-        }
+    public event DynamicFormDesignGrid.CreateVariableHandler OnCreateVariable
+    {
+        add => Grid.OnCreateVariable += value;
+        remove => Grid.OnCreateVariable -= value;
+    }
 
-        public string SaveLayout()
-        {
-            return Form.SaveLayout();
-        }
+    public event DynamicFormDesignGrid.EditVariableHandler OnEditVariable
+    {
+        add => Grid.OnEditVariable += value;
+        remove => Grid.OnEditVariable -= value;
+    }
 
-        private void SwitchView_Click(object sender, RoutedEventArgs e)
-        {
-            Designing = !Designing;
-        }
+    public string SaveLayout()
+    {
+        return Grid.Form.SaveLayout();
+    }
 
-        private void OK_Click(object sender, RoutedEventArgs e)
-        {
-            DialogResult = true;
-        }
+    private void SwitchView_Click(object sender, RoutedEventArgs e)
+    {
+        Designing = !Designing;
+    }
 
-        private void Cancel_Click(object sender, RoutedEventArgs e)
-        {
-            DialogResult = false;
-        }
+    private void OK_Click(object sender, RoutedEventArgs e)
+    {
+        DialogResult = true;
+    }
 
-        private void DynamicFormWindow_KeyDown(object sender, KeyEventArgs e)
-        {
-            Grid.HandleKeyDown(e);
-        }
+    private void Cancel_Click(object sender, RoutedEventArgs e)
+    {
+        DialogResult = false;
+    }
+
+    private void DynamicFormWindow_KeyDown(object sender, KeyEventArgs e)
+    {
+        Grid.HandleKeyDown(e);
     }
 }

+ 29 - 67
inabox.wpf/DigitalForms/Designer/DynamicFormWindow.cs

@@ -1,4 +1,6 @@
-using InABox.Core;
+using com.sun.org.apache.xpath.@internal.operations;
+using FastReport.DevComponents.Editors;
+using InABox.Core;
 using InABox.Wpf;
 using System;
 using System.Collections.Generic;
@@ -7,81 +9,41 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+public interface IDynamicFormWindow
 {
-    public abstract class DynamicFormWindow : ThemableWindow
-    {
-        protected abstract DynamicFormDesignGrid Grid { get; }
+    DynamicFormDesignGrid Grid { get; }
+}
 
-        private DFLayoutType _type;
-        public DFLayoutType Type
-        {
-            get => _type;
-            set
-            {
-                _type = value;
-                Width = _type == DFLayoutType.Mobile ? 600 : 1000;
-                Height = 800;
-            }
-        }
+public static class DynamicFormWindowExtensions
+{
+    public static void LoadLayout(this IDynamicFormWindow window, DigitalFormLayout layout, IList<DigitalFormVariable> variables)
+    {
+        window.Grid.Variables = variables;
 
-        public DFLayout Form
+        var f = new DFLayout();
+        if (!string.IsNullOrWhiteSpace(layout.Layout))
         {
-            get => Grid.Form;
-            set => Grid.Form = value;
+            f.LoadLayout(layout.Layout);
         }
-
-        public IList<DigitalFormVariable> Variables
+        else
         {
-            get => Grid.Variables;
-            set => Grid.Variables = value;
+            f = new DFLayout();
+            f.RowHeights.Add("Auto");
+            f.ColumnWidths.AddRange(new[] { "*", "Auto" });
         }
 
-        /// <summary>
-        /// Renders the form; after this is called, any changes to properties triggers a refresh.
-        /// </summary>
-        public void Initialize()
-        {
-            Grid.Initialize();
-        }
-
-        public void LoadLayout(DigitalFormLayout layout, IList<DigitalFormVariable> variables)
-        {
-            Variables = variables;
-
-            var f = new DFLayout();
-            if (!string.IsNullOrWhiteSpace(layout.Layout))
-            {
-                f.LoadLayout(layout.Layout);
-            }
-            else
-            {
-                f = new DFLayout();
-                f.RowHeights.Add("Auto");
-                f.ColumnWidths.AddRange(new[] { "*", "Auto" });
-            }
-
-            f.LoadVariables(variables);
-
-            Form = f;
-        }
+        f.LoadVariables(variables);
 
+        window.Grid.Form = f;
+    }
 
-        /*public static T LoadDigitalForm<T>(Guid id) where T : Entity, IPersistent, IRemotable, IDigitalFormInstance, new()
-        {
-            var result = new Client<T>().Query(
-                new Filter<T>(x => x.ID).IsEqualTo(id),
-                new Columns<T>(
-                    x => x.ID,
-                    x => x.Form.ID,
-                    x => x.Form.Description,
-                    x => x.FormCompleted,
-                    x => x.FormCompletedBy.ID,
-                    x => x.FormCompletedBy.UserID,
-                    x => x.FormData
-                )
-            ).Rows.FirstOrDefault()?.ToObject<T>();
-            return result;
-        }*/
+    /// <summary>
+    /// Renders the form; after this is called, any changes to properties triggers a refresh.
+    /// </summary>
+    public static void Initialize(this IDynamicFormWindow window)
+    {
+        window.Grid.Initialize();
     }
 }

+ 6 - 1
inabox.wpf/DocumentUtils.cs

@@ -16,7 +16,12 @@ public static class DocumentUtils
 {
     public static IEnumerable<Tuple<string, Stream?>>? HandleFileDrop(DragEventArgs e)
     {
-        var dataObject = new OutlookDataObject(e.Data);
+        return HandleFileDrop(e.Data);
+    }
+
+    public static IEnumerable<Tuple<string, Stream?>>? HandleFileDrop(System.Windows.IDataObject obj)
+    {
+        var dataObject = new OutlookDataObject(obj);
 
         string? desc = null;
         if (dataObject.GetDataPresent("FileGroupDescriptor")) desc = "FileGroupDescriptor";

+ 2 - 2
inabox.wpf/DynamicGrid/Columns/DynamicImageManagerColumn.cs

@@ -196,7 +196,7 @@ namespace InABox.DynamicGrid
                 {
                     var filename = dlg.FileName.ToLower();
 
-                    Bitmap bmp;
+                    Bitmap? bmp;
                     if (Path.GetExtension(filename).ToLower().Equals(".dxf"))
                     {
                         DxfUtils.OnProcessError += ((message) =>
@@ -206,7 +206,7 @@ namespace InABox.DynamicGrid
                         bmp = DxfUtils.DXFToBitmap(filename);
                     }
                     else
-                        bmp = (System.Drawing.Image.FromFile(filename) as Bitmap)!;
+                        bmp = System.Drawing.Image.FromFile(filename) as Bitmap;
 
                     SaveBitmapToDatabase(row, Guid.Empty, filename, bmp);
                     //_parent?.Refresh(false, false);

+ 4 - 4
inabox.wpf/DynamicGrid/DynamicEditorButton.cs

@@ -7,9 +7,9 @@ namespace InABox.DynamicGrid
 {
     public class DynamicEditorButton
     {
-        public delegate void ClickHandler(object sender, object item);
+        public delegate void ClickHandler(object sender, object? item);
 
-        public DynamicEditorButton(string name, BitmapImage? image, object item, ClickHandler onclick)
+        public DynamicEditorButton(string name, BitmapImage? image, object? item, ClickHandler onclick)
         {
             Name = name;
             OnClick = onclick;
@@ -19,7 +19,7 @@ namespace InABox.DynamicGrid
 
         public string Name { get; set; }
 
-        public object Item { get; set; }
+        public object? Item { get; set; }
 
         public BitmapImage? Image { get; set; }
 
@@ -35,7 +35,7 @@ namespace InABox.DynamicGrid
 
     public class DynamicEditorButtons : List<DynamicEditorButton>
     {
-        public DynamicEditorButton Add(string name, BitmapImage? image, object item, ClickHandler onclick)
+        public DynamicEditorButton Add(string name, BitmapImage? image, object? item, ClickHandler onclick)
         {
             var editorbutton = new DynamicEditorButton(name, image, item, onclick);
             Add(editorbutton);

+ 523 - 524
inabox.wpf/DynamicGrid/DynamicGridUtils.cs

@@ -15,669 +15,668 @@ using Syncfusion.Data.Extensions;
 using System.Diagnostics.CodeAnalysis;
 using System.Data;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+[Caption("Set Default Column Selections")]
+public class CanSetDefaultColumns : EnabledSecurityDescriptor<CoreLicense>
 {
-    [Caption("Set Default Column Selections")]
-    public class CanSetDefaultColumns : EnabledSecurityDescriptor<CoreLicense>
-    {
-    }
+}
 
-    [LibraryInitializer]
-    public static class DynamicGridUtils
-    {
-        private static IEnumerable<Type>? _allm2mtypes;
-        private static IEnumerable<Type>? _allm2mpages;
+[LibraryInitializer]
+public static class DynamicGridUtils
+{
+    private static IEnumerable<Type>? _allm2mtypes;
+    private static IEnumerable<Type>? _allm2mpages;
 
-        private static IEnumerable<Type>? _allo2mtypes;
-        private static IEnumerable<Type>? _allo2mpages;
+    private static IEnumerable<Type>? _allo2mtypes;
+    private static IEnumerable<Type>? _allo2mpages;
 
-        private static IEnumerable<Type>? _allcepages;
+    private static IEnumerable<Type>? _allcepages;
 
-        private static IEnumerable<Type>? _alleltypes;
-        
-        private static Dictionary<Type, IList<Type>> _onetomanypages = new();
-        private static Dictionary<Type, IList<Type>> _manytomanytomanypages = new();
-        private static Dictionary<Type, IList<Tuple<Type, PropertyInfo>>> _enclosedlistpages = new();
-        private static Dictionary<Type, IList<Type>> _customeditorpages = new();
+    private static IEnumerable<Type>? _alleltypes;
+    
+    private static Dictionary<Type, IList<Type>> _onetomanypages = new();
+    private static Dictionary<Type, IList<Type>> _manytomanytomanypages = new();
+    private static Dictionary<Type, IList<Tuple<Type, PropertyInfo>>> _enclosedlistpages = new();
+    private static Dictionary<Type, IList<Type>> _customeditorpages = new();
 
-        // HACK: These are really dumb
-        public static Action<ReportTemplate, DataModel>? PreviewReport { get; set; }
-        public static Action<FrameworkElement?, string, DataModel, bool>? PrintMenu { get; set; }
+    // HACK: These are really dumb
+    public static Action<ReportTemplate, DataModel>? PreviewReport { get; set; }
+    public static Action<FrameworkElement?, string, DataModel, bool>? PrintMenu { get; set; }
 
-        public static readonly MainResources Resources = new();
+    public static readonly MainResources Resources = new();
 
-        public static void RegisterClasses()
-        {
-            // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name;
-            // AssemblyName assemblyName = new AssemblyName(assyname);
-            // AppDomain appDomain = Thread.GetDomain();
-            //
-            // String assyFile = String.Format("{0}.dll", assemblyName.Name);
-            // String path = "";
-            // if (Assembly.GetEntryAssembly() != null)
-            // {
-            //     path = Path.Combine(
-            //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
-            //         Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)
-            //     );
-            // }
-            // else
-            // {
-            //     path = Path.Combine(
-            //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
-            //         Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)
-            //     );
-            // }
-            //
-            // if (!Directory.Exists(path))
-            //     Directory.CreateDirectory(path);
-
-            // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path);
-            //
-            // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true);
-            //
-            // if (_allm2mtypes == null)
-            // {
-            //     _allm2mtypes = CoreUtils.TypeList(
-            //         AppDomain.CurrentDomain.GetAssemblies(),
-            //         x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
-            //     );
-            // }
-            //
-            // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))));
-            //
-            // foreach (var map in maps)
-            // {
-            //     var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)));
-            //     Type entity = intf.GenericTypeArguments.First();
-            //     Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity);
-            //     TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class);
-            //     tbService.SetParent(basetype);
-            //     Type final = tbService.CreateType();
-            // }
-            //
-            // try
-            // {
-            //     assemblyBuilder.Save(assyFile);
-            // }
-            // catch (Exception e)
-            // {
-            //     Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-            // }
-            
-        }
+    public static void RegisterClasses()
+    {
+        // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name;
+        // AssemblyName assemblyName = new AssemblyName(assyname);
+        // AppDomain appDomain = Thread.GetDomain();
+        //
+        // String assyFile = String.Format("{0}.dll", assemblyName.Name);
+        // String path = "";
+        // if (Assembly.GetEntryAssembly() != null)
+        // {
+        //     path = Path.Combine(
+        //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+        //         Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)
+        //     );
+        // }
+        // else
+        // {
+        //     path = Path.Combine(
+        //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+        //         Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)
+        //     );
+        // }
+        //
+        // if (!Directory.Exists(path))
+        //     Directory.CreateDirectory(path);
+
+        // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path);
+        //
+        // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true);
+        //
+        // if (_allm2mtypes == null)
+        // {
+        //     _allm2mtypes = CoreUtils.TypeList(
+        //         AppDomain.CurrentDomain.GetAssemblies(),
+        //         x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
+        //     );
+        // }
+        //
+        // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))));
+        //
+        // foreach (var map in maps)
+        // {
+        //     var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)));
+        //     Type entity = intf.GenericTypeArguments.First();
+        //     Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity);
+        //     TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class);
+        //     tbService.SetParent(basetype);
+        //     Type final = tbService.CreateType();
+        // }
+        //
+        // try
+        // {
+        //     assemblyBuilder.Save(assyFile);
+        // }
+        // catch (Exception e)
+        // {
+        //     Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
+        // }
+        
+    }
 
-        #region Pages
+    #region Pages
 
-        public static IEnumerable<Type> GetManyToManyTypes(Type type)
-        {
-            _allm2mtypes ??= CoreUtils.TypeList(
-                AppDomain.CurrentDomain.GetAssemblies(),
-                x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
-            );
-            return _allm2mtypes.Where(x => x.GetInterfaces().Any(
-                i => i.IsGenericType
-                && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)
-                && i.GenericTypeArguments[0] == type)
-            );
-        }
+    public static IEnumerable<Type> GetManyToManyTypes(Type type)
+    {
+        _allm2mtypes ??= CoreUtils.TypeList(
+            AppDomain.CurrentDomain.GetAssemblies(),
+            x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
+        );
+        return _allm2mtypes.Where(x => x.GetInterfaces().Any(
+            i => i.IsGenericType
+            && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)
+            && i.GenericTypeArguments[0] == type)
+        );
+    }
 
-        public static void LoadManyToManyPages(Type type, DynamicEditorPages pages)
+    public static void LoadManyToManyPages(Type type, DynamicEditorPages pages)
+    {
+        if (!_manytomanytomanypages.TryGetValue(type, out var pageTypes))
         {
-            if (!_manytomanytomanypages.TryGetValue(type, out var pageTypes))
-            {
-                pageTypes = new List<Type>();
+            pageTypes = new List<Type>();
 
-                var maps = GetManyToManyTypes(type);
+            var maps = GetManyToManyTypes(type);
 
-                foreach (var map in maps)
+            foreach (var map in maps)
+            {
+                if (ClientFactory.IsSupported(map))
                 {
-                    if (ClientFactory.IsSupported(map))
-                    {
-                        _allm2mpages ??= CoreUtils.TypeList(
-                                AppDomain.CurrentDomain.GetAssemblies(),
-                                x => x.IsClass
-                                     && !x.IsAbstract
-                                     && !x.IsGenericType
-                                     && x.GetInterfaces().Any(
-                                         i => i.IsGenericType
-                                              && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>)
-                                     )
-                            );
-
-                        var subtypes = _allm2mpages.Where(
-                            x => x.GetInterfaces().Any(i =>
-                                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) &&
-                                i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type))
+                    _allm2mpages ??= CoreUtils.TypeList(
+                            AppDomain.CurrentDomain.GetAssemblies(),
+                            x => x.IsClass
+                                 && !x.IsAbstract
+                                 && !x.IsGenericType
+                                 && x.GetInterfaces().Any(
+                                     i => i.IsGenericType
+                                          && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>)
+                                 )
                         );
 
-                        if (subtypes.Any())
-                        {
-                            pageTypes.Add(subtypes.First());
-                        }
-                        else
-                        {
-                            pageTypes.Add(typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type));
-                        }
-                    }
+                    var subtypes = _allm2mpages.Where(
+                        x => x.GetInterfaces().Any(i =>
+                            i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) &&
+                            i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type))
+                    );
 
+                    if (subtypes.Any())
+                    {
+                        pageTypes.Add(subtypes.First());
+                    }
+                    else
+                    {
+                        pageTypes.Add(typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type));
+                    }
                 }
-                _manytomanytomanypages[type] = pageTypes.ToArray();
+
             }
-            pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
+            _manytomanytomanypages[type] = pageTypes.ToArray();
         }
+        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
+    }
 
-        public static IEnumerable<Type> GetOneToManyTypes(Type type)
-        {
-            _allo2mtypes ??= CoreUtils.TypeList(
-                AppDomain.CurrentDomain.GetAssemblies(),
-                x => x.GetInterfaces().Any(i =>
-                    i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOneToMany<>) && x.GetCustomAttribute<ObsoleteAttribute>() == null)
-            );
-            return _allo2mtypes
-                .Where(x => x.GetInterfaces().Any(i =>
-                    i.IsGenericType
-                    && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
-                    && i.GenericTypeArguments.Contains(type)))
-                .OrderBy(x => x.EntityName());
-        }
-        
-        public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
+    public static IEnumerable<Type> GetOneToManyTypes(Type type)
+    {
+        _allo2mtypes ??= CoreUtils.TypeList(
+            AppDomain.CurrentDomain.GetAssemblies(),
+            x => x.GetInterfaces().Any(i =>
+                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOneToMany<>) && x.GetCustomAttribute<ObsoleteAttribute>() == null)
+        );
+        return _allo2mtypes
+            .Where(x => x.GetInterfaces().Any(i =>
+                i.IsGenericType
+                && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
+                && i.GenericTypeArguments.Contains(type)))
+            .OrderBy(x => x.EntityName());
+    }
+    
+    public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
+    {
+        if (!_onetomanypages.TryGetValue(type, out var pageTypes))
         {
-            if (!_onetomanypages.TryGetValue(type, out var pageTypes))
-            {
-                pageTypes = new List<Type>();
+            pageTypes = new List<Type>();
 
-                var maps = GetOneToManyTypes(type);
+            var maps = GetOneToManyTypes(type);
 
-                foreach (var map in maps)
+            foreach (var map in maps)
+            {
+                if (ClientFactory.IsSupported(map))
                 {
-                    if (ClientFactory.IsSupported(map))
-                    {
 
-                        _allo2mpages ??= CoreUtils.TypeList(
-                                AppDomain.CurrentDomain.GetAssemblies(),
-                                x =>
-                                    x.IsClass
-                                    && !x.IsAbstract
-                                    && !x.IsGenericType
-                                    && x.GetInterfaces().Any(i =>
-                                        i.IsGenericType
-                                        && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
-                                    )
-                            );
-
-                        var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
-                                i => i.IsGenericType
-                                     && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
-                                     && i.GenericTypeArguments.First().Equals(type)
-                                     && i.GenericTypeArguments.Last().Equals(map)
-                            )
+                    _allo2mpages ??= CoreUtils.TypeList(
+                            AppDomain.CurrentDomain.GetAssemblies(),
+                            x =>
+                                x.IsClass
+                                && !x.IsAbstract
+                                && !x.IsGenericType
+                                && x.GetInterfaces().Any(i =>
+                                    i.IsGenericType
+                                    && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
+                                )
                         );
 
-                        if (subtypes.Any())
-                        {
-                            pageTypes.Add(subtypes.First());
-                        }
-                        else
-                        {
-                            pageTypes.Add(typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map));
-                        }
+                    var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
+                            i => i.IsGenericType
+                                 && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
+                                 && i.GenericTypeArguments.First().Equals(type)
+                                 && i.GenericTypeArguments.Last().Equals(map)
+                        )
+                    );
+
+                    if (subtypes.Any())
+                    {
+                        pageTypes.Add(subtypes.First());
+                    }
+                    else
+                    {
+                        pageTypes.Add(typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map));
                     }
                 }
-
-                _onetomanypages[type] = pageTypes.ToArray();
             }
-            pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
+
+            _onetomanypages[type] = pageTypes.ToArray();
         }
+        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
+    }
 
-        public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
+    public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
+    {
+        if (!_customeditorpages.TryGetValue(type, out var pageTypes))
         {
-            if (!_customeditorpages.TryGetValue(type, out var pageTypes))
-            {
-                _allcepages ??= CoreUtils.TypeList(
-                        AppDomain.CurrentDomain.GetAssemblies(),
-                        x => x.IsClass
-                             && !x.IsAbstract
-                             && !x.IsGenericType
-                             && x.GetInterfaces().Any(i =>
-                                 i.IsGenericType
-                                 && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>)
-                             )
-                    );
-
-                pageTypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
-                    i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
-                    i.GenericTypeArguments.First().Equals(type))).ToArray();
-
-                _customeditorpages[type] = pageTypes;
-            }
-            pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
+            _allcepages ??= CoreUtils.TypeList(
+                    AppDomain.CurrentDomain.GetAssemblies(),
+                    x => x.IsClass
+                         && !x.IsAbstract
+                         && !x.IsGenericType
+                         && x.GetInterfaces().Any(i =>
+                             i.IsGenericType
+                             && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>)
+                         )
+                );
+
+            pageTypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
+                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
+                i.GenericTypeArguments.First().Equals(type))).ToArray();
+
+            _customeditorpages[type] = pageTypes;
         }
+        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
+    }
 
-        public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
+    public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
+    {
+        if (!_enclosedlistpages.TryGetValue(type, out var pageTypes))
         {
-            if (!_enclosedlistpages.TryGetValue(type, out var pageTypes))
+            pageTypes = new List<Tuple<Type, PropertyInfo>>();
+            foreach (var property in type.GetProperties())
             {
-                pageTypes = new List<Tuple<Type, PropertyInfo>>();
-                foreach (var property in type.GetProperties())
+                if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
                 {
-                    if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
+                    var curtype = property.PropertyType;
+                    var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
+                    while (gentype == null && curtype?.BaseType != null)
                     {
-                        var curtype = property.PropertyType;
-                        var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
-                        while (gentype == null && curtype?.BaseType != null)
-                        {
-                            curtype = curtype.BaseType;
-                            gentype = curtype?.GetGenericArguments().FirstOrDefault();
-                        }
+                        curtype = curtype.BaseType;
+                        gentype = curtype?.GetGenericArguments().FirstOrDefault();
+                    }
 
-                        if (gentype != null)
-                            if (gentype.IsSubclassOf(typeof(BaseObject)))
+                    if (gentype != null)
+                        if (gentype.IsSubclassOf(typeof(BaseObject)))
+                        {
+                            var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
+                            if (editor == null || !(editor is NullEditor))
                             {
-                                var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
-                                if (editor == null || !(editor is NullEditor))
-                                {
-                                    _alleltypes ??= CoreUtils.TypeList(
-                                            AppDomain.CurrentDomain.GetAssemblies(),
-                                            x => x.IsClass
-                                                 && !x.IsAbstract
-                                                 && !x.IsGenericType
-                                                 && x.GetInterfaces().Any(i =>
-                                                     i.IsGenericType
-                                                     && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
-                                                 )
-                                        );
-
-                                    var subtypes = _alleltypes.Where(
-                                        x => x.GetInterfaces().Any(
-                                            i => i.IsGenericType
+                                _alleltypes ??= CoreUtils.TypeList(
+                                        AppDomain.CurrentDomain.GetAssemblies(),
+                                        x => x.IsClass
+                                             && !x.IsAbstract
+                                             && !x.IsGenericType
+                                             && x.GetInterfaces().Any(i =>
+                                                 i.IsGenericType
                                                  && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
-                                                 && i.GenericTypeArguments.First().Equals(type)
-                                                 && i.GenericTypeArguments.Last().Equals(gentype)
-                                        )
+                                             )
                                     );
 
+                                var subtypes = _alleltypes.Where(
+                                    x => x.GetInterfaces().Any(
+                                        i => i.IsGenericType
+                                             && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
+                                             && i.GenericTypeArguments.First().Equals(type)
+                                             && i.GenericTypeArguments.Last().Equals(gentype)
+                                    )
+                                );
+
+                                if (subtypes.Any())
+                                {
+                                    pageTypes.Add(new(subtypes.First(), property));
+                                }
+                                else
+                                {
+                                    subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => i.GenericTypeArguments.Last().Equals(gentype)));
+
                                     if (subtypes.Any())
                                     {
-                                        pageTypes.Add(new(subtypes.First(), property));
+                                        pageTypes.Add(new(subtypes.First().MakeGenericType(type), property));
                                     }
                                     else
                                     {
-                                        subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => i.GenericTypeArguments.Last().Equals(gentype)));
-
-                                        if (subtypes.Any())
+                                        try
                                         {
-                                            pageTypes.Add(new(subtypes.First().MakeGenericType(type), property));
+                                            pageTypes.Add(new(typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype), property));
                                         }
-                                        else
+                                        catch (Exception e)
                                         {
-                                            try
-                                            {
-                                                pageTypes.Add(new(typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype), property));
-                                            }
-                                            catch (Exception e)
-                                            {
-                                                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-                                            }
+                                            Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                                         }
                                     }
                                 }
                             }
-                    }
+                        }
                 }
-
-                _enclosedlistpages[type] = pageTypes.ToArray();
             }
-            pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x.Item1, x.Item2) as IDynamicEditorPage)!));
+
+            _enclosedlistpages[type] = pageTypes.ToArray();
         }
+        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x.Item1, x.Item2) as IDynamicEditorPage)!));
+    }
 
-        #endregion
+    #endregion
 
-        #region Columns
+    #region Columns
 
-        public static Columns<T> LoadEditorColumns<T>(Columns<T> additional)
-        {
-            var result = new Columns<T>().Default(
-                ColumnType.IncludeOptional,
-                ColumnType.IncludeForeignKeys,
-                ColumnType.IncludeUserProperties,
-                ColumnType.IncludeEditable);
+    public static Columns<T> LoadEditorColumns<T>(Columns<T> additional)
+    {
+        var result = new Columns<T>().Default(
+            ColumnType.IncludeOptional,
+            ColumnType.IncludeForeignKeys,
+            ColumnType.IncludeUserProperties,
+            ColumnType.IncludeEditable);
 
-            foreach (var col in additional.Items)
-                if (!result.Items.Any(x => string.Equals(x.Property, col.Property)))
-                    result.Add(col.Property);
+        foreach (var col in additional.Items)
+            if (!result.Items.Any(x => string.Equals(x.Property, col.Property)))
+                result.Add(col.Property);
 
-            foreach (var col in result.Items)
+        foreach (var col in result.Items)
+        {
+            var prop = DatabaseSchema.Property(typeof(T), col.Property);
+            if (prop?.Editor is DataLookupEditor dataLookup)
             {
-                var prop = DatabaseSchema.Property(typeof(T), col.Property);
-                if (prop?.Editor is DataLookupEditor dataLookup)
+                foreach (var lookupColumn in LookupFactory.DefineFilterColumns<T>(dataLookup.Type).ColumnNames())
                 {
-                    foreach (var lookupColumn in LookupFactory.DefineFilterColumns<T>(dataLookup.Type).ColumnNames())
-                    {
-                        result.Add(lookupColumn);
-                    }
+                    result.Add(lookupColumn);
                 }
             }
-
-            return result;
         }
 
-        #endregion
+        return result;
+    }
 
-        #region Editor Values
+    #endregion
 
-        public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object? value)
-        {
-            Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
-            var sw = new Stopwatch();
+    #region Editor Values
 
-            var changes = new Dictionary<string, object?>(); 
-            var props = DatabaseSchema.Properties(items.First().GetType()).ToArray();
-            foreach (var item in items)
-            {
-                //Dictionary<String, object> previous = new Dictionary<string, object>();
-                var previous = CoreUtils.GetValues(item, props);
+    public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object? value)
+    {
+        Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
+        var sw = new Stopwatch();
 
-                //if (item.OriginalValues != null)
-                //{
-                //    foreach (var key in item.OriginalValues.Keys)
-                //        previous[key] = item.OriginalValues[key];
-                //}
+        var changes = new Dictionary<string, object?>(); 
+        var props = DatabaseSchema.Properties(items.First().GetType()).ToArray();
+        foreach (var item in items)
+        {
+            //Dictionary<String, object> previous = new Dictionary<string, object>();
+            var previous = CoreUtils.GetValues(item, props);
 
-                var prop = DatabaseSchema.Property(item.GetType(), name);
-                if (prop is CustomProperty)
-                {
-                    if (!item.HasOriginalValue(name))
-                        item.SetOriginalValue(name, item.UserProperties[name]);
-                    item.UserProperties[name] = value;
-                }
-                else
-                {
-                    if (prop != null)
-                        try
-                        {
-                            var getter = prop.Getter();
-                            var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);
+            //if (item.OriginalValues != null)
+            //{
+            //    foreach (var key in item.OriginalValues.Keys)
+            //        previous[key] = item.OriginalValues[key];
+            //}
 
-                            item.OnPropertyChanged(name, oldvalue, value);
+            var prop = DatabaseSchema.Property(item.GetType(), name);
+            if (prop is CustomProperty)
+            {
+                if (!item.HasOriginalValue(name))
+                    item.SetOriginalValue(name, item.UserProperties[name]);
+                item.UserProperties[name] = value;
+            }
+            else
+            {
+                if (prop != null)
+                    try
+                    {
+                        var getter = prop.Getter();
+                        var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);
 
-                            var setter = prop.Setter();
-                            if (setter != null && value != null)
-                                setter.Invoke(item, value);
-                            else
-                                CoreUtils.SetPropertyValue(item, name, value);
-                        }
-                        catch (Exception)
-                        {
-                            Logger.Send(LogType.Error, "",
-                                string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
-                        }
-                }
-                var current = CoreUtils.GetValues(item, props);
-                CoreUtils.MergeChanges(previous, current, changes);
+                        item.OnPropertyChanged(name, oldvalue, value);
 
+                        var setter = prop.Setter();
+                        if (setter != null && value != null)
+                            setter.Invoke(item, value);
+                        else
+                            CoreUtils.SetPropertyValue(item, name, value);
+                    }
+                    catch (Exception)
+                    {
+                        Logger.Send(LogType.Error, "",
+                            string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
+                    }
             }
-            
-            return changes;
-        }
+            var current = CoreUtils.GetValues(item, props);
+            CoreUtils.MergeChanges(previous, current, changes);
 
-        public static void UpdateEditorValue(BaseObject[] items, string name, object? value, Dictionary<string, object?> changes)
-        {
-            var results = UpdateEditorValue(items, name, value);
-            foreach (var key in results.Keys)
-                changes[key] = results[key];
         }
+        
+        return changes;
+    }
 
-        #endregion
+    public static void UpdateEditorValue(BaseObject[] items, string name, object? value, Dictionary<string, object?> changes)
+    {
+        var results = UpdateEditorValue(items, name, value);
+        foreach (var key in results.Keys)
+            changes[key] = results[key];
+    }
 
-        #region Dynamic Grid Creation
+    #endregion
 
-        public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
-        {
-            var type = FindDynamicGrid(gridType, entityType);
-            return (Activator.CreateInstance(type) as IDynamicGrid)
-                ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
-        }
+    #region Dynamic Grid Creation
 
-        private static Dictionary<Type, Type[]> _dynamicGrids = new();
+    public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
+    {
+        var type = FindDynamicGrid(gridType, entityType);
+        return (Activator.CreateInstance(type) as IDynamicGrid)
+            ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
+    }
 
-        public static bool TryFindDynamicGrid(Type gridType, Type entityType, [NotNullWhen(true)] out Type? grid)
-        {
-            if (!_dynamicGrids.TryGetValue(gridType, out var grids))
-            {
-                grids = CoreUtils.TypeList(
-                    AppDomain.CurrentDomain.GetAssemblies(),
-                    myType =>
-                        myType.IsClass
-                        && !myType.IsAbstract
-                        && !myType.IsGenericType
-                        && myType.IsAssignableTo(typeof(IDynamicGrid))
-                        && !myType.IsAssignableTo(typeof(ISpecificGrid))
-                ).ToArray();
-                _dynamicGrids[gridType] = grids;
-            }
-            grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray();
-            var entityGrids = grids.Where(x =>
-            {
-                var baseGrid = x.GetSuperclassDefinition(typeof(BaseDynamicGrid<>));
-                return baseGrid?.GenericTypeArguments[0] == entityType;
-            }).ToList();
+    private static Dictionary<Type, Type[]> _dynamicGrids = new();
 
-            var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
-            if (defaults.Count > 0)
-            {
-                if (defaults.Count > 1)
-                {
-                    Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
-                }
-                grid = defaults.First();
-                return true;
-            }
-
-            grid = entityGrids.FirstOrDefault();
-            return grid is not null;
+    public static bool TryFindDynamicGrid(Type gridType, Type entityType, [NotNullWhen(true)] out Type? grid)
+    {
+        if (!_dynamicGrids.TryGetValue(gridType, out var grids))
+        {
+            grids = CoreUtils.TypeList(
+                AppDomain.CurrentDomain.GetAssemblies(),
+                myType =>
+                    myType.IsClass
+                    && !myType.IsAbstract
+                    && !myType.IsGenericType
+                    && myType.IsAssignableTo(typeof(IDynamicGrid))
+                    && !myType.IsAssignableTo(typeof(ISpecificGrid))
+            ).ToArray();
+            _dynamicGrids[gridType] = grids;
         }
+        grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray();
+        var entityGrids = grids.Where(x =>
+        {
+            var baseGrid = x.GetSuperclassDefinition(typeof(BaseDynamicGrid<>));
+            return baseGrid?.GenericTypeArguments[0] == entityType;
+        }).ToList();
 
-        public static Type FindDynamicGrid(Type gridType, Type entityType)
+        var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
+        if (defaults.Count > 0)
         {
-            if(TryFindDynamicGrid(gridType, entityType, out var grid))
+            if (defaults.Count > 1)
             {
-                return grid;
+                Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
             }
-            return gridType.MakeGenericType(entityType);
+            grid = defaults.First();
+            return true;
         }
 
-        public static Window CreateGridWindow(string title, BaseDynamicGrid dynamicGrid)
+        grid = entityGrids.FirstOrDefault();
+        return grid is not null;
+    }
+
+    public static Type FindDynamicGrid(Type gridType, Type entityType)
+    {
+        if(TryFindDynamicGrid(gridType, entityType, out var grid))
         {
-            dynamicGrid.Margin = new Thickness(5);
+            return grid;
+        }
+        return gridType.MakeGenericType(entityType);
+    }
 
-            var window = new ThemableWindow { Title = title, Content = dynamicGrid };
+    public static Window CreateGridWindow(string title, BaseDynamicGrid dynamicGrid)
+    {
+        dynamicGrid.Margin = new Thickness(5);
 
-            (dynamicGrid as IDynamicGrid)!.Refresh(true, true);
+        var window = new ThemableWindow { Title = title, Content = dynamicGrid };
 
-            return window;
-        }
-        public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null)
-        {
-            gridType ??= typeof(DynamicGrid<>);
-            var grid = CreateDynamicGrid(gridType, entityType) as BaseDynamicGrid;
-            return CreateGridWindow(title, grid!);
-        }
-        public static Window CreateGridWindow<TGrid, TEntity>(string title)
-            where TEntity : BaseObject
-            where TGrid : IDynamicGrid
-        {
-            return CreateGridWindow(title, typeof(TEntity), typeof(TGrid));
-        }
-        public static Window CreateGridWindow<TEntity>(string title)
-            where TEntity : BaseObject
-        {
-            return CreateGridWindow(title, typeof(TEntity));
-        }
+        (dynamicGrid as IDynamicGrid)!.Refresh(true, true);
+
+        return window;
+    }
+    public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null)
+    {
+        gridType ??= typeof(DynamicGrid<>);
+        var grid = CreateDynamicGrid(gridType, entityType) as BaseDynamicGrid;
+        return CreateGridWindow(title, grid!);
+    }
+    public static Window CreateGridWindow<TGrid, TEntity>(string title)
+        where TEntity : BaseObject
+        where TGrid : IDynamicGrid
+    {
+        return CreateGridWindow(title, typeof(TEntity), typeof(TGrid));
+    }
+    public static Window CreateGridWindow<TEntity>(string title)
+        where TEntity : BaseObject
+    {
+        return CreateGridWindow(title, typeof(TEntity));
+    }
 
-        #endregion
+    #endregion
 
-        #region Drag + Drop
+    #region Drag + Drop
 
-        public static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? "";
+    public static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? "";
 
-        /// <summary>
-        /// Try to get data dragged from a <see cref="DynamicGrid{T}"/> from a <see cref="DragEventArgs"/>, returning <see langword="true"/>
-        /// if data was present.
-        /// </summary>
-        /// <param name="e"></param>
-        public static bool TryGetDropData(
-            DragEventArgs e,
-            [NotNullWhen(true)] out Type? type,
-            [NotNullWhen(true)] out CoreTable? table)
+    /// <summary>
+    /// Try to get data dragged from a <see cref="DynamicGrid{T}"/> from a <see cref="DragEventArgs"/>, returning <see langword="true"/>
+    /// if data was present.
+    /// </summary>
+    /// <param name="e"></param>
+    public static bool TryGetDropData(
+        DragEventArgs e,
+        [NotNullWhen(true)] out Type? type,
+        [NotNullWhen(true)] out CoreTable? table)
+    {
+        if (e.Data.GetDataPresent(DragFormat))
         {
-            if (e.Data.GetDataPresent(DragFormat))
+            var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat;
+            if (data is not null)
             {
-                var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat;
-                if (data is not null)
+                table = new CoreTable();
+                foreach (var column in data.Table.Columns)
                 {
-                    table = new CoreTable();
-                    foreach (var column in data.Table.Columns)
+                    if (column is DataColumn dataColumn)
                     {
-                        if (column is DataColumn dataColumn)
-                        {
-                            table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType });
-                        }
+                        table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType });
                     }
-                    foreach (var row in data.Table.Rows)
+                }
+                foreach (var row in data.Table.Rows)
+                {
+                    if (row is DataRow dataRow)
                     {
-                        if (row is DataRow dataRow)
-                        {
-                            var coreRow = table.NewRow();
-                            coreRow.LoadValues(dataRow.ItemArray);
-                            table.Rows.Add(coreRow);
-                        }
+                        var coreRow = table.NewRow();
+                        coreRow.LoadValues(dataRow.ItemArray);
+                        table.Rows.Add(coreRow);
                     }
-
-                    type = data.Entity;
-                    return true;
                 }
+
+                type = data.Entity;
+                return true;
             }
-            table = null;
-            type = null;
-            return false;
         }
+        table = null;
+        type = null;
+        return false;
+    }
 
-        #endregion
-
-        public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
-            ItemsControl menu,
-            Guid entityID,
-            Func<TEntity> loadEntity,
-            bool editOnAdd = false
-            )
-            where TEntityForm : EntityForm<TEntity, TEntityLink, TEntityForm>, new()
-            where TEntity : Entity
-            where TEntityLink : IEntityLink<TEntity>, new()
+    #endregion
+
+    public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
+        ItemsControl menu,
+        Guid entityID,
+        Func<TEntity> loadEntity,
+        bool editOnAdd = false,
+        DynamicFormEditWindow.CustomiseDynamicFormEditWindow? customiseEditor = null)
+        where TEntityForm : EntityForm<TEntity, TEntityLink, TEntityForm>, new()
+        where TEntity : Entity
+        where TEntityLink : IEntityLink<TEntity>, new()
+    {
+        var task = Task.Run(() =>
         {
-            var task = Task.Run(() =>
-            {
-                return new Client<TEntityForm>().Query(
-                    new Filter<TEntityForm>(x => x.Parent.ID).IsEqualTo(entityID),
-                    null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
-            });
+            return new Client<TEntityForm>().Query(
+                new Filter<TEntityForm>(x => x.Parent.ID).IsEqualTo(entityID),
+                null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
+        });
 
-            var addForm = new MenuItem { Header = "Add Form" };
-            addForm.Click += (o, e) =>
+        var addForm = new MenuItem { Header = "Add Form" };
+        addForm.Click += (o, e) =>
+        {
+            var entity = loadEntity();
+            var filter = LookupFactory.DefineFilter<TEntity, DigitalForm>(new TEntity[] { entity })
+                ?? LookupFactory.DefineFilter<TEntityForm, DigitalForm>(Array.Empty<TEntityForm>());
+
+            var select = new MultiSelectDialog<DigitalForm>(
+                filter,
+                LookupFactory.DefineColumns<DigitalForm>()
+                    .Add(x => x.Description),
+                false);
+            if(select.ShowDialog() == true)
             {
-                var entity = loadEntity();
-                var filter = LookupFactory.DefineFilter<TEntity, DigitalForm>(new TEntity[] { entity })
-                    ?? LookupFactory.DefineFilter<TEntityForm, DigitalForm>(Array.Empty<TEntityForm>());
-
-                var select = new MultiSelectDialog<DigitalForm>(
-                    filter,
-                    LookupFactory.DefineColumns<DigitalForm>()
-                        .Add(x => x.Description),
-                    false);
-                if(select.ShowDialog() == true)
-                {
-                    var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();
+                var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();
 
-                    if(digitalForm is not null)
+                if(digitalForm is not null)
+                {
+                    var form = new TEntityForm
                     {
-                        var form = new TEntityForm
-                        {
-                            Description = digitalForm.Description
-                        };
-                        form.Parent.ID = entityID;
-                        form.Form.ID = digitalForm.ID;
-                        if (editOnAdd)
-                        {
-                            if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel))
-                            {
-                                dataModel.Update(null);
-                            }
-                        }
-                        else
+                        Description = digitalForm.Description
+                    };
+                    form.Parent.ID = entityID;
+                    form.Form.ID = digitalForm.ID;
+                    if (editOnAdd)
+                    {
+                        if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel, customise: customiseEditor))
                         {
-                            new Client<TEntityForm>().Save(form, "Added by user");
+                            dataModel.Update(null);
                         }
                     }
-                };
+                    else
+                    {
+                        new Client<TEntityForm>().Save(form, "Added by user");
+                    }
+                }
             };
+        };
 
-            var manageForms = new MenuItem { Header = "Manage Forms..." };
-            manageForms.Click += (o, e) =>
-            {
-                var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };
+        var manageForms = new MenuItem { Header = "Manage Forms..." };
+        manageForms.Click += (o, e) =>
+        {
+            var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };
 
-                var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(loadEntity());
+            var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(loadEntity());
 
-                grid.Refresh(true, true);
-                grid.Margin = new Thickness(5);
+            grid.Refresh(true, true);
+            grid.Margin = new Thickness(5);
 
-                window.Content = grid;
+            window.Content = grid;
 
-                window.ShowDialog();
-            };
+            window.ShowDialog();
+        };
+
+        menu.Items.Add(addForm);
+        menu.Items.Add(new Separator());
+        menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
+        menu.Items.Add(new Separator());
+        menu.Items.Add(manageForms);
 
+        task.ContinueWith((task) =>
+        {
+            var entityForms = task.Result;
+            menu.Items.Clear();
             menu.Items.Add(addForm);
             menu.Items.Add(new Separator());
-            menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
-            menu.Items.Add(new Separator());
-            menu.Items.Add(manageForms);
 
-            task.ContinueWith((task) =>
+            if (entityForms.Any())
             {
-                var entityForms = task.Result;
-                menu.Items.Clear();
-                menu.Items.Add(addForm);
-                menu.Items.Add(new Separator());
-
-                if (entityForms.Any())
+                foreach (var entityForm in entityForms)
                 {
-                    foreach (var entityForm in entityForms)
+                    var description = entityForm.Description;
+                    if (string.IsNullOrWhiteSpace(description))
+                    {
+                        description = entityForm.Form.Description;
+                    }
+                    var formItem = new MenuItem { Header = $"{entityForm.Number} : {description}" };
+                    formItem.Click += (o, e) =>
                     {
-                        var description = entityForm.Description;
-                        if (string.IsNullOrWhiteSpace(description))
+                        if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel, customise: customiseEditor))
                         {
-                            description = entityForm.Form.Description;
+                            dataModel.Update(null);
                         }
-                        var formItem = new MenuItem { Header = $"{entityForm.Number} : {description}" };
-                        formItem.Click += (o, e) =>
-                        {
-                            if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
-                            {
-                                dataModel.Update(null);
-                            }
-                        };
+                    };
 
-                        menu.Items.Add(formItem);
-                    }
-                }
-                else
-                {
-                    menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
+                    menu.Items.Add(formItem);
                 }
-                menu.Items.Add(new Separator());
-                menu.Items.Add(manageForms);
-            }, TaskScheduler.FromCurrentSynchronizationContext());
-        }
+            }
+            else
+            {
+                menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
+            }
+            menu.Items.Add(new Separator());
+            menu.Items.Add(manageForms);
+        }, TaskScheduler.FromCurrentSynchronizationContext());
     }
 }

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

@@ -320,9 +320,10 @@ namespace InABox.DynamicGrid
                 buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
         }
 
-        private void AuditTrailClick(object sender, object item)
+        private void AuditTrailClick(object sender, object? item)
         {
-            var entity = (TMany)item;
+            if (item is not TMany entity) return;
+
             var window = new AuditWindow(entity.ID);
             window.ShowDialog();
         }

+ 11 - 13
inabox.wpf/ProgressWindow/Progress.cs

@@ -21,20 +21,20 @@ namespace InABox.WPF
 
     public static class Progress
     {
-        private static ProgressForm form;
+        private static ProgressForm? form;
 
-        public static BitmapImage DisplayImage { get; set; }
+        public static BitmapImage? DisplayImage { get; set; }
 
         public static bool IsVisible => form != null;
 
-        public static string Message => form?.GetMessage();
-
         public static void ShowModal(params ProgressSection[] sections)
         {
             if (!sections.Any())
                 return;
-            var progress = new ProgressForm();
-            progress.DisplayImage = DisplayImage;
+            var progress = new ProgressForm
+            {
+                DisplayImage = DisplayImage
+            };
             progress.Activated += (_, args) =>
             {
                 progress.Dispatcher.Invoke(() =>
@@ -52,10 +52,6 @@ namespace InABox.WPF
             progress.ShowDialog();
         }
 
-        private static void RunShowModal(ProgressForm progress, Action<IProgress<string>> work)
-        {
-        }
-
         /// <summary>
         /// Shows progress dialog modally, with a cancel button.
         /// </summary>
@@ -140,8 +136,10 @@ namespace InABox.WPF
         {
             if (form == null)
             {
-                form = new ProgressForm();
-                form.DisplayImage = DisplayImage;
+                form = new ProgressForm
+                {
+                    DisplayImage = DisplayImage
+                };
                 form.Show();
             }
 
@@ -150,7 +148,7 @@ namespace InABox.WPF
 
         public static void SetMessage(string message)
         {
-            form.UpdateWindow(message);
+            form?.UpdateWindow(message);
         }
 
         public static void Close()