using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using InABox.Core;
using InABox.DynamicGrid.Properties;
using InABox.Wpf;
using InABox.WPF;
using Syncfusion.Windows.Shared;
using Syncfusion.Windows.Tools.Controls;
namespace InABox.DynamicGrid
{
public delegate BaseEditor? DefineEditorEventHandler(object item, DynamicGridColumn column);
public delegate void DynamicGridSaveEvent(object sender, CancelEventArgs args);
public interface IDynamicEditorForm
{
}
public class UtilityItem
{
public string Name { get; set; }
public ImageSource Icon { get; set; }
public string Text { get; set; }
public SizeMode Mode { get; set; }
public ICommand Command { get; set; }
}
public class UtilityViewModel
{
///
/// Constructor of the UtilityViewModel class.
///
public UtilityViewModel()
{
var utilities = new ObservableCollection();
utilities.Add(new UtilityItem
{ Name = "Help", Icon = Resources.help.AsBitmapImage(), Text = "", Mode = SizeMode.Normal, Command = HelpCommand });
Utilities = utilities;
}
///
/// Collection containing the complete details of the items to be bound in the title bar.
///
public ObservableCollection Utilities { get; }
///
/// Commmand for the Help button.
///
public DelegateCommand HelpCommand => new(HelpCommandAction);
public static string Slug { get; set; }
///
/// Action that is performed when clicking the help button.
///
private void HelpCommandAction(object param)
{
Process.Start("https://prs-software.com.au/wiki/index.php/" + Slug);
}
}
///
/// Interaction logic for DynamicEditor.xaml
///
public partial class DynamicEditorForm : ThemableChromelessWindow, IDynamicEditorForm
{
public delegate Document? FindDocumentEvent(string FileName);
public delegate Document? GetDocumentEvent(Guid id);
public delegate void SaveDocumentEvent(Document document);
private BaseObject[] _items;
public DynamicEditorGrid Editor;
public DynamicEditorForm(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null,
Func? PageDataHandler = null, bool PreloadPages = false)
{
ReadOnly = false;
InitializeComponent();
//this.Loaded += new RoutedEventHandler(ConfigureSystemMenu);
var grid = Content as Grid;
Editor = grid.Children.OfType().FirstOrDefault();
Editor.UnderlyingType = type;
Editor.OnCustomiseColumns += Editor_OnCustomiseColumns;
Editor.OnDefineFilter += (sender, t) => OnDefineFilter?.Invoke(sender, t);
Editor.OnEditorCreated += Editor_OnEditorCreated;
Editor.OnLoadPage += page => { page.Load(Items.First(), PageDataHandler); };
Editor.OnSelectPage += (tab, items) => { OnSelectPage?.Invoke(tab, items); };
Editor.PreloadPages = PreloadPages;
Editor.OnUnloadPage += (page, saved) =>
{
if (!saved)
page.BeforeSave(Items.First());
else
page.AfterSave(Items.First());
};
//Editor.OnGetPropertyInfo += (o, c) => { return CoreUtils.GetProperty(_item.GetType(), c); };
Editor.ReconfigureEditors += g => { ReconfigureEditors?.Invoke(g); };
Editor.OnGetEditor += c =>
{
if (_items != null && _items.Any())
{
var property = DatabaseSchema.Property(type, c.ColumnName);
if (property == null) return new NullEditor();
if (property.Editor is NullEditor)
return property.Editor;
BaseEditor editor;
if (property is CustomProperty)
{
editor = property.Editor.CloneEditor();
}
else
{
editor = OnDefineEditor?.Invoke(_items[0], c) ?? c.Editor.CloneEditor();
var propEditor = property.Editor;
editor.Page = propEditor.Page;
editor.Caption = propEditor.Caption;
}
//defaultEditor.EditorSequence
//EditorUtils.GetPropertyEditor(type, property, defaultEditor);
/*BaseEditor editor = new NullEditor();
var caption = "";
var page = "";
try
{
var comps = c.ColumnName.Split('.');
for (var i = 0; i < comps.Length; i++)
{
var column = string.Join(".", comps.Take(i + 1));
var prop = CoreUtils.GetProperty(type, column);
if (column.Equals(c.ColumnName))
{
if (OnDefineEditor != null)
editor = OnDefineEditor(_items[0], c);
else
editor = c.Editor != null ? c.Editor : new NullEditor();
}
else
{
var pedit = prop.GetEditor();
if (pedit is NullEditor)
return pedit;
}
editor = editor == null ? new NullEditor() : editor.Clone() as BaseEditor;
var capattr = prop.GetCustomAttribute();
var subcap = capattr != null ? capattr.Text : comps[i];
var path = capattr != null ? capattr.IncludePath : true;
if (!string.IsNullOrWhiteSpace(subcap))
caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);
if (string.IsNullOrWhiteSpace(page))
{
var pageattr = prop.GetCustomAttribute();
if (pageattr != null)
page = pageattr.Page;
}
}
editor.Caption = caption;
editor.Page = page;
}
catch (Exception e)
{
var dmprop = DatabaseSchema.Property(_items[0].GetType(), c.ColumnName);
if (dmprop is CustomProperty)
{
editor = dmprop.Editor.Clone() as BaseEditor;
editor.Caption = dmprop.Caption;
editor.Page = string.IsNullOrWhiteSpace(dmprop.Page) ? "Custom Fields" : dmprop.Page;
}
}*/
if (ReadOnly && editor.Editable.Equals(Editable.Enabled))
editor.Editable = Editable.Disabled;
return editor;
}
return new NullEditor();
};
Editor.OnGridCustomiseEditor += (sender, column, editor) => OnFormCustomiseEditor?.Invoke(this, Items, column, editor);
Editor.OnGetSequence += c => CoreUtils.GetPropertySequence(_items.First().GetType(), c.ColumnName);
Editor.OnGetPropertyValue += (o, c) =>
{
if (!_items.Any())
return null;
object? result;
try
{
result = CoreUtils.GetPropertyValue(_items.First(), c);
}
catch
{
result = _items.First().UserProperties.ContainsKey(c) ? _items.First().UserProperties[c] : null;
}
if (result == null)
return null;
foreach (var _item in _items)
{
object? curvalue;
try
{
curvalue = CoreUtils.GetPropertyValue(_item, c);
}
catch
{
curvalue = _item.UserProperties.ContainsKey(c) ? _item.UserProperties[c] : null;
}
if (curvalue == null)
return null;
if (!curvalue.Equals(result))
return null;
}
return result;
};
Editor.OnSetPropertyValue += (o, c, v) =>
{
foreach (var _item in _items)
if (_item.UserProperties.ContainsKey(c))
_item.UserProperties[c] = v;
else
CoreUtils.SetPropertyValue(_item, c, v);
};
Editor.OnEditorValueChanged += EditorValueChanged;
Editor.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
Editor.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
Editor.OnGetDocument += id => { return OnGetDocument?.Invoke(id); };
Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };
Pages = pages;
if (Pages == null || Pages.Count == 0)
Editor.Margin = new Thickness(5, 5, 5, 0);
if (buttons != null)
foreach (var button in buttons)
{
var btn = new Button();
UpdateButton(btn, button.Image, button.Name);
btn.Tag = button;
btn.Margin = new Thickness(5, 5, 0, 5);
btn.Padding = new Thickness(5, 0, 5, 0);
btn.Click += Btn_Click;
Buttons.Children.Add(btn);
button.Button = btn;
button.Form = this;
}
}
public DynamicEditorPages? Pages { get; }
public BaseObject[] Items
{
get => _items;
set
{
_items = value;
UtilityViewModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : "";
Editor.Load(_items.First().GetType().EntityName(), Pages);
}
}
public bool ReadOnly { get; set; }
public OnValidateData? OnValidateData;
public event OnCustomiseColumns? OnCustomiseColumns;
public event OnDefineFilter? OnDefineFilter;
public event DefineEditorEventHandler? OnDefineEditor;
public event OnFormCustomiseEditor? OnFormCustomiseEditor;
public event OnReconfigureEditors? ReconfigureEditors;
//public delegate void EditorValueChangedHandler(object sender, String name, object value, List changes);
//public event EditorValueChangedHandler OnEditorValueChanged;
public event EditorValueChangedHandler? OnEditorValueChanged;
//public event DefineFilter OnDefineFilter;
public event OnDefineLookup? OnDefineLookups;
public event OnLookupsDefined? OnLookupsDefined;
public event GetDocumentEvent? OnGetDocument;
public event FindDocumentEvent? OnFindDocument;
public event SaveDocumentEvent? OnSaveDocument;
public event OnSelectPage? OnSelectPage;
public event DynamicGridSaveEvent? OnSaveItem;
public void UnloadEditorPages(DynamicEditorPages pages, object item, bool saved)
{
Editor.UnloadPages(saved);
}
protected void UpdateButton(Button button, BitmapImage? image, string text)
{
var stackPnl = new StackPanel();
stackPnl.Orientation = Orientation.Horizontal;
//stackPnl.Margin = new Thickness(2);
if (image != null)
{
var img = new Image();
img.Source = image;
img.Margin = new Thickness(2);
stackPnl.Children.Add(img);
}
if (!string.IsNullOrEmpty(text))
{
var lbl = new Label();
lbl.Content = text;
lbl.VerticalAlignment = VerticalAlignment.Stretch;
lbl.VerticalContentAlignment = VerticalAlignment.Center;
lbl.Margin = new Thickness(2, 0, 5, 0);
stackPnl.Children.Add(lbl);
}
button.Content = stackPnl;
}
private Dictionary EditorValueChanged(object sender, string name, object value)
{
if (OnEditorValueChanged != null)
return OnEditorValueChanged(sender, name, value);
return DynamicGridUtils.UpdateEditorValue(_items, name, value);
}
private void Editor_OnEditorCreated(object sender, double height, double width)
{
var screen = WpfScreen.GetScreenFrom(new Point(Left, Top));
double spareheight = 90;
double sparewidth = 25;
var desiredheight = height + spareheight;
var desiredwidth = width + sparewidth;
if (Pages != null)
foreach (var page in Pages)
{
if (desiredheight < page.MinimumSize().Height)
desiredheight = page.MinimumSize().Height;
if (desiredwidth < page.MinimumSize().Width)
desiredwidth = page.MinimumSize().Width;
}
var maxheight = screen.WorkingArea.Height - 0;
Height = desiredheight > maxheight ? maxheight : desiredheight;
var maxwidth = screen.WorkingArea.Width - 0;
Width = desiredwidth > maxwidth ? maxwidth : desiredwidth;
Left = screen.DeviceBounds.Left + (screen.DeviceBounds.Width - Width) / 2.0F;
Top = screen.DeviceBounds.Top + (screen.DeviceBounds.Height - Height) / 2.0F;
Editor.VerticalAlignment = VerticalAlignment.Stretch;
Editor.HorizontalAlignment = HorizontalAlignment.Stretch;
var scaption = _items[0].GetType().GetCaption();
Title = "Edit " + scaption.SplitCamelCase();
if (Editor.IsCustomLayout)
Title = Title + "*";
OKButton.IsEnabled = !ReadOnly;
}
private DynamicGridColumns Editor_OnCustomiseColumns(object sender, DynamicGridColumns? source)
{
var columns = new DynamicGridColumns();
if (_items != null && _items.Any())
columns.ExtractColumns(_items.First().GetType(), "");
if (OnCustomiseColumns != null)
columns = OnCustomiseColumns.Invoke(this, columns);
return columns;
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var deb = (DynamicEditorButton)button.Tag;
deb.Click();
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
var errors = OnValidateData?.Invoke(this, Items);
if (errors != null && errors.Any())
{
MessageBox.Show(
string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
string.Join("\n- ", errors)), "Validation Error");
return;
}
// Don't Commit the changes here, because we want to refer back to thos changes when we save the item
// to trigger specific processes in the database
DialogResult = true;
//Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
// However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state
foreach (var item in _items)
item.CancelChanges();
DialogResult = false;
//Close();
}
public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
{
return Editor.TryFindEditor(columnname, out editor);
}
public IDynamicEditorControl? FindEditor(string columnname)
{
return Editor.FindEditor(columnname);
}
private void Window_Closing(object sender, CancelEventArgs e)
{
if (DialogResult == true)
OnSaveItem?.Invoke(this, e);
}
#region Win32 API Stuff
// Define the Win32 API methods we are going to use
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
/// Define our Constants we will use
public const int WM_SYSCOMMAND = 0x112;
public const int MF_SEPARATOR = 0x800;
public const int MF_BYPOSITION = 0x400;
public const int MF_STRING = 0x0;
public const int _EditLayoutID = 1000;
public const int _ResetLayoutID = 1001;
public IntPtr Handle => new WindowInteropHelper(this).Handle;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Check if a System Command has been executed
if (msg == WM_SYSCOMMAND)
// Execute the appropriate code for the System Menu item that was clicked
switch (wParam.ToInt32())
{
case _EditLayoutID:
Editor.EditLayout();
handled = true;
break;
case _ResetLayoutID:
if (MessageBox.Show(
"WARNING: This will delete any customisations you have made!\n\nAre you sure you wish to reset this layout?",
"Confirm Layout Reset", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
Editor.ResetLayout();
handled = true;
break;
}
return IntPtr.Zero;
}
private void ConfigureSystemMenu(object sender, RoutedEventArgs e)
{
/// Get the Handle for the Forms System Menu
var systemMenuHandle = GetSystemMenu(Handle, false);
/// Create our new System Menu items just before the Close menu item
InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _EditLayoutID, "Edit Layout");
InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _ResetLayoutID, "Reset Layout");
// Attach our WndProc handler to this Window
var source = HwndSource.FromHwnd(Handle);
source.AddHook(WndProc);
}
#endregion
//private void Wiki_Click(object sender, RoutedEventArgs e)
//{
// System.Diagnostics.Process.Start("https://prs-software.com.au/wiki/index.php/" + CurrentPanelSlug());
//}
//private string CurrentPanelSlug()
//{
// if ((Items != null) && Items.Any())
// return Items.First().GetType().EntityName().Split('.').Last();
// return "";
//}
}
}