Browse Source

Fixes to engine reconnection loop

Kenric Nugteren 1 năm trước cách đây
mục cha
commit
cf261cbde4

+ 1286 - 1287
prs.desktop/Dashboards/Common/DigitalFormsDashboard.xaml.cs

@@ -47,1588 +47,1587 @@ using System.IO;
 using sun.security.krb5.@internal;
 using Columns = InABox.Core.Columns;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public enum DateFilterType
 {
-    public enum DateFilterType
-    {
-        Today,
-        Yesterday,
-        Week,
-        SevenDays,
-        Month,
-        ThirtyDays,
-        Year,
-        TwelveMonths,
-        Custom
-    }
+    Today,
+    Yesterday,
+    Week,
+    SevenDays,
+    Month,
+    ThirtyDays,
+    Year,
+    TwelveMonths,
+    Custom
+}
 
-    public class DFFilter : BaseObject
-    {
-        [EditorSequence(1)]
-        [TextBoxEditor]
-        public string Name { get; set; } = "";
+public class DFFilter : BaseObject
+{
+    [EditorSequence(1)]
+    [TextBoxEditor]
+    public string Name { get; set; } = "";
 
-        [EditorSequence(2)]
-        [FilterEditor]
-        public string Filter { get; set; } = "";
-    }
+    [EditorSequence(2)]
+    [FilterEditor]
+    public string Filter { get; set; } = "";
+}
 
-    internal class MileStoneImageConverter : IValueConverter
+internal class MileStoneImageConverter : IValueConverter
+{
+    public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
     {
-        public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            return Equals(value, DateTime.MinValue) ? null : Resources.milestone.AsBitmapImage();
-        }
-
-        public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            return null;
-        }
+        return Equals(value, DateTime.MinValue) ? null : Resources.milestone.AsBitmapImage();
     }
 
-    public class DigitalFormsDashboardProperties : IUserConfigurationSettings, IDashboardProperties
+    public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
     {
-        public bool ShowJobFilter { get; set; } = false;
-        public bool ShowDateFilter { get; set; } = true;
+        return null;
+    }
+}
+
+public class DigitalFormsDashboardProperties : IUserConfigurationSettings, IDashboardProperties
+{
+    public bool ShowJobFilter { get; set; } = false;
+    public bool ShowDateFilter { get; set; } = true;
 
-        public bool ShowIncompleteForms { get; set; } = false;
+    public bool ShowIncompleteForms { get; set; } = false;
 
-        public bool UseIconsForFormTypes { get; set; } = false;
+    public bool UseIconsForFormTypes { get; set; } = false;
 
-        public string? Category { get; set; }
+    public string? Category { get; set; }
 
-        public Guid SelectedForm { get; set; }
+    public Guid SelectedForm { get; set; }
 
-        public Guid JobID { get; set; }
+    public Guid JobID { get; set; }
 
-        public DateFilterType DateFilterType { get; set; } = DateFilterType.Today;
+    public DateFilterType DateFilterType { get; set; } = DateFilterType.Today;
 
-        public DateTime FromDate { get; set; }
-        public DateTime ToDate { get; set; }
+    public DateTime FromDate { get; set; }
+    public DateTime ToDate { get; set; }
 
-        public Dictionary<string, List<DFFilter>> Filters { get; set; } = new();
-    }
+    public Dictionary<string, List<DFFilter>> Filters { get; set; } = new();
+}
 
-    public class DigitalFormsDashboardElement : DashboardElement<DigitalFormsDashboard, Common, DigitalFormsDashboardProperties> { }
+public class DigitalFormsDashboardElement : DashboardElement<DigitalFormsDashboard, Common, DigitalFormsDashboardProperties> { }
 
-    /// <summary>
-    /// Interaction logic for DigitalFormsDashboard.xaml
-    /// </summary>
-    public partial class DigitalFormsDashboard : UserControl,
-        IDashboardWidget<Common, DigitalFormsDashboardProperties>,
-        IBasePanel,
-        IRequiresSecurity<CanViewDigitalFormsDashbaord>,
-        IHeaderDashboard, IActionsDashboard
-    {
-        public DigitalFormsDashboardProperties Properties { get; set; }
-        public event LoadSettings<DigitalFormsDashboardProperties>? LoadSettings;
-        public event SaveSettings<DigitalFormsDashboardProperties>? SaveSettings;
+/// <summary>
+/// Interaction logic for DigitalFormsDashboard.xaml
+/// </summary>
+public partial class DigitalFormsDashboard : UserControl,
+    IDashboardWidget<Common, DigitalFormsDashboardProperties>,
+    IBasePanel,
+    IRequiresSecurity<CanViewDigitalFormsDashbaord>,
+    IHeaderDashboard, IActionsDashboard
+{
+    public DigitalFormsDashboardProperties Properties { get; set; }
+    public event LoadSettings<DigitalFormsDashboardProperties>? LoadSettings;
+    public event SaveSettings<DigitalFormsDashboardProperties>? SaveSettings;
 
-        private List<DigitalForm> DigitalForms;
-        private List<Job> Jobs;
+    private List<DigitalForm> DigitalForms;
+    private List<Job> Jobs;
 
-        private List<Tuple<string, Type?, string>> Categories;
+    private List<Tuple<string, Type?, string>> Categories;
 
-        public DashboardHeader Header { get; set; } = new();
+    public DashboardHeader Header { get; set; } = new();
 
-        private bool IsQAForm = false;
-        private List<QAQuestion> Questions = new();
+    private bool IsQAForm = false;
+    private List<QAQuestion> Questions = new();
 
-        private bool IsSetup = false;
+    private bool IsSetup = false;
 
-        public DigitalFormsDashboard()
-        {
-            InitializeComponent();
+    public DigitalFormsDashboard()
+    {
+        InitializeComponent();
 
-            DataGrid.RowStyleSelector = new RowStyleSelector();
-        }
+        DataGrid.RowStyleSelector = new RowStyleSelector();
+    }
 
-        public void Setup()
-        {
-            var results = Client.QueryMultiple(
-                new KeyedQueryDef<DigitalForm>(new Filter<DigitalForm>(x => x.Active).IsEqualTo(true)),
-                new KeyedQueryDef<Job>(
-                    LookupFactory.DefineFilter<Job>(),
-                    Columns.None<Job>().Add(x => x.ID)
-                        .Add(x => x.JobNumber)
-                        .Add(x => x.Name)));
+    public void Setup()
+    {
+        var results = Client.QueryMultiple(
+            new KeyedQueryDef<DigitalForm>(new Filter<DigitalForm>(x => x.Active).IsEqualTo(true)),
+            new KeyedQueryDef<Job>(
+                LookupFactory.DefineFilter<Job>(),
+                Columns.None<Job>().Add(x => x.ID)
+                    .Add(x => x.JobNumber)
+                    .Add(x => x.Name)));
 
-            DigitalForms = results.Get<DigitalForm>().ToObjects<DigitalForm>().ToList();
+        DigitalForms = results.Get<DigitalForm>().ToObjects<DigitalForm>().ToList();
 
-            var categories = new DigitalFormCategoryLookups(null);
-            categories.OnAfterGenerateLookups += (sender, entries) => { entries.Insert(0, new LookupEntry("", "Select Category")); };
+        var categories = new DigitalFormCategoryLookups(null);
+        categories.OnAfterGenerateLookups += (sender, entries) => { entries.Insert(0, new LookupEntry("", "Select Category")); };
 
-            Categories = categories.AsTable("AppliesTo")
-                .Rows.Select(x =>
+        Categories = categories.AsTable("AppliesTo")
+            .Rows.Select(x =>
+            {
+                var appliesTo = x.Get<string>("AppliesTo");
+                if (CategoryToType(appliesTo, out var formType, out var parentType))
                 {
-                    var appliesTo = x.Get<string>("AppliesTo");
-                    if (CategoryToType(appliesTo, out var formType, out var parentType))
-                    {
-                        return new Tuple<string, Type?, string>(appliesTo, formType, x.Get<string>("Display"));
-                    }
-                    else
-                    {
-                        return new Tuple<string, Type?, string>(appliesTo, null, x.Get<string>("Display"));
-                    }
-                }).ToList();
+                    return new Tuple<string, Type?, string>(appliesTo, formType, x.Get<string>("Display"));
+                }
+                else
+                {
+                    return new Tuple<string, Type?, string>(appliesTo, null, x.Get<string>("Display"));
+                }
+            }).ToList();
 
-            Jobs = results.Get<Job>().ToObjects<Job>().ToList();
-            Jobs.Insert(0, new Job { ID = Guid.Empty, JobNumber = "ALL", Name = "All Jobs" });
+        Jobs = results.Get<Job>().ToObjects<Job>().ToList();
+        Jobs.Insert(0, new Job { ID = Guid.Empty, JobNumber = "ALL", Name = "All Jobs" });
 
-            SetupHeader();
-            SetupFilters();
+        SetupHeader();
+        SetupFilters();
 
-            IsSetup = true;
-        }
+        IsSetup = true;
+    }
 
-        #region Header
+    #region Header
 
-        private StackPanel CategoryButtonPanel;
-        private Dictionary<Type, Button> CategoryButtons = new();
-        private ComboBox CategoryBox;
-        private ComboBox FormBox;
-        private ComboBox JobBox;
-        private ComboBox IncompleteFormsBox;
+    private StackPanel CategoryButtonPanel;
+    private Dictionary<Type, Button> CategoryButtons = new();
+    private ComboBox CategoryBox;
+    private ComboBox FormBox;
+    private ComboBox JobBox;
+    private ComboBox IncompleteFormsBox;
 
-        private ComboBox DateTypeBox;
+    private ComboBox DateTypeBox;
 
-        private Label FromLabel;
-        private DatePicker FromPicker;
+    private Label FromLabel;
+    private DatePicker FromPicker;
 
-        private Label ToLabel;
-        private DatePicker ToPicker;
+    private Label ToLabel;
+    private DatePicker ToPicker;
 
-        private Button Print;
-        private Button FilterBtn;
+    private Button Print;
+    private Button FilterBtn;
 
-        private static Dictionary<DateFilterType, string> FilterTypes = new()
+    private static Dictionary<DateFilterType, string> FilterTypes = new()
+    {
+        { DateFilterType.Today,         "Today" },
+        { DateFilterType.Yesterday,     "Yesterday" },
+        { DateFilterType.Week,          "Week to Date" },
+        { DateFilterType.SevenDays,     "Last 7 Days" },
+        { DateFilterType.Month,         "Month to Date" },
+        { DateFilterType.ThirtyDays,    "Last 30 Days" },
+        { DateFilterType.Year,          "Year to Date" },
+        { DateFilterType.TwelveMonths,  "Last 12 Months" },
+        { DateFilterType.Custom,        "Custom" }
+    };
+
+    private static readonly SolidColorBrush EnabledBrush = new SolidColorBrush(Colors.LightYellow);
+    private static readonly SolidColorBrush DisabledBrush = new SolidColorBrush(Colors.LightGray);
+
+    private static readonly Dictionary<Type, Bitmap> CategoryImages = new()
+    {
+        { typeof(AssignmentForm), PRSDesktop.Resources.assignments },
+        { typeof(KanbanForm), PRSDesktop.Resources.kanban },
+        { typeof(JobForm), PRSDesktop.Resources.project },
+        { typeof(JobITPForm), PRSDesktop.Resources.checklist },
+        { typeof(EmployeeForm), PRSDesktop.Resources.employees },
+        { typeof(LeaveRequestForm), PRSDesktop.Resources.leave },
+        { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory },
+        { typeof(TimeSheetForm), PRSDesktop.Resources.time },
+        { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase },
+        { typeof(DeliveryForm), PRSDesktop.Resources.truck },
+    };
+
+    public void SetupHeader()
+    {
+        CategoryBox = new ComboBox
         {
-            { DateFilterType.Today,         "Today" },
-            { DateFilterType.Yesterday,     "Yesterday" },
-            { DateFilterType.Week,          "Week to Date" },
-            { DateFilterType.SevenDays,     "Last 7 Days" },
-            { DateFilterType.Month,         "Month to Date" },
-            { DateFilterType.ThirtyDays,    "Last 30 Days" },
-            { DateFilterType.Year,          "Year to Date" },
-            { DateFilterType.TwelveMonths,  "Last 12 Months" },
-            { DateFilterType.Custom,        "Custom" }
+            Width = 150,
+            VerticalContentAlignment = VerticalAlignment.Center,
+            Margin = new Thickness(0, 0, 5, 0)
         };
+        CategoryBox.ItemsSource = Categories;
+        CategoryBox.SelectedValuePath = "Item1";
+        CategoryBox.DisplayMemberPath = "Item3";
+        CategoryBox.SelectedValue = Properties.Category;
+        CategoryBox.SelectionChanged += Category_SelectionChanged;
 
-        private static readonly SolidColorBrush EnabledBrush = new SolidColorBrush(Colors.LightYellow);
-        private static readonly SolidColorBrush DisabledBrush = new SolidColorBrush(Colors.LightGray);
-
-        private static readonly Dictionary<Type, Bitmap> CategoryImages = new()
-        {
-            { typeof(AssignmentForm), PRSDesktop.Resources.assignments },
-            { typeof(KanbanForm), PRSDesktop.Resources.kanban },
-            { typeof(JobForm), PRSDesktop.Resources.project },
-            { typeof(JobITPForm), PRSDesktop.Resources.checklist },
-            { typeof(EmployeeForm), PRSDesktop.Resources.employees },
-            { typeof(LeaveRequestForm), PRSDesktop.Resources.leave },
-            { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory },
-            { typeof(TimeSheetForm), PRSDesktop.Resources.time },
-            { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase },
-            { typeof(DeliveryForm), PRSDesktop.Resources.truck },
+        CategoryButtonPanel = new StackPanel
+        {
+            Orientation = Orientation.Horizontal,
+            Margin = new Thickness(0, 0, 5, 0)
         };
-
-        public void SetupHeader()
+        
+        CategoryButtons.Clear();
+        foreach(var (appliesTo, category, display) in Categories)
         {
-            CategoryBox = new ComboBox
-            {
-                Width = 150,
-                VerticalContentAlignment = VerticalAlignment.Center,
-                Margin = new Thickness(0, 0, 5, 0)
-            };
-            CategoryBox.ItemsSource = Categories;
-            CategoryBox.SelectedValuePath = "Item1";
-            CategoryBox.DisplayMemberPath = "Item3";
-            CategoryBox.SelectedValue = Properties.Category;
-            CategoryBox.SelectionChanged += Category_SelectionChanged;
-
-            CategoryButtonPanel = new StackPanel
-            {
-                Orientation = Orientation.Horizontal,
-                Margin = new Thickness(0, 0, 5, 0)
-            };
-            
-            CategoryButtons.Clear();
-            foreach(var (appliesTo, category, display) in Categories)
+            if(category is null)
             {
-                if(category is null)
-                {
-                    continue;
-                }
-                var button = new Button();
-                button.Tag = appliesTo;
-                button.Margin = new Thickness(0, 0, 2, 0);
-                button.BorderBrush = new SolidColorBrush(Colors.Gray);
-                button.BorderThickness = new Thickness(0.75);
-                button.Width = 25D;
-                button.Padding = new Thickness(2);
-                button.ToolTip = category.EntityName().Split('.').Last().SplitCamelCase();
-                if (CategoryImages.TryGetValue(category, out var image))
-                {
-                    button.Content = new Image { Source = image.AsBitmapImage() };
-                }
-                else
-                {
-                    button.Content = display;
-                }
-
-                button.Click += CatagoryButton_Click;
-                CategoryButtons.Add(category, button);
-                CategoryButtonPanel.Children.Add(button);
+                continue;
             }
-
-            if (Properties.UseIconsForFormTypes)
-            {
-                CategoryButtonPanel.Visibility = Visibility.Visible;
-                CategoryBox.Visibility = Visibility.Collapsed;
+            var button = new Button();
+            button.Tag = appliesTo;
+            button.Margin = new Thickness(0, 0, 2, 0);
+            button.BorderBrush = new SolidColorBrush(Colors.Gray);
+            button.BorderThickness = new Thickness(0.75);
+            button.Width = 25D;
+            button.Padding = new Thickness(2);
+            button.ToolTip = category.EntityName().Split('.').Last().SplitCamelCase();
+            if (CategoryImages.TryGetValue(category, out var image))
+            {
+                button.Content = new Image { Source = image.AsBitmapImage() };
             }
             else
             {
-                CategoryButtonPanel.Visibility = Visibility.Collapsed;
-                CategoryBox.Visibility = Visibility.Visible;
+                button.Content = display;
             }
 
-            FormBox = new ComboBox
-            {
-                Width = 250,
-                VerticalContentAlignment = VerticalAlignment.Center,
-                Margin = new Thickness(0, 0, 5, 0),
-                IsEnabled = false
-            };
-            FormBox.SelectionChanged += FormBox_SelectionChanged;
-            FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
+            button.Click += CatagoryButton_Click;
+            CategoryButtons.Add(category, button);
+            CategoryButtonPanel.Children.Add(button);
+        }
 
-            JobBox = new ComboBox
-            {
-                Width = 250,
-                Margin = new Thickness(0, 0, 5, 0),
-                VerticalContentAlignment = VerticalAlignment.Center
-            };
-            JobBox.ItemsSource = Jobs.ToDictionary(x => x.ID, x => $"{x.JobNumber} : {x.Name}");
-            JobBox.SelectedIndex = 0;
-            JobBox.SelectedValuePath = "Key";
-            JobBox.DisplayMemberPath = "Value";
-            JobBox.SelectionChanged += JobBox_SelectionChanged;
+        if (Properties.UseIconsForFormTypes)
+        {
+            CategoryButtonPanel.Visibility = Visibility.Visible;
+            CategoryBox.Visibility = Visibility.Collapsed;
+        }
+        else
+        {
+            CategoryButtonPanel.Visibility = Visibility.Collapsed;
+            CategoryBox.Visibility = Visibility.Visible;
+        }
 
-            DateTypeBox = new ComboBox
-            {
-                Width = 120,
-                VerticalContentAlignment = VerticalAlignment.Center
-            };
-            DateTypeBox.ItemsSource = FilterTypes;
-            DateTypeBox.SelectedValuePath = "Key";
-            DateTypeBox.DisplayMemberPath = "Value";
-            DateTypeBox.SelectedValue = Properties.DateFilterType;
-            DateTypeBox.SelectionChanged += DateTypeBox_SelectionChanged;
-
-            FromLabel = new Label { Content = "From", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
-            FromPicker = new DatePicker
-            {
-                Width = 100,
-                Background = new SolidColorBrush(Colors.LightYellow),
-                VerticalContentAlignment = VerticalAlignment.Center,
-                FirstDayOfWeek = DayOfWeek.Monday,
-                Margin = new Thickness(0, 0, 5, 0)
-            };
-            FromPicker.SelectedDateChanged += FromPicker_SelectedDateChanged;
+        FormBox = new ComboBox
+        {
+            Width = 250,
+            VerticalContentAlignment = VerticalAlignment.Center,
+            Margin = new Thickness(0, 0, 5, 0),
+            IsEnabled = false
+        };
+        FormBox.SelectionChanged += FormBox_SelectionChanged;
+        FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
 
-            ToLabel = new Label { Content = "To", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
-            ToPicker = new DatePicker
-            {
-                Width = 100,
-                Background = new SolidColorBrush(Colors.LightYellow),
-                VerticalContentAlignment = VerticalAlignment.Center,
-                FirstDayOfWeek = DayOfWeek.Monday,
-                Margin = new Thickness(0, 0, 5, 0)
-            };
-            ToPicker.SelectedDateChanged += ToPicker_SelectedDateChanged;
+        JobBox = new ComboBox
+        {
+            Width = 250,
+            Margin = new Thickness(0, 0, 5, 0),
+            VerticalContentAlignment = VerticalAlignment.Center
+        };
+        JobBox.ItemsSource = Jobs.ToDictionary(x => x.ID, x => $"{x.JobNumber} : {x.Name}");
+        JobBox.SelectedIndex = 0;
+        JobBox.SelectedValuePath = "Key";
+        JobBox.DisplayMemberPath = "Value";
+        JobBox.SelectionChanged += JobBox_SelectionChanged;
 
-            IncompleteFormsBox = new ComboBox
-            {
-                Width = 130,
-                Margin = new Thickness(0, 0, 5, 0),
-                VerticalContentAlignment = VerticalAlignment.Center
-            };
-            IncompleteFormsBox.ItemsSource = new List<Tuple<string, bool>>
-            {
-                new Tuple<string, bool>("Completed Forms", false),
-                new Tuple<string, bool>("All Forms", true)
-            };
-            IncompleteFormsBox.DisplayMemberPath = "Item1";
-            IncompleteFormsBox.SelectedValuePath = "Item2";
-            IncompleteFormsBox.SelectedValue = Properties.ShowIncompleteForms;
-            IncompleteFormsBox.SelectionChanged += IncompleteFormsBox_SelectionChanged;
+        DateTypeBox = new ComboBox
+        {
+            Width = 120,
+            VerticalContentAlignment = VerticalAlignment.Center
+        };
+        DateTypeBox.ItemsSource = FilterTypes;
+        DateTypeBox.SelectedValuePath = "Key";
+        DateTypeBox.DisplayMemberPath = "Value";
+        DateTypeBox.SelectedValue = Properties.DateFilterType;
+        DateTypeBox.SelectionChanged += DateTypeBox_SelectionChanged;
+
+        FromLabel = new Label { Content = "From", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
+        FromPicker = new DatePicker
+        {
+            Width = 100,
+            Background = new SolidColorBrush(Colors.LightYellow),
+            VerticalContentAlignment = VerticalAlignment.Center,
+            FirstDayOfWeek = DayOfWeek.Monday,
+            Margin = new Thickness(0, 0, 5, 0)
+        };
+        FromPicker.SelectedDateChanged += FromPicker_SelectedDateChanged;
 
-            Print = new Button
-            {
-                Width = 25,
-                Height = 25,
-                Content = new Image { Source = PRSDesktop.Resources.printer.AsBitmapImage() }
-            };
-            Print.Click += Print_Click;
+        ToLabel = new Label { Content = "To", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
+        ToPicker = new DatePicker
+        {
+            Width = 100,
+            Background = new SolidColorBrush(Colors.LightYellow),
+            VerticalContentAlignment = VerticalAlignment.Center,
+            FirstDayOfWeek = DayOfWeek.Monday,
+            Margin = new Thickness(0, 0, 5, 0)
+        };
+        ToPicker.SelectedDateChanged += ToPicker_SelectedDateChanged;
 
-            FilterBtn = new Button
-            {
-                Width = 25,
-                Height = 25,
-                Content = new Image { Source = InABox.Wpf.Resources.filter.AsBitmapImage() },
-                Margin = new Thickness(0, 0, 5, 0)
-            };
-            FilterBtn.Click += Filter_Click;
-
-            Header.BeginUpdate()
-                .Clear()
-                .Add(CategoryBox)
-                .Add(CategoryButtonPanel)
-                .Add(FormBox)
-                .Add(JobBox)
-                .Add(DateTypeBox)
-                .Add(FromLabel)
-                .Add(FromPicker)
-                .Add(ToLabel)
-                .Add(ToPicker)
-                .Add(IncompleteFormsBox)
-                .AddRight(FilterBtn)
-                .AddRight(Print)
-                .EndUpdate();
-
-            UpdateCategory(Properties.Category);
-        }
+        IncompleteFormsBox = new ComboBox
+        {
+            Width = 130,
+            Margin = new Thickness(0, 0, 5, 0),
+            VerticalContentAlignment = VerticalAlignment.Center
+        };
+        IncompleteFormsBox.ItemsSource = new List<Tuple<string, bool>>
+        {
+            new Tuple<string, bool>("Completed Forms", false),
+            new Tuple<string, bool>("All Forms", true)
+        };
+        IncompleteFormsBox.DisplayMemberPath = "Item1";
+        IncompleteFormsBox.SelectedValuePath = "Item2";
+        IncompleteFormsBox.SelectedValue = Properties.ShowIncompleteForms;
+        IncompleteFormsBox.SelectionChanged += IncompleteFormsBox_SelectionChanged;
 
-        private void IncompleteFormsBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        Print = new Button
         {
-            Properties.ShowIncompleteForms = (bool)IncompleteFormsBox.SelectedValue;
-            Refresh();
-        }
+            Width = 25,
+            Height = 25,
+            Content = new Image { Source = PRSDesktop.Resources.printer.AsBitmapImage() }
+        };
+        Print.Click += Print_Click;
 
-        private void Filter_Click(object sender, RoutedEventArgs e)
+        FilterBtn = new Button
         {
-            var menu = new ContextMenu();
+            Width = 25,
+            Height = 25,
+            Content = new Image { Source = InABox.Wpf.Resources.filter.AsBitmapImage() },
+            Margin = new Thickness(0, 0, 5, 0)
+        };
+        FilterBtn.Click += Filter_Click;
+
+        Header.BeginUpdate()
+            .Clear()
+            .Add(CategoryBox)
+            .Add(CategoryButtonPanel)
+            .Add(FormBox)
+            .Add(JobBox)
+            .Add(DateTypeBox)
+            .Add(FromLabel)
+            .Add(FromPicker)
+            .Add(ToLabel)
+            .Add(ToPicker)
+            .Add(IncompleteFormsBox)
+            .AddRight(FilterBtn)
+            .AddRight(Print)
+            .EndUpdate();
+
+        UpdateCategory(Properties.Category);
+    }
+
+    private void IncompleteFormsBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        Properties.ShowIncompleteForms = (bool)IncompleteFormsBox.SelectedValue;
+        Refresh();
+    }
+
+    private void Filter_Click(object sender, RoutedEventArgs e)
+    {
+        var menu = new ContextMenu();
 
-            menu.AddCheckItem("Use Form Type Icons", ToggleFormTypeIcons, isChecked: Properties.UseIconsForFormTypes);
-            menu.AddCheckItem("Show Date Filter", ToggleDateFilter, Properties.ShowDateFilter);
-            menu.AddCheckItem("Show Job Filter", ToggleJobFilter, Properties.ShowJobFilter);
-            menu.AddSeparator();
+        menu.AddCheckItem("Use Form Type Icons", ToggleFormTypeIcons, isChecked: Properties.UseIconsForFormTypes);
+        menu.AddCheckItem("Show Date Filter", ToggleDateFilter, Properties.ShowDateFilter);
+        menu.AddCheckItem("Show Job Filter", ToggleJobFilter, Properties.ShowJobFilter);
+        menu.AddSeparator();
 
-            if (ParentType is not null)
+        if (ParentType is not null)
+        {
+            if (Properties.Filters.TryGetValue(ParentType.Name, out var filters))
             {
-                if (Properties.Filters.TryGetValue(ParentType.Name, out var filters))
+                var i = 0;
+                var items = new List<MenuItem>();
+                foreach (var filter in filters)
                 {
-                    var i = 0;
-                    var items = new List<MenuItem>();
-                    foreach (var filter in filters)
-                    {
-                        items.Add(menu.AddCheckItem(
-                            filter.Name,
-                            new Tuple<int, string, List<MenuItem>>(i, filter.Filter, items),
-                            ExecuteFilter,
-                            i == CustomFilterIndex));
-                        ++i;
-                    }
+                    items.Add(menu.AddCheckItem(
+                        filter.Name,
+                        new Tuple<int, string, List<MenuItem>>(i, filter.Filter, items),
+                        ExecuteFilter,
+                        i == CustomFilterIndex));
+                    ++i;
                 }
-                menu.AddSeparatorIfNeeded();
-                menu.AddItem("Manage Filters", InABox.Wpf.Resources.filter, ManageFilters_Click);
             }
+            menu.AddSeparatorIfNeeded();
+            menu.AddItem("Manage Filters", InABox.Wpf.Resources.filter, ManageFilters_Click);
+        }
+
+        menu.IsOpen = true;
+    }
+
+    private void ToggleFormTypeIcons(bool isChecked)
+    {
+        Properties.UseIconsForFormTypes = !Properties.UseIconsForFormTypes;
+        if (Properties.UseIconsForFormTypes)
+        {
+            CategoryButtonPanel.Visibility = Visibility.Visible;
+            CategoryBox.Visibility = Visibility.Collapsed;
+        }
+        else
+        {
+            CategoryButtonPanel.Visibility = Visibility.Collapsed;
+            CategoryBox.Visibility = Visibility.Visible;
+        }
+    }
 
-            menu.IsOpen = true;
+    private void Print_Click(object sender, RoutedEventArgs e)
+    {
+        var menu = new ContextMenu();
+        foreach (var report in ReportUtils.LoadReports(SectionName, DataModel(Selection.None)))
+        {
+            menu.AddItem(report.Name, PRSDesktop.Resources.printer, report, PrintReport_Click);
         }
+        if (Security.IsAllowed<CanDesignReports>())
+        {
+            menu.AddSeparatorIfNeeded();
+            menu.AddItem("Manage Reports", PRSDesktop.Resources.printer, ManageReports_Click);
+        }
+        menu.IsOpen = true;
+    }
+    private void PrintReport_Click(ReportTemplate obj)
+    {
+        Selection selection;
+        if (obj.SelectedRecords && obj.AllRecords)
+            selection = RecordSelectionDialog.Execute();
+        else if (obj.SelectedRecords)
+            selection = Selection.Selected;
+        else if (obj.AllRecords)
+            selection = Selection.All;
+        else
+            selection = Selection.None;
+
+        ReportUtils.PreviewReport(obj, DataModel(selection), false, Security.IsAllowed<CanDesignReports>());
+    }
+
+    private void ManageReports_Click()
+    {
+        var manager = new ReportManager()
+        {
+            DataModel = DataModel(Selection.None),
+            Section = SectionName,
+            Populate = true
+        };
+        manager.ShowDialog();
+    }
+
+    private void Search_KeyUp(object sender, KeyEventArgs e)
+    {
+        Refresh();
+    }
+
+    private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        Properties.JobID = (Guid)JobBox.SelectedValue;
+        Refresh();
+    }
 
-        private void ToggleFormTypeIcons(bool isChecked)
+    private void SetDateFilterVisibility(bool visible)
+    {
+        var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
+        FromLabel.Visibility = visibility;
+        FromPicker.Visibility = visibility;
+        ToLabel.Visibility = visibility;
+        ToPicker.Visibility = visibility;
+        DateTypeBox.Visibility = visibility;
+    }
+    private void SetJobFilterVisibility(bool visible)
+    {
+        var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
+        JobBox.Visibility = visibility;
+    }
+
+    private void DateTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        var filterType = (DateFilterType)DateTypeBox.SelectedValue;
+        Properties.DateFilterType = filterType;
+        if (filterType == DateFilterType.Custom)
         {
-            Properties.UseIconsForFormTypes = !Properties.UseIconsForFormTypes;
-            if (Properties.UseIconsForFormTypes)
+            if (FromPicker.SelectedDate == null || FromPicker.SelectedDate == DateTime.MinValue)
             {
-                CategoryButtonPanel.Visibility = Visibility.Visible;
-                CategoryBox.Visibility = Visibility.Collapsed;
+                Properties.FromDate = DateTime.Today;
             }
             else
             {
-                CategoryButtonPanel.Visibility = Visibility.Collapsed;
-                CategoryBox.Visibility = Visibility.Visible;
+                Properties.FromDate = FromPicker.SelectedDate.Value;
             }
-        }
-
-        private void Print_Click(object sender, RoutedEventArgs e)
-        {
-            var menu = new ContextMenu();
-            foreach (var report in ReportUtils.LoadReports(SectionName, DataModel(Selection.None)))
+            if (ToPicker.SelectedDate == null || ToPicker.SelectedDate == DateTime.MinValue)
             {
-                menu.AddItem(report.Name, PRSDesktop.Resources.printer, report, PrintReport_Click);
+                Properties.ToDate = DateTime.Today;
             }
-            if (Security.IsAllowed<CanDesignReports>())
+            else
             {
-                menu.AddSeparatorIfNeeded();
-                menu.AddItem("Manage Reports", PRSDesktop.Resources.printer, ManageReports_Click);
+                Properties.ToDate = ToPicker.SelectedDate.Value;
             }
-            menu.IsOpen = true;
-        }
-        private void PrintReport_Click(ReportTemplate obj)
-        {
-            Selection selection;
-            if (obj.SelectedRecords && obj.AllRecords)
-                selection = RecordSelectionDialog.Execute();
-            else if (obj.SelectedRecords)
-                selection = Selection.Selected;
-            else if (obj.AllRecords)
-                selection = Selection.All;
-            else
-                selection = Selection.None;
-
-            ReportUtils.PreviewReport(obj, DataModel(selection), false, Security.IsAllowed<CanDesignReports>());
         }
+        SetupDateFilters();
+        Refresh();
+    }
 
-        private void ManageReports_Click()
-        {
-            var manager = new ReportManager()
-            {
-                DataModel = DataModel(Selection.None),
-                Section = SectionName,
-                Populate = true
-            };
-            manager.ShowDialog();
-        }
+    private void FromPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
+    {
+        Properties.FromDate = FromPicker.SelectedDate ?? DateTime.Today;
+        Refresh();
+    }
 
-        private void Search_KeyUp(object sender, KeyEventArgs e)
-        {
-            Refresh();
-        }
+    private void ToPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
+    {
+        Properties.ToDate = ToPicker.SelectedDate ?? DateTime.Today;
+        Refresh();
+    }
 
-        private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            Properties.JobID = (Guid)JobBox.SelectedValue;
-            Refresh();
-        }
+    private bool _changing = false;
 
-        private void SetDateFilterVisibility(bool visible)
-        {
-            var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
-            FromLabel.Visibility = visibility;
-            FromPicker.Visibility = visibility;
-            ToLabel.Visibility = visibility;
-            ToPicker.Visibility = visibility;
-            DateTypeBox.Visibility = visibility;
-        }
-        private void SetJobFilterVisibility(bool visible)
-        {
-            var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
-            JobBox.Visibility = visibility;
-        }
+    private void UpdateCategory(string? category)
+    {
+        _changing = true;
+        
+        Properties.Category = category;
+        SetCategory(Properties.Category);
 
-        private void DateTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        foreach(var (type, button) in CategoryButtons)
         {
-            var filterType = (DateFilterType)DateTypeBox.SelectedValue;
-            Properties.DateFilterType = filterType;
-            if (filterType == DateFilterType.Custom)
+            if(type == FormType)
             {
-                if (FromPicker.SelectedDate == null || FromPicker.SelectedDate == DateTime.MinValue)
-                {
-                    Properties.FromDate = DateTime.Today;
-                }
-                else
-                {
-                    Properties.FromDate = FromPicker.SelectedDate.Value;
-                }
-                if (ToPicker.SelectedDate == null || ToPicker.SelectedDate == DateTime.MinValue)
-                {
-                    Properties.ToDate = DateTime.Today;
-                }
-                else
-                {
-                    Properties.ToDate = ToPicker.SelectedDate.Value;
-                }
+                button.Background = EnabledBrush;
+            }
+            else
+            {
+                button.Background = DisabledBrush;
             }
-            SetupDateFilters();
-            Refresh();
         }
 
-        private void FromPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
+        var jobLink = FormType is not null ? GetJobLink("", FormType) : "";
+        if (string.IsNullOrWhiteSpace(jobLink))
         {
-            Properties.FromDate = FromPicker.SelectedDate ?? DateTime.Today;
-            Refresh();
+            var jobID = Properties.JobID;
+            JobBox.SelectedValue = Guid.Empty;
+            JobBox.IsEnabled = false;
+            Properties.JobID = jobID;
         }
-
-        private void ToPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
+        else
         {
-            Properties.ToDate = ToPicker.SelectedDate ?? DateTime.Today;
-            Refresh();
+            JobBox.SelectedValue = Properties.JobID;
+            JobBox.IsEnabled = true;
         }
 
-        private bool _changing = false;
-
-        private void UpdateCategory(string? category)
+        if (ParentType is null)
         {
-            _changing = true;
-            
-            Properties.Category = category;
-            SetCategory(Properties.Category);
-
-            foreach(var (type, button) in CategoryButtons)
-            {
-                if(type == FormType)
-                {
-                    button.Background = EnabledBrush;
-                }
-                else
-                {
-                    button.Background = DisabledBrush;
-                }
-            }
+            FormBox.IsEnabled = false;
+            FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
+        }
+        else
+        {
+            var forms = DigitalForms.Where(x => x.AppliesTo == ParentType.Name).ToList();
+            forms.Insert(0, new DigitalForm { ID = Guid.Empty, Description = "Select Form" });
+            FormBox.ItemsSource = forms;
 
-            var jobLink = FormType is not null ? GetJobLink("", FormType) : "";
-            if (string.IsNullOrWhiteSpace(jobLink))
+            if (Properties.SelectedForm != Guid.Empty && forms.Where(x => x.ID == Properties.SelectedForm).FirstOrDefault() is DigitalForm form)
             {
-                var jobID = Properties.JobID;
-                JobBox.SelectedValue = Guid.Empty;
-                JobBox.IsEnabled = false;
-                Properties.JobID = jobID;
+                FormBox.SelectedItem = form;
             }
             else
             {
-                JobBox.SelectedValue = Properties.JobID;
-                JobBox.IsEnabled = true;
+                FormBox.SelectedIndex = 0;
             }
 
-            if (ParentType is null)
-            {
-                FormBox.IsEnabled = false;
-                FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
-            }
-            else
-            {
-                var forms = DigitalForms.Where(x => x.AppliesTo == ParentType.Name).ToList();
-                forms.Insert(0, new DigitalForm { ID = Guid.Empty, Description = "Select Form" });
-                FormBox.ItemsSource = forms;
+            FormBox.DisplayMemberPath = "Description";
 
-                if (Properties.SelectedForm != Guid.Empty && forms.Where(x => x.ID == Properties.SelectedForm).FirstOrDefault() is DigitalForm form)
-                {
-                    FormBox.SelectedItem = form;
-                }
-                else
-                {
-                    FormBox.SelectedIndex = 0;
-                }
+            FormBox.IsEnabled = true;
+        }
+        _changing = false;
 
-                FormBox.DisplayMemberPath = "Description";
+        OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None));
+    }
 
-                FormBox.IsEnabled = true;
-            }
-            _changing = false;
+    private void CatagoryButton_Click(object sender, RoutedEventArgs e)
+    {
+        UpdateCategory(((sender as Button)!.Tag as string)!);
+        Refresh();
+    }
 
-            OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None));
-        }
+    private void Category_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        UpdateCategory((CategoryBox.SelectedValue as string)!);
+        Refresh();
+    }
 
-        private void CatagoryButton_Click(object sender, RoutedEventArgs e)
-        {
-            UpdateCategory(((sender as Button)!.Tag as string)!);
-            Refresh();
-        }
+    private void FormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        Form = FormBox.SelectedValue as DigitalForm;
+        Properties.SelectedForm = Form?.ID ?? Guid.Empty;
 
-        private void Category_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        if (!_changing)
         {
-            UpdateCategory((CategoryBox.SelectedValue as string)!);
-            Refresh();
+            OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None));
         }
 
-        private void FormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            Form = FormBox.SelectedValue as DigitalForm;
-            Properties.SelectedForm = Form?.ID ?? Guid.Empty;
+        Refresh();
+    }
 
-            if (!_changing)
-            {
-                OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None));
-            }
+    #endregion
 
-            Refresh();
-        }
+    #region IBasePanel
+
+    public bool IsReady { get; set; }
 
-        #endregion
+    public event DataModelUpdateEvent? OnUpdateDataModel;
+
+    public void CreateToolbarButtons(IPanelHost host)
+    {
+        host.CreatePanelAction(new PanelAction("Export Forms", PRSDesktop.Resources.disk, action => SaveToFolder_Click()));
+    }
 
-        #region IBasePanel
+    public void Heartbeat(TimeSpan time)
+    {
+    }
 
-        public bool IsReady { get; set; }
+    public Dictionary<string, object[]> Selected()
+    {
+        return new Dictionary<string, object[]>();
+    }
 
-        public event DataModelUpdateEvent? OnUpdateDataModel;
+    #endregion
 
-        public void CreateToolbarButtons(IPanelHost host)
+    public string SectionName
+    {
+        get
         {
-            host.CreatePanelAction(new PanelAction("Export Forms", PRSDesktop.Resources.disk, action => SaveToFolder_Click()));
+            if (Form is null || Form.ID == Guid.Empty)
+                return "Digital Forms";
+            return Form.ID.ToString() ?? "Digital Forms";
         }
-
-        public void Heartbeat(TimeSpan time)
+    }
+    public DataModel DataModel(Selection selection)
+    {
+        if (FormType is null || Form is null || Form.ID == Guid.Empty)
         {
+            return new AutoDataModel<DigitalForm>(new Filter<DigitalForm>().None());
         }
 
-        public Dictionary<string, object[]> Selected()
+        IFilter filter;
+        switch (selection)
         {
-            return new Dictionary<string, object[]>();
+            case Selection.Selected:
+                var formids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row["ID"]).ToArray();
+                filter = Filter.Create<Entity>(FormType, x => x.ID).InList(formids);
+                break;
+            case Selection.All:
+                filter = Filter.Create(FormType).All();
+                break;
+            case Selection.None:
+            default:
+                filter = Filter.Create(FormType).None();
+                break;
         }
 
-        #endregion
+        return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)!
+            .MakeGenericType(FormType), new object?[] { filter, Form.ID }) as DataModel)!;
+    }
 
-        public string SectionName
+    public void BuildActionsMenu(ContextMenu menu)
+    {
+        menu.AddItem("Export", InABox.Wpf.Resources.doc_xls, Export_Click, Form is not null && Form.ID != Guid.Empty);
+        var loadingModules = menu.AddItem("Loading Modules...", null, null, false);
+        Task.Run(() =>
         {
-            get
-            {
-                if (Form is null || Form.ID == Guid.Empty)
-                    return "Digital Forms";
-                return Form.ID.ToString() ?? "Digital Forms";
-            }
-        }
-        public DataModel DataModel(Selection selection)
+            return CustomModuleUtils.LoadCustomModuleThumbnails(SectionName, DataModel(Selection.None));
+        }).ContinueWith((task) =>
         {
-            if (FormType is null || Form is null || Form.ID == Guid.Empty)
+            var modules = task.Result;
+            var index = menu.Items.IndexOf(loadingModules);
+            menu.Items.RemoveAt(index);
+            foreach (var (module, image) in modules)
             {
-                return new AutoDataModel<DigitalForm>(new Filter<DigitalForm>().None());
+                menu.AddItem(module.Name, image, module, ExecuteModule_Click, index: index);
+                ++index;
             }
+        }, TaskScheduler.FromCurrentSynchronizationContext());
 
-            IFilter filter;
-            switch (selection)
-            {
-                case Selection.Selected:
-                    var formids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row["ID"]).ToArray();
-                    filter = Filter.Create<Entity>(FormType, x => x.ID).InList(formids);
-                    break;
-                case Selection.All:
-                    filter = Filter.Create(FormType).All();
-                    break;
-                case Selection.None:
-                default:
-                    filter = Filter.Create(FormType).None();
-                    break;
-            }
-
-            return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)!
-                .MakeGenericType(FormType), new object?[] { filter, Form.ID }) as DataModel)!;
-        }
-
-        public void BuildActionsMenu(ContextMenu menu)
+        if (Security.IsAllowed<CanCustomiseModules>())
         {
-            menu.AddItem("Export", InABox.Wpf.Resources.doc_xls, Export_Click, Form is not null && Form.ID != Guid.Empty);
-            var loadingModules = menu.AddItem("Loading Modules...", null, null, false);
-            Task.Run(() =>
-            {
-                return CustomModuleUtils.LoadCustomModuleThumbnails(SectionName, DataModel(Selection.None));
-            }).ContinueWith((task) =>
-            {
-                var modules = task.Result;
-                var index = menu.Items.IndexOf(loadingModules);
-                menu.Items.RemoveAt(index);
-                foreach (var (module, image) in modules)
-                {
-                    menu.AddItem(module.Name, image, module, ExecuteModule_Click, index: index);
-                    ++index;
-                }
-            }, TaskScheduler.FromCurrentSynchronizationContext());
-
-            if (Security.IsAllowed<CanCustomiseModules>())
-            {
-                menu.AddSeparatorIfNeeded();
-                menu.AddItem("Manage Modules", PRSDesktop.Resources.script, ManageModules_Click);
-            }
+            menu.AddSeparatorIfNeeded();
+            menu.AddItem("Manage Modules", PRSDesktop.Resources.script, ManageModules_Click);
         }
+    }
 
-        private void SaveToFolder_Click()
+    private void SaveToFolder_Click()
+    {
+        if (Form is null || FormType is null)
         {
-            if (Form is null || FormType is null)
-            {
-                MessageWindow.ShowMessage("Please select a form first.", "Select form");
-                return;
-            }
-
-            var model = DataModel(Selection.None);
-            
-            var reports = ReportUtils.LoadReports(Form.ID.ToString(), model).Where(x => x.Visible).ToList();
+            MessageWindow.ShowMessage("Please select a form first.", "Select form");
+            return;
+        }
 
-            var method = typeof(DigitalFormsDashboard).GetMethod("SaveToFolder", BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType);
+        var model = DataModel(Selection.None);
+        
+        var reports = ReportUtils.LoadReports(Form.ID.ToString(), model).Where(x => x.Visible).ToList();
 
-            if (reports.Count == 0)
-            {
-                MessageWindow.ShowMessage("No reports are currently defined for this Digital Form!", "No report found");
-                return;
-            }
-            
-            if(reports.Count == 1)
-            {
-                method.Invoke(this, new object[] { reports[0] });
-                return;
-            }
+        var method = typeof(DigitalFormsDashboard).GetMethod("SaveToFolder", BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType);
 
-            var menu = new ContextMenu();
-            foreach (var report in reports)
-                menu.AddItem(report.Name, null, report, r => method.Invoke(this, new[] { r }));
-            menu.IsOpen = true;
+        if (reports.Count == 0)
+        {
+            MessageWindow.ShowMessage("No reports are currently defined for this Digital Form!", "No report found");
+            return;
         }
+        
+        if(reports.Count == 1)
+        {
+            method.Invoke(this, new object[] { reports[0] });
+            return;
+        }
+
+        var menu = new ContextMenu();
+        foreach (var report in reports)
+            menu.AddItem(report.Name, null, report, r => method.Invoke(this, new[] { r }));
+        menu.IsOpen = true;
+    }
 
-        private void SaveToFolder<TForm>(ReportTemplate report)
-            where TForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
+    private void SaveToFolder<TForm>(ReportTemplate report)
+        where TForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
+    {
+        using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
         {
-            using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
+            var result = dialog.ShowDialog();
+            if(result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dialog.SelectedPath))
             {
-                var result = dialog.ShowDialog();
-                if(result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dialog.SelectedPath))
+                var data = DataGrid.SelectedItems.OfType<DataRowView>().Select(x => x.Row).ToList();
+                Progress.ShowModal("Saving forms", progress =>
                 {
-                    var data = DataGrid.SelectedItems.OfType<DataRowView>().Select(x => x.Row).ToList();
-                    Progress.ShowModal("Saving forms", progress =>
+                    foreach (var row in data)
                     {
-                        foreach (var row in data)
-                        {
-                            var id = (Guid)row["ID"];
-                            var number = (string)row["Number"];
-                            progress.Report($"Saving form {number}");
-                            var dataModel = new DigitalFormReportDataModel<TForm>(
-                                new Filter<TForm>(x => x.ID).IsEqualTo(id),
-                                Form!.ID);
-                            var pdfData = ReportUtils.ReportToPDF(report, dataModel, true);
-
-                            var expr = dataModel.EvaluateExpression(Form.ExportExpression)?.Trim();
-                            var filename = String.IsNullOrWhiteSpace(expr)
-                                ? number
-                                : $"{number} - {CoreUtils.SanitiseFileName(expr)}";
-                            File.WriteAllBytes(Path.Combine(dialog.SelectedPath, Path.ChangeExtension(filename, ".pdf")), pdfData);
-                        }
-                    });
-                    Process.Start("explorer.exe" , dialog.SelectedPath);
-                }
+                        var id = (Guid)row["ID"];
+                        var number = (string)row["Number"];
+                        progress.Report($"Saving form {number}");
+                        var dataModel = new DigitalFormReportDataModel<TForm>(
+                            new Filter<TForm>(x => x.ID).IsEqualTo(id),
+                            Form!.ID);
+                        var pdfData = ReportUtils.ReportToPDF(report, dataModel, true);
+
+                        var expr = dataModel.EvaluateExpression(Form.ExportExpression)?.Trim();
+                        var filename = String.IsNullOrWhiteSpace(expr)
+                            ? number
+                            : $"{number} - {CoreUtils.SanitiseFileName(expr)}";
+                        File.WriteAllBytes(Path.Combine(dialog.SelectedPath, Path.ChangeExtension(filename, ".pdf")), pdfData);
+                    }
+                });
+                Process.Start("explorer.exe" , dialog.SelectedPath);
             }
         }
+    }
 
-        private void Export_Click()
-        {
-            var formName = Regex.Replace(Form?.Description ?? "", "[^ a-zA-Z0-9]", "");
-            var filename = string.Format("{0} - {1} - {2:yyyy-MM-dd} - {3:yyyy-MM-dd}.xlsx", ParentType!.Name, formName, From, To);
-
-            var options = new ExcelExportingOptions();
-            options.ExcelVersion = ExcelVersion.Excel2013;
-            options.ExportStackedHeaders = true;
-            var excelEngine = DataGrid.ExportToExcel(DataGrid.View, options);
-            var workBook = excelEngine.Excel.Workbooks[0];
-            var sheet = workBook.Worksheets[0];
-            sheet.Name = "Summary";
-            sheet.UsedRange.AutofitRows();
-            sheet.UsedRange.AutofitColumns();
-
-            sheet = workBook.Worksheets.Create("Questions");
-            sheet.Move(0);
-            var questions = new Client<QAQuestion>().Query(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID));
-            sheet.Range[1, 1].Text = Form?.Description ?? "";
-            sheet.Range[1, 1, 1, 3].Merge();
-            var i = 1;
-            foreach (var row in questions.Rows)
-                if (!row.Get<QAQuestion, QAAnswer>(x => x.Answer).Equals(QAAnswer.Comment))
-                {
-                    sheet.Range[i + 2, 1].Text = string.Format("{0}.", i);
-                    sheet.Range[i + 2, 2].Text = string.Format("{0}", row.Get<QAQuestion, string>(x => x.Question));
-                    sheet.Range[i + 2, 3].Text = string.Format("[{0}]", row.Get<QAQuestion, string>(x => x.Code));
-                    i++;
-                }
+    private void Export_Click()
+    {
+        var formName = Regex.Replace(Form?.Description ?? "", "[^ a-zA-Z0-9]", "");
+        var filename = string.Format("{0} - {1} - {2:yyyy-MM-dd} - {3:yyyy-MM-dd}.xlsx", ParentType!.Name, formName, From, To);
+
+        var options = new ExcelExportingOptions();
+        options.ExcelVersion = ExcelVersion.Excel2013;
+        options.ExportStackedHeaders = true;
+        var excelEngine = DataGrid.ExportToExcel(DataGrid.View, options);
+        var workBook = excelEngine.Excel.Workbooks[0];
+        var sheet = workBook.Worksheets[0];
+        sheet.Name = "Summary";
+        sheet.UsedRange.AutofitRows();
+        sheet.UsedRange.AutofitColumns();
+
+        sheet = workBook.Worksheets.Create("Questions");
+        sheet.Move(0);
+        var questions = new Client<QAQuestion>().Query(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID));
+        sheet.Range[1, 1].Text = Form?.Description ?? "";
+        sheet.Range[1, 1, 1, 3].Merge();
+        var i = 1;
+        foreach (var row in questions.Rows)
+            if (!row.Get<QAQuestion, QAAnswer>(x => x.Answer).Equals(QAAnswer.Comment))
+            {
+                sheet.Range[i + 2, 1].Text = string.Format("{0}.", i);
+                sheet.Range[i + 2, 2].Text = string.Format("{0}", row.Get<QAQuestion, string>(x => x.Question));
+                sheet.Range[i + 2, 3].Text = string.Format("[{0}]", row.Get<QAQuestion, string>(x => x.Code));
+                i++;
+            }
 
-            sheet.UsedRange.AutofitRows();
-            sheet.UsedRange.AutofitColumns();
+        sheet.UsedRange.AutofitRows();
+        sheet.UsedRange.AutofitColumns();
 
-            try
-            {
-                workBook.SaveAs(filename);
+        try
+        {
+            workBook.SaveAs(filename);
 
-                var startInfo = new ProcessStartInfo(filename);
-                startInfo.Verb = "open";
-                startInfo.UseShellExecute = true;
-                Process.Start(startInfo);
-            }
-            catch
-            {
-                MessageBox.Show(string.Format("Unable to Save/Launch [{0}]!\n\nIs the file already open?", filename));
-            }
+            var startInfo = new ProcessStartInfo(filename);
+            startInfo.Verb = "open";
+            startInfo.UseShellExecute = true;
+            Process.Start(startInfo);
         }
-
-        private void ManageFilters_Click()
+        catch
         {
-            var filters = Properties.Filters.GetValueOrDefault(ParentType!.Name) ?? new List<DFFilter>();
-            var gridFilters = new CoreFilterDefinitions();
-            gridFilters.AddRange(filters.Select(x => new CoreFilterDefinition { Name = x.Name, Filter = x.Filter }));
-
-            var grid = new DynamicGridFilterEditor(gridFilters, FormType!);
-            if (grid.ShowDialog() == true)
-            {
-                Properties.Filters[ParentType!.Name] = grid.Filters.Select(x => new DFFilter { Name = x.Name, Filter = x.Filter }).ToList();
-
-                if (CustomFilterIndex != null)
-                {
-                    Refresh();
-                }
-            }
+            MessageBox.Show(string.Format("Unable to Save/Launch [{0}]!\n\nIs the file already open?", filename));
         }
+    }
 
-        private void ExecuteFilter(Tuple<int, string, List<MenuItem>> tag, bool isChecked)
-        {
-            var (index, filter, items) = tag;
+    private void ManageFilters_Click()
+    {
+        var filters = Properties.Filters.GetValueOrDefault(ParentType!.Name) ?? new List<DFFilter>();
+        var gridFilters = new CoreFilterDefinitions();
+        gridFilters.AddRange(filters.Select(x => new CoreFilterDefinition { Name = x.Name, Filter = x.Filter }));
 
-            if (isChecked)
-            {
-                var i = 0;
-                foreach (var item in items)
-                {
-                    item.IsChecked = i == index;
-                    ++i;
-                }
-            }
+        var grid = new DynamicGridFilterEditor(gridFilters, FormType!);
+        if (grid.ShowDialog() == true)
+        {
+            Properties.Filters[ParentType!.Name] = grid.Filters.Select(x => new DFFilter { Name = x.Name, Filter = x.Filter }).ToList();
 
-            if (isChecked)
-            {
-                CustomFilter = Serialization.Deserialize(typeof(Filter<>).MakeGenericType(FormType!), filter) as IFilter;
-                CustomFilterIndex = index;
-                Refresh();
-            }
-            else if (index == CustomFilterIndex)
+            if (CustomFilterIndex != null)
             {
-                CustomFilter = null;
-                CustomFilterIndex = null;
                 Refresh();
             }
         }
+    }
 
-        private void ExecuteModule_Click(CustomModule obj)
-        {
-            if (!string.IsNullOrWhiteSpace(obj.Script))
-                try
-                {
-                    Selection selection;
-                    if (obj.SelectedRecords && obj.AllRecords)
-                        selection = RecordSelectionDialog.Execute();
-                    else if (obj.SelectedRecords)
-                        selection = Selection.Selected;
-                    else if (obj.AllRecords)
-                        selection = Selection.All;
-                    else
-                        selection = Selection.None;
-
-                    var result = ScriptDocument.RunCustomModule(DataModel(selection), new Dictionary<string, object[]>(), obj.Script);
-                    if (result)
-                        Refresh();
-                }
-                catch (CompileException c)
-                {
-                    MessageBox.Show(c.Message);
-                }
-                catch (Exception err)
-                {
-                    MessageBox.Show(CoreUtils.FormatException(err));
-                }
-            else
-                MessageBox.Show("Unable to load " + obj.Name);
-        }
+    private void ExecuteFilter(Tuple<int, string, List<MenuItem>> tag, bool isChecked)
+    {
+        var (index, filter, items) = tag;
 
-        private void ManageModules_Click()
+        if (isChecked)
         {
-            var section = SectionName;
-            var dataModel = DataModel(Selection.Selected);
-
-            var manager = new CustomModuleManager()
+            var i = 0;
+            foreach (var item in items)
             {
-                Section = section,
-                DataModel = dataModel
-            };
-            manager.ShowDialog();
+                item.IsChecked = i == index;
+                ++i;
+            }
         }
 
-        private void ToggleDateFilter(bool isChecked)
+        if (isChecked)
         {
-            Properties.ShowDateFilter = isChecked;
-            SetDateFilterVisibility(Properties.ShowDateFilter);
+            CustomFilter = Serialization.Deserialize(typeof(Filter<>).MakeGenericType(FormType!), filter) as IFilter;
+            CustomFilterIndex = index;
+            Refresh();
         }
-
-        private void ToggleJobFilter(bool isChecked)
+        else if (index == CustomFilterIndex)
         {
-            Properties.ShowJobFilter = isChecked;
-            SetJobFilterVisibility(Properties.ShowJobFilter);
+            CustomFilter = null;
+            CustomFilterIndex = null;
             Refresh();
         }
+    }
 
-        #region Filtering
+    private void ExecuteModule_Click(CustomModule obj)
+    {
+        if (!string.IsNullOrWhiteSpace(obj.Script))
+            try
+            {
+                Selection selection;
+                if (obj.SelectedRecords && obj.AllRecords)
+                    selection = RecordSelectionDialog.Execute();
+                else if (obj.SelectedRecords)
+                    selection = Selection.Selected;
+                else if (obj.AllRecords)
+                    selection = Selection.All;
+                else
+                    selection = Selection.None;
 
-        private DateTime From { get; set; }
-        private DateTime To { get; set; }
-        private bool IsEntityForm { get; set; }
-        private Type? ParentType { get; set; }
-        private Type? FormType { get; set; }
-        private DigitalForm? Form { get; set; }
-        private IFilter? CustomFilter { get; set; }
-        private int? CustomFilterIndex { get; set; }
+                var result = ScriptDocument.RunCustomModule(DataModel(selection), new Dictionary<string, object[]>(), obj.Script);
+                if (result)
+                    Refresh();
+            }
+            catch (CompileException c)
+            {
+                MessageBox.Show(c.Message);
+            }
+            catch (Exception err)
+            {
+                MessageBox.Show(CoreUtils.FormatException(err));
+            }
+        else
+            MessageBox.Show("Unable to load " + obj.Name);
+    }
 
-        private readonly Dictionary<string, string> QuestionCodes = new();
+    private void ManageModules_Click()
+    {
+        var section = SectionName;
+        var dataModel = DataModel(Selection.Selected);
 
-        private static int WeekDay(DateTime date)
+        var manager = new CustomModuleManager()
         {
-            if (date.DayOfWeek == DayOfWeek.Sunday)
-                return 7;
-            return (int)date.DayOfWeek - 1;
-        }
+            Section = section,
+            DataModel = dataModel
+        };
+        manager.ShowDialog();
+    }
 
-        private void SetupDateFilters()
-        {
-            switch (Properties.DateFilterType)
-            {
-                case DateFilterType.Today:
-                    From = DateTime.Today;
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.Yesterday:
-                    From = DateTime.Today.AddDays(-1);
-                    To = DateTime.Today.AddDays(-1);
-                    break;
-                case DateFilterType.Week:
-                    From = DateTime.Today.AddDays(-WeekDay(DateTime.Today));
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.SevenDays:
-                    From = DateTime.Today.AddDays(-6);
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.Month:
-                    From = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.ThirtyDays:
-                    From = DateTime.Today.AddDays(-29);
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.Year:
-                    From = new DateTime(DateTime.Today.Year, 1, 1);
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.TwelveMonths:
-                    From = DateTime.Today.AddYears(-1).AddDays(1);
-                    To = DateTime.Today;
-                    break;
-                case DateFilterType.Custom:
-                    From = Properties.FromDate;
-                    To = Properties.ToDate;
-                    break;
-            }
+    private void ToggleDateFilter(bool isChecked)
+    {
+        Properties.ShowDateFilter = isChecked;
+        SetDateFilterVisibility(Properties.ShowDateFilter);
+    }
+
+    private void ToggleJobFilter(bool isChecked)
+    {
+        Properties.ShowJobFilter = isChecked;
+        SetJobFilterVisibility(Properties.ShowJobFilter);
+        Refresh();
+    }
 
-            DateTypeBox.SelectedValue = Properties.DateFilterType;
+    #region Filtering
 
-            FromPicker.SelectedDate = From;
-            ToPicker.SelectedDate = To;
+    private DateTime From { get; set; }
+    private DateTime To { get; set; }
+    private bool IsEntityForm { get; set; }
+    private Type? ParentType { get; set; }
+    private Type? FormType { get; set; }
+    private DigitalForm? Form { get; set; }
+    private IFilter? CustomFilter { get; set; }
+    private int? CustomFilterIndex { get; set; }
 
-            var enabledPicker = Properties.DateFilterType == DateFilterType.Custom;
-            FromPicker.IsEnabled = enabledPicker;
-            ToPicker.IsEnabled = enabledPicker;
-        }
+    private readonly Dictionary<string, string> QuestionCodes = new();
 
-        private void SetupJobFilter()
-        {
-            JobBox.SelectedValue = Properties.JobID;
-        }
+    private static int WeekDay(DateTime date)
+    {
+        if (date.DayOfWeek == DayOfWeek.Sunday)
+            return 7;
+        return (int)date.DayOfWeek - 1;
+    }
 
-        private void SetupFilters()
+    private void SetupDateFilters()
+    {
+        switch (Properties.DateFilterType)
         {
-            SetupDateFilters();
-            SetupJobFilter();
-            SetDateFilterVisibility(Properties.ShowDateFilter);
-            SetJobFilterVisibility(Properties.ShowJobFilter);
+            case DateFilterType.Today:
+                From = DateTime.Today;
+                To = DateTime.Today;
+                break;
+            case DateFilterType.Yesterday:
+                From = DateTime.Today.AddDays(-1);
+                To = DateTime.Today.AddDays(-1);
+                break;
+            case DateFilterType.Week:
+                From = DateTime.Today.AddDays(-WeekDay(DateTime.Today));
+                To = DateTime.Today;
+                break;
+            case DateFilterType.SevenDays:
+                From = DateTime.Today.AddDays(-6);
+                To = DateTime.Today;
+                break;
+            case DateFilterType.Month:
+                From = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
+                To = DateTime.Today;
+                break;
+            case DateFilterType.ThirtyDays:
+                From = DateTime.Today.AddDays(-29);
+                To = DateTime.Today;
+                break;
+            case DateFilterType.Year:
+                From = new DateTime(DateTime.Today.Year, 1, 1);
+                To = DateTime.Today;
+                break;
+            case DateFilterType.TwelveMonths:
+                From = DateTime.Today.AddYears(-1).AddDays(1);
+                To = DateTime.Today;
+                break;
+            case DateFilterType.Custom:
+                From = Properties.FromDate;
+                To = Properties.ToDate;
+                break;
         }
 
-        #region Categories
+        DateTypeBox.SelectedValue = Properties.DateFilterType;
 
-        private static Dictionary<string, Tuple<Type, Type>>? FormInstanceTypes;
+        FromPicker.SelectedDate = From;
+        ToPicker.SelectedDate = To;
 
-        private static readonly Dictionary<Type, List<Tuple<string, string>>> parentColumns = new()
-        {
-            { typeof(Kanban), new() { new("Parent.Number", "Task No") } },
-            { typeof(Job), new() { new("Parent.JobNumber", "Job No") } },
-            { typeof(JobITP), new() { new("Parent.Code", "Code") } },
-            { typeof(Assignment), new() { new("Parent.Number", "Ass. No") } },
-            { typeof(TimeSheet), new() { } },
-            { typeof(LeaveRequest), new() { } },
-            { typeof(Employee), new() { new("Parent.Code", "Employee") } },
-            { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } },
-        };
+        var enabledPicker = Properties.DateFilterType == DateFilterType.Custom;
+        FromPicker.IsEnabled = enabledPicker;
+        ToPicker.IsEnabled = enabledPicker;
+    }
+
+    private void SetupJobFilter()
+    {
+        JobBox.SelectedValue = Properties.JobID;
+    }
+
+    private void SetupFilters()
+    {
+        SetupDateFilters();
+        SetupJobFilter();
+        SetDateFilterVisibility(Properties.ShowDateFilter);
+        SetJobFilterVisibility(Properties.ShowJobFilter);
+    }
+
+    #region Categories
 
-        private static bool CategoryToType(string category, [NotNullWhen(true)] out Type? formType, [NotNullWhen(true)] out Type? parentType)
+    private static Dictionary<string, Tuple<Type, Type>>? FormInstanceTypes;
+
+    private static readonly Dictionary<Type, List<Tuple<string, string>>> parentColumns = new()
+    {
+        { typeof(Kanban), new() { new("Parent.Number", "Task No") } },
+        { typeof(Job), new() { new("Parent.JobNumber", "Job No") } },
+        { typeof(JobITP), new() { new("Parent.Code", "Code") } },
+        { typeof(Assignment), new() { new("Parent.Number", "Ass. No") } },
+        { typeof(TimeSheet), new() { } },
+        { typeof(LeaveRequest), new() { } },
+        { typeof(Employee), new() { new("Parent.Code", "Employee") } },
+        { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } },
+    };
+
+    private static bool CategoryToType(string category, [NotNullWhen(true)] out Type? formType, [NotNullWhen(true)] out Type? parentType)
+    {
+        FormInstanceTypes ??= CoreUtils.TypeList(
+            AppDomain.CurrentDomain.GetAssemblies(),
+            x => !x.IsAbstract && x.GetInterfaces().Contains(typeof(IDigitalFormInstance))
+        ).Select(x =>
         {
-            FormInstanceTypes ??= CoreUtils.TypeList(
-                AppDomain.CurrentDomain.GetAssemblies(),
-                x => !x.IsAbstract && x.GetInterfaces().Contains(typeof(IDigitalFormInstance))
-            ).Select(x =>
+            var inter = x.GetInterfaces()
+                .Where(x => x.IsGenericType && x.GetGenericTypeDefinition().Equals(typeof(IDigitalFormInstance<>))).FirstOrDefault();
+            if (inter is not null)
             {
-                var inter = x.GetInterfaces()
-                    .Where(x => x.IsGenericType && x.GetGenericTypeDefinition().Equals(typeof(IDigitalFormInstance<>))).FirstOrDefault();
-                if (inter is not null)
+                var link = inter.GenericTypeArguments[0];
+                var entityLinkDef = link.GetSuperclassDefinition(typeof(EntityLink<>));
+                if (entityLinkDef is not null)
                 {
-                    var link = inter.GenericTypeArguments[0];
-                    var entityLinkDef = link.GetSuperclassDefinition(typeof(EntityLink<>));
-                    if (entityLinkDef is not null)
-                    {
-                        var entityType = entityLinkDef.GenericTypeArguments[0];
-                        return new Tuple<string, Type, Type>(entityType.Name, x, entityType);
-                    }
+                    var entityType = entityLinkDef.GenericTypeArguments[0];
+                    return new Tuple<string, Type, Type>(entityType.Name, x, entityType);
                 }
-                return null;
-            }).Where(x => x is not null).ToDictionary(x => x!.Item1, x => new Tuple<Type, Type>(x!.Item2, x!.Item3));
-
-            if (!FormInstanceTypes.TryGetValue(category, out var result))
-            {
-                formType = null;
-                parentType = null;
-                return false;
             }
-            formType = result.Item1;
-            parentType = result.Item2;
-            return true;
-        }
+            return null;
+        }).Where(x => x is not null).ToDictionary(x => x!.Item1, x => new Tuple<Type, Type>(x!.Item2, x!.Item3));
 
-        private void SetCategory(string? category)
+        if (!FormInstanceTypes.TryGetValue(category, out var result))
         {
-            CustomFilter = null;
-            CustomFilterIndex = null;
-            if (category is null || !CategoryToType(category, out var formType, out var parentType))
-            {
-                IsEntityForm = false;
-                ParentType = null;
-                FormType = null;
-                return;
-            }
-            IsEntityForm = formType.IsSubclassOfRawGeneric(typeof(EntityForm<,,>));
-            ParentType = parentType;
-            FormType = formType;
+            formType = null;
+            parentType = null;
+            return false;
         }
+        formType = result.Item1;
+        parentType = result.Item2;
+        return true;
+    }
 
-        #endregion
-
-        private string GetJobLink(string prefix, Type type)
+    private void SetCategory(string? category)
+    {
+        CustomFilter = null;
+        CustomFilterIndex = null;
+        if (category is null || !CategoryToType(category, out var formType, out var parentType))
         {
-            var props = type.GetProperties().Where(x =>
-                x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType &&
-                x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>));
-            foreach (var prop in props)
-            {
-                if (prop.PropertyType == typeof(JobLink))
-                    return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name;
-                var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType);
-                if (!string.IsNullOrEmpty(result))
-                    return result;
-            }
-
-            return "";
+            IsEntityForm = false;
+            ParentType = null;
+            FormType = null;
+            return;
         }
+        IsEntityForm = formType.IsSubclassOfRawGeneric(typeof(EntityForm<,,>));
+        ParentType = parentType;
+        FormType = formType;
+    }
 
-        /// <summary>
-        /// Find a link from the form type to an associated <see cref="Job"/>, allowing us to filter based on jobs.
-        /// </summary>
-        /// <returns>The property name of the <see cref="JobLink"/>.</returns>
-        private string GetJobLink<T>() where T : IDigitalFormInstance
-            => GetJobLink("", typeof(T));
+    #endregion
 
-        private IKeyedQueryDef GetFormQuery<T>()
-            where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
+    private string GetJobLink(string prefix, Type type)
+    {
+        var props = type.GetProperties().Where(x =>
+            x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType &&
+            x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>));
+        foreach (var prop in props)
         {
-            var sort = LookupFactory.DefineSort<T>();
+            if (prop.PropertyType == typeof(JobLink))
+                return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name;
+            var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType);
+            if (!string.IsNullOrEmpty(result))
+                return result;
+        }
 
-            var jobLink = GetJobLink<T>();
+        return "";
+    }
 
-            var completedFilter = new Filter<T>(x => x.FormCompleted).IsGreaterThanOrEqualTo(From)
-                .And(x => x.FormCompleted).IsLessThan(To.AddDays(1));
-            if (Properties.ShowIncompleteForms)
-            {
-                completedFilter.Or(x => x.FormCompleted).IsEqualTo(FilterConstant.Null);
-            }
+    /// <summary>
+    /// Find a link from the form type to an associated <see cref="Job"/>, allowing us to filter based on jobs.
+    /// </summary>
+    /// <returns>The property name of the <see cref="JobLink"/>.</returns>
+    private string GetJobLink<T>() where T : IDigitalFormInstance
+        => GetJobLink("", typeof(T));
 
-            var filter = new Filter<T>(x => x.Form.ID).IsEqualTo(Form!.ID)
-                .And(x => x.FormCancelled).IsEqualTo(DateTime.MinValue)
-                .And(completedFilter);
+    private IKeyedQueryDef GetFormQuery<T>()
+        where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
+    {
+        var sort = LookupFactory.DefineSort<T>();
 
-            if (Properties.JobID != Guid.Empty && Properties.ShowJobFilter)
-            {
-                filter.And(jobLink + ".ID").IsEqualTo(Properties.JobID);
-            }
-            if (CustomFilter is not null)
-            {
-                filter.And(CustomFilter);
-            }
+        var jobLink = GetJobLink<T>();
 
-            var columns = Columns.None<T>().Add(x => x.ID)
-                .Add(x => x.Number)
-                .Add(x => x.Description)
-                .Add(x => x.CreatedBy)
-                .Add(x => x.Created)
-                .Add(x => x.Form.ID)
-                .Add(x => x.FormData)
-                .Add(x => x.FormStarted)
-                .Add(x => x.FormCompleted)
-                .Add(x => x.FormCompletedBy.UserID)
-                .Add(x => x.Location.Timestamp)
-                .Add(x => x.Location.Latitude)
-                .Add(x => x.Location.Longitude);
-
-            if (IsEntityForm)
-                columns.Add(x => x.FormProcessed); //"Processed");
-
-            var parentcols = LookupFactory.DefineColumns(ParentType!);
-            foreach (var col in parentcols.ColumnNames())
-                columns.Add("Parent." + col);
-            if (parentColumns.TryGetValue(ParentType!, out var pColumns))
-            {
-                foreach (var (field, name) in pColumns)
-                {
-                    columns.Add(field);
-                }
-            }
+        var completedFilter = new Filter<T>(x => x.FormCompleted).IsGreaterThanOrEqualTo(From)
+            .And(x => x.FormCompleted).IsLessThan(To.AddDays(1));
+        if (Properties.ShowIncompleteForms)
+        {
+            completedFilter.Or(x => x.FormCompleted).IsEqualTo(FilterConstant.Null);
+        }
 
-            if (!string.IsNullOrWhiteSpace(jobLink))
-                columns.Add(jobLink + ".ID");
+        var filter = new Filter<T>(x => x.Form.ID).IsEqualTo(Form!.ID)
+            .And(x => x.FormCancelled).IsEqualTo(DateTime.MinValue)
+            .And(completedFilter);
 
-            return new KeyedQueryDef<T>(filter, columns, sort);
+        if (Properties.JobID != Guid.Empty && Properties.ShowJobFilter)
+        {
+            filter.And(jobLink + ".ID").IsEqualTo(Properties.JobID);
+        }
+        if (CustomFilter is not null)
+        {
+            filter.And(CustomFilter);
         }
 
-        #endregion
-
-        private void LoadDataIntoGrid(List<DigitalFormVariable> variables, List<QAQuestion> questions, CoreTable formData, List<string> additionalColumns, CoreTable? jobITPs)
-        {
-            var data = new DataTable();
-            data.Columns.Add("ID", typeof(Guid));
-            data.Columns.Add("Form_ID", typeof(Guid));
-            data.Columns.Add("Parent_ID", typeof(Guid));
-            data.Columns.Add("Location_Timestamp", typeof(DateTime));
-            data.Columns.Add("Location_Latitude", typeof(double));
-            data.Columns.Add("Location_Longitude", typeof(double));
-            data.Columns.Add("FormData", typeof(string));
-            data.Columns.Add("Number", typeof(string));
-            data.Columns.Add("Description", typeof(string));
-            
-            if (ParentType == typeof(JobITP))
+        var columns = Columns.None<T>().Add(x => x.ID)
+            .Add(x => x.Number)
+            .Add(x => x.Description)
+            .Add(x => x.CreatedBy)
+            .Add(x => x.Created)
+            .Add(x => x.Form.ID)
+            .Add(x => x.FormData)
+            .Add(x => x.FormStarted)
+            .Add(x => x.FormCompleted)
+            .Add(x => x.FormCompletedBy.UserID)
+            .Add(x => x.Location.Timestamp)
+            .Add(x => x.Location.Latitude)
+            .Add(x => x.Location.Longitude);
+
+        if (IsEntityForm)
+            columns.Add(x => x.FormProcessed); //"Processed");
+
+        var parentcols = LookupFactory.DefineColumns(ParentType!);
+        foreach (var col in parentcols.ColumnNames())
+            columns.Add("Parent." + col);
+        if (parentColumns.TryGetValue(ParentType!, out var pColumns))
+        {
+            foreach (var (field, name) in pColumns)
             {
-                data.Columns.Add("Job No", typeof(string));
+                columns.Add(field);
             }
-            if (parentColumns.TryGetValue(ParentType!, out var pColumns))
+        }
+
+        if (!string.IsNullOrWhiteSpace(jobLink))
+            columns.Add(jobLink + ".ID");
+
+        return new KeyedQueryDef<T>(filter, columns, sort);
+    }
+
+    #endregion
+
+    private void LoadDataIntoGrid(List<DigitalFormVariable> variables, List<QAQuestion> questions, CoreTable formData, List<string> additionalColumns, CoreTable? jobITPs)
+    {
+        var data = new DataTable();
+        data.Columns.Add("ID", typeof(Guid));
+        data.Columns.Add("Form_ID", typeof(Guid));
+        data.Columns.Add("Parent_ID", typeof(Guid));
+        data.Columns.Add("Location_Timestamp", typeof(DateTime));
+        data.Columns.Add("Location_Latitude", typeof(double));
+        data.Columns.Add("Location_Longitude", typeof(double));
+        data.Columns.Add("FormData", typeof(string));
+        data.Columns.Add("Number", typeof(string));
+        data.Columns.Add("Description", typeof(string));
+        
+        if (ParentType == typeof(JobITP))
+        {
+            data.Columns.Add("Job No", typeof(string));
+        }
+        if (parentColumns.TryGetValue(ParentType!, out var pColumns))
+        {
+            foreach (var (field, name) in pColumns)
             {
-                foreach (var (field, name) in pColumns)
-                {
-                    data.Columns.Add(name, typeof(string));
-                }
+                data.Columns.Add(name, typeof(string));
             }
+        }
 
-            data.Columns.Add("Parent_Description", typeof(string));
-            data.Columns.Add("Created", typeof(DateTime));
-            data.Columns.Add("Created By", typeof(string));
-            data.Columns.Add("Completed", typeof(DateTime));
-            data.Columns.Add("Completed By", typeof(string));
-            if (IsEntityForm)
-                data.Columns.Add("Processed", typeof(bool));
+        data.Columns.Add("Parent_Description", typeof(string));
+        data.Columns.Add("Created", typeof(DateTime));
+        data.Columns.Add("Created By", typeof(string));
+        data.Columns.Add("Completed", typeof(DateTime));
+        data.Columns.Add("Completed By", typeof(string));
+        if (IsEntityForm)
+            data.Columns.Add("Processed", typeof(bool));
 
-            if (variables.Any())
+        if (variables.Any())
+        {
+            foreach (var variable in variables)
             {
-                foreach (var variable in variables)
+                var code = variable.Code.Replace("/", " ");
+                QuestionCodes[code] = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(code.ToLower());
+                try
                 {
-                    var code = variable.Code.Replace("/", " ");
-                    QuestionCodes[code] = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(code.ToLower());
-                    try
-                    {
-                        data.Columns.Add(code, typeof(string));
-                    }
-                    catch (DuplicateNameException)
-                    {
-                        MessageBox.Show($"Error: duplicate variable code {code}");
-                    }
+                    data.Columns.Add(code, typeof(string));
+                }
+                catch (DuplicateNameException)
+                {
+                    MessageBox.Show($"Error: duplicate variable code {code}");
                 }
             }
-            else if (questions.Any())
+        }
+        else if (questions.Any())
+        {
+            Questions = questions;
+            Progress.SetMessage("Loading Checks");
+            QAGrid.Clear();
+            QAGrid.LoadChecks(Form!.Description, Questions, new Dictionary<Guid, object>());
+            QAGrid.CollapseMargins();
+
+            var i = 1;
+            foreach (var question in Questions)
             {
-                Questions = questions;
-                Progress.SetMessage("Loading Checks");
-                QAGrid.Clear();
-                QAGrid.LoadChecks(Form!.Description, Questions, new Dictionary<Guid, object>());
-                QAGrid.CollapseMargins();
-
-                var i = 1;
-                foreach (var question in Questions)
+                var id = question.ID.ToString();
+                if (!question.Answer.Equals(QAAnswer.Comment))
                 {
-                    var id = question.ID.ToString();
-                    if (!question.Answer.Equals(QAAnswer.Comment))
-                    {
-                        data.Columns.Add(id, typeof(string));
-                        var code = question.Code;
-                        QuestionCodes[id] = string.IsNullOrEmpty(code) ? string.Format("{0}.", i) : code;
-                        i++;
-                    }
+                    data.Columns.Add(id, typeof(string));
+                    var code = question.Code;
+                    QuestionCodes[id] = string.IsNullOrEmpty(code) ? string.Format("{0}.", i) : code;
+                    i++;
                 }
             }
+        }
 
-            foreach (var row in formData.Rows)
-            {
-                var form = (row.ToObject(FormType!) as IDigitalFormInstance)!;
-                if (true) //(!string.IsNullOrWhiteSpace(form.FormData))
+        foreach (var row in formData.Rows)
+        {
+            var form = (row.ToObject(FormType!) as IDigitalFormInstance)!;
+            if (true) //(!string.IsNullOrWhiteSpace(form.FormData))
+            {
+                var dataRow = data.NewRow();
+
+                dataRow["ID"] = form.ID;
+                dataRow["Form_ID"] = form.Form.ID;
+                dataRow["Parent_ID"] = form.ParentID();
+                dataRow["Location_Timestamp"] = form.Location.Timestamp;
+                dataRow["Location_Latitude"] = form.Location.Latitude;
+                dataRow["Location_Longitude"] = form.Location.Longitude;
+                dataRow["FormData"] = form.FormData;
+                dataRow["Number"] = form.Number;
+                dataRow["Description"] = form.Description;
+                
+
+                var desc = new List<string>();
+                foreach (var col in additionalColumns)
                 {
-                    var dataRow = data.NewRow();
-
-                    dataRow["ID"] = form.ID;
-                    dataRow["Form_ID"] = form.Form.ID;
-                    dataRow["Parent_ID"] = form.ParentID();
-                    dataRow["Location_Timestamp"] = form.Location.Timestamp;
-                    dataRow["Location_Latitude"] = form.Location.Latitude;
-                    dataRow["Location_Longitude"] = form.Location.Longitude;
-                    dataRow["FormData"] = form.FormData;
-                    dataRow["Number"] = form.Number;
-                    dataRow["Description"] = form.Description;
-                    
-
-                    var desc = new List<string>();
-                    foreach (var col in additionalColumns)
-                    {
-                        var val = row[col];
-                        if (val != null && val is not Guid)
-                            desc.Add(val.ToString() ?? "");
-                    }
+                    var val = row[col];
+                    if (val != null && val is not Guid)
+                        desc.Add(val.ToString() ?? "");
+                }
 
-                    dataRow["Parent_Description"] = string.Join(" : ", desc);
+                dataRow["Parent_Description"] = string.Join(" : ", desc);
 
-                    dataRow["Created"] = (form as Entity)!.Created.IsEmpty()
-                        ? form.FormStarted
-                        : (form as Entity)!.Created;
-                    dataRow["Created By"] = (form as Entity)!.CreatedBy;
-                    dataRow["Completed"] = form.FormCompleted;
-                    dataRow["Completed By"] = form.FormCompletedBy.UserID;
+                dataRow["Created"] = (form as Entity)!.Created.IsEmpty()
+                    ? form.FormStarted
+                    : (form as Entity)!.Created;
+                dataRow["Created By"] = (form as Entity)!.CreatedBy;
+                dataRow["Completed"] = form.FormCompleted;
+                dataRow["Completed By"] = form.FormCompletedBy.UserID;
 
-                    if (IsEntityForm)
-                        dataRow["Processed"] = form.FormProcessed > DateTime.MinValue;
+                if (IsEntityForm)
+                    dataRow["Processed"] = form.FormProcessed > DateTime.MinValue;
 
-                    if (ParentType == typeof(JobITP))
+                if (ParentType == typeof(JobITP))
+                {
+                    var jobITP = jobITPs!.Rows.FirstOrDefault(x => x.Get<JobITP, Guid>(x => x.ID) == form.ParentID());
+                    if (jobITP is not null)
                     {
-                        var jobITP = jobITPs!.Rows.FirstOrDefault(x => x.Get<JobITP, Guid>(x => x.ID) == form.ParentID());
-                        if (jobITP is not null)
-                        {
-                            var jobID = jobITP.Get<JobITP, Guid>(x => x.Job.ID);
-                            dataRow["Job No"] = Jobs.Where(x => x.ID == jobID).FirstOrDefault()?.JobNumber;
-                        }
+                        var jobID = jobITP.Get<JobITP, Guid>(x => x.Job.ID);
+                        dataRow["Job No"] = Jobs.Where(x => x.ID == jobID).FirstOrDefault()?.JobNumber;
                     }
+                }
 
-                    if (pColumns != null)
+                if (pColumns != null)
+                {
+                    foreach (var (field, name) in pColumns)
                     {
-                        foreach (var (field, name) in pColumns)
-                        {
-                            dataRow[name] = row[field]?.ToString();
-                        }
+                        dataRow[name] = row[field]?.ToString();
                     }
+                }
 
-                    //datarow["Job No"] = (String)row[JobLink + ".JobNumber"];
+                //datarow["Job No"] = (String)row[JobLink + ".JobNumber"];
 
-                    var bHasData = false;
-                    if (variables.Any())
+                var bHasData = false;
+                if (variables.Any())
+                {
+                    var dict = Serialization.Deserialize<Dictionary<string, object?>>(form.FormData);
+                    if (dict is not null)
                     {
-                        var dict = Serialization.Deserialize<Dictionary<string, object?>>(form.FormData);
-                        if (dict is not null)
+                        var storage = new DFLoadStorage(dict, null);
+                        foreach (var variable in variables)
                         {
-                            var storage = new DFLoadStorage(dict, null);
-                            foreach (var variable in variables)
+                            var value = variable.Deserialize(storage.GetEntry(variable.Code));
+                            var format = variable.FormatValue(value);
+                            var sKey = variable.Code.Replace("/", " ");
+                            if (data.Columns.Contains(sKey))
                             {
-                                var value = variable.Deserialize(storage.GetEntry(variable.Code));
-                                var format = variable.FormatValue(value);
-                                var sKey = variable.Code.Replace("/", " ");
-                                if (data.Columns.Contains(sKey))
-                                {
-                                    dataRow[sKey] = format;
-                                    bHasData = true;
-                                }
+                                dataRow[sKey] = format;
+                                bHasData = true;
                             }
                         }
                     }
-                    else
-                    {
-                        var dict = Serialization.Deserialize<Dictionary<Guid, object>>(form.FormData);
-                        if (dict is not null)
-                            foreach (var key in dict.Keys)
-                                if (data.Columns.Contains(key.ToString()))
-                                {
-                                    dataRow[key.ToString()] = dict[key];
-                                    bHasData = true;
-                                }
-                    }
-
-
-                    //if (bHasData)
-                    data.Rows.Add(dataRow);
                 }
-            }
-            DataGrid.ItemsSource = data;
-
-            IsQAForm = !variables.Any() && questions.Any();
-            QAGrid.Visibility = IsQAForm ? Visibility.Visible : Visibility.Collapsed;
-            DataGrid.Visibility = Visibility.Visible;
-        }
+                else
+                {
+                    var dict = Serialization.Deserialize<Dictionary<Guid, object>>(form.FormData);
+                    if (dict is not null)
+                        foreach (var key in dict.Keys)
+                            if (data.Columns.Contains(key.ToString()))
+                            {
+                                dataRow[key.ToString()] = dict[key];
+                                bHasData = true;
+                            }
+                }
 
-        private void RefreshData<TForm>()
-            where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
-        {
-            var formQuery = GetFormQuery<TForm>();
 
-            var queries = new List<IKeyedQueryDef>()
-                {
-                    new KeyedQueryDef<QAQuestion>(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID)),
-                    new KeyedQueryDef<DigitalFormVariable>(
-                        new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(Form.ID),
-                        null,
-                        new SortOrder<DigitalFormVariable>(x => x.Sequence)),
-                    formQuery
-                };
-            if (ParentType == typeof(JobITP))
-            {
-                queries.Add(new KeyedQueryDef<JobITP>(
-                    new Filter<JobITP>(x => x.ID).InQuery((formQuery.Filter as Filter<JobITPForm>)!, x => x.Parent.ID),
-                    Columns.None<JobITP>().Add(x => x.ID, x => x.Job.JobNumber)));
+                //if (bHasData)
+                data.Rows.Add(dataRow);
             }
+        }
+        DataGrid.ItemsSource = data;
 
-            var results = Client.QueryMultiple(queries);
+        IsQAForm = !variables.Any() && questions.Any();
+        QAGrid.Visibility = IsQAForm ? Visibility.Visible : Visibility.Collapsed;
+        DataGrid.Visibility = Visibility.Visible;
+    }
 
-            var questions = results.Get<QAQuestion>().ToObjects<QAQuestion>().ToList();
-            var variables = results.Get<DigitalFormVariable>().ToObjects<DigitalFormVariable>().ToList();
-            var formData = results.Get(formQuery.Key);
+    private void RefreshData<TForm>()
+        where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
+    {
+        var formQuery = GetFormQuery<TForm>();
 
-            LoadDataIntoGrid(
-                variables, questions,
-                formData,
-                formQuery.Columns!.ColumnNames().Where(x => x.StartsWith("Parent.")).ToList(),
-                ParentType == typeof(JobITP) ? results.Get<JobITP>() : null);
+        var queries = new List<IKeyedQueryDef>()
+            {
+                new KeyedQueryDef<QAQuestion>(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID)),
+                new KeyedQueryDef<DigitalFormVariable>(
+                    new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(Form.ID),
+                    null,
+                    new SortOrder<DigitalFormVariable>(x => x.Sequence)),
+                formQuery
+            };
+        if (ParentType == typeof(JobITP))
+        {
+            queries.Add(new KeyedQueryDef<JobITP>(
+                new Filter<JobITP>(x => x.ID).InQuery((formQuery.Filter as Filter<JobITPForm>)!, x => x.Parent.ID),
+                Columns.None<JobITP>().Add(x => x.ID, x => x.Job.JobNumber)));
         }
 
-        public void Refresh()
-        {
-            if (!IsSetup) return;
+        var results = Client.QueryMultiple(queries);
 
-            Questions.Clear();
-            QAGrid.Clear();
-            QAGrid.LoadChecks("", Array.Empty<QAQuestion>(), new Dictionary<Guid, object>());
-            DataGrid.ItemsSource = null;
+        var questions = results.Get<QAQuestion>().ToObjects<QAQuestion>().ToList();
+        var variables = results.Get<DigitalFormVariable>().ToObjects<DigitalFormVariable>().ToList();
+        var formData = results.Get(formQuery.Key);
 
-            if (ParentType is null || FormType is null || Form is null || Form.ID == Guid.Empty)
-            {
-                QAGrid.Visibility = Visibility.Collapsed;
-                DataGrid.Visibility = Visibility.Collapsed;
-                return;
-            }
+        LoadDataIntoGrid(
+            variables, questions,
+            formData,
+            formQuery.Columns!.ColumnNames().Where(x => x.StartsWith("Parent.")).ToList(),
+            ParentType == typeof(JobITP) ? results.Get<JobITP>() : null);
+    }
 
-            var refreshMethod = typeof(DigitalFormsDashboard).GetMethod(nameof(RefreshData), BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType);
-            refreshMethod.Invoke(this, Array.Empty<object?>());
-        }
+    public void Refresh()
+    {
+        if (!IsSetup) return;
+
+        Questions.Clear();
+        QAGrid.Clear();
+        QAGrid.LoadChecks("", Array.Empty<QAQuestion>(), new Dictionary<Guid, object>());
+        DataGrid.ItemsSource = null;
 
-        public void Shutdown(CancelEventArgs? cancel)
+        if (ParentType is null || FormType is null || Form is null || Form.ID == Guid.Empty)
         {
+            QAGrid.Visibility = Visibility.Collapsed;
+            DataGrid.Visibility = Visibility.Collapsed;
+            return;
         }
 
-        #region DataGrid Configuration
+        var refreshMethod = typeof(DigitalFormsDashboard).GetMethod(nameof(RefreshData), BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType);
+        refreshMethod.Invoke(this, Array.Empty<object?>());
+    }
 
-        private class RowStyleSelector : StyleSelector
-        {
-            private Style NormalStyle;
+    public void Shutdown(CancelEventArgs? cancel)
+    {
+    }
 
-            private Style IncompleteStyle;
+    #region DataGrid Configuration
 
-            public RowStyleSelector()
-            {
-                NormalStyle = new Style();
-                NormalStyle.Setters.Add(new Setter(VirtualizingCellsControl.BackgroundProperty, new SolidColorBrush(Colors.White)));
+    private class RowStyleSelector : StyleSelector
+    {
+        private Style NormalStyle;
 
-                IncompleteStyle = new Style();
-                IncompleteStyle.Setters.Add(new Setter(VirtualizingCellsControl.BackgroundProperty, new SolidColorBrush(Colors.LightSalmon)));
-            }
+        private Style IncompleteStyle;
 
-            public override Style SelectStyle(object item, DependencyObject container)
-            {
-                var row = (item as DataRowBase)?.RowData as DataRowView;
-                if (row is null) return NormalStyle;
+        public RowStyleSelector()
+        {
+            NormalStyle = new Style();
+            NormalStyle.Setters.Add(new Setter(VirtualizingCellsControl.BackgroundProperty, new SolidColorBrush(Colors.White)));
 
-                return (DateTime)row["Completed"] == DateTime.MinValue
-                    ? IncompleteStyle
-                    : NormalStyle;
-            }
+            IncompleteStyle = new Style();
+            IncompleteStyle.Setters.Add(new Setter(VirtualizingCellsControl.BackgroundProperty, new SolidColorBrush(Colors.LightSalmon)));
         }
 
-        private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
+        public override Style SelectStyle(object item, DependencyObject container)
         {
-            e.Column.TextAlignment = TextAlignment.Center;
-            e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
-            e.Column.ColumnSizer = GridLengthUnitType.None;
-            e.Column.ImmediateUpdateColumnFilter = true;
-            e.Column.FilterRowCondition = FilterRowCondition.Contains;
-            e.Column.FilterRowOptionsVisibility = Visibility.Collapsed;
+            var row = (item as DataRowBase)?.RowData as DataRowView;
+            if (row is null) return NormalStyle;
 
-            var value = (e.Column.ValueBinding as Binding)!;
-            if (value.Path.Path.Equals("ID") || value.Path.Path.Equals("Form_ID") || value.Path.Path.Equals("Parent_ID") ||
-                value.Path.Path.Equals("FormData") || value.Path.Path.Equals("Location_Latitude") || value.Path.Path.Equals("Location_Longitude"))
-            {
-                e.Cancel = true;
-            }
-            else if (value.Path.Path.Equals("Number"))
-            {
-                e.Column.Width = 80;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Description"))
-            {
-                e.Column.Width = 250;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-                e.Column.TextAlignment = TextAlignment.Left;
-                e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Left;
-            }
-            else if (value.Path.Path.Equals("Location_Timestamp"))
-            {
-                e.Column = new GridImageColumn();
-                e.Column.Width = DataGrid.RowHeight;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-                e.Column.HeaderText = "";
-                e.Column.Padding = new Thickness(4);
-                e.Column.ValueBinding = new Binding
-                {
-                    Path = new PropertyPath(value.Path.Path),
-                    Converter = new MileStoneImageConverter()
-                };
-                e.Column.MappingName = "Location.Timestamp";
-            }
-            else if (ParentType is not null && parentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.Any(x => x.Item2.Equals(value.Path.Path)))
-            {
-                e.Column.ColumnSizer = GridLengthUnitType.Auto;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Job No"))
-            {
-                e.Column.Width = 60;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Parent_Description"))
-            {
-                e.Column.HeaderText = Categories.FirstOrDefault(x => x.Item2 == FormType)?.Item3 ?? "Parent";
-                e.Column.TextAlignment = TextAlignment.Left;
-                e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Left;
-                e.Column.Width = 250;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Completed"))
-            {
-                e.Column.Width = 100;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-                (e.Column as GridDateTimeColumn)!.DisplayBinding = new Binding {
-                    Path = new PropertyPath(value.Path.Path),
-                    Converter = new DateTimeToStringConverter("dd MMM yy hh:mm")
-                };
-            }
-            else if (value.Path.Path.Equals("Completed By"))
-            {
-                e.Column.Width = 100;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Processed"))
-            {
-                e.Column.Width = 100;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Created By"))
-            {
-                e.Column.Width = 100;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            }
-            else if (value.Path.Path.Equals("Created"))
-            {
-                e.Column.Width = 100;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-                (e.Column as GridDateTimeColumn)!.DisplayBinding = new Binding {
-                    Path = new PropertyPath(value.Path.Path),
-                    Converter = new DateTimeToStringConverter("dd MMM yy hh:mm")
-                };
-            }
-            else
-            {
-                var data = DataGrid.ItemsSource as DataTable;
-                //int index = data.Columns.IndexOf(e.Column.MappingName) - 2;
-                //Style style = new Style(typeof(GridCell));
-                //e.Column.CellStyle = style;
-                e.Column.Width = 100;
-                e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-                e.Column.HeaderText = QuestionCodes[e.Column.MappingName];
-            }
+            return (DateTime)row["Completed"] == DateTime.MinValue
+                ? IncompleteStyle
+                : NormalStyle;
         }
+    }
 
-        private Entity? GetEntityForm<T>(Guid id) where T : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
+    private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
+    {
+        e.Column.TextAlignment = TextAlignment.Center;
+        e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
+        e.Column.ColumnSizer = GridLengthUnitType.None;
+        e.Column.ImmediateUpdateColumnFilter = true;
+        e.Column.FilterRowCondition = FilterRowCondition.Contains;
+        e.Column.FilterRowOptionsVisibility = Visibility.Collapsed;
+
+        var value = (e.Column.ValueBinding as Binding)!;
+        if (value.Path.Path.Equals("ID") || value.Path.Path.Equals("Form_ID") || value.Path.Path.Equals("Parent_ID") ||
+            value.Path.Path.Equals("FormData") || value.Path.Path.Equals("Location_Latitude") || value.Path.Path.Equals("Location_Longitude"))
         {
-            var columns = DynamicFormEditWindow.FormColumns<T>();
-
-            return new Client<T>().Query(
-                new Filter<T>(x => x.ID).IsEqualTo(id),
-                columns).Rows.FirstOrDefault()?.ToObject<T>();
+            e.Cancel = true;
         }
-
-        private void DataGrid_CellDoubleTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellDoubleTappedEventArgs e)
+        else if (value.Path.Path.Equals("Number"))
+        {
+            e.Column.Width = 80;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Description"))
+        {
+            e.Column.Width = 250;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            e.Column.TextAlignment = TextAlignment.Left;
+            e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Left;
+        }
+        else if (value.Path.Path.Equals("Location_Timestamp"))
+        {
+            e.Column = new GridImageColumn();
+            e.Column.Width = DataGrid.RowHeight;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            e.Column.HeaderText = "";
+            e.Column.Padding = new Thickness(4);
+            e.Column.ValueBinding = new Binding
+            {
+                Path = new PropertyPath(value.Path.Path),
+                Converter = new MileStoneImageConverter()
+            };
+            e.Column.MappingName = "Location.Timestamp";
+        }
+        else if (ParentType is not null && parentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.Any(x => x.Item2.Equals(value.Path.Path)))
+        {
+            e.Column.ColumnSizer = GridLengthUnitType.Auto;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Job No"))
+        {
+            e.Column.Width = 60;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Parent_Description"))
+        {
+            e.Column.HeaderText = Categories.FirstOrDefault(x => x.Item2 == FormType)?.Item3 ?? "Parent";
+            e.Column.TextAlignment = TextAlignment.Left;
+            e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Left;
+            e.Column.Width = 250;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Completed"))
         {
-            if (e.RowColumnIndex.RowIndex < 2)
-                return;
-            var rowOffset = -2;
+            e.Column.Width = 100;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            (e.Column as GridDateTimeColumn)!.DisplayBinding = new Binding {
+                Path = new PropertyPath(value.Path.Path),
+                Converter = new DateTimeToStringConverter("dd MMM yy hh:mm")
+            };
+        }
+        else if (value.Path.Path.Equals("Completed By"))
+        {
+            e.Column.Width = 100;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Processed"))
+        {
+            e.Column.Width = 100;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Created By"))
+        {
+            e.Column.Width = 100;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+        }
+        else if (value.Path.Path.Equals("Created"))
+        {
+            e.Column.Width = 100;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            (e.Column as GridDateTimeColumn)!.DisplayBinding = new Binding {
+                Path = new PropertyPath(value.Path.Path),
+                Converter = new DateTimeToStringConverter("dd MMM yy hh:mm")
+            };
+        }
+        else
+        {
+            var data = DataGrid.ItemsSource as DataTable;
+            //int index = data.Columns.IndexOf(e.Column.MappingName) - 2;
+            //Style style = new Style(typeof(GridCell));
+            //e.Column.CellStyle = style;
+            e.Column.Width = 100;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            e.Column.HeaderText = QuestionCodes[e.Column.MappingName];
+        }
+    }
 
-            //var table = (DataGrid.ItemsSource as DataTable)!;
-            var row = (e.Record as DataRowView)?.Row;
-            if (row == null)
-            {
-                MessageBox.Show($"Unexpected Record type ({e.Record?.GetType().EntityName() ?? "NULL"}");
-                return;
-            }
-            var formid = (Guid)row["Form_ID"];
-            var formdata = (string)row["FormData"];
-            var id = (Guid)row["ID"];
+    private Entity? GetEntityForm<T>(Guid id) where T : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
+    {
+        var columns = DynamicFormEditWindow.FormColumns<T>();
+
+        return new Client<T>().Query(
+            new Filter<T>(x => x.ID).IsEqualTo(id),
+            columns).Rows.FirstOrDefault()?.ToObject<T>();
+    }
+
+    private void DataGrid_CellDoubleTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellDoubleTappedEventArgs e)
+    {
+        if (e.RowColumnIndex.RowIndex < 2)
+            return;
+        var rowOffset = -2;
 
-            if (FormType is null) return;
+        //var table = (DataGrid.ItemsSource as DataTable)!;
+        var row = (e.Record as DataRowView)?.Row;
+        if (row == null)
+        {
+            MessageBox.Show($"Unexpected Record type ({e.Record?.GetType().EntityName() ?? "NULL"}");
+            return;
+        }
+        var formid = (Guid)row["Form_ID"];
+        var formdata = (string)row["FormData"];
+        var id = (Guid)row["ID"];
+
+        if (FormType is null) return;
 
-            if (IsQAForm)
+        if (IsQAForm)
+        {
+            var values = new Dictionary<Guid, object>();
+            var formData = Serialization.Deserialize<Dictionary<string, object>>(formdata);
+            if (formData is not null)
             {
-                var values = new Dictionary<Guid, object>();
-                var formData = Serialization.Deserialize<Dictionary<string, object>>(formdata);
-                if (formData is not null)
+                foreach (var (idStr, value) in formData)
                 {
-                    foreach (var (idStr, value) in formData)
+                    if (Guid.TryParse(idStr, out var codeID))
                     {
-                        if (Guid.TryParse(idStr, out var codeID))
-                        {
-                            values[codeID] = value;
-                        }
+                        values[codeID] = value;
                     }
                 }
-
-                QAGrid.Clear();
-                QAGrid.LoadChecks(Form!.Description, Questions, values);
-                QAGrid.CollapseMargins();
-                return;
             }
 
-            var entityForm = typeof(DigitalFormsDashboard)
-                .GetMethod(nameof(GetEntityForm), BindingFlags.NonPublic | BindingFlags.Instance)!
-                .MakeGenericMethod(FormType)
-                .Invoke(this, new object[] { id }) as IDigitalFormInstance;
-            if (entityForm is not null)
-            {
-                if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
-                {
-                    dataModel.Update(null);
-                    /*typeof(QADashboard)
-                        .GetMethod(nameof(SaveEntityForm), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
-                        .MakeGenericMethod(formType)
-                        .Invoke(this, new object[] { entityForm });*/
-                    Refresh();
-                }
-            }
+            QAGrid.Clear();
+            QAGrid.LoadChecks(Form!.Description, Questions, values);
+            QAGrid.CollapseMargins();
+            return;
         }
 
-        private void DataGrid_CellTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellTappedEventArgs e)
+        var entityForm = typeof(DigitalFormsDashboard)
+            .GetMethod(nameof(GetEntityForm), BindingFlags.NonPublic | BindingFlags.Instance)!
+            .MakeGenericMethod(FormType)
+            .Invoke(this, new object[] { id }) as IDigitalFormInstance;
+        if (entityForm is not null)
         {
-            if (e.RowColumnIndex.ColumnIndex == 0)
+            if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
             {
-                var timestamp = (DateTime)(e.Record as DataRowView)!.Row["Location_Timestamp"];
-                var latitude = (double)(e.Record as DataRowView)!.Row["Location_Latitude"];
-                var longitude = (double)(e.Record as DataRowView)!.Row["Location_Longitude"];
-
-                var form = new MapForm(latitude, longitude, timestamp);
-                form.ShowDialog();
+                dataModel.Update(null);
+                /*typeof(QADashboard)
+                    .GetMethod(nameof(SaveEntityForm), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
+                    .MakeGenericMethod(formType)
+                    .Invoke(this, new object[] { entityForm });*/
+                Refresh();
             }
         }
+    }
 
-        #endregion
+    private void DataGrid_CellTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellTappedEventArgs e)
+    {
+        if (e.RowColumnIndex.ColumnIndex == 0)
+        {
+            var timestamp = (DateTime)(e.Record as DataRowView)!.Row["Location_Timestamp"];
+            var latitude = (double)(e.Record as DataRowView)!.Row["Location_Latitude"];
+            var longitude = (double)(e.Record as DataRowView)!.Row["Location_Longitude"];
+
+            var form = new MapForm(latitude, longitude, timestamp);
+            form.ShowDialog();
+        }
     }
+
+    #endregion
 }

+ 1 - 16
prs.licensing/Engine/LicensingEngine.cs

@@ -28,9 +28,7 @@ public class LicensingEngine : Engine<LicensingEngineProperties>
             return;
         }
 
-        var transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(Properties.Server, true));
-        ClientFactory.SetClientType(typeof(RpcClient<>), Platform.LicensingEngine, Version, transport);
-        CheckConnection();
+        InitialiseConnection(Properties.Server, Platform.LicensingEngine);
         
         Logger.Send(LogType.Information, "", "Registering Classes");
 
@@ -59,19 +57,6 @@ public class LicensingEngine : Engine<LicensingEngineProperties>
 
     private string CertificateFileName() => Properties.CertificateFile;
 
-    private void CheckConnection()
-    {
-        // Wait for server connection
-        while (!Client.Ping())
-        {
-            Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
-            Task.Delay(30_000).Wait();
-            Logger.Send(LogType.Information, "", "Retrying connection...");
-        }
-
-        ClientFactory.SetBypass();
-    }
-
     public override void Stop()
     {
         Logger.Send(LogType.Information, "", "Stopping");

+ 1 - 32
prs.server/Engines/GPS/GPSEngine.cs

@@ -286,12 +286,7 @@ public class GPSEngine : Engine<GPSServerProperties>
         ComalUtils.RegisterClasses();
         PRSSharedUtils.RegisterClasses();
 
-        //ClientFactory.SetClientType(typeof(IPCClient<>), Platform.GPSEngine, Version, DatabaseServerProperties.GetPipeName(Properties.Server,false));
-        transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(Properties.Server, true));
-        ClientFactory.SetClientType(typeof(RpcClient<>), Platform.GPSEngine, Version, transport);
-        transport.OnClose += Transport_OnClose;
-        CheckConnection();
-        ClientFactory.SetBypass();
+        InitialiseConnection(Properties.Server, Platform.GPSEngine);
 
         UpdateQueue.InitQueueFolder();
 
@@ -308,32 +303,6 @@ public class GPSEngine : Engine<GPSServerProperties>
         StartUpdateServerTask();
     }
 
-    private void Transport_OnClose(IRpcTransport transport, RpcTransportCloseArgs e)
-    {
-        // Try to reconnect when lost connection.
-        if (!_connecting)
-        {
-            CheckConnection();
-        }
-    }
-
-    private bool _connecting = false;
-
-    private bool CheckConnection()
-    {
-        _connecting = true;
-        // Wait for server connection
-        while (!Client.Ping())
-        {
-            Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
-            Task.Delay(30_000).Wait();
-            Logger.Send(LogType.Information, "", "Retrying connection...");
-        }
-        _connecting = false;
-
-        return true;
-    }
-
     public override void Stop()
     {
         oemListener.Stop();

+ 2 - 18
prs.server/Engines/Scheduler/ScheduleEngine.cs

@@ -13,19 +13,6 @@ internal class ScheduleEngine : Engine<ScheduleServerProperties>
 {
     private readonly Scheduler scheduler = new();
 
-    private void CheckConnection()
-    {
-        // Wait for server connection
-        while (!Client.Ping())
-        {
-            Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
-            Task.Delay(30_000).Wait();
-            Logger.Send(LogType.Information, "", "Retrying connection...");
-        }
-
-        ClientFactory.SetBypass();
-    }
-
     public override void Run()
     {
         try
@@ -35,11 +22,8 @@ internal class ScheduleEngine : Engine<ScheduleServerProperties>
                 Logger.Send(LogType.Error, "", "Server is blank!");
                 return;
             }
-            
-            //ClientFactory.SetClientType(typeof(IPCClient<>), Platform.SchedulerEngine, Version, DatabaseServerProperties.GetPipeName(Properties.Server, false));
-            var transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(Properties.Server, true));
-            ClientFactory.SetClientType(typeof(RpcClient<>), Platform.SchedulerEngine, Version, transport);
-            CheckConnection();
+
+            InitialiseConnection(Properties.Server, Platform.SchedulerEngine);
 
             Logger.Send(LogType.Information, "", "Starting Scheduler: ");
             scheduler.Start();

+ 1 - 17
prs.server/Engines/WebEngine/WebEngine.cs

@@ -32,10 +32,7 @@ public class WebEngine : Engine<WebServerProperties>
             Logger.Send(LogType.Error, "", "Server is blank!");
             return;
         }
-        //ClientFactory.SetClientType(typeof(IPCClient<>), Platform.WebEngine, Version, DatabaseServerProperties.GetPipeName(Properties.Server, false));
-        var transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(Properties.Server, true));
-        ClientFactory.SetClientType(typeof(RpcClient<>), Platform.WebEngine, Version, transport);
-        CheckConnection();
+        InitialiseConnection(Properties.Server, Platform.WebEngine);
         
         Logger.Send(LogType.Information, "", "Registering Classes");
 
@@ -61,19 +58,6 @@ public class WebEngine : Engine<WebServerProperties>
         }
     }
 
-    private void CheckConnection()
-    {
-        // Wait for server connection
-        while (!Client.Ping())
-        {
-            Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
-            Task.Delay(30_000).Wait();
-            Logger.Send(LogType.Information, "", "Retrying connection...");
-        }
-
-        ClientFactory.SetBypass();
-    }
-
     public override void Stop()
     {
         Logger.Send(LogType.Information, "", "Stopping..");

+ 43 - 0
prs.services/Engine.cs

@@ -2,6 +2,7 @@
 using System.IO;
 using System.Reflection;
 using System.Security.Cryptography.X509Certificates;
+using InABox.Clients;
 using InABox.Core;
 using InABox.IPC;
 using InABox.Logging;
@@ -57,6 +58,48 @@ public abstract class Engine<TProperties> : IEngine where TProperties : ServerPr
         _enginemanager.Start();
     }
 
+    private bool _connecting = false;
+
+    protected void CheckConnection()
+    {
+        if (_connecting)
+        {
+            return;
+        }
+
+        _connecting = true;
+        // Wait for server connection
+        while (!Client.Ping())
+        {
+            Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
+            Task.Delay(30_000).Wait();
+            Logger.Send(LogType.Information, "", "Retrying connection...");
+        }
+
+        _connecting = false;
+
+        ClientFactory.SetBypass();
+
+        return;
+    }
+
+    /// <summary>
+    /// Initialise the client to the database server, and set up reconnection loop.
+    /// </summary>
+    protected void InitialiseConnection(string serverKey, Platform clientPlatform)
+    {
+        var transport = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(serverKey, true));
+        ClientFactory.SetClientType(typeof(RpcClient<>), clientPlatform, Version, transport);
+        transport.OnClose += Transport_OnClose;
+        CheckConnection();
+    }
+
+    private void Transport_OnClose(IRpcTransport transport, RpcTransportCloseArgs e)
+    {
+        // Try to reconnect when lost connection.
+        CheckConnection();
+    }
+
     public static string GetPath(string key)
     {
         if (Assembly.GetEntryAssembly() != null)

+ 1 - 0
prs.services/PRSServices.csproj

@@ -15,6 +15,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\inabox\InABox.Client.RPC\InABox.Client.RPC.csproj" />
     <ProjectReference Include="..\..\inabox\InABox.Logging\InABox.Logging.csproj" />
     <ProjectReference Include="..\..\inabox\InABox.RPC.Shared\InABox.RPC.Shared.csproj" />
     <ProjectReference Include="..\..\inabox\InABox.Server\InABox.Server.csproj" />