using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Design; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Effects; using System.Windows.Media.Imaging; using InABox.Clients; using InABox.Core; using InABox.DynamicGrid.Properties; using InABox.WPF; using Microsoft.Win32; using Syncfusion.Windows.Shared; using Brush = System.Windows.Media.Brush; using Color = System.Drawing.Color; using Image = System.Windows.Controls.Image; using Point = System.Windows.Point; namespace InABox.DynamicGrid { public class DynamicFormCreateElementArgs : EventArgs { public DynamicFormCreateElementArgs(DFLayoutElement element) { Element = element; } public DFLayoutElement Element { get; } } public delegate FrameworkElement DynamicFormCreateElementDelegate(object sender, DynamicFormCreateElementArgs e); public delegate void DynamicFormAfterDesignDelegate(object sender); public delegate void DynamicFormAfterRenderDelegate(DynamicFormDesignGrid sender); public delegate void DynamicFormOnChangedDelegate(DynamicFormDesignGrid sender, string fieldName); public enum FormMode { /// /// The form is read-only. /// ReadOnly, /// /// Standard option for when the form is being filled in by the associated employee. /// Filling, /// /// Standard option for when the form is being previewed - that is, it is acting with placeholder values that will not get saved. /// It is still editable. /// Preview, /// /// Once the form has been completed, or the person editing is not the employee the form applies to, then they are put in Editing Mode.
/// This is only possible if is enabled. ///
Editing, /// /// The form layout is being designed /// Designing } public class DynamicFormDesignGrid : Grid, IDFRenderer { //private bool _designing; public bool GridInitialized { get; private set; } = false; //private bool _readonly; private readonly SolidColorBrush BackgroundBrush = new(Colors.WhiteSmoke); private readonly SolidColorBrush BorderBrush = new(Colors.Silver); private readonly Dictionary elementgrids = new(); private readonly Dictionary elementmap = new(); private readonly Dictionary borderMap = new(); private readonly SolidColorBrush FieldBrush = new(Colors.LightYellow); private readonly SolidColorBrush RequiredFieldBrush = new(Colors.Orange); #region Backing Properties private readonly List _elements = new(); private bool _showBorders = true; private IDigitalFormDataModel? _datamodel; private DFLayout form = new(); private FormMode _mode; private IList _variables = new List(); private bool _isChanged; private bool _changing = false; #endregion #region Public Properties public bool ShowBorders { get => _showBorders || _mode == FormMode.Designing; set { _showBorders = value; CheckRefresh(); } } public IDigitalFormDataModel? DataModel { get => _datamodel; set { _datamodel = value; if(_datamodel != null) { var task = Task.Run(() => { var waiting = true; _datamodel.Load(m => { waiting = false; }); while (waiting) ; }); task.Wait(); } CheckRefresh(); } } public DFLayout Form { get => form; set { form = value; form.Renderer = this; CheckRefresh(); } } public FormMode Mode { get => _mode; set { if (_mode != value) { var oldMode = _mode; if (_mode == FormMode.Designing) { OnAfterDesign?.Invoke(this); } _mode = value; CheckRefresh(oldMode != FormMode.Designing); } } } public bool IsDesigning { get => _mode == FormMode.Designing; } public bool IsReadOnly { get => _mode == FormMode.ReadOnly; } public bool IsEditing { get => _mode == FormMode.Editing || _mode == FormMode.Filling; } public bool IsChanged => _isChanged; public IList Variables { get => _variables; set { _variables = value; CheckRefresh(); } } #endregion #region Events public event DynamicFormAfterDesignDelegate? OnAfterDesign; public event DynamicFormCreateElementDelegate? OnCreateElement; public event DynamicFormAfterRenderDelegate? OnAfterRender; public event DynamicFormOnChangedDelegate? OnChanged; #endregion #region Rows class RowData { public string RowHeight { get; set; } public RowData(string rowHeight) { RowHeight = rowHeight; } } internal void CollapseRows(FormHeader header, bool collapsed) { var startRow = this.GetRow(header) + this.GetRowSpan(header); var nextRow = elementmap .Where(x => x.Value is DFHeaderControl headerControl && headerControl.Header != header) .Select(x => this.GetRow(x.Value)) .Where(x => x >= startRow).DefaultIfEmpty(-1).Min(); if (nextRow == -1) { nextRow = RowDefinitions.Count; } for (int row = startRow; row < nextRow; ++row) { var rowDefinition = RowDefinitions[row]; if(rowDefinition.Tag is not RowData rowData) { throw new Exception("Row definition tag is not RowData"); } if (collapsed) { rowDefinition.Height = new GridLength(0); } else { rowDefinition.Height = StringToGridLength(rowData.RowHeight); } } } #endregion #region Elements class DynamicFormElement { public string Caption { get; set; } public Type ElementType { get; set; } public string Category { get; set; } public FrameworkElement? Element { get; set; } public bool AllowDuplicate { get; set; } public DynamicFormElement(string caption, Type elementType, string category, FrameworkElement? element, bool allowDuplicate) { Caption = caption; ElementType = elementType; Category = category; Element = element; AllowDuplicate = allowDuplicate; } } public void AddElement(string caption, string category, bool allowduplicate = false) where TElement : DFLayoutElement { AddElement(typeof(TElement), caption, category, allowduplicate); } public void AddElement(Type TElement, string caption, string category, bool allowduplicate = false) { _elements.Add(new DynamicFormElement(caption, TElement, category, null, allowduplicate)); } internal FrameworkElement? CreateElement(DFLayoutElement element) { var elementType = element.GetType(); if(_elements.Any(x => x.ElementType == elementType)) { return OnCreateElement?.Invoke(this, new DynamicFormCreateElementArgs(element)); } return null; } #endregion #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); } private static string GridLengthToString(GridLength length) { if (length.IsStar) return length.Value == 1.0F ? "*" : string.Format("{0}*", length.Value); if (length.IsAuto) return "Auto"; return length.Value.ToString(); } private static string FormatGridLength(GridLength length, IEnumerable others) { if (length.IsStar) { double total = 0.0F; foreach (var other in others.Where(x => x.IsStar)) total += other.Value; return string.Format("{0:F0}%", total != 0 ? length.Value * 100.0F / total : 0); } if (length.IsAuto) return "Auto"; return string.Format("{0}px", length.Value); } private static MenuItem CreateMenuItem(string caption, TTag tag, Action? click) { var result = new MenuItem { Header = caption, Tag = tag }; if (click != null) result.Click += (o, e) => click((TTag)(o as MenuItem)!.Tag); return result; } private static void AddClick(ButtonBase button, TTag tag, Action click) { button.Tag = tag; button.Click += (o, e) => click((TTag)(o as FrameworkElement)!.Tag); } #endregion #region Design Mode #region Rows private void ShiftDown(int row) { foreach (var element in form.Elements) if (element.Row > row) element.Row++; else if (element.Row + element.RowSpan - 1 > row) element.RowSpan++; } private void AddRowBeforeClick(int row) { var length = new GridLength(1.0F, GridUnitType.Auto); var result = GridLengthToString(length); if (form.RowHeights.Count == 0) form.RowHeights.Add(result); else form.RowHeights.Insert(row, result); ShiftDown(row); Render(); } private void AddRowAfterClick(int row) { var length = new GridLength(1.0F, GridUnitType.Auto); var result = GridLengthToString(length); if (row == form.RowHeights.Count - 1) form.RowHeights.Add(result); else form.RowHeights.Insert(row + 1, result); ShiftDown(row + 1); Render(); } private void SplitRow(int row) { foreach (var element in form.Elements) if (element.Row > row) element.Row++; else if (element.Row == row) element.RowSpan++; else if (element.Row + element.RowSpan - 1 >= row) element.RowSpan++; } private void SplitRowClick(int row) { var result = form.RowHeights[row]; if (row == form.RowHeights.Count - 1) form.RowHeights.Add(result); else form.RowHeights.Insert(row + 1, result); SplitRow(row + 1); Render(); } private void RowPropertiesClick(int row) { var length = StringToGridLength(form.RowHeights[row]); var editor = new DynamicFormDesignLength(length); if (editor.ShowDialog() == true) { form.RowHeights[row] = GridLengthToString(editor.GridLength); Render(); } } private void ShiftUp(int row) { var deletes = new List(); foreach (var element in form.Elements) if (element.Row > row) element.Row--; else if (element.Row == row && element.RowSpan <= 1) deletes.Add(element); else if (element.Row + element.RowSpan - 1 >= row) element.RowSpan--; foreach (var delete in deletes) form.Elements.Remove(delete); } private void DeleteRowClick(int row) { form.RowHeights.RemoveAt(row); ShiftUp(row + 1); Render(); } private ContextMenu CreateRowMenu(Button button, int row) { var result = new ContextMenu(); result.Items.Add(CreateMenuItem("Add Row Before", row, AddRowBeforeClick)); result.Items.Add(CreateMenuItem("Add Row After", row, AddRowAfterClick)); result.Items.Add(new Separator()); result.Items.Add(CreateMenuItem("Split Row", row, SplitRowClick)); var propertiesSeparator = new Separator(); var propertiesMenu = CreateMenuItem("Row Properties", row, RowPropertiesClick); result.Items.Add(propertiesSeparator); result.Items.Add(propertiesMenu); result.Items.Add(new Separator()); result.Items.Add(CreateMenuItem("Delete Row", row, DeleteRowClick)); result.Opened += (o, e) => { propertiesSeparator.Visibility = form.RowHeights.Count > 1 ? Visibility.Visible : Visibility.Collapsed; propertiesMenu.Visibility = form.RowHeights.Count > 1 ? Visibility.Visible : Visibility.Collapsed; }; button.SetValue(ContextMenuProperty, result); return result; } #endregion #region Columns private void ShiftRight(int column) { foreach (var element in form.Elements) if (element.Column > column) element.Column++; else if (element.Column + element.ColumnSpan - 1 > column) element.ColumnSpan++; } private void AddColumnBeforeClick(int column) { var length = new GridLength(1.0F, form.ColumnWidths.Count == 0 ? GridUnitType.Star : GridUnitType.Auto); var result = GridLengthToString(length); if (form.ColumnWidths.Count == 0) form.ColumnWidths.Add(result); else form.ColumnWidths.Insert(column, result); ShiftRight(column); Render(); } private void AddColumnAfterClick(int column) { var length = new GridLength(1.0F, form.ColumnWidths.Count == 0 ? GridUnitType.Star : GridUnitType.Auto); var result = GridLengthToString(length); if (column == form.ColumnWidths.Count - 1) form.ColumnWidths.Add(result); else form.ColumnWidths.Insert(column + 1, result); ShiftRight(column + 1); Render(); } private void SplitColumn(int column) { foreach (var element in form.Elements) if (element.Column > column) element.Column++; else if (element.Column == column) element.ColumnSpan++; else if (element.Column + element.ColumnSpan - 1 >= column) element.ColumnSpan++; } private void SplitColumnClick(int column) { var result = form.ColumnWidths[column]; if (column == form.ColumnWidths.Count - 1) form.ColumnWidths.Add(result); else form.ColumnWidths.Insert(column + 1, result); SplitColumn(column + 1); Render(); } private void ColumnPropertiesClick(int column) { var length = StringToGridLength(form.ColumnWidths[column]); var editor = new DynamicFormDesignLength(length); if (editor.ShowDialog() == true) { form.ColumnWidths[column] = GridLengthToString(editor.GridLength); Render(); } } private void ShiftLeft(int column) { var deletes = new List(); foreach (var element in form.Elements) if (element.Column > column) element.Column--; else if (element.Column == column && element.ColumnSpan <= 1) deletes.Add(element); else if (element.Column + element.ColumnSpan - 1 >= column) element.ColumnSpan--; foreach (var delete in deletes) form.Elements.Remove(delete); } private void DeleteColumnClick(int column) { form.ColumnWidths.RemoveAt(column); ShiftLeft(column + 1); Render(); } private ContextMenu CreateColumnMenu(Button button, int column) { var result = new ContextMenu(); result.Items.Add(CreateMenuItem("Add Column Before", column, AddColumnBeforeClick)); result.Items.Add(CreateMenuItem("Add Column After", column, AddColumnAfterClick)); result.Items.Add(new Separator()); result.Items.Add(CreateMenuItem("Split Column", column, SplitColumnClick)); var propertiesSeparator = new Separator(); var propertiesMenu = CreateMenuItem("Column Properties", column, ColumnPropertiesClick); result.Items.Add(propertiesSeparator); result.Items.Add(propertiesMenu); result.Items.Add(new Separator()); result.Items.Add(CreateMenuItem("Delete Column", column, DeleteColumnClick)); result.Opened += (o, e) => { propertiesSeparator.Visibility = form.ColumnWidths.Count > 1 ? Visibility.Visible : Visibility.Collapsed; propertiesMenu.Visibility = form.ColumnWidths.Count > 1 ? Visibility.Visible : Visibility.Collapsed; }; button.SetValue(ContextMenuProperty, result); return result; } #endregion #region Add Element private void AddNewElement(int row, int column) where TElement : DFLayoutElement, new() { var element = new TElement(); element.Row = row; element.Column = column; var result = new FormControlGrid().EditItems(new[] { element }); if (result) { form.Elements.Add(element); Render(); } } private void AddElementClick(Tuple tuple) { var method = typeof(DynamicFormDesignGrid) .GetMethod(nameof(AddNewElement), BindingFlags.NonPublic | BindingFlags.Instance)! .MakeGenericMethod(tuple.Item1); method.Invoke(this, new object[] { tuple.Item3, tuple.Item2 }); } private void CreateLabelClick(Point point) { var label = new DFLayoutLabel { Row = (int)point.Y, Column = (int)point.X }; var result = new FormControlGrid().EditItems(new[] { label }); if (result) { form.Elements.Add(label); Render(); } } private void CreateHeaderClick(Point point) { var header = new DFLayoutHeader { Row = (int)point.Y, Column = (int)point.X }; var result = new FormControlGrid().EditItems(new[] { header }); if (result) { form.Elements.Add(header); Render(); } } private void CreateImageClick(Point point) { var image = new DFLayoutImage { Row = (int)point.Y, Column = (int)point.X }; var result = new FormControlGrid().EditItems(new[] { image }); if (result) { form.Elements.Add(image); Render(); } } private void AddVariableClick(Tuple tuple) { if(Activator.CreateInstance(tuple.Item1.FieldType()) is not DFLayoutField field) { MessageBox.Show(string.Format("{0}: Unknown Type {1}", tuple.Item1.Code, tuple.Item1.VariableType.ToString())); return; } field.Name = tuple.Item1.Code; field.Column = tuple.Item2; field.Row = tuple.Item3; form.Elements.Add(field); form.LoadVariable(tuple.Item1, field); Render(); } private ContextMenu CreateEmptyCellMenu(Border border, int row, int column) { var result = new ContextMenu(); result.Opened += (o, e) => { result.Items.Clear(); result.Items.Add(CreateMenuItem("Add Label", new Point(column, row), CreateLabelClick)); result.Items.Add(CreateMenuItem("Add Header", new Point(column, row), CreateHeaderClick)); result.Items.Add(CreateMenuItem("Add Image", new Point(column, row), CreateImageClick)); var fields = CreateMenuItem("Add Field", new Point(column, row), null); var filtered = _variables.Where(x => !form.Elements.Any(v => string.Equals((v as DFLayoutField)?.Name, x.Code))); foreach (var variable in filtered) fields.Items.Add(CreateMenuItem(variable.Code, new Tuple(variable, column, row), AddVariableClick)); if (fields.Items.Count > 0) result.Items.Add(fields); var elements = CreateMenuItem("Add Object", new Point(column, row), null); var available = _elements.Where(x => x.AllowDuplicate || !form.Elements.Any(v => (v as DFLayoutElement)?.GetType() == x.ElementType)).ToArray(); var cats = available.Select(x => x.Category).Distinct().OrderBy(x => x); foreach (var cat in cats) { var menu = elements; if (!string.IsNullOrWhiteSpace(cat)) { menu = new MenuItem { Header = cat }; elements.Items.Add(menu); } foreach (var element in available.Where(x => string.Equals(x.Category, cat))) menu.Items.Add(CreateMenuItem(element.Caption, new Tuple(element.ElementType, column, row), AddElementClick)); } if (elements.Items.Count > 0) result.Items.Add(elements); }; border.SetValue(ContextMenuProperty, result); return result; } #endregion #region Element Context Menu private void DeleteElementClick(DFLayoutControl control) { if (form.Elements.Contains(control)) { form.Elements.Remove(control); Render(); } } private void ElementPropertiesClick(DFLayoutControl control) { if(!elementgrids.TryGetValue(control.GetType(), out var grid)) { var type = typeof(FormControlGrid<>).MakeGenericType(control.GetType()); grid = (Activator.CreateInstance(type) as IDynamicGrid)!; elementgrids[control.GetType()] = grid; } if (grid.EditItems(new[] { control })) Render(); } private ContextMenu CreateElementContextMenu(FrameworkElement element, DFLayoutControl control) { var result = new ContextMenu(); result.Items.Add(CreateMenuItem("Edit Properties", control, ElementPropertiesClick)); result.Items.Add(new Separator()); result.Items.Add(CreateMenuItem("Delete Item", control, DeleteElementClick)); element.SetValue(ContextMenuProperty, result); return result; } #endregion #endregion #region Render private static void SetDimensions(FrameworkElement element, int row, int column, int rowspan, int colspan, int offset) { if (column <= 0) return; if (row <= 0) return; element.MinHeight = 50.0F; element.MinWidth = 50.0F; element.SetGridPosition(row - offset, column - offset, rowspan, colspan); } private Border CreateBorder(int row, int column, int rowspan, int colspan, int offset, bool horzstar, bool vertstar) { var border = new Border(); if (ShowBorders) { border.BorderThickness = new Thickness( 0.0F,//!IsDesigning && column == 1 - offset ? 0.00F /*0.75F*/ : 0.0F, 0.0F,//!IsDesigning && row == 1 - offset ? 0.00F /*0.75F*/ : 0.0F, column - offset == ColumnDefinitions.Count - 1 ? horzstar ? 0.00F : 0.75F : 0.75F, row - offset == RowDefinitions.Count - 1 ? vertstar ? 0.00F : 0.75F : 0.75F ); border.BorderBrush = BorderBrush; } border.Background = BackgroundBrush; SetDimensions(border, row, column, rowspan, colspan, offset); return border; } private static BitmapImage HeaderClosed = Properties.Resources.rightarrow.AsBitmapImage(32, 32); private static BitmapImage HeaderOpen = Properties.Resources.downarrow.AsBitmapImage(32, 32); private FrameworkElement CreateHeader(DFLayoutHeader header) { var formHeader = new FormHeader { Collapsed = header.Collapsed, HeaderText = header.Header }; formHeader.CollapsedChanged += (o, c) => { CollapseRows(formHeader, c); }; if (IsDesigning) { formHeader.IsEnabled = false; } return formHeader; } // DFLayoutField -> IDynamicFormFieldControl private static Dictionary? _fieldControls; private DynamicFormControl? CreateFieldControl(DFLayoutField field) { if (_fieldControls == null) { _fieldControls = new(); foreach (var controlType in CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.IsAssignableTo(typeof(IDynamicFormFieldControl)))) { var superDefinition = controlType.GetSuperclassDefinition(typeof(DynamicFormFieldControl<,,>)); 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 DynamicFormControl)!; if(element is IDynamicFormFieldControl fieldControl) { fieldControl.FieldChangedEvent += () => { ChangeField(field.Name); }; } return element; } return null; } private DynamicFormControl? CreateFieldPlaceholder(DFLayoutField field) { var controlType = typeof(DFFieldPlaceholderControl<>).MakeGenericType(field.GetType()); return Activator.CreateInstance(controlType) as DynamicFormControl; } private DynamicFormControl? CreateControl(DFLayoutControl item) { if (item is DFLayoutField field) { if (IsDesigning) { return CreateFieldPlaceholder(field); } return CreateFieldControl(field); } else if (item is DFLayoutElement) { return IsDesigning ? new DFElementPlaceholderControl() : new DFElementControl(); } 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 FrameworkElement? CreateElement(DFLayoutControl item) { var control = CreateControl(item); if(control != null) { control.FormDesignGrid = this; control.SetControl(item); } return control; } private Brush GetItemBackgroundBrush(DFLayoutControl item, Color? colour = null) { if(colour != null) { var colourValue = colour.Value; return new SolidColorBrush(new System.Windows.Media.Color { R = colourValue.R, G = colourValue.G, B = colourValue.B, A = colourValue.A }); } if (item is DFLayoutField field) return field.GetPropertyValue("Required") ? RequiredFieldBrush : FieldBrush; else return IsDesigning ? FieldBrush : BackgroundBrush; } private void RenderElement(DFLayoutControl item, int offset, bool horzstar, bool vertstar) { var border = CreateBorder(item.Row, item.Column, item.RowSpan, item.ColumnSpan, offset, horzstar, vertstar); border.Background = GetItemBackgroundBrush(item); if (!ShowBorders) border.Padding = new Thickness( item.Column == 1 ? 4 : 2, item.Row == 1 ? 4 : 2, item.Column + item.ColumnSpan - 1 == ColumnDefinitions.Count ? 4 : 2, item.Row + item.RowSpan - 1 == RowDefinitions.Count ? 4 : 2 ); else border.Padding = new Thickness(5); var element = CreateElement(item); border.Child = element; Children.Add(border); if (IsDesigning) { CreateElementContextMenu(border, item); } borderMap[item] = border; if (element != null) { if(item is DFLayoutField) { element.IsEnabled = element.IsEnabled && !IsReadOnly; } elementmap[item] = element; element.HorizontalAlignment = GetHorizontalAlignment(item.HorizontalAlignment); element.VerticalAlignment = GetVerticalAlignment(item.VerticalAlignment); if (IsDesigning) { CreateElementContextMenu(element, item); } } } private void AfterRender() { if (!IsDesigning) { foreach (var header in elementmap.Where(x => x.Value is DFHeaderControl head).Select(x => x.Value).Cast()) { if (header.Header.Collapsed) { CollapseRows(header.Header, true); } } } OnAfterRender?.Invoke(this); } private void Render() { foreach (var item in elementmap.Keys) { var e = elementmap[item]; if (VisualTreeHelper.GetParent(e) is Border parent) parent.Child = null; } elementmap.Clear(); Children.Clear(); ColumnDefinitions.Clear(); RowDefinitions.Clear(); if (form == null) return; foreach (var column in form.ColumnWidths) ColumnDefinitions.Add(new ColumnDefinition { Width = StringToGridLength(column) }); foreach (var row in form.RowHeights) RowDefinitions.Add(new RowDefinition { Height = StringToGridLength(row), Tag = new RowData(row) }); var horzstar = ColumnDefinitions.Any(x => x.Width.IsStar); var vertstar = RowDefinitions.Any(x => x.Height.IsStar); var offset = 1; if (IsDesigning) { ColumnDefinitions.Insert(0, new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); RowDefinitions.Insert(0, new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }); var topleft = new Button(); topleft.SetValue(RowProperty, 0); topleft.SetValue(ColumnProperty, 0); topleft.Content = ""; // new Image() { Source = _designing ? Properties.Resources.view.AsBitmapImage() : Properties.Resources.design.AsBitmapImage() }; topleft.BorderBrush = BorderBrush; topleft.BorderThickness = new Thickness( 0.00F, 0.00F, 0.75F, 0.75F ); topleft.Background = BackgroundBrush; //topleft.Click += (o, e) => //{ // Designing = !Designing; //}; Children.Add(topleft); for (var i = 1; i < ColumnDefinitions.Count; i++) { var Button = new Button(); Button.SetValue(RowProperty, 0); Button.SetValue(ColumnProperty, i); Button.Content = FormatGridLength(ColumnDefinitions[i].Width, ColumnDefinitions.Select(x => x.Width)); Button.BorderBrush = BorderBrush; Button.BorderThickness = new Thickness( 0.00F, //(i == 1) ? 0.75F : 0.0F, 0.00F, //0.75F, i == ColumnDefinitions.Count - 1 ? horzstar ? 0.00F : 0.75F : 0.75F, 0.75F ); Button.Background = BackgroundBrush; Button.Padding = new Thickness(10); Button.MinHeight = 35; CreateColumnMenu(Button, i - 1); AddClick(Button, i - 1, ColumnPropertiesClick); Children.Add(Button); } for (var i = 1; i < RowDefinitions.Count; i++) { var Button = new Button(); Button.SetValue(RowProperty, i); Button.SetValue(ColumnProperty, 0); Button.Content = FormatGridLength(RowDefinitions[i].Height, RowDefinitions.Select(x => x.Height)); Button.BorderBrush = BorderBrush; Button.BorderThickness = new Thickness( 0.00F, //0.75F, 0.00F, //(i == 1) ? 0.75F : 0.0F, 0.75F, i == RowDefinitions.Count - 1 ? vertstar ? 0.00F : 0.75F : 0.75F ); Button.Background = BackgroundBrush; Button.Padding = new Thickness(10); CreateRowMenu(Button, i - 1); AddClick(Button, i - 1, RowPropertiesClick); Children.Add(Button); } offset = 0; } var filledcells = new List(); foreach (var item in form.Elements) { try { if(item.Row > 0 && item.Column > 0) { RenderElement(item, offset, horzstar, vertstar); for (var c = item.Column; c < item.Column + item.ColumnSpan; c++) for (var r = item.Row; r < item.Row + item.RowSpan; r++) filledcells.Add(new Point(c, r)); } } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } for (var c = 1; c <= form.ColumnWidths.Count; c++) for (var r = 1; r <= form.RowHeights.Count; r++) if (!filledcells.Any(x => x.X.Equals(c) && x.Y.Equals(r))) { var border = CreateBorder(r, c, 1, 1, offset, horzstar, vertstar); if (IsDesigning) { CreateEmptyCellMenu(border, r, c); } Children.Add(border); } AfterRender(); } #endregion /// /// Renders the form; after this is called, any changes to properties triggers a refresh. /// /// /// This should be called before or /// public void Initialize() { GridInitialized = true; CheckRefresh(false); } private void CheckRefresh(bool keepValues = true) { if (!GridInitialized) return; if (_mode == FormMode.Designing) { Render(); } else if(keepValues) { var values = SaveValues(); Render(); LoadValues(values); } else { Render(); Form.EvaluateExpressions(); } } #region Fields private void ChangeField(string fieldName) { if (!_changing) { form.ChangeField(fieldName); _isChanged = true; OnChanged?.Invoke(this, fieldName); } } private IDynamicFormFieldControl? GetFieldControl(DFLayoutField field) { return elementmap.GetValueOrDefault(field) as IDynamicFormFieldControl; } public void SetFieldValue(string fieldName, object? value) { var field = form.Elements.FirstOrDefault(x => string.Equals(fieldName, (x as DFLayoutField)?.Name)) as DFLayoutField; if (field != null) { value = field.ParseValue(value); var fieldControl = GetFieldControl(field); if (fieldControl != null) { string? property = null; if(DataModel != null) { property = field.GetPropertyValue("Property"); if (!string.IsNullOrWhiteSpace(property)) { value = DataModel.GetEntityValue(property); } } if (string.IsNullOrWhiteSpace(property)) { fieldControl.SetValue(value); } else { fieldControl.SetEntityValue(value); } } } } private object? GetFieldValue(DFLayoutField field, out object? entityValue, out Dictionary? additionalValues) { var fieldControl = GetFieldControl(field); if(fieldControl != null) { entityValue = fieldControl.GetEntityValue(); additionalValues = fieldControl.GetAdditionalValues(); return fieldControl.GetValue(); } entityValue = null; additionalValues = null; return null; } public object? GetFieldValue(string fieldName) { var field = form.Elements.Where(x => (x as DFLayoutField)?.Name == fieldName).FirstOrDefault() as DFLayoutField; if (field is null) { return null; } return GetFieldValue(field, out var entityValue, out var additionalValues); } private object? GetFieldData(DFLayoutField field, string dataField) { var fieldControl = GetFieldControl(field); if (fieldControl is not null) { return fieldControl.GetData(dataField); } return null; } public object? GetFieldData(string fieldName, string dataField) { var field = form.Elements.Where(x => (x as DFLayoutField)?.Name == fieldName).FirstOrDefault() as DFLayoutField; if (field is null) { return null; } return GetFieldData(field, dataField); } public void SetFieldColour(string fieldName, Color? colour) { var field = form.Elements.Where(x => (x as DFLayoutField)?.Name == fieldName).FirstOrDefault() as DFLayoutField; if (field is null || !borderMap.TryGetValue(field, out var border)) { return; } border.Background = GetItemBackgroundBrush(field, colour); } /// /// Load values into editor; Must be called after . /// /// /// Resets . /// /// A dictionary of -> value. public void LoadValues(Dictionary values) { _changing = true; foreach (var (name, value) in values) { SetFieldValue(name, value); } Form.EvaluateExpressions(); _changing = false; _isChanged = false; } /// /// Takes values from editors and saves them to a dictionary; must be called after . /// /// A dictionary of -> value. public Dictionary SaveValues() { var result = new Dictionary(); foreach (var formElement in form.Elements) if (formElement is DFLayoutField field) { var value = GetFieldValue(field, out var entityValue, out var additionalValues); if (value != null) result[field.Name] = value; if(additionalValues is not null) { foreach(var (fieldName, fieldValue) in additionalValues) { if(fieldValue is not null) { result[$"{field.Name}.{fieldName}"] = fieldValue; } } } if(DataModel != null) { var property = field.GetPropertyValue("Property"); if (!string.IsNullOrWhiteSpace(property)) DataModel.SetEntityValue(property, entityValue); } } return result; } public bool Validate(out List messages) { messages = new List(); var valid = true; foreach(var formElement in form.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 } }