Browse Source

Moved MasterDetailControl to InABox.Wpf

Kenric Nugteren 1 year ago
parent
commit
e2e7df1f1e

+ 17 - 0
inabox.wpf/MasterDetailPanel/IMasterDetailControl.cs

@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+using InABox.Core;
+
+namespace InABox.Wpf;
+
+public interface IMasterDetailControl<TMaster>
+{
+    TMaster? Master { get; set; }
+}
+
+public interface IMasterDetailControl<TMaster,TDetail> : IMasterDetailControl<TMaster>
+{
+    // If Master == null, then Master Detail is not active,
+    //      and we should return .All() or .None() depending on circumstances
+    // If Master != null, we need to filter by Master.ID (usually?)
+    Filter<TDetail> MasterDetailFilter { get; }
+}

+ 16 - 0
inabox.wpf/MasterDetailPanel/IMasterDetailPage.cs

@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace InABox.Wpf;
+
+public interface IMasterDetailPage<TMaster>
+{
+    DynamicTabItem Tab { get; set; }
+
+    Dictionary<string, object[]>? Selected();
+
+    IDataModelSource Refresh(TMaster? entity);
+    
+    TMaster? Master { get; set; }
+}

+ 11 - 0
inabox.wpf/MasterDetailPanel/IMasterDetailSettings.cs

@@ -0,0 +1,11 @@
+using System;
+using InABox.DynamicGrid;
+
+namespace InABox.Wpf;
+
+public interface IMasterDetailSettings
+{
+    public DynamicSplitPanelView ViewType { get; set; }
+    public double AnchorWidth { get; set; }
+    public Guid MasterID { get; set; }
+}

+ 49 - 0
inabox.wpf/MasterDetailPanel/MasterDetailGridPage.cs

@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace InABox.Wpf;
+
+public abstract class MasterDetailGridPage<TMaster, TGrid, TDetail> : MasterDetailPage<TMaster> 
+    where TGrid : DynamicGrid<TDetail>, IDataModelSource, IMasterDetailControl<TMaster,TDetail>, new()
+    where TDetail : BaseObject, new()
+{
+
+    public TGrid? Grid { get; set; }
+
+    public MasterDetailGridPage(DynamicTabItem tab) : base(tab)
+    {
+        
+    }
+
+    public override Dictionary<string, object[]>? Selected()
+    {
+        return Grid is not null
+            ? new Dictionary<string, object[]>
+            {
+                { typeof(TDetail).EntityName(), Grid.SelectedRows }
+            }
+            : null;
+    }
+
+    protected abstract void DoRefresh(TGrid grid, bool columns);
+
+    protected override IDataModelSource Refresh()
+    {
+        var bInitialised = false;
+        if (Grid == null)
+        {
+            Grid = new TGrid();
+            Tab.Content = Grid;
+        }
+        else
+        {
+            bInitialised = true;
+        }
+
+        Grid.Master = Master;
+        DoRefresh(Grid,!bInitialised);
+
+        return Grid;
+    }
+}

+ 28 - 0
inabox.wpf/MasterDetailPanel/MasterDetailPage.cs

@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace InABox.Wpf;
+
+public abstract class MasterDetailPage<TMaster> : IMasterDetailPage<TMaster>
+{
+
+    protected MasterDetailPage(DynamicTabItem tab)
+    {
+        Tab = tab;
+    }
+    
+    public DynamicTabItem Tab { get; set; }
+
+    public abstract Dictionary<string, object[]>? Selected();
+
+    public IDataModelSource Refresh(TMaster? master)
+    {
+        Master = master;
+        return Refresh();
+    }
+    
+    protected abstract IDataModelSource Refresh();
+    
+    public TMaster? Master { get; set; }
+}

+ 43 - 0
inabox.wpf/MasterDetailPanel/MasterDetailPanel.xaml

@@ -0,0 +1,43 @@
+<UserControl x:Class="InABox.Wpf.MasterDetailPanel"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:dynamicGrid="clr-namespace:InABox.DynamicGrid"
+             mc:Ignorable="d"
+             d:DesignHeight="300" d:DesignWidth="300" Background="WhiteSmoke">
+    
+    <dynamicGrid:DynamicSplitPanel 
+        x:Name="_splitPanel" 
+        View="Combined" 
+        AnchorWidth="300" 
+        MasterCaption="{Binding MasterCaption}"
+        DetailCaption="{Binding DetailCaption}"
+        OnChanged="SplitPanel_OnChanged">
+
+        <dynamicGrid:DynamicSplitPanel.Header>
+            <Border 
+                BorderBrush="Gray" 
+                BorderThickness="0.75" 
+                Background="WhiteSmoke" 
+                Height="25">
+                <Label 
+                    x:Name="_header" 
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+            </Border>
+        </dynamicGrid:DynamicSplitPanel.Header>
+
+        <dynamicGrid:DynamicSplitPanel.Detail>
+            <dynamicGrid:DynamicTabControl 
+                x:Name="_tabControl" 
+                Grid.Column="2" 
+                Grid.Row="0" 
+                Grid.RowSpan="2"
+                SelectionChanged="Pages_OnChanged" >
+                
+            </dynamicGrid:DynamicTabControl>
+        </dynamicGrid:DynamicSplitPanel.Detail>
+
+    </dynamicGrid:DynamicSplitPanel>
+</UserControl>

+ 238 - 0
inabox.wpf/MasterDetailPanel/MasterDetailPanel.xaml.cs

@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Threading;
+using InABox.Configuration;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace InABox.Wpf;
+
+public abstract class MasterDetailPanel<TMaster, TMasterGrid, TSettings> : MasterDetailPanel, IPanel<TMaster>
+    where TMaster : Entity, IRemotable, IPersistent, new()
+    where TMasterGrid : DynamicDataGrid<TMaster>, new()
+    where TSettings : BaseObject, IUserConfigurationSettings, IMasterDetailSettings, new()
+{
+    
+    private DispatcherTimer? timer;
+    private readonly List<IMasterDetailPage<TMaster>> _pages = new();
+    private DateTime _lastselection = DateTime.MaxValue;
+    private int _currentPage = -1;
+    private readonly TMasterGrid _masterGrid;
+    private IDataModelSource? _modelsource;
+
+    protected TSettings Settings { get; private set; } = new();
+
+    private void LoadSettings()
+    {
+        Settings = new UserConfiguration<TSettings>().Load();
+        _splitPanel.AnchorWidth = Settings.AnchorWidth;
+        _splitPanel.View = Settings.ViewType;
+        AfterLoadSettings(Settings);
+    }
+    
+    protected void SaveSettings()
+    {
+        BeforeSaveSettings(Settings);
+        Settings.AnchorWidth = _splitPanel.AnchorWidth;
+        Settings.ViewType = _splitPanel.View;
+        Settings.MasterID = _masterGrid.SelectedRows.FirstOrDefault()?.Get<TMaster, Guid>(x => x.ID) ?? Guid.Empty;
+        new UserConfiguration<TSettings>().Save(Settings);
+    }
+    
+    protected abstract string MasterCaption { get; }
+    protected abstract string DetailCaption { get; }
+    protected abstract void CreatePages();
+    protected abstract void AfterLoadSettings(TSettings settings);
+    protected abstract void BeforeSaveSettings(TSettings settings);
+    protected abstract string MasterColumnsTag { get; }
+    public abstract string SectionName { get; }
+    public abstract void CreateToolbarButtons(IPanelHost host);
+    
+    protected override void DoSplitPanelChanged()
+    {
+        SaveSettings();
+        var newTag = GetMasterColumnsTag();
+        if (!String.Equals(_masterGrid.ColumnsTag, newTag))
+        {
+            _masterGrid.ColumnsTag = newTag;
+            _masterGrid.Refresh(true, true);
+        }
+    }
+
+    private string GetMasterColumnsTag()
+    {
+        var newTag = Settings.ViewType == DynamicSplitPanelView.Master
+            ? $"{MasterColumnsTag}:Master"
+            : $"{MasterColumnsTag}:Combined";
+        return newTag;
+    }
+
+    protected MasterDetailPanel()
+    {
+        _masterGrid = new TMasterGrid();
+        _masterGrid.OnSelectItem += MasterGrid_OnSelectItem;
+        _masterGrid.BeforeRefresh += MasterGrid_BeforeRefresh;
+        _masterGrid.AfterRefresh += MasterGrid_AfterRefresh;
+    }
+
+    public void Setup()
+    {
+        LoadSettings();
+        
+        _masterGrid.ColumnsTag = GetMasterColumnsTag();
+        
+        _splitPanel.Master = _masterGrid;
+        _splitPanel.MasterCaption = MasterCaption;
+        _splitPanel.DetailCaption = DetailCaption;
+
+        _header.Content = MasterCaption;
+        
+        CreatePages();
+        
+        timer = new DispatcherTimer();
+        timer.Tick += Timer_Tick;
+        timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
+        
+    }
+    
+    protected void CreatePage<TPage>(Func<bool> isAllowed, string caption) 
+        where TPage : class, IMasterDetailPage<TMaster>
+    {
+        if (isAllowed())
+        {
+            var header = new DynamicTabItem() { Header = caption };
+            _tabControl.Items.Add(header);
+            if (Activator.CreateInstance(typeof(TPage), header) is TPage page)
+                _pages.Add(page);
+        }
+    }
+
+    protected void CreatePage<TPage>(string caption)
+        where TPage : class, IMasterDetailPage<TMaster>
+    {
+        CreatePage<TPage>(() => true, caption);
+    }
+
+    public void Shutdown(CancelEventArgs? cancel)
+    {
+        if (timer != null)
+            timer.IsEnabled = false;
+    }
+    
+    public void Refresh()
+    {
+        _masterGrid.Refresh(timer?.IsEnabled == false, true);
+        _lastselection = DateTime.MinValue;
+        if (timer != null)
+            timer.IsEnabled = true;
+    }
+    
+    public DataModel DataModel(Selection selection)
+    {
+        if (_modelsource == null)
+        {
+            var row = _masterGrid.SelectedRows.FirstOrDefault();
+            var filter = row != null
+                ? new Filter<TMaster>(x => x.ID).IsEqualTo(row.Get<TMaster, Guid>(x => x.ID))
+                : new Filter<TMaster>().None();
+            return new AutoDataModel<TMaster>(filter);
+        }
+
+        return _modelsource.DataModel(selection);
+    }
+
+    public event DataModelUpdateEvent? OnUpdateDataModel;
+    
+    public bool IsReady { get; set; }
+    
+    private IMasterDetailPage<TMaster>? SelectedPage => _pages.FirstOrDefault(x => x.Tab == _tabControl.SelectedTab);
+    
+    public Dictionary<string, object[]> Selected()
+    {
+        return SelectedPage != null
+           ? SelectedPage.Selected()
+           : new Dictionary<string, object[]> { { typeof(TMaster).EntityName(), _masterGrid.SelectedRows } };
+    }
+
+    public void Heartbeat(TimeSpan time)
+    {
+    }
+    
+    protected override void DoPagesChanged()
+    {
+        _lastselection = DateTime.MinValue;
+    }
+    
+    
+    private void Timer_Tick(object? sender, EventArgs e)
+    {
+        if (_lastselection < DateTime.Now.AddMilliseconds(-500) && !_masterGridRefreshing)
+        {
+            _lastselection = DateTime.MaxValue;
+            var master = _masterGrid.SelectedRows.FirstOrDefault()?.ToObject<TMaster>() ?? new TMaster();
+            var dataModelSource = SelectedPage?.Refresh(master);
+            if (_tabControl.SelectedIndex != _currentPage)
+            {
+                if(dataModelSource is not null)
+                {
+                    _modelsource = dataModelSource;
+                    OnUpdateDataModel?.Invoke(dataModelSource.SectionName, dataModelSource.DataModel(Selection.None));
+                }
+                _currentPage = _tabControl.SelectedIndex;
+            }
+        }
+    }
+    
+    private bool _masterGridRefreshing;
+
+    private void MasterGrid_BeforeRefresh(object sender, BeforeRefreshEventArgs args)
+    {
+        _masterGridRefreshing = true;
+    }
+
+    private void MasterGrid_AfterRefresh(object sender, AfterRefreshEventArgs args)
+    {
+
+        _masterGrid.SelectedRows = Settings.MasterID == Guid.Empty
+            ? Array.Empty<CoreRow>()
+            : _masterGrid.Data.Rows.Where(r => r.Get<TMaster, Guid>(c => c.ID) == Settings.MasterID).ToArray();
+        _masterGridRefreshing = false;
+    }
+
+    private void MasterGrid_OnSelectItem(object sender, DynamicGridSelectionEventArgs e)
+    {
+        if (!_masterGridRefreshing)
+        {
+            _lastselection = DateTime.Now;
+            SaveSettings();
+        }
+    }
+    
+}
+
+public abstract partial class MasterDetailPanel : UserControl
+{
+    
+    protected MasterDetailPanel()
+    {
+        InitializeComponent();
+    }
+
+    protected abstract void DoSplitPanelChanged();
+    
+    private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e)
+    {
+        DoSplitPanelChanged();
+    }
+
+    protected abstract void DoPagesChanged();
+    
+    private void Pages_OnChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (Equals(e.Source, _tabControl))
+            DoPagesChanged();
+    }
+}

+ 38 - 0
inabox.wpf/MasterDetailPanel/MasterDetailPanelPage.cs

@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace InABox.Wpf;
+
+public abstract class MasterDetailPanelPage<TMaster,TPanel> : MasterDetailPage<TMaster>
+    where TPanel : class, IBasePanel, IMasterDetailControl<TMaster>, new()
+{
+
+    public MasterDetailPanelPage(DynamicTabItem tab) : base(tab) { }
+    
+    public TPanel? Panel { get; set; }
+
+    public override Dictionary<string, object[]>? Selected() => Panel?.Selected();
+
+    protected abstract void DoRefresh(TPanel panel);
+    
+    protected override IDataModelSource Refresh()
+    {
+        
+        if (Panel == null)
+        {
+            Panel = new TPanel
+            {
+                IsReady = false
+            };
+            Panel.Setup();
+            Panel.IsReady = true;
+            Tab.Content = Panel;
+        }
+
+        Panel.Master = Master;
+        DoRefresh(Panel);
+
+        return Panel;
+    }
+}

+ 123 - 0
inabox.wpf/Panel/IPanel.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Controls;
+using InABox.Configuration;
+using InABox.Core;
+
+namespace InABox.Wpf;
+
+public interface IPanelActionItem
+{
+
+}
+
+public class PanelAction : IPanelActionItem
+{
+    public Action<PanelAction> OnExecute { get; set; }
+
+    public string Caption { get; set; }
+
+    public Bitmap Image { get; set; }
+
+    public ContextMenu? Menu { get; set; }
+
+    public PanelAction()
+    {
+    }
+    public PanelAction(string caption, Bitmap image, Action<PanelAction> onExecute, ContextMenu? menu = null)
+    {
+        Caption = caption;
+        Image = image;
+        Menu = menu;
+        OnExecute = onExecute;
+    }
+
+    public void Execute()
+    {
+        OnExecute?.Invoke(this);
+    }
+}
+public class PanelActionSeparator : IPanelActionItem
+{
+
+}
+
+public interface ICorePanel
+{
+    void Setup();
+
+    /// <summary>
+    /// Shutdown the panel.
+    /// </summary>
+    /// <param name="cancel">If the operation can be cancelled, this is not <see langword="null"/></param>
+    void Shutdown(CancelEventArgs? cancel);
+
+    void Refresh();
+}
+
+public interface IBasePanel : ICorePanel, IDataModelSource
+{
+    bool IsReady { get; set; }
+    void CreateToolbarButtons(IPanelHost host);
+
+    Dictionary<string, object[]> Selected();
+
+    void Heartbeat(TimeSpan time);
+}
+
+public interface IPanel<T> : IBasePanel
+{
+}
+
+public interface IPropertiesPanel<TProperties>
+    where TProperties : BaseObject, IGlobalConfigurationSettings, new()
+{
+    public TProperties Properties { get; set; }
+}
+
+public interface IPropertiesPanel<TProperties, TSecurity> : IPropertiesPanel<TProperties>
+    where TProperties : BaseObject, IGlobalConfigurationSettings, new()
+    where TSecurity : ISecurityDescriptor, new()
+{
+}
+
+public interface IPanelHost
+{
+    void CreatePanelAction(PanelAction action);
+
+    void CreateReport(PanelAction action);
+
+    void CreateSetupAction(PanelAction action);
+
+    void CreateSetupSeparator();
+}
+
+public static class IPanelHostExtensions
+{
+    public static void CreateSetupAction(this IPanelHost host, string caption, Bitmap image, Action<PanelAction> onExecute, ContextMenu? menu = null)
+    {
+        host.CreateSetupAction(new PanelAction(caption, image, onExecute, menu));
+    }
+
+    public static void CreateSetupActionIf(this IPanelHost host, string caption, Bitmap image, Action<PanelAction> onExecute, bool canView, ContextMenu? menu = null)
+    {
+        if (canView)
+        {
+            host.CreateSetupAction(new PanelAction(caption, image, onExecute, menu));
+        }
+    }
+
+    public static void CreateSetupActionIf<TSecurity>(this IPanelHost host, string caption, Bitmap image, Action<PanelAction> onExecute, ContextMenu? menu = null)
+        where TSecurity : ISecurityDescriptor, new()
+    {
+        host.CreateSetupActionIf(caption, image, onExecute, Security.IsAllowed<TSecurity>(), menu);
+    }
+
+    public static void CreateSetupActionIfCanView<T>(this IPanelHost host, string caption, Bitmap image, Action<PanelAction> onExecute, ContextMenu? menu = null)
+        where T : Entity, new()
+    {
+        host.CreateSetupActionIf(caption, image, onExecute, Security.CanView<T>(), menu);
+    }
+}