浏览代码

Started work on DigitalForms editor

Kenric Nugteren 6 月之前
父节点
当前提交
44ea933ed8

+ 7 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormViewer.axaml

@@ -0,0 +1,7 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PRS.Avalonia.DigitalForms.DigitalFormViewer">
+</UserControl>

+ 27 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormViewer.axaml.cs

@@ -0,0 +1,27 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using InABox.Core;
+
+namespace PRS.Avalonia.DigitalForms;
+
+public partial class DigitalFormViewer : UserControl
+{
+    public static readonly StyledProperty<IDigitalFormInstance> FormProperty =
+        AvaloniaProperty.Register<DigitalFormViewer, IDigitalFormInstance>(nameof(Form));
+    public static readonly StyledProperty<Entity> EntityProperty =
+        AvaloniaProperty.Register<DigitalFormViewer, Entity>(nameof(Entity));
+    public static readonly StyledProperty<DFLayout> LayoutProperty =
+        AvaloniaProperty.Register<DigitalFormViewer, DFLayout>(nameof(Layout));
+
+    public IDigitalFormInstance Form { get; set; }
+
+    public Entity Entity { get; set; }
+
+    public DFLayout Layout { get; set; }
+
+    public DigitalFormViewer()
+    {
+        InitializeComponent();
+    }
+}

+ 9 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormsHostView.axaml

@@ -2,6 +2,15 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+			 xmlns:forms="using:PRS.Avalonia.DigitalForms"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PRS.Avalonia.DigitalForms.DigitalFormsHostView">
+	<TabControl TabStripPlacement="Bottom">
+		<TabItem Header="Form">
+			<ScrollViewer>
+				<forms:DigitalFormViewer/>
+			</ScrollViewer>
+		</TabItem>
+		<TabItem Header="Documents"/>
+	</TabControl>
 </UserControl>

+ 140 - 22
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormsHostViewModel.cs

@@ -1,5 +1,7 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using Comal.Classes;
+using CommunityToolkit.Mvvm.ComponentModel;
 using InABox.Avalonia;
+using InABox.Clients;
 using InABox.Core;
 using PRS.Avalonia.Modules;
 using System;
@@ -10,12 +12,36 @@ using System.Threading.Tasks;
 
 namespace PRS.Avalonia.DigitalForms;
 
-public partial class DigitalFormsHostViewModel : ModuleViewModel
+public class DigitalFormCacheModel<TParent, TParentLink, TForm> : ISerializeBinary
+    where TParent : Entity, IRemotable, IPersistent, new()
+    where TParentLink : EntityLink<TParent>, new()
+    where TForm : EntityForm<TParent, TParentLink, TForm>, IRemotable, IPersistent, IDigitalFormInstance<TParentLink>, new()
 {
-    public override string Title => "WIP";
+    public TParent Parent { get; set; }
 
-    [ObservableProperty]
-    private Entity _parent;
+    public TForm Form { get; set; }
+
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+        Parent = reader.ReadObject<TParent>();
+        Form = reader.ReadObject<TForm>();
+    }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        writer.WriteObject(Parent);
+        writer.WriteObject(Form);
+    }
+}
+
+public partial class DigitalFormsHostViewModel<TModel, TShell, TParent, TParentLink, TForm> : ModuleViewModel
+    where TModel : DigitalFormInstanceModel<TModel, TShell, TForm>
+    where TShell : DigitalFormInstanceShell<TModel, TParent, TParentLink, TForm>, new()
+    where TParent : Entity, IRemotable, IPersistent, new()
+    where TParentLink : EntityLink<TParent>, new()
+    where TForm : EntityForm<TParent, TParentLink, TForm>, IRemotable, IPersistent, IDigitalFormInstance<TParentLink>, new()
+{
+    public override string Title => "WIP";
 
     [ObservableProperty]
     private Guid _formID;
@@ -23,37 +49,129 @@ public partial class DigitalFormsHostViewModel : ModuleViewModel
     [ObservableProperty]
     private Guid _instanceID;
 
+    [ObservableProperty]
+    private DigitalFormLayout _digitalFormLayout;
+
+    [ObservableProperty]
+    private DigitalFormVariable[] _variables;
+
+    [ObservableProperty]
+    private DigitalFormModel _model;
+
+    [ObservableProperty]
+    private DigitalFormDocumentModel _documents;
+
+    [ObservableProperty]
+    private TParent _parent;
+
+    [ObservableProperty]
+    private TForm _form;
+
+    [ObservableProperty]
+    private DFLayout _layout = new();
+
+    [ObservableProperty]
+    private bool _newForm;
+
+    [ObservableProperty]
+    private bool _readOnly;
+
     public event Action? OnSaved;
 
-    public void Configure(Entity parent, Guid formID, Guid instanceID)
+    private string CacheFileName => $"{typeof(TForm)}.{InstanceID}";
+
+    public DigitalFormsHostViewModel()
+    {
+        PrimaryMenu.Add(new(Images.save, SaveForm));
+
+        ProgressVisible = true;
+    }
+
+    public void Configure(TParent parent, Guid formID, Entity instance)
+    {
+    }
+
+    public void Configure(TParent parent, Guid formID, Guid instanceID)
     {
         Parent = parent;
         FormID = formID;
         InstanceID = instanceID;
 
-        // TODO: LoadItems; show loading screen or progress ring while loading.
+        Model = DigitalFormModel.CreateModel(DataAccess, Parent.GetType().Name);
+        Documents = new DigitalFormDocumentModel(DataAccess,
+            () => new Filter<DigitalFormDocument>(x => x.EntityLink.ID).IsEqualTo(FormID),
+            () => $"{nameof(DigitalFormDocument)}.{FormID}");
+    }
+
+    protected override async Task<TimeSpan> OnRefresh()
+    {
+        await Model.RefreshAsync(false);
+
+        Variables = Model.Variables.Where(x => x.FormID == FormID).Select(x => x.Entity).ToArray();
+        DigitalFormLayout = Model.Layouts.First().Entity;
+
+        if(DataAccess.Status == ConnectionStatus.Connected)
+        {
+            Parent = Client.Query(
+                new Filter<TParent>(x => x.ID).IsEqualTo(Parent.ID),
+                DFUtils.EntityColumns<TParent>(Variables))
+                .ToObjects<TParent>()
+                .First();
+            Form = Client.Query(
+                new Filter<TForm>(x => x.ID).IsEqualTo(InstanceID),
+                Columns.None<TForm>()
+                    .Add(
+                        x => x.ID,
+                        x => x.Number,
+                        x => x.FormData,
+                        x => x.Form.ID,
+                        x => x.Form.Description,
+                        x => x.Form.DescriptionExpression,
+                        x => x.FormCompleted,
+                        x => x.FormCompletedBy.ID,
+                        x => x.Created,
+                        x => x.FormOpen,
+                        x => x.BlobData))
+                .ToObjects<TForm>()
+                .First();
+            CacheManager.SaveBinary<DigitalFormCacheModel<TParent, TParentLink, TForm>>(CacheFileName, new()
+            {
+                Parent = Parent,
+                Form = Form
+            }, DateTime.MaxValue);
+        }
+        else
+        {
+            if(CacheManager.TryLoadBinary<DigitalFormCacheModel<TParent, TParentLink, TForm>>(CacheFileName, out var model, out var _))
+            {
+                Parent = model.Parent;
+                Form = model.Form;
+            }
+        }
+
+        Layout.LoadLayout(DigitalFormLayout.Layout);
+        Layout.LoadVariables(Variables);
+
+        NewForm = Form.FormData.IsNullOrWhiteSpace();
+        ReadOnly = Form.FormCompleted != DateTime.MinValue;
+
+        ProgressVisible = false;
+        return TimeSpan.Zero;
+    }
+
+    private async Task<bool> SaveForm()
+    {
+        throw new NotImplementedException();
     }
 
-    /// <summary>
-    /// Edit a form.
-    /// </summary>
-    /// <typeparam name="TParent"></typeparam>
-    /// <typeparam name="TParentLink"></typeparam>
-    /// <typeparam name="TForm"></typeparam>
-    /// <param name="shell"></param>
-    /// <param name="parent"></param>
-    /// <param name="model">A model to refresh on saving, or <see langword="null"/>.</param>
-    public static void EditForm<TParent, TParentLink, TForm>(IDigitalFormInstanceShell shell, TParent parent, ICoreRepository? model = null)
-        where TParent : Entity, IRemotable, IPersistent, new()
-        where TParentLink : EntityLink<TParent>, new()
-        where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance<TParentLink>, new()
+    public static void EditForm(TModel model, TShell shell, TParent parent)
     {
-        Navigation.Navigate<DigitalFormsHostViewModel>(x =>
+        Navigation.Navigate<DigitalFormsHostViewModel<TModel, TShell, TParent, TParentLink, TForm>>(x =>
         {
             x.Configure(parent, shell.FormID, shell.ID);
             x.OnSaved += () =>
             {
-                model?.RefreshAsync(true).ContinueWith(task =>
+                model.RefreshAsync(true).ContinueWith(task =>
                 {
                     if(task.Exception is not null)
                     {

+ 0 - 3
PRS.Avalonia/PRS.Avalonia/Components/FormsList/FormsList.axaml.cs

@@ -72,9 +72,6 @@ public class FormsListSearchEventArgs(IDigitalFormInstanceShell shell)
 }
 public delegate bool FormsListSearchEvent(object sender, FormsListSearchEventArgs args);
 
-// TODO: RefreshRequested, and thus RefreshData; FilterShell
-// FormTapped
-
 public partial class FormsList : UserControl
 {
     public static readonly StyledProperty<bool> SeparateHistoryProperty =

+ 25 - 22
PRS.Avalonia/PRS.Avalonia/Images/Images.cs

@@ -23,39 +23,42 @@ public static class Images
 
         return result;
     }
+
+    // Note: this list is alphabeticised.
     
-    public static SvgImage? menu => LoadSVG("/Images/menu.svg");
-    public static SvgImage? tick => LoadSVG("/Images/tick.svg");
-    public static SvgImage? lines => LoadSVG("/Images/lines.svg");
-    public static SvgImage? schedule => LoadSVG("/Images/schedule.svg");
+    public static SvgImage? badge => LoadSVG("/Images/badge.svg");
+    public static SvgImage? barcode => LoadSVG("/Images/barcode.svg");
+    public static SvgImage? books => LoadSVG("/Images/books.svg");
+    public static SvgImage? camera => LoadSVG("/Images/camera.svg");
+    public static SvgImage? clock => LoadSVG("/Images/clock.svg");
+    public static SvgImage? construction => LoadSVG("/Images/construction.svg");
     public static SvgImage? delivery => LoadSVG("/Images/delivery.svg");
-    public static SvgImage? drill => LoadSVG("/Images/drill.svg");
     public static SvgImage? digitalform => LoadSVG("/Images/digitalform.svg");
-    public static SvgImage? clock => LoadSVG("/Images/clock.svg");
+    public static SvgImage? drawing => LoadSVG("/Images/drawing.svg");
+    public static SvgImage? drill => LoadSVG("/Images/drill.svg");
     public static SvgImage? factory => LoadSVG("/Images/factory.svg");
+    public static SvgImage? holiday => LoadSVG("/Images/holiday.svg");
+    public static SvgImage? key => LoadSVG("/Images/key.svg");
+    public static SvgImage? lines => LoadSVG("/Images/lines.svg");
+    public static SvgImage? map => LoadSVG("/Images/map.svg");
     public static SvgImage? meeting => LoadSVG("/Images/meeting.svg");
-    public static SvgImage? person => LoadSVG("/Images/person.svg");
+    public static SvgImage? menu => LoadSVG("/Images/menu.svg");
     public static SvgImage? notification => LoadSVG("/Images/notification.svg");
-    public static SvgImage? stock => LoadSVG("/Images/stock.svg");
+    public static SvgImage? person => LoadSVG("/Images/person.svg");
+    public static SvgImage? plus => LoadSVG("/Images/plus.svg");
+    public static SvgImage? refresh => LoadSVG("/Images/refresh.svg");
+    public static SvgImage? save => LoadSVG("/Images/save.svg");
+    public static SvgImage? schedule => LoadSVG("/Images/schedule.svg");
     public static SvgImage? shoppingcart => LoadSVG("/Images/shoppingcart.svg");
-    public static SvgImage? camera => LoadSVG("/Images/camera.svg");
-    public static SvgImage? construction => LoadSVG("/Images/construction.svg");
-    public static SvgImage? task => LoadSVG("/Images/task.svg");
-    public static SvgImage? warehouse => LoadSVG("/Images/warehouse.svg");
-    public static SvgImage? version => LoadSVG("/Images/version.svg");
-    public static SvgImage? barcode => LoadSVG("/Images/barcode.svg");
-    public static SvgImage? map => LoadSVG("/Images/map.svg");
-    public static SvgImage? key => LoadSVG("/Images/key.svg");
-    public static SvgImage? badge => LoadSVG("/Images/badge.svg");
-    public static SvgImage? holiday => LoadSVG("/Images/holiday.svg");
+    public static SvgImage? stock => LoadSVG("/Images/stock.svg");
     public static SvgImage? stock_receive => LoadSVG("/Images/stock_receive.svg");
     public static SvgImage? stock_transfer => LoadSVG("/Images/stock_transfer.svg");
     public static SvgImage? stock_relocate => LoadSVG("/Images/stock_relocate.svg");
     public static SvgImage? stock_stocktake => LoadSVG("/Images/stock_stocktake.svg");
+    public static SvgImage? task => LoadSVG("/Images/task.svg");
+    public static SvgImage? tick => LoadSVG("/Images/tick.svg");
     public static SvgImage? trolley => LoadSVG("/Images/trolley.svg");
-    public static SvgImage? drawing => LoadSVG("/Images/drawing.svg");
-    public static SvgImage? books => LoadSVG("/Images/books.svg");
-    public static SvgImage? refresh => LoadSVG("/Images/refresh.svg");
-    public static SvgImage? plus => LoadSVG("/Images/plus.svg");
+    public static SvgImage? version => LoadSVG("/Images/version.svg");
+    public static SvgImage? warehouse => LoadSVG("/Images/warehouse.svg");
 
 }

+ 1 - 1
PRS.Avalonia/PRS.Avalonia/Modules/Site/SiteItps/SiteITPFormsViewModel.cs

@@ -61,7 +61,7 @@ public partial class SiteITPFormsViewModel : ModuleViewModel
     {
         if (ITP is not JobITPShell itp) return;
 
-        DigitalFormsHostViewModel.EditForm<JobITP, JobITPLink, JobITPForm>(shell, itp.Entity);
+        DigitalFormsHostViewModel<JobITPFormModel, JobITPFormShell, JobITP, JobITPLink, JobITPForm>.EditForm(Forms, shell, itp.Entity);
     }
 
     public bool Search(JobITPFormShell shell)

+ 2 - 2
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalForm/DigitalFormLayoutShell.cs

@@ -9,13 +9,13 @@ public class DigitalFormLayoutShell : Shell<DigitalFormModel, DigitalFormLayout>
 {
     public string Layout => Get<string>();
 
-    private Guid _formid => Get<Guid>();
+    public Guid FormID => Get<Guid>();
     public DigitalFormShell Form => Parent.Items.FirstOrDefault(x => Equals(x.ID, ID));
 
     protected override void ConfigureColumns(ShellColumns<DigitalFormModel, DigitalFormLayout> columns)
     {
         columns
             .Map(nameof(Layout), x => x.Layout)
-            .Map(nameof(_formid), x => x.Form.ID);
+            .Map(nameof(FormID), x => x.Form.ID);
     }
 }

+ 27 - 4
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalForm/DigitalFormModel.cs

@@ -6,18 +6,20 @@ using InABox.Core;
 
 namespace PRS.Avalonia;
 
-public class DigitalFormModel : CoreRepository<DigitalFormModel, DigitalFormShell, EmployeeDigitalForm>
+public class DigitalFormModel : CoreRepository<DigitalFormModel, DigitalFormShell, DigitalForm>
 {
-    public DigitalFormModel(IModelHost host, Func<Filter<EmployeeDigitalForm>> filter) : base(host, filter)
+    public DigitalFormModel(IModelHost host, Func<Filter<DigitalForm>> filter, Func<string>? filename = null) : base(host, filter, filename)
     {
     }
 
-    public CoreObservableCollection<DigitalFormLayoutShell> Layouts { get; private set; }
+    public CoreObservableCollection<DigitalFormLayoutShell> Layouts { get; private set; } = null!;
+    public CoreObservableCollection<DigitalFormVariableShell> Variables { get; private set; } = null!;
 
     protected override void Initialize()
     {
         base.Initialize();
         Layouts = new CoreObservableCollection<DigitalFormLayoutShell>();
+        Variables = new CoreObservableCollection<DigitalFormVariableShell>();
         //_documents = new CoreObservableCollection<IDocumentShell>();
     }
 
@@ -31,11 +33,14 @@ public class DigitalFormModel : CoreRepository<DigitalFormModel, DigitalFormShel
     {
         base.BeforeLoad(query);
         query.Add(
-            new Filter<DigitalFormLayout>(x => x.Form.ID).InQuery(EffectiveFilter(), x => x.Form.ID)
+            new Filter<DigitalFormLayout>(x => x.Form.ID).InQuery(EffectiveFilter(), x => x.ID)
                 .And(x => x.Type).IsEqualTo(DFLayoutType.Mobile)
                 .And(x => x.Active).IsEqualTo(true),
             GetColumns<DigitalFormLayoutShell, DigitalFormLayout>()
         );
+        query.Add(
+            new Filter<DigitalFormVariable>(x => x.Form.ID).InQuery(EffectiveFilter(), x => x.ID),
+            GetColumns<DigitalFormVariableShell, DigitalFormVariable>());
         //query.Add(
         //    new Filter<DigitalFormDocument>(x => x.EntityLink.ID).InQuery(EffectiveFilter(), x => x.Form.ID),
         //    GetColumns<DigitalFormDocumentShell,DigitalFormDocument>()
@@ -52,6 +57,9 @@ public class DigitalFormModel : CoreRepository<DigitalFormModel, DigitalFormShel
                 .Select(x => new DigitalFormLayoutShell { Row = x, Parent = this })
                 .ToArray()
         );
+        Variables.ReplaceRange(
+            query.Get<DigitalFormVariable>()
+            .Rows.ToArray(x => new DigitalFormVariableShell { Row = x, Parent = this }));
 
         //_documents.ReplaceRange(
         //    query.Get<DigitalFormDocument>()
@@ -60,4 +68,19 @@ public class DigitalFormModel : CoreRepository<DigitalFormModel, DigitalFormShel
         //        .ToArray()
         //);
     }
+
+    public static DigitalFormModel CreateModel(IModelHost host, string appliesTo)
+    {
+        return new DigitalFormModel(host,
+            () => new Filter<DigitalForm>(x => x.AppliesTo).IsEqualTo(appliesTo),
+            () => $"{nameof(DigitalForm)}.{appliesTo}");
+    }
+    public static DigitalFormModel CreateModel<TForm, TParent, TParentLink>(IModelHost host)
+        where TForm : IDigitalFormInstance<TParentLink>
+        where TParentLink : IEntityLink<TParent>
+    {
+        return CreateModel(host, typeof(TParent).Name);
+    }
+
+
 }

+ 8 - 7
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalForm/DigitalFormShell.cs

@@ -1,23 +1,24 @@
 using Comal.Classes;
 using InABox.Avalonia;
+using InABox.Core;
 
 namespace PRS.Avalonia;
 
-public class DigitalFormShell : Shell<DigitalFormModel, EmployeeDigitalForm>
+public class DigitalFormShell : Shell<DigitalFormModel, DigitalForm>
 {
     public string Code => Get<string>();
     public string Description => Get<string>();
     public string AppliesTo => Get<string>();
     public string Group => Get<string>();
 
-    protected override void ConfigureColumns(ShellColumns<DigitalFormModel, EmployeeDigitalForm> columns)
+    protected override void ConfigureColumns(ShellColumns<DigitalFormModel, DigitalForm> columns)
     {
         columns
-            .Map(nameof(ID), x => x.Form.ID)
-            .Map(nameof(Code), x => x.Form.Code)
-            .Map(nameof(Description), x => x.Form.Description)
-            .Map(nameof(AppliesTo), x => x.Form.AppliesTo)
-            .Map(nameof(Group), x => x.Form.Group.Description);
+            .Map(nameof(ID), x => x.ID)
+            .Map(nameof(Code), x => x.Code)
+            .Map(nameof(Description), x => x.Description)
+            .Map(nameof(AppliesTo), x => x.AppliesTo)
+            .Map(nameof(Group), x => x.Group.Description);
     }
 
     //public IDocumentShell[] Documents()

+ 25 - 0
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalForm/DigitalFormVariableShell.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Linq;
+using InABox.Avalonia;
+using InABox.Core;
+
+namespace PRS.Avalonia;
+
+public class DigitalFormVariableShell : Shell<DigitalFormModel, DigitalFormVariable>
+{
+    public string Code => Get<string>();
+    public string Parameters => Get<string>();
+    public string Description => Get<string>();
+    public string VariableType => Get<string>();
+    public Guid FormID => Get<Guid>();
+
+    protected override void ConfigureColumns(ShellColumns<DigitalFormModel, DigitalFormVariable> columns)
+    {
+        columns
+            .Map(nameof(Code), x => x.Code)
+            .Map(nameof(FormID), x => x.Form.ID)
+            .Map(nameof(Parameters), x => x.Parameters)
+            .Map(nameof(Description), x => x.Description)
+            .Map(nameof(VariableType), x => x.VariableType);
+    }
+}

+ 2 - 3
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalFormDocument/DigitalFormDocumentModel.cs

@@ -4,10 +4,9 @@ using InABox.Core;
 
 namespace PRS.Avalonia;
 
-public class
-    DigitalFormDocumentModel : CoreRepository<DigitalFormDocumentModel, DigitalFormDocumentShell, DigitalFormDocument>
+public class DigitalFormDocumentModel : CoreRepository<DigitalFormDocumentModel, DigitalFormDocumentShell, DigitalFormDocument>
 {
-    public DigitalFormDocumentModel(IModelHost host, Func<Filter<DigitalFormDocument>> filter) : base(host, filter)
+    public DigitalFormDocumentModel(IModelHost host, Func<Filter<DigitalFormDocument>> filter, Func<string>? filename = null) : base(host, filter, filename)
     {
     }
 }

+ 8 - 1
PRS.Avalonia/PRS.Avalonia/ViewLocator.cs

@@ -11,7 +11,14 @@ public class ViewLocator : IDataTemplate
         if (param is null)
             return null;
 
-        var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
+        var name = param.GetType().FullName;
+        var index = name.IndexOf('`'); // Allow for generic names.
+        if(index != -1)
+        {
+            name = name.Substring(0, index);
+        }
+        name = name.Replace("ViewModel", "View", StringComparison.Ordinal);
+
         var type = Type.GetType(name);
 
         if (type != null) return (Control)Activator.CreateInstance(type)!;