|
|
@@ -1,11 +1,16 @@
|
|
|
using Avalonia;
|
|
|
using Avalonia.Controls;
|
|
|
+using Avalonia.Layout;
|
|
|
using Avalonia.Markup.Xaml;
|
|
|
using InABox.Core;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Drawing;
|
|
|
+using System.Linq;
|
|
|
|
|
|
namespace PRS.Avalonia.DigitalForms;
|
|
|
|
|
|
-public partial class DigitalFormViewer : UserControl
|
|
|
+public partial class DigitalFormViewer : UserControl, IDFRenderer
|
|
|
{
|
|
|
public static readonly StyledProperty<IDigitalFormInstance> FormProperty =
|
|
|
AvaloniaProperty.Register<DigitalFormViewer, IDigitalFormInstance>(nameof(Form));
|
|
|
@@ -13,15 +18,464 @@ public partial class DigitalFormViewer : UserControl
|
|
|
AvaloniaProperty.Register<DigitalFormViewer, Entity>(nameof(Entity));
|
|
|
public static readonly StyledProperty<DFLayout> LayoutProperty =
|
|
|
AvaloniaProperty.Register<DigitalFormViewer, DFLayout>(nameof(Layout));
|
|
|
+ public static readonly StyledProperty<bool> ReadOnlyProperty =
|
|
|
+ AvaloniaProperty.Register<DigitalFormViewer, bool>(nameof(ReadOnly));
|
|
|
|
|
|
- public IDigitalFormInstance Form { get; set; }
|
|
|
+ public IDigitalFormInstance Form
|
|
|
+ {
|
|
|
+ get => GetValue(FormProperty);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Entity Entity
|
|
|
+ {
|
|
|
+ get => GetValue(EntityProperty);
|
|
|
+ }
|
|
|
+
|
|
|
+ public DFLayout Layout
|
|
|
+ {
|
|
|
+ get => GetValue(LayoutProperty);
|
|
|
+ }
|
|
|
|
|
|
- public Entity Entity { get; set; }
|
|
|
+ public bool ReadOnly { get; set; }
|
|
|
+
|
|
|
+ private DFLoadStorage RetainedData { get; set; }
|
|
|
+
|
|
|
+ private bool _changing;
|
|
|
+ private bool _isChanged;
|
|
|
+
|
|
|
+ private readonly Dictionary<DFLayoutControl, DigitalFormControl> _elementMap = new Dictionary<DFLayoutControl, DigitalFormControl>();
|
|
|
+
|
|
|
+ static DigitalFormViewer()
|
|
|
+ {
|
|
|
+ LayoutProperty.Changed.AddClassHandler<DigitalFormViewer>(Layout_Changed);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void Layout_Changed(DigitalFormViewer viewer, AvaloniaPropertyChangedEventArgs args)
|
|
|
+ {
|
|
|
+ if (viewer.Layout is null) return;
|
|
|
|
|
|
- public DFLayout Layout { get; set; }
|
|
|
+ viewer.Layout.Renderer = viewer;
|
|
|
+ viewer.Initialise();
|
|
|
+ }
|
|
|
|
|
|
public DigitalFormViewer()
|
|
|
{
|
|
|
InitializeComponent();
|
|
|
}
|
|
|
+
|
|
|
+ #region Utilities
|
|
|
+
|
|
|
+ private static VerticalAlignment GetVerticalAlignment(DFLayoutAlignment alignment)
|
|
|
+ {
|
|
|
+ return alignment == DFLayoutAlignment.Start
|
|
|
+ ? VerticalAlignment.Top
|
|
|
+ : alignment == DFLayoutAlignment.End
|
|
|
+ ? VerticalAlignment.Bottom
|
|
|
+ : alignment == DFLayoutAlignment.Middle
|
|
|
+ ? VerticalAlignment.Center
|
|
|
+ : VerticalAlignment.Stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static HorizontalAlignment GetHorizontalAlignment(DFLayoutAlignment alignment)
|
|
|
+ {
|
|
|
+ return alignment == DFLayoutAlignment.Start
|
|
|
+ ? HorizontalAlignment.Left
|
|
|
+ : alignment == DFLayoutAlignment.End
|
|
|
+ ? HorizontalAlignment.Right
|
|
|
+ : alignment == DFLayoutAlignment.Middle
|
|
|
+ ? HorizontalAlignment.Center
|
|
|
+ : HorizontalAlignment.Stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static GridLength StringToGridLength(string length)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(length) || string.Equals(length.ToUpper(), "AUTO"))
|
|
|
+ return new GridLength(1, GridUnitType.Auto);
|
|
|
+
|
|
|
+ if (!double.TryParse(length.Replace("*", ""), out var value))
|
|
|
+ value = 1.0F;
|
|
|
+ var type = length.Contains('*') ? GridUnitType.Star : GridUnitType.Pixel;
|
|
|
+ return new GridLength(value, type);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Rows
|
|
|
+
|
|
|
+ internal void CollapseRows(DFHeaderControl header, bool collapsed)
|
|
|
+ {
|
|
|
+ var startRow = Grid.GetRow(header) + Grid.GetRowSpan(header);
|
|
|
+
|
|
|
+ var nextRow = _elementMap
|
|
|
+ .Where(x => x.Value is DFHeaderControl headerControl && headerControl != header)
|
|
|
+ .Select(x => Grid.GetRow(x.Value))
|
|
|
+ .Where(x => x >= startRow).DefaultIfEmpty(-1).Min();
|
|
|
+
|
|
|
+ if (nextRow == -1)
|
|
|
+ {
|
|
|
+ nextRow = Grid.RowDefinitions.Count;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int row = startRow; row < nextRow; ++row)
|
|
|
+ {
|
|
|
+ var rowDefinition = Grid.RowDefinitions[row];
|
|
|
+
|
|
|
+ if (collapsed)
|
|
|
+ {
|
|
|
+ rowDefinition.Height = new GridLength(0);
|
|
|
+ rowDefinition.MinHeight = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ rowDefinition.Height = StringToGridLength(Layout.RowHeights[row]);
|
|
|
+ rowDefinition.MinHeight = 50;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach(var (item, element) in _elementMap)
|
|
|
+ {
|
|
|
+ if(startRow <= item.Row - 1 && item.Row - 1 < nextRow)
|
|
|
+ {
|
|
|
+ element.IsVisible = !collapsed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ private void DoChanged(string fieldName)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Initialise()
|
|
|
+ {
|
|
|
+ Render();
|
|
|
+ Layout.EvaluateExpressions();
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Render
|
|
|
+
|
|
|
+ private static void SetDimensions(Control element, int row, int column, int rowspan, int colspan)
|
|
|
+ {
|
|
|
+ if (column <= 0) return;
|
|
|
+ if (row <= 0) return;
|
|
|
+
|
|
|
+ element.MinHeight = 50.0F;
|
|
|
+ element.MinWidth = 50.0F;
|
|
|
+ Grid.SetRow(element, row - 1);
|
|
|
+ Grid.SetColumn(element, column - 1);
|
|
|
+ Grid.SetRowSpan(element, rowspan);
|
|
|
+ Grid.SetColumnSpan(element, colspan);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Dictionary<Type, Type>? _fieldControls;
|
|
|
+ private DigitalFormControl? CreateFieldControl(DFLayoutField field)
|
|
|
+ {
|
|
|
+ if (_fieldControls == null)
|
|
|
+ {
|
|
|
+ _fieldControls = new();
|
|
|
+ foreach (var controlType in CoreUtils.Entities.Where(x => x.IsClass && !x.IsGenericType && x.HasInterface<IDigitalFormField>()))
|
|
|
+ {
|
|
|
+ var superDefinition = controlType.GetSuperclassDefinition(typeof(DigitalFormFieldControl<,,,>));
|
|
|
+ if (superDefinition != null)
|
|
|
+ {
|
|
|
+ _fieldControls[superDefinition.GenericTypeArguments[0]] = controlType;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var fieldControlType = _fieldControls.GetValueOrDefault(field.GetType());
|
|
|
+ if (fieldControlType is not null)
|
|
|
+ {
|
|
|
+ var element = (Activator.CreateInstance(fieldControlType) as DigitalFormControl)!;
|
|
|
+
|
|
|
+ if(element is IDigitalFormField fieldControl)
|
|
|
+ {
|
|
|
+ fieldControl.FieldChangedEvent += () =>
|
|
|
+ {
|
|
|
+ ChangeField(field.Name);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return element;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private DigitalFormControl? CreateControl(DFLayoutControl item)
|
|
|
+ {
|
|
|
+ if (item is DFLayoutField field)
|
|
|
+ {
|
|
|
+ return CreateFieldControl(field);
|
|
|
+ }
|
|
|
+ else if (item is DFLayoutElement)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ else if (item is DFLayoutLabel)
|
|
|
+ {
|
|
|
+ return new DFLabelControl();
|
|
|
+ }
|
|
|
+ else if (item is DFLayoutHeader)
|
|
|
+ {
|
|
|
+ return new DFHeaderControl();
|
|
|
+ }
|
|
|
+ else if (item is DFLayoutImage)
|
|
|
+ {
|
|
|
+ return new DFImageControl();
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private DigitalFormControl? CreateElement(DFLayoutControl item)
|
|
|
+ {
|
|
|
+ var control = CreateControl(item);
|
|
|
+ if(control != null)
|
|
|
+ {
|
|
|
+ control.FormViewer = this;
|
|
|
+ control.SetControl(item);
|
|
|
+ }
|
|
|
+ return control;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void RenderElement(DFLayoutControl item)
|
|
|
+ {
|
|
|
+ var element = CreateElement(item);
|
|
|
+ if(element is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ SetDimensions(element, item.Row, item.Column, item.RowSpan, item.ColumnSpan);
|
|
|
+ Grid.Children.Add(element);
|
|
|
+
|
|
|
+ if (element != null)
|
|
|
+ {
|
|
|
+ if(item is DFLayoutField)
|
|
|
+ {
|
|
|
+ element.IsEnabled = element.IsEnabled && !ReadOnly;
|
|
|
+ }
|
|
|
+ _elementMap[item] = element;
|
|
|
+
|
|
|
+ element.HorizontalAlignment = GetHorizontalAlignment(item.HorizontalAlignment);
|
|
|
+ element.VerticalAlignment = GetVerticalAlignment(item.VerticalAlignment);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Render()
|
|
|
+ {
|
|
|
+ _elementMap.Clear();
|
|
|
+
|
|
|
+ Grid.Children.Clear();
|
|
|
+ Grid.RowDefinitions.Clear();
|
|
|
+ Grid.ColumnDefinitions.Clear();
|
|
|
+
|
|
|
+ foreach (var column in Layout.ColumnWidths)
|
|
|
+ Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = StringToGridLength(column) });
|
|
|
+
|
|
|
+ foreach (var row in Layout.RowHeights)
|
|
|
+ Grid.RowDefinitions.Add(new RowDefinition { Height = StringToGridLength(row), MinHeight = 50 });
|
|
|
+
|
|
|
+ foreach (var item in Layout.GetElements(false))
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if(item.Row > 0 && item.Column > 0)
|
|
|
+ {
|
|
|
+ RenderElement(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ AfterRender();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AfterRender()
|
|
|
+ {
|
|
|
+ foreach (var header in _elementMap.Values.OfType<DFHeaderControl>())
|
|
|
+ {
|
|
|
+ if (header.Collapsed)
|
|
|
+ {
|
|
|
+ CollapseRows(header, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Fields
|
|
|
+
|
|
|
+ public void ChangeField(string fieldName)
|
|
|
+ {
|
|
|
+ if (!_changing)
|
|
|
+ {
|
|
|
+ Layout.ChangeField(fieldName);
|
|
|
+ _isChanged = true;
|
|
|
+ DoChanged(fieldName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private IDigitalFormField? GetFieldControl(DFLayoutField field)
|
|
|
+ {
|
|
|
+ return _elementMap.GetValueOrDefault(field) as IDigitalFormField;
|
|
|
+ }
|
|
|
+
|
|
|
+ private DFLayoutField? GetField(string fieldName)
|
|
|
+ {
|
|
|
+ return Layout.Elements.OfType<DFLayoutField>().FirstOrDefault(x => fieldName == x.Name);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SetFieldValue(string fieldName, object? value)
|
|
|
+ {
|
|
|
+ var field = GetField(fieldName);
|
|
|
+ if (field is null) return;
|
|
|
+
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ if(fieldControl is null) return;
|
|
|
+
|
|
|
+ var property = field.GetPropertyValue<string>("Property");
|
|
|
+ if (Entity != null && !property.IsNullOrWhiteSpace())
|
|
|
+ fieldControl.SetValue(CoreUtils.GetPropertyValue(Entity, property));
|
|
|
+ else
|
|
|
+ fieldControl.SetValue(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void GetFieldValue(DFLayoutField field, DFSaveStorageEntry storage, DFSaveStorageEntry? retained, out object? entityValue)
|
|
|
+ {
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ if(fieldControl != null)
|
|
|
+ {
|
|
|
+ entityValue = fieldControl.GetEntityValue();
|
|
|
+ fieldControl.Serialize(storage);
|
|
|
+
|
|
|
+ if(retained is not null)
|
|
|
+ {
|
|
|
+ fieldControl.Serialize(retained);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ entityValue = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public object? GetFieldValue(string fieldName)
|
|
|
+ {
|
|
|
+ var field = GetField(fieldName);
|
|
|
+ if (field is null)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ return fieldControl?.GetValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ public object? GetFieldData(string fieldName, string dataField)
|
|
|
+ {
|
|
|
+ var field = GetField(fieldName);
|
|
|
+ if (field is null)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ return fieldControl?.GetData(dataField);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SetFieldColour(string fieldName, Color? colour)
|
|
|
+ {
|
|
|
+ var field = GetField(fieldName);
|
|
|
+ if (field is null) return;
|
|
|
+
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ if (fieldControl is null) return;
|
|
|
+
|
|
|
+ fieldControl.SetColour(colour);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void LoadValues(DFLoadStorage data)
|
|
|
+ {
|
|
|
+ _changing = true;
|
|
|
+
|
|
|
+ foreach(var field in Layout.Elements.OfType<DFLayoutField>())
|
|
|
+ {
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ if(fieldControl is null)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ var property = field.GetPropertyValue<string>("Property");
|
|
|
+ if (Entity != null && !property.IsNullOrWhiteSpace())
|
|
|
+ {
|
|
|
+ fieldControl.SetValue(CoreUtils.GetPropertyValue(Entity, property));
|
|
|
+ }
|
|
|
+ else if (RetainedData.HasValue(field.Name))
|
|
|
+ {
|
|
|
+ fieldControl.Deserialize(RetainedData.GetEntry(field.Name));
|
|
|
+ }
|
|
|
+ else if (data.HasValue(field.Name))
|
|
|
+ {
|
|
|
+ fieldControl.Deserialize(data.GetEntry(field.Name));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ fieldControl.SetValue(field.GetProperties().GetDefaultValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Layout.EvaluateExpressions();
|
|
|
+ _changing = false;
|
|
|
+ _isChanged = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Takes values from editors and saves them to a dictionary; must be called after <see cref="Initialize"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>A dictionary of <see cref="DigitalFormVariable.Code"/> -> value.</returns>
|
|
|
+ public DFSaveStorage SaveValues()
|
|
|
+ {
|
|
|
+ var result = new DFSaveStorage();
|
|
|
+ var retained = new DFSaveStorage();
|
|
|
+ foreach (var formElement in Layout.Elements)
|
|
|
+ if (formElement is DFLayoutField field)
|
|
|
+ {
|
|
|
+ GetFieldValue(field, result.GetEntry(field.Name), field.GetProperties().Retain ? retained.GetEntry(field.Name) : null, out var entityValue);
|
|
|
+
|
|
|
+ if(Entity != null)
|
|
|
+ {
|
|
|
+ var property = field.GetPropertyValue<string>("Property");
|
|
|
+ if (!string.IsNullOrWhiteSpace(property))
|
|
|
+ CoreUtils.SetPropertyValue(Entity, property, entityValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ RetainedData = retained.ToLoadStorage();
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Validate(out List<string> messages)
|
|
|
+ {
|
|
|
+ messages = new List<string>();
|
|
|
+ var valid = true;
|
|
|
+ foreach(var formElement in Layout.Elements)
|
|
|
+ {
|
|
|
+ if(formElement is DFLayoutField field)
|
|
|
+ {
|
|
|
+ var fieldControl = GetFieldControl(field);
|
|
|
+ if(fieldControl != null && !fieldControl.Validate(out var message))
|
|
|
+ {
|
|
|
+ messages.Add(message);
|
|
|
+ valid = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return valid;
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
}
|