소스 검색

Tweaked appearance of Progress / Splash form
Tweaked width calculation of Master Lists
Updated Fast Reports to latest Nuget version
Consolidated Digital Form Layout Importer code and prototype Digital Form Report Generator
Added abililty to create a Digital Form, Layout and Report directly from Excel spreadsheet

Frank van den Bos 2 년 전
부모
커밋
7fb00ccada

+ 3 - 0
InABox.Core/DigitalForms/DFUtils.cs

@@ -125,6 +125,7 @@ namespace InABox.Core
         {
             _formUtils.Add(typeof(TForm), new DelegateEntityFormUtils<TForm, TEntity, TEntityLink>(editFormFunc, newEntityFunc, beforeSaveFunc));
         }
+        
         public static void AddFormUtils<TForm, TEntity, TEntityLink>(EntityFormUtils<TForm, TEntity, TEntityLink> formUtils)
             where TForm : EntityForm<TEntity, TEntityLink>
             where TEntity : Entity, new()
@@ -141,6 +142,7 @@ namespace InABox.Core
             }
             return false;
         }
+        
         public static bool CanEditForm<TForm, TEntity, TEntityLink>(TForm Form, TEntity Entity)
             where TForm : EntityForm<TEntity, TEntityLink>
             where TEntity : Entity, new()
@@ -173,6 +175,7 @@ namespace InABox.Core
                 utils.OnSave(form, entity);
             }
         }
+        
         public static void OnSave<TForm, TEntity, TEntityLink>(TForm form, TEntity entity)
             where TForm : EntityForm<TEntity, TEntityLink>
             where TEntity : Entity, new()

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

@@ -23,7 +23,7 @@ namespace InABox.Core
 
         private CoreTable? FormDataTable { get; set; } = null;
 
-        private DigitalFormVariable[]? _variables = null;
+        //private DigitalFormVariable[]? _variables = null;
 
         public DigitalFormVariable[]? Variables { get; set; }
 
@@ -51,20 +51,10 @@ namespace InABox.Core
                 DataType = typeof(Guid)
             });
 
-            var variables = _variables;
-            if(variables is null)
-            {
-                if(Variables is null)
-                {
-                    _variables = new Client<DigitalFormVariable>().Load(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formID));
-                    variables = _variables;
-                }
-                else
-                {
-                    variables = Variables;
-                }
-            }
-            foreach (var variable in variables)
+            if(Variables is null)
+                Variables = new Client<DigitalFormVariable>().Load(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formID));
+            
+            foreach (var variable in Variables)
                 foreach (var column in variable.GetVariableColumns())
                     formDataTable.Columns.Add(column);
 

+ 10 - 3
InABox.Core/DigitalForms/Forms/DigitalFormCategoryLookups.cs

@@ -6,9 +6,10 @@ namespace InABox.Core
 {
     public class DigitalFormCategoryLookups : LookupGenerator<object>
     {
-        public DigitalFormCategoryLookups(object[]? items) : base(items)
+
+        public Dictionary<String, String> Lookups()
         {
-            var lookups = new Dictionary<string, string>(); // { { "", "Select Category" } };
+            var result = new Dictionary<string, string>(); // { { "", "Select Category" } };
 
             var types = CoreUtils.TypeList(
                 AppDomain.CurrentDomain.GetAssemblies(),
@@ -20,9 +21,15 @@ namespace InABox.Core
                 var forms = type.GetInterfaces().Where(x => x.GetInterfaces().Contains(typeof(IDigitalForm)) && x.IsGenericType);
                 foreach (var form in forms)
                 foreach (var generic in form.GenericTypeArguments)
-                    lookups[generic.EntityName().Split('.').Last()] = generic.GetCaption();
+                    result[generic.EntityName().Split('.').Last()] = generic.GetCaption();
             }
 
+            return result;
+        }
+        
+        public DigitalFormCategoryLookups(object[]? items) : base(items)
+        {
+            var lookups = Lookups();
             foreach (var key in lookups.Keys)
                 AddValue(key, lookups[key]);
         }

+ 1 - 1
inabox.wpf/DigitalForms/Designer/Controls/FormHeader.xaml

@@ -25,7 +25,7 @@
             Background="{Binding Path=Background, ElementName=UserControl}" BorderBrush="Transparent">
         <StackPanel Orientation="Horizontal">
             <Image Width="32" Height="32"
-                   RenderTransformOrigin="0.5,0.5" Source="/InABox.DynamicGrid;component/Resources/header_closed.png"/>
+                   RenderTransformOrigin="0.5,0.5" Source="../../../Resources/header_closed.png"/>
             <TextBlock x:Name="TextBlock" 
                        Text="{Binding Path=HeaderText, ElementName=UserControl}" 
                        FontWeight="{Binding Path=FontWeight, ElementName=UserControl}"

+ 58 - 37
inabox.wpf/DigitalForms/Designer/DynamicEditFormWindow.xaml.cs

@@ -6,6 +6,7 @@ using System.Reflection;
 using System.Windows;
 using InABox.Clients;
 using InABox.Core;
+using InABox.WPF;
 
 namespace InABox.DynamicGrid
 {
@@ -187,54 +188,74 @@ namespace InABox.DynamicGrid
             [NotNullWhen(true)] out IDigitalFormDataModel? dataModel,
             Entity? parent = null)
         {
+
             dataModel = null;
-            var formid = formInstance.Form.ID;
+            
+            DigitalFormLayout layout = null;
+            DigitalFormVariable[] variables = null;
+            Dictionary<string, object?> values = null;
+            String error = "";
 
-            var values = DigitalForm.DeserializeFormData(formInstance);
+            Progress.ShowModal("Loading Form", (progress) =>
+            {
 
-            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("")));
+                var formid = formInstance.Form.ID;
 
-            var variables = results[nameof(DigitalFormVariable)].Rows.Select(x => x.ToObject<DigitalFormVariable>()).ToArray();
+                values = DigitalForm.DeserializeFormData(formInstance);
 
-            var desktopLayout = results[nameof(DigitalFormLayout)]
-                .Rows.FirstOrDefault(x => x.Get<DigitalFormLayout, DFLayoutType>(x => x.Type) == DFLayoutType.Desktop)
-                ?.ToObject<DigitalFormLayout>();
+                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("")));
 
-            var layout = desktopLayout ?? results[nameof(DigitalFormLayout)].ToObjects<DigitalFormLayout>().FirstOrDefault();
-            if (layout == null)
-            {
-                MessageBox.Show("No layout found form form!");
-                return false;
-            }
+                variables = results[nameof(DigitalFormVariable)].Rows.Select(x => x.ToObject<DigitalFormVariable>()).ToArray();
 
-            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();
+                var desktopLayout = results[nameof(DigitalFormLayout)]
+                    .Rows.FirstOrDefault(x => x.Get<DigitalFormLayout, DFLayoutType>(x => x.Type) == DFLayoutType.Desktop)
+                    ?.ToObject<DigitalFormLayout>();
 
-                if (parenttype != null && parentlink != null)
+                layout = desktopLayout ?? results[nameof(DigitalFormLayout)].ToObjects<DigitalFormLayout>().FirstOrDefault();
+                if (layout != 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}");
-                    MessageBox.Show("An error occurred while loading the form: Form Entity is null");
-                    return false;
+
+                    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!";
+            });
+            
+            if (!String.IsNullOrWhiteSpace(error))
+            {
+                MessageBox.Show(error);
+                return false;
             }
 
             var form = new DynamicFormEditWindow

+ 93 - 1
inabox.wpf/DigitalForms/DigitalFormGrid.cs

@@ -1,11 +1,17 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
 using System.Linq;
+using System.Text;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media.Imaging;
+using FastReport;
 using InABox.Clients;
 using InABox.Core;
+using InABox.Core.Reports;
+using InABox.Scripting;
 using InABox.WPF;
 using Microsoft.Win32;
 
@@ -264,7 +270,7 @@ namespace InABox.DynamicGrid
             return base.EditItems(items, PageDataHandler, true);
         }
 
-        private const string ExportFileFilter = "Digital Forms (*.prs-form)|*.prs-form";
+        private const string ExportFileFilter = "Excel Files (*.xls, *xlsx)|*.xls;*.xlsx|Digital Forms (*.prs-form)|*.prs-form";
 
         protected override void DoImport()
         {
@@ -275,6 +281,92 @@ namespace InABox.DynamicGrid
             if (dialog.ShowDialog() != true)
                 return;
 
+            String extension = Path.GetExtension(dialog.FileName) ?? "";
+            if (extension.ToLower().Equals(".xls") || extension.ToLower().Equals(".xlsx"))
+            {
+                
+                String appliesto = "";
+                String code = "";
+                var lookups = new DigitalFormCategoryLookups(null).Lookups();
+                if (!DictionaryEdit.Execute<String>(lookups, ref appliesto, "Applies To", "Select Form Type") || String.IsNullOrWhiteSpace(appliesto))
+                    return;
+                Progress.ShowModal("Creating Form", (progress) =>
+                {
+
+                    var codes = new Client<DigitalForm>().Query(
+                        null,
+                        new Columns<DigitalForm>(x => x.Code),
+                        null
+                    ).Rows.Select(r => r.Get<DigitalForm, String>(c => c.Code)).ToArray();
+
+                    int i = 1;
+                    code = Path.GetFileNameWithoutExtension(dialog.FileName).ToUpper();
+                    while (codes.Contains(code))
+                        code = $"{Path.GetFileNameWithoutExtension(dialog.FileName).ToUpper()} ({i++})";
+                    
+                    DigitalForm form = new DigitalForm();
+                    form.Code = code;
+                    form.Description = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Path.GetFileNameWithoutExtension(dialog.FileName));
+                    form.AppliesTo = appliesto;
+                    new Client<DigitalForm>().Save(form, $"Imported from {dialog.FileName}");
+
+                    progress.Report("Importing Data");
+                    DFLayout data = null;
+                    var variables = new List<DigitalFormVariable>();
+                    
+                    var layout = new DigitalFormLayout();
+                    layout.Form.ID = form.ID;
+                    layout.Code = form.Code;
+                    layout.Description = form.Description;
+                    using (var fs = new FileStream(dialog.FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
+                    {
+                        var spreadsheet = new Spreadsheet(fs);
+                        data = DigitalFormUtils.LoadLayout(spreadsheet);
+                        layout.Layout = data.SaveLayout();
+                    }
+                    new Client<DigitalFormLayout>().Save(layout, $"Imported from {dialog.FileName}");
+
+                    progress.Report("Setting Up Variables");
+
+                    String group = "";
+                    foreach (var element in data.Elements)
+                    {
+                        if (element is DFLayoutHeader header)
+                        {
+                            group = header.Header;
+                        }
+                        else if (element is DFLayoutField field)
+                        {
+                            var variable = new DigitalFormVariable();
+                            variable.Form.ID = form.ID;
+                            variable.SetFieldType(field.GetType());
+                            variable.SaveProperties(field.GetProperties());
+                            variable.Group = group;
+                            variable.Code = field.Name;
+                            variable.Description = field.Name;
+                            variables.Add(variable);
+                        }
+                    }
+                    if (variables.Any())
+                        new Client<DigitalFormVariable>().Save(variables, $"Imported from {dialog.FileName}");
+
+                    progress.Report("Creating Report");
+                    var model = DigitalFormUtils.GetDataModel(appliesto, variables);
+                    var template = DigitalFormUtils.GenerateReport(layout, model);
+                    var report = new ReportTemplate();
+                    report.Section = form.ID.ToString();
+                    report.DataModel = model.Name;
+                    report.Name = form.Description;
+                    report.RDL = template?.SaveToString();
+                    new Client<ReportTemplate>().Save(report, $"Imported from {dialog.FileName}");
+
+                });
+
+                MessageBox.Show($"[{code}] imported successully!");
+                Refresh(false, true);
+                return;
+            }
+
             DigitalFormExportData? data;
             using(var stream = dialog.OpenFile())
             {

+ 13 - 44
inabox.wpf/DigitalForms/DigitalFormReportGrid.cs

@@ -1,19 +1,19 @@
 using FastReport;
-using FastReport.Data;
 using InABox.Clients;
 using InABox.Core;
 using InABox.Core.Reports;
 using InABox.Wpf.Reports;
 using InABox.WPF;
-using Syncfusion.DocIO.DLS;
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media.Imaging;
+using FastReport.Table;
+using FastReport.Utils;
+using Border = System.Windows.Controls.Border;
+using Size = System.Windows.Size;
 
 namespace InABox.DynamicGrid
 {
@@ -82,23 +82,7 @@ namespace InABox.DynamicGrid
         private List<DigitalFormLayout> GetLayouts()
             => GetLayoutGrid()?.Items.ToList() ?? new List<DigitalFormLayout>();
 
-        private static List<Type>? _entityForms;
-        private DataModel? GetDataModel()
-        {
-            _entityForms ??= CoreUtils.Entities
-                .Where(x => x.IsSubclassOfRawGeneric(typeof(EntityForm<,>)))
-                .ToList();
-            var entityForm = _entityForms
-                .Where(x => x.GetSuperclassDefinition(typeof(EntityForm<,>))?.GenericTypeArguments[0].Name == Form.AppliesTo)
-                .FirstOrDefault();
-            if(entityForm is not null)
-            {
-                var model = (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>).MakeGenericType(entityForm), Filter.Create(entityForm).None(), null) as DataModel)!;
-                (model as IDigitalFormReportDataModel)!.Variables = GetVariables().ToArray();
-                return model;
-            }
-            return null;
-        }
+ 
         private BitmapImage? ScriptImage(CoreRow? arg)
         {
             return arg == null ? Wpf.Resources.edit.AsBitmapImage() :
@@ -109,7 +93,7 @@ namespace InABox.DynamicGrid
         {
             if (arg != null)
             {
-                if (GetDataModel() is not DataModel model)
+                if (DigitalFormUtils.GetDataModel(Form.AppliesTo,GetVariables()) is not DataModel model)
                 {
                     Logger.Send(LogType.Error, "", "Invalid entity type for data model.");
                     return false;
@@ -135,7 +119,7 @@ namespace InABox.DynamicGrid
         private bool DesignClick(CoreRow? arg)
         {
             if (arg is null) return false;
-            if (GetDataModel() is not DataModel model)
+            if (DigitalFormUtils.GetDataModel(Form.AppliesTo,GetVariables()) is not DataModel model)
             {
                 Logger.Send(LogType.Error, "", "Invalid entity type for data model.");
                 return false;
@@ -163,30 +147,15 @@ namespace InABox.DynamicGrid
                 menu.IsOpen = true;
             }
         }
-
-        private Report? GenerateReport(DigitalFormLayout layout)
-        {
-            var model = GetDataModel();
-            if (model is null) return null;
-
-            var report = ReportUtils.SetupReport(null, model, true);
-
-            var dfLayout = new DFLayout();
-            dfLayout.LoadLayout(layout.Layout);
-
-            foreach(var element in dfLayout.Elements)
-            {
-                
-            }
-
-            return report;
-        }
-
+        
         private void AddLayout(DigitalFormLayout layout)
         {
+            var model = DigitalFormUtils.GetDataModel(Form.AppliesTo,GetVariables());
+            
             var item = CreateItem();
+            item.DataModel = model.Name;
             item.Name = string.IsNullOrWhiteSpace(layout.Description) ? layout.Code : layout.Description;
-            item.RDL = GenerateReport(layout)?.SaveToString();
+            item.RDL = DigitalFormUtils.GenerateReport(layout,model)?.SaveToString();
 
             if (EditItems(new[] { item }))
             {

+ 466 - 0
inabox.wpf/DigitalForms/DigitalFormUtils.cs

@@ -0,0 +1,466 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text.RegularExpressions;
+using FastReport;
+using FastReport.Table;
+using FastReport.Utils;
+using InABox.Core;
+using InABox.Scripting;
+using InABox.Wpf.Reports;
+using UnderlineType = InABox.Core.UnderlineType;
+
+namespace InABox.DynamicGrid
+{
+
+    public static class DigitalFormUtils
+    {
+        
+        #region Layout Importer
+        
+                private class Cell
+        {
+            public string Content { get; set; }
+
+            public int Row { get; set; }
+
+            public int Column { get; set; }
+
+            public int RowSpan { get; set; } = 1;
+
+            public int ColumnSpan { get; set; } = 1;
+
+            public ICell InnerCell { get; set; }
+
+            public Cell(int row, int column, string content, ICell cell)
+            {
+                Row = row;
+                Column = column;
+                Content = content;
+                InnerCell = cell;
+            }
+        }
+
+        private static void DeleteColumn(List<Cell> cells, int column)
+        {
+            foreach(var cell in cells)
+            {
+                if(cell.Column <= column && cell.Column + cell.ColumnSpan - 1 >= column)
+                {
+                    --cell.ColumnSpan;
+                }
+                else if(cell.Column > column)
+                {
+                    --cell.Column;
+                }
+            }
+            cells.RemoveAll(x => x.ColumnSpan < 0);
+        }
+
+        private static List<Cell> GetCells(ISheet sheet)
+        {
+            var grid = new Dictionary<int, Dictionary<int, Cell>>();
+
+            for (int rowIdx = sheet.FirstRow; rowIdx <= sheet.LastRow; ++rowIdx)
+            {
+                var row = sheet.GetRow(rowIdx);
+                if (row is not null && row.FirstColumn >= 0)
+                {
+                    var rowCells = new Dictionary<int, Cell>();
+                    for (int colIdx = row.FirstColumn; colIdx <= row.LastColumn; ++colIdx)
+                    {
+                        var cell = row.GetCell(colIdx);
+                        if (cell is not null)
+                        {
+                            rowCells.Add(colIdx, new Cell(rowIdx, colIdx, cell.GetValue(), cell));
+                        }
+                    }
+                    grid.Add(rowIdx, rowCells);
+                }
+            }
+
+            foreach (var region in sheet.GetMergedCells())
+            {
+                for (int r = region.FirstRow; r <= region.LastRow; ++r)
+                {
+                    if (!grid.TryGetValue(r, out var row)) continue;
+
+                    for (int c = region.FirstColumn; c <= region.LastColumn; ++c)
+                    {
+                        if ((r - region.FirstRow) + (c - region.FirstColumn) != 0)
+                        {
+                            row.Remove(c);
+                        }
+                    }
+                    if (row.Count == 0)
+                    {
+                        grid.Remove(r);
+                    }
+                }
+
+                if (grid.TryGetValue(region.FirstRow, out var cRow) && cRow.TryGetValue(region.FirstColumn, out var cCell))
+                {
+                    cCell.RowSpan = region.LastRow - region.FirstRow + 1;
+                    cCell.ColumnSpan = region.LastColumn - region.FirstColumn + 1;
+                }
+            }
+
+            var cells = new List<Cell>();
+            foreach (var row in grid.Values)
+            {
+                foreach (var cell in row.Values)
+                {
+                    cells.Add(cell);
+                }
+            }
+            return cells;
+        }
+
+        private static Regex VariableRegex = new(@"^\[(?<VAR>[^:\]]+)(?::(?<TYPE>[^:\]]*))?(?::(?<PROPERTIES>[^\]]*))?\]$");
+        private static Regex HeaderRegex = new(@"^{(?<HEADER>[^:}]+)(?::(?<COLLAPSED>[^}]*))?}$");
+
+        public static DFLayout LoadLayout(ISpreadsheet spreadsheet)
+        {
+            var sheet = spreadsheet.GetSheet(0);
+
+            var cells = GetCells(sheet);
+
+            int firstRow = int.MaxValue;
+            int lastRow = 0;
+            int firstCol = int.MaxValue;
+            int lastCol = 0;
+
+            foreach (var cell in cells)
+            {
+                firstCol = Math.Min(cell.Column, firstCol);
+                lastCol = Math.Max(cell.Column + cell.ColumnSpan - 1, lastCol);
+
+                firstRow = Math.Min(cell.Row, firstRow);
+                lastRow = Math.Max(cell.Row + cell.RowSpan - 1, lastRow);
+            }
+
+            var layout = new DFLayout();
+
+            var columnWidths = new Dictionary<int, float>();
+            var colOffset = 0;
+            for (int col = firstCol; col <= lastCol; ++col)
+            {
+                var width = sheet.GetColumnWidth(col);
+                if(width == float.MinValue)
+                {
+                    layout.ColumnWidths.Add("10*");
+                }
+                else if(width <= 0f)
+                {
+                    DeleteColumn(cells, col);
+                }
+                else
+                {
+                    layout.ColumnWidths.Add($"{width}*");
+                }
+            }
+
+            for (int row = firstRow; row <= lastRow; ++row)
+                layout.RowHeights.Add("Auto");
+
+                foreach(var cell in cells)
+            {
+                var style = cell.InnerCell.GetStyle();
+
+                if (string.IsNullOrWhiteSpace(cell.Content) && style.Foreground == Color.Empty) continue;
+
+                DFLayoutControl? control;
+                String content = cell.Content?.Trim() ?? "";
+
+                var headermatch = HeaderRegex.Match(content);
+                var variablematch = VariableRegex.Match(content);
+
+                if (headermatch.Success)
+                {
+                    var text = headermatch.Groups["HEADER"];
+                    var collapsed = headermatch.Groups["COLLAPSED"];
+                    var header = new DFLayoutHeader()
+                    {
+                        Header = text.Value, 
+                        Collapsed = collapsed.Success ? String.Equals(collapsed.Value.ToUpper(),"COLLAPSED") : false,
+                        Style = CreateStyle(style)
+                    };
+                    
+                    control = header;
+                }
+                else if (variablematch.Success)
+                {
+                    var variableName = variablematch.Groups["VAR"];
+                    var variableType = variablematch.Groups["TYPE"];
+                    var variableProps = variablematch.Groups["PROPERTIES"];
+                    
+                    Type? fieldType = null;
+                    if (variableType.Success)
+                        fieldType = DFUtils.GetFieldType(variableType.Value);
+                    fieldType ??= typeof(DFLayoutStringField);
+                    
+                    var field = (Activator.CreateInstance(fieldType) as DFLayoutField)!;
+                    field.Name = variableName.Value;
+                    
+                    if (variableProps.Success)
+                    {
+                        if (field is DFLayoutOptionField option)
+                            option.Properties.Options = variableProps.Value;
+                        if (field is DFLayoutStringField text)
+                            text.Properties.TextWrapping = style.WrapText;
+                        // need to populate other variable types here
+                    }
+                    
+                    
+                    control = field;
+                }
+                else
+                {
+                    control = new DFLayoutLabel
+                    {
+                        Caption = cell.Content,
+                        Style = CreateStyle(style)
+                    };
+                }
+                
+                if(control is not null)
+                {
+                    control.Row = cell.Row - firstRow + 1;
+                    control.Column = cell.Column - firstCol + 1 - colOffset;
+                    control.RowSpan = cell.RowSpan;
+                    control.ColumnSpan = cell.ColumnSpan;
+                    layout.Elements.Add(control);
+                }
+            }
+
+            return layout;
+        }
+
+        private static DFLayoutTextStyle CreateStyle(ICellStyle style)
+        {
+            if (style == null)
+                return new DFLayoutTextStyle();
+
+            var result = new DFLayoutTextStyle
+            {
+                FontSize = style.Font.FontSize,
+
+                IsItalic = style.Font.Italic,
+                IsBold = style.Font.Bold,
+                Underline = style.Font.Underline switch
+                {
+                    Scripting.UnderlineType.None => UnderlineType.None,
+                    Scripting.UnderlineType.Single or Scripting.UnderlineType.SingleAccounting => UnderlineType.Single,
+                    Scripting.UnderlineType.Double or Scripting.UnderlineType.DoubleAccounting => UnderlineType.Double,
+                    _ => UnderlineType.None
+                },
+                BackgroundColour = style.Background,
+                ForegroundColour = style.Font.Colour,
+
+                HorizontalTextAlignment = style.HorizontalAlignment switch
+                {
+                     CellAlignment.Middle => DFLayoutAlignment.Middle,
+                     CellAlignment.End => DFLayoutAlignment.End,
+                     CellAlignment.Justify => DFLayoutAlignment.Stretch,
+                     _ => DFLayoutAlignment.Start
+                },
+                
+                VerticalTextAlignment = style.VerticalAlignment switch
+                {
+                    CellAlignment.Start => DFLayoutAlignment.Start,
+                    CellAlignment.End => DFLayoutAlignment.End,
+                    CellAlignment.Justify => DFLayoutAlignment.Stretch,
+                    _ => DFLayoutAlignment.Middle
+                },
+                
+                TextWrapping = style.WrapText
+
+            };
+            return result;
+        }
+        
+        #endregion
+        
+        
+        #region Report Generator
+        
+        public static Report? GenerateReport(DigitalFormLayout layout, DataModel model)
+        {
+            
+            var report = ReportUtils.SetupReport(null, model, true);
+
+            var dfLayout = new DFLayout();
+            dfLayout.LoadLayout(layout.Layout);
+
+            var page = new ReportPage();
+            page.Name = "Page1";
+            page.PaperWidth = 210;
+            page.PaperHeight = 297;
+            page.Landscape = false;
+            page.LeftMargin = 10;
+            page.TopMargin = 10;
+            page.RightMargin = 10;
+            page.BottomMargin = 10;
+            report.Pages.Add(page);
+
+            DataBand band = new DataBand();
+            band.Name = "Data1";
+            band.Height = Units.Millimeters * (page.PaperHeight - (page.TopMargin + page.BottomMargin));
+            band.Width = Units.Millimeters * (page.PaperWidth - (page.LeftMargin + page.RightMargin));
+            band.PrintIfDatasourceEmpty = true;
+            band.DataSource = report.GetDataSource("Form_Data");
+            page.AddChild(band);
+            
+            TableObject table = new TableObject();
+            table.ColumnCount = dfLayout.ColumnWidths.Count;
+            table.RowCount = dfLayout.RowHeights.Count;
+            ProcessColumnWidths(band.Width, dfLayout.ColumnWidths, table.Columns);
+            ProcessRowHeights(band.Height, dfLayout.RowHeights, table.Rows);
+            band.AddChild(table);
+            foreach(var element in dfLayout.Elements)
+            {
+                var row = table.Rows[element.Row-1];
+                var cell = row.ChildObjects[element.Column-1] as TableCell;
+                cell.Border.Lines = BorderLines.All;
+                cell.ColSpan = element.ColumnSpan;
+                cell.RowSpan = element.RowSpan;
+                if (element is DFLayoutField field)
+                {
+                    cell.Text = $"[Form_Data.{field.Name}]";
+                    cell.Font = new System.Drawing.Font(cell.Font.FontFamily, 8F);
+                }
+                else if (element is DFLayoutLabel label)
+                {
+                    cell.Text = label.Description;
+                    ApplyStyle(cell, label.Style);
+                }
+                else if (element is DFLayoutHeader header)
+                {
+                    cell.Text = header.Header;
+                    ApplyStyle(cell, header.Style);
+                }
+            }
+
+
+            return report;
+        }
+
+        private static void ApplyStyle(TableCell cell, DFLayoutTextStyle style)
+        {
+            cell.FillColor = style.BackgroundColour;
+            FontStyle fontstyle = System.Drawing.FontStyle.Regular;
+            if (style.IsBold)
+                fontstyle = fontstyle | System.Drawing.FontStyle.Bold;
+            if (style.IsItalic)
+                fontstyle = fontstyle | System.Drawing.FontStyle.Italic;
+            float fontsize = (float)style.FontSize * 8F / 12F;
+            fontsize = fontsize == 0F ? 8F : fontsize;
+            cell.Font = new System.Drawing.Font(cell.Font.FontFamily, fontsize, fontstyle);
+            cell.HorzAlign = style.HorizontalTextAlignment switch
+            {
+                DFLayoutAlignment.Start => HorzAlign.Left,
+                DFLayoutAlignment.Middle => HorzAlign.Center,
+                DFLayoutAlignment.End => HorzAlign.Right,
+                DFLayoutAlignment.Stretch => HorzAlign.Justify
+            };
+            cell.VertAlign = style.VerticalTextAlignment switch
+            {
+                DFLayoutAlignment.Start => VertAlign.Top,
+                DFLayoutAlignment.Middle => VertAlign.Center,
+                DFLayoutAlignment.End => VertAlign.Bottom,
+                DFLayoutAlignment.Stretch => VertAlign.Center
+            };
+            cell.WordWrap = style.TextWrapping;
+        }
+
+        private static void ProcessRowHeights(float bandheight, List<string> values, TableRowCollection rows)
+        {
+            float fixedtotal = 0F;
+            for (int iFixed = 0; iFixed < values.Count; iFixed++)
+            {
+                if (!values[iFixed].Contains("*"))
+                {
+                    if (!float.TryParse(values[iFixed], out float value))
+                        value = 4F;
+                    rows[iFixed].Height = Units.Millimeters * value;
+                    fixedtotal += Units.Millimeters * value;
+                }
+            }
+
+            Dictionary<int, float> starvalues = new Dictionary<int, float>();
+            float startotal = 0F;
+            for (int iStar = 0; iStar < values.Count; iStar++)
+            {
+                float fstartotal = 0F;
+                if (values[iStar].Contains("*"))
+                {
+                    if (!float.TryParse(values[iStar].Replace("*", ""), out float value))
+                        value += 1;
+                    starvalues[iStar] = value;
+                    startotal += value;
+                }
+            }
+            foreach (var key in starvalues.Keys)
+                rows[key].Height = (starvalues[key] / startotal) * (bandheight - fixedtotal);        
+        }
+
+        private static void ProcessColumnWidths(float bandwidth, List<string> values, TableColumnCollection columns)
+        {
+            float fixedtotal = 0F;
+            for (int iFixed = 0; iFixed < values.Count; iFixed++)
+            {
+                if (!values[iFixed].Contains("*"))
+                {
+                    if (!float.TryParse(values[iFixed], out float value))
+                        value = 20F;
+                    columns[iFixed].Width = Units.Millimeters * value;
+                    fixedtotal += Units.Millimeters * value;
+                }
+            }
+
+            Dictionary<int, float> starvalues = new Dictionary<int, float>();
+            float startotal = 0F;
+            for (int iStar = 0; iStar < values.Count; iStar++)
+            {
+                float fstartotal = 0F;
+                if (values[iStar].Contains("*"))
+                {
+                    if (!float.TryParse(values[iStar].Replace("*", ""), out float value))
+                        value += 1;
+                    starvalues[iStar] = value;
+                    startotal += value;
+                }
+            }
+            
+            foreach (var key in starvalues.Keys)
+                columns[key].Width = (starvalues[key] / startotal) * (bandwidth - fixedtotal);
+        }
+        
+        #endregion
+        
+        #region Data Model
+        
+        private static List<Type>? _entityForms;
+        public static DataModel? GetDataModel(String appliesto, IEnumerable<DigitalFormVariable> variables)
+        {
+            _entityForms ??= CoreUtils.Entities
+                .Where(x => x.IsSubclassOfRawGeneric(typeof(EntityForm<,>)))
+                .ToList();
+            var entityForm = _entityForms
+                .Where(x => x.GetSuperclassDefinition(typeof(EntityForm<,>))?.GenericTypeArguments[0].Name == appliesto)
+                .FirstOrDefault();
+            if(entityForm is not null)
+            {
+                var model = (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>).MakeGenericType(entityForm), Filter.Create(entityForm).None(), null) as DataModel)!;
+                (model as IDigitalFormReportDataModel)!.Variables = variables.ToArray();
+                return model;
+            }
+            return null;
+        }
+        
+        #endregion
+        
+    }
+    
+}

+ 1 - 268
inabox.wpf/DigitalForms/DynamicFormLayoutGrid.cs

@@ -18,273 +18,6 @@ using UnderlineType = InABox.Core.UnderlineType;
 
 namespace InABox.DynamicGrid
 {
-
-    public static class FormLayoutImporter
-    {
-        private class Cell
-        {
-            public string Content { get; set; }
-
-            public int Row { get; set; }
-
-            public int Column { get; set; }
-
-            public int RowSpan { get; set; } = 1;
-
-            public int ColumnSpan { get; set; } = 1;
-
-            public ICell InnerCell { get; set; }
-
-            public Cell(int row, int column, string content, ICell cell)
-            {
-                Row = row;
-                Column = column;
-                Content = content;
-                InnerCell = cell;
-            }
-        }
-
-        private static void DeleteColumn(List<Cell> cells, int column)
-        {
-            foreach(var cell in cells)
-            {
-                if(cell.Column <= column && cell.Column + cell.ColumnSpan - 1 >= column)
-                {
-                    --cell.ColumnSpan;
-                }
-                else if(cell.Column > column)
-                {
-                    --cell.Column;
-                }
-            }
-            cells.RemoveAll(x => x.ColumnSpan < 0);
-        }
-
-        private static List<Cell> GetCells(ISheet sheet)
-        {
-            var grid = new Dictionary<int, Dictionary<int, Cell>>();
-
-            for (int rowIdx = sheet.FirstRow; rowIdx <= sheet.LastRow; ++rowIdx)
-            {
-                var row = sheet.GetRow(rowIdx);
-                if (row is not null && row.FirstColumn >= 0)
-                {
-                    var rowCells = new Dictionary<int, Cell>();
-                    for (int colIdx = row.FirstColumn; colIdx <= row.LastColumn; ++colIdx)
-                    {
-                        var cell = row.GetCell(colIdx);
-                        if (cell is not null)
-                        {
-                            rowCells.Add(colIdx, new Cell(rowIdx, colIdx, cell.GetValue(), cell));
-                        }
-                    }
-                    grid.Add(rowIdx, rowCells);
-                }
-            }
-
-            foreach (var region in sheet.GetMergedCells())
-            {
-                for (int r = region.FirstRow; r <= region.LastRow; ++r)
-                {
-                    if (!grid.TryGetValue(r, out var row)) continue;
-
-                    for (int c = region.FirstColumn; c <= region.LastColumn; ++c)
-                    {
-                        if ((r - region.FirstRow) + (c - region.FirstColumn) != 0)
-                        {
-                            row.Remove(c);
-                        }
-                    }
-                    if (row.Count == 0)
-                    {
-                        grid.Remove(r);
-                    }
-                }
-
-                if (grid.TryGetValue(region.FirstRow, out var cRow) && cRow.TryGetValue(region.FirstColumn, out var cCell))
-                {
-                    cCell.RowSpan = region.LastRow - region.FirstRow + 1;
-                    cCell.ColumnSpan = region.LastColumn - region.FirstColumn + 1;
-                }
-            }
-
-            var cells = new List<Cell>();
-            foreach (var row in grid.Values)
-            {
-                foreach (var cell in row.Values)
-                {
-                    cells.Add(cell);
-                }
-            }
-            return cells;
-        }
-
-        private static Regex VariableRegex = new(@"^\[(?<VAR>[^:\]]+)(?::(?<TYPE>[^:\]]*))?(?::(?<PROPERTIES>[^\]]*))?\]$");
-        private static Regex HeaderRegex = new(@"^{(?<HEADER>[^:}]+)(?::(?<COLLAPSED>[^}]*))?}$");
-
-        public static DFLayout LoadLayout(ISpreadsheet spreadsheet)
-        {
-            var sheet = spreadsheet.GetSheet(0);
-
-            var cells = GetCells(sheet);
-
-            int firstRow = int.MaxValue;
-            int lastRow = 0;
-            int firstCol = int.MaxValue;
-            int lastCol = 0;
-
-            foreach (var cell in cells)
-            {
-                firstCol = Math.Min(cell.Column, firstCol);
-                lastCol = Math.Max(cell.Column + cell.ColumnSpan - 1, lastCol);
-
-                firstRow = Math.Min(cell.Row, firstRow);
-                lastRow = Math.Max(cell.Row + cell.RowSpan - 1, lastRow);
-            }
-
-            var layout = new DFLayout();
-
-            var columnWidths = new Dictionary<int, float>();
-            var colOffset = 0;
-            for (int col = firstCol; col <= lastCol; ++col)
-            {
-                var width = sheet.GetColumnWidth(col);
-                if(width == float.MinValue)
-                {
-                    layout.ColumnWidths.Add("10*");
-                }
-                else if(width <= 0f)
-                {
-                    DeleteColumn(cells, col);
-                }
-                else
-                {
-                    layout.ColumnWidths.Add($"{width}*");
-                }
-            }
-
-            for (int row = firstRow; row <= lastRow; ++row)
-                layout.RowHeights.Add("Auto");
-
-                foreach(var cell in cells)
-            {
-                var style = cell.InnerCell.GetStyle();
-
-                if (string.IsNullOrWhiteSpace(cell.Content) && style.Foreground == Color.Empty) continue;
-
-                DFLayoutControl? control;
-                String content = cell.Content?.Trim() ?? "";
-
-                var headermatch = HeaderRegex.Match(content);
-                var variablematch = VariableRegex.Match(content);
-
-                if (headermatch.Success)
-                {
-                    var text = headermatch.Groups["HEADER"];
-                    var collapsed = headermatch.Groups["COLLAPSED"];
-                    var header = new DFLayoutHeader()
-                    {
-                        Header = text.Value, 
-                        Collapsed = collapsed.Success ? String.Equals(collapsed.Value.ToUpper(),"COLLAPSED") : false,
-                        Style = CreateStyle(style)
-                    };
-                    
-                    control = header;
-                }
-                else if (variablematch.Success)
-                {
-                    var variableName = variablematch.Groups["VAR"];
-                    var variableType = variablematch.Groups["TYPE"];
-                    var variableProps = variablematch.Groups["PROPERTIES"];
-                    
-                    Type? fieldType = null;
-                    if (variableType.Success)
-                        fieldType = DFUtils.GetFieldType(variableType.Value);
-                    fieldType ??= typeof(DFLayoutStringField);
-                    
-                    var field = (Activator.CreateInstance(fieldType) as DFLayoutField)!;
-                    field.Name = variableName.Value;
-                    
-                    if (variableProps.Success)
-                    {
-                        if (field is DFLayoutOptionField option)
-                            option.Properties.Options = variableProps.Value;
-                        if (field is DFLayoutStringField text)
-                            text.Properties.TextWrapping = style.WrapText;
-                        // need to populate other variable types here
-                    }
-                    
-                    
-                    control = field;
-                }
-                else
-                {
-                    control = new DFLayoutLabel
-                    {
-                        Caption = cell.Content,
-                        Style = CreateStyle(style)
-                    };
-                }
-                
-                if(control is not null)
-                {
-                    control.Row = cell.Row - firstRow + 1;
-                    control.Column = cell.Column - firstCol + 1 - colOffset;
-                    control.RowSpan = cell.RowSpan;
-                    control.ColumnSpan = cell.ColumnSpan;
-                    layout.Elements.Add(control);
-                }
-            }
-
-            return layout;
-        }
-
-        private static DFLayoutTextStyle CreateStyle(ICellStyle style)
-        {
-            if (style == null)
-                return new DFLayoutTextStyle();
-
-            var result = new DFLayoutTextStyle
-            {
-                FontSize = style.Font.FontSize,
-
-                IsItalic = style.Font.Italic,
-                IsBold = style.Font.Bold,
-                Underline = style.Font.Underline switch
-                {
-                    Scripting.UnderlineType.None => UnderlineType.None,
-                    Scripting.UnderlineType.Single or Scripting.UnderlineType.SingleAccounting => UnderlineType.Single,
-                    Scripting.UnderlineType.Double or Scripting.UnderlineType.DoubleAccounting => UnderlineType.Double,
-                    _ => UnderlineType.None
-                },
-                BackgroundColour = style.Background,
-                ForegroundColour = style.Font.Colour,
-
-                HorizontalTextAlignment = style.HorizontalAlignment switch
-                {
-                     CellAlignment.Middle => DFLayoutAlignment.Middle,
-                     CellAlignment.End => DFLayoutAlignment.End,
-                     CellAlignment.Justify => DFLayoutAlignment.Stretch,
-                     _ => DFLayoutAlignment.Start
-                },
-                
-                VerticalTextAlignment = style.VerticalAlignment switch
-                {
-                    CellAlignment.Start => DFLayoutAlignment.Start,
-                    CellAlignment.End => DFLayoutAlignment.End,
-                    CellAlignment.Justify => DFLayoutAlignment.Stretch,
-                    _ => DFLayoutAlignment.Middle
-                },
-                
-                TextWrapping = style.WrapText
-
-            };
-            return result;
-        }
-    }
-    
-
-
     public abstract class DynamicFormLayoutGrid : DynamicOneToManyGrid<DigitalForm, DigitalFormLayout>
     {
         private readonly BitmapImage design = Wpf.Resources.design.AsBitmapImage();
@@ -302,7 +35,7 @@ namespace InABox.DynamicGrid
 
         private DFLayout LoadLayoutFromSpreadsheet(ISpreadsheet spreadsheet)
         {
-            return FormLayoutImporter.LoadLayout(spreadsheet);
+            return DigitalFormUtils.LoadLayout(spreadsheet);
         }
 
         protected override void DoImport()

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

@@ -2067,7 +2067,7 @@ namespace InABox.DynamicGrid
         {
             into.Rows.Clear();
             recordMap?.Clear();
-            foreach (var row in from.Rows)
+            foreach (var row in from.Rows.ToArray())
                 if (FilterRecord(row) && filter?.Invoke(row) != false)
                 {
                     var newrow = into.NewRow();

+ 1 - 1
inabox.wpf/DynamicGrid/MasterList.xaml

@@ -5,7 +5,7 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:wpf="clr-namespace:InABox.Wpf"
         mc:Ignorable="d"
-        Title="MasterList" Height="600" Width="450" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded">
+        Title="MasterList" Height="600" MinWidth="450" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded" SizeToContent="Width">
     <Grid x:Name="layoutGrid">
         <Grid.RowDefinitions>
             <!--<RowDefinition Height="40"/>-->

+ 18 - 20
inabox.wpf/DynamicGrid/MasterList.xaml.cs

@@ -226,28 +226,26 @@ namespace InABox.DynamicGrid
 
             var screen = WpfScreen.GetScreenFrom(this);
             var maxwidth = (int)screen.WorkingArea.Width - 200;
-
-            var DesiredWidth = Math.Max(600, grid.DesiredWidth());
-            if (DesiredWidth > maxwidth)
-                DesiredWidth = maxwidth;
-            if (DesiredWidth > Width)
-                Width = DesiredWidth;
-            if (!string.IsNullOrEmpty(GroupBy))
-                Width += Groups.DesiredSize.Width + 45.0F;
-
-            var maxheight = (int)screen.WorkingArea.Height - 200;
-            var DesiredHeight = (int)(Width * 0.75);
-            if (DesiredHeight > maxheight)
-                DesiredHeight = maxheight;
-            if (DesiredHeight > Height)
-                Height = DesiredHeight;
-
-            //WpfScreen.CenterWindowOnScreen(this);
-
+            ((FrameworkElement)grid).Width = Math.Min(Math.Max(grid.DesiredWidth(),450),maxwidth);
+            
+            // var DesiredWidth = Math.Max(600, grid.DesiredWidth());
+            // if (DesiredWidth > maxwidth)
+            //     DesiredWidth = maxwidth;
+            // if (DesiredWidth > Width)
+            //     Width = DesiredWidth;
+            // if (!string.IsNullOrEmpty(GroupBy))
+            //     Width += Groups.DesiredSize.Width + 45.0F;
+            //
+            // var maxheight = (int)screen.WorkingArea.Height - 200;
+            // var DesiredHeight = (int)(Width * 0.75);
+            // if (DesiredHeight > maxheight)
+            //     DesiredHeight = maxheight;
+            // if (DesiredHeight > Height)
+            //     Height = DesiredHeight;
+            
             Left = center.X - Width / 2;
             Top = center.Y - Height / 2;
-            //Left = screen.DeviceBounds.Left + ((screen.DeviceBounds.Width - Width) / 2.0F);
-            //Top = screen.DeviceBounds.Top + ((screen.DeviceBounds.Height - Height) / 2.0F);
+
         }
     }
 }

+ 10 - 1
inabox.wpf/Editors/DictionaryEdit.xaml.cs

@@ -11,6 +11,13 @@ namespace InABox.WPF
     /// </summary>
     public partial class DictionaryEdit : ThemableWindow
     {
+
+        public string Description
+        {
+            get => Label.Content as string ?? "Select Value";
+            set => Label.Content = value;
+        }
+        
         public DictionaryEdit(string[] values, ref int index)
         {
             InitializeComponent();
@@ -39,10 +46,12 @@ namespace InABox.WPF
             Close();
         }
 
-        public static bool Execute<T>(Dictionary<T, string> values, ref T value)
+        public static bool Execute<T>(Dictionary<T, string> values, ref T value, string? title = null, string? description = null) where T : notnull
         {
             var index = values.Keys.ToList().IndexOf(value);
             var edit = new DictionaryEdit(values.Values.ToArray(), ref index);
+            edit.Title = title ?? "Value Selection";
+            edit.Description = description ?? "Select Value";
             if (edit.ShowDialog() == true)
             {
                 value = edit.Index > -1 ? values.Keys.ToList()[edit.Index] : default;

+ 5 - 1
inabox.wpf/InABox.Wpf.csproj

@@ -126,7 +126,7 @@
         <PackageReference Include="ColorHelper" Version="1.8.0" />
         <PackageReference Include="ControlzEx" Version="5.0.2" />
         <PackageReference Include="Extended.Wpf.Toolkit" Version="4.4.0" />
-        <PackageReference Include="FastReport.Net.Pro" Version="2022.3.12" />
+        <PackageReference Include="FastReport.Net.Pro" Version="2023.1.13" />
         <PackageReference Include="GhostScript.NetCore" Version="1.0.1" />
         <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.0.1" />
         <PackageReference Include="Syncfusion.Grid.WPF" Version="20.2.0.46" />
@@ -419,6 +419,10 @@
       <Resource Include="Resources\zoomout.png">
         <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       </Resource>
+      <None Remove="Resources\splash.png" />
+      <Resource Include="Resources\splash.png">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </Resource>
     </ItemGroup>
 
     <ItemGroup>

+ 8 - 7
inabox.wpf/ProgressWindow/ProgressForm.xaml

@@ -12,24 +12,25 @@
         BorderBrush="Transparent"
         AllowsTransparency="True"
         Background="Transparent"
-        SizeToContent="WidthAndHeight"
         MouseDown="Window_MouseDown"
-        MaxWidth="550" MaxHeight="550">
-    <Border CornerRadius="20" BorderBrush="Gray" BorderThickness="0.75" Padding="50,20,50,10" Background="White">
-        <Grid Margin="10">
+        Width="350" Height="200">
+    <Border CornerRadius="10" BorderBrush="Gray" BorderThickness="0.75" Padding="5,5,5,0" Background="White">
+        <Grid Margin="5">
             <Grid.RowDefinitions>
                 <RowDefinition Height="*" />
                 <RowDefinition Height="Auto" />
             </Grid.RowDefinitions>
-            <Border x:Name="Image" CornerRadius="10" BorderBrush="Transparent" />
+            <Image x:Name="Splash" Margin="20,10,20,10" Stretch="Uniform" Source="../Resources/splash.png" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
+            
 
             <Label
                 Grid.Row="1"
-                FontSize="18"
+                FontSize="14"
                 FontStyle="Oblique"
                 x:Name="Progress"
                 Content="Please wait.."
-                Margin="10,10,10,0"
+                Margin="0"
+                HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                 HorizontalContentAlignment="Center"
                 VerticalContentAlignment="Center"
                 Background="Transparent" />

+ 14 - 39
inabox.wpf/ProgressWindow/ProgressForm.xaml.cs

@@ -1,8 +1,9 @@
 using InABox.Wpf;
 using System;
 using System.Windows;
+using System.Windows.Controls;
 using System.Windows.Media;
-using System.Windows.Threading;
+using System.Windows.Media.Animation;
 
 namespace InABox.WPF
 {
@@ -11,69 +12,43 @@ namespace InABox.WPF
     /// </summary>
     public partial class ProgressForm : ThemableWindow
     {
-        private readonly ImageSource _image = null;
+        private ImageSource? _image = null;
 
-        private bool on = true;
+        private DoubleAnimation _fadein = new DoubleAnimation(1d, new Duration(TimeSpan.FromSeconds(3)));
+        private DoubleAnimation _fadeout = new DoubleAnimation(0.2d, new Duration(TimeSpan.FromSeconds(3)));
 
         public ProgressForm()
         {
             InitializeComponent();
             Topmost = true;
-            var timer = new DispatcherTimer(DispatcherPriority.Send)
+            Loaded += (sender, args) =>
             {
-                Interval = new TimeSpan(0, 0, 0, 0, 500)
+                _fadeout.Completed += (o, e) => Splash.BeginAnimation(Image.OpacityProperty, _fadein);
+                _fadein.Completed += (o, e) => Splash.BeginAnimation(Image.OpacityProperty, _fadeout);
+                Splash.BeginAnimation(Image.OpacityProperty, _fadeout);
             };
-            timer.Tick += (o, e) =>
-            {
-                on = !on;
-                Progress.Background = new SolidColorBrush(on ? Colors.LightGreen : Colors.LightSalmon);
-            };
-            timer.IsEnabled = false;
         }
-
-        public ImageSource DisplayImage
+        
+        public ImageSource? DisplayImage
         {
-            get => _image;
-            set
-            {
-                Image.Width = value.Width;
-                Image.Height = value.Height;
-                Image.Background = new ImageBrush(value) { Stretch = Stretch.UniformToFill };
-            }
+            get => Splash.Source;
+            set => Splash.Source = value;
         }
-
-        //public void OpenWindow(String message)
-        //{
-        //    if (Progress.Dispatcher.CheckAccess())
-        //        Update(message);
-        //    else
-        //        Progress.Dispatcher.BeginInvoke(new Action(() => { Update(message); }), DispatcherPriority.Send);
-        //}
-
-        //private delegate void UpdateDelegate(string message);
-        //private void Update(String message)
-        //{
-        //    Progress.Content = message;
-        //}
-
+        
         public void UpdateWindow(string message)
         {
             if (Progress.Dispatcher.CheckAccess())
                 Progress.Content = message;
             else
                 Progress.Dispatcher.Invoke(() => { Progress.Content = message; });
-            //Progress.Dispatcher.BeginInvoke(new Action(() => { Update(message);  }), DispatcherPriority.Send);
         }
 
-        //private delegate void HideDelegate();
-
         public void CloseWindow()
         {
             if (Progress.Dispatcher.CheckAccess())
                 Close();
             else
                 Progress.Dispatcher.Invoke(() => { Close(); });
-            //Progress.Dispatcher.BeginInvoke(new Action(() => { Close(); }), DispatcherPriority.Send);
         }
 
         public string GetMessage()

BIN
inabox.wpf/Resources/splash.png