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
}
}