Browse Source

Merge remote-tracking branch 'origin/frank' into tree_view_stuff

# Conflicts:
#	InABox.Core/CoreTreeNodes.cs
#	inabox.wpf/DynamicGrid/BaseDynamicGrid.cs
#	inabox.wpf/DynamicGrid/DynamicGrid.cs
#	inabox.wpf/DynamicGrid/DynamicTreeView.cs
Kenric Nugteren 1 year ago
parent
commit
65b29101c6

+ 1 - 1
inabox.scripting/FileReader/ExcelFileReader.cs

@@ -109,7 +109,7 @@ namespace InABox.Scripting
         {
             if (!EndOfData)
             {
-                EndOfData = rows.MoveNext();
+                EndOfData = !rows.MoveNext();
                 if (!EndOfData)
                 {
                     _row = rows.Current;

+ 2 - 2
inabox.wpf/Converters/UtilityConverter.cs → inabox.wpf/Converters/AbstractConverter.cs

@@ -25,9 +25,9 @@ public delegate void UtilityCoverterEvent(object sender, UtilityConverterEventAr
 // Freezable is a way that might allow us to pass in a DataContext (ie Viewmodel) 
 // to static resources with XAML.  Not yet tested, kept for reference
 // The uncertain bit is the DependencyProperty "typeof(IValueConverter)" - this
-// might not do what we want, as In suspect it might require a concrete type?
+// might not do what we want, as I suspect it might require a concrete type?
 // Ref: https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
-public abstract class UtilityConverter<TIn, TOut> : /*Freezable, */ IValueConverter
+public abstract class AbstractConverter<TIn, TOut> : /*Freezable, */ IValueConverter
 {
 
     public event UtilityCoverterEvent? Converting;

+ 10 - 0
inabox.wpf/Converters/BitmapToBitmapImageConverter.cs

@@ -0,0 +1,10 @@
+using System.Drawing;
+using System.Windows.Media.Imaging;
+
+namespace InABox.WPF;
+
+public class BitmapToBitmapImageConverter : AbstractConverter<Bitmap, BitmapImage?>
+{
+    public override BitmapImage? Convert(Bitmap value)
+        => value?.ToBitmapImage();
+}

+ 1 - 1
inabox.wpf/Converters/BooleanConverter.cs

@@ -1,6 +1,6 @@
 namespace InABox.WPF;
 
-public class BooleanConverter<T> : UtilityConverter<bool, T?>
+public class BooleanConverter<T> : AbstractConverter<bool, T?>
 {
     public T? TrueValue { get; set; }
     public T? FalseValue { get; set; }

+ 1 - 1
inabox.wpf/Converters/BooleanToBooleanConverter.cs

@@ -1,6 +1,6 @@
 namespace InABox.WPF;
 
-public class BooleanToBooleanConverter : UtilityConverter<bool,bool>
+public class BooleanToBooleanConverter : AbstractConverter<bool,bool>
 {
     public bool Invert { get; set; }
     

+ 1 - 1
inabox.wpf/Converters/BytesToBitmapImageConverter.cs

@@ -2,7 +2,7 @@ using System.Windows.Media.Imaging;
 
 namespace InABox.WPF;
 
-public class BytesToBitmapImageConverter : UtilityConverter<byte[], BitmapImage>
+public class BytesToBitmapImageConverter : AbstractConverter<byte[], BitmapImage>
 {
     public override BitmapImage Convert(byte[] value)
         => ImageUtils.LoadImage(value);

+ 1 - 1
inabox.wpf/Converters/DateTimeToStringConverter.cs

@@ -4,7 +4,7 @@ using InABox.Core;
 
 namespace InABox.WPF;
 
-public class DateTimeToStringConverter : UtilityConverter<DateTime,String>
+public class DateTimeToStringConverter : AbstractConverter<DateTime,String>
 {
     public string Format { get; }
         

+ 1 - 1
inabox.wpf/Converters/DateTimeToVisibilityConverter.cs

@@ -5,7 +5,7 @@ using InABox.Core;
 
 namespace InABox.WPF;
 
-public class DateTimeToVisibilityConverter : UtilityConverter<DateTime,Visibility>
+public class DateTimeToVisibilityConverter : AbstractConverter<DateTime,Visibility>
 {
     public bool HideIfEmpty { get; set; } = true;
     

+ 1 - 1
inabox.wpf/Converters/DateToStringConverter.cs

@@ -4,7 +4,7 @@ using InABox.Core;
 
 namespace InABox.WPF;
 
-public class DateToStringConverter : UtilityConverter<DateTime,String>
+public class DateToStringConverter : AbstractConverter<DateTime,String>
 {
     public string Format { get; }
         

+ 18 - 0
inabox.wpf/Converters/GuidToImageSourceConverter.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+
+namespace InABox.WPF;
+
+public class GuidToImageSourceConverter : AbstractConverter<Guid,ImageSource?>
+{
+    public Dictionary<Guid, byte[]>? Images { get; init; }
+    
+    public override ImageSource? Convert(Guid value)
+    {
+        if (Images?.TryGetValue(value, out byte[]? result) == true)
+            return ImageUtils.BitmapImageFromBytes(result);
+        return null;
+    }
+    
+}

+ 1 - 1
inabox.wpf/Converters/TimeSpanToStringConverter.cs

@@ -4,7 +4,7 @@ using System.Linq;
 
 namespace InABox.WPF;
 
-public class TimeSpanToStringConverter : UtilityConverter<TimeSpan,String>
+public class TimeSpanToStringConverter : AbstractConverter<TimeSpan,String>
 {
     public TimeSpanToStringConverter(string? format)
     {

+ 12 - 5
inabox.wpf/DynamicGrid/Columns/EditorColumns/DynamicGridNumericColumn.cs

@@ -27,11 +27,18 @@ public abstract class DynamicGridNumericColumn<TEntity, TEditor, TGridColumn, TT
         int digits = editor.Digits;
         if (!string.IsNullOrWhiteSpace(Definition?.Format))
         {
-            var formatted = 0.ToString(Definition.Format);
-            var separator = formatted.IndexOf(".", StringComparison.InvariantCulture);
-            digits = separator == -1
-                ? 0
-                : formatted.Length - (separator + 1);
+            try
+            {
+                var formatted = 0.ToString(Definition.Format);
+                var separator = formatted.IndexOf(".", StringComparison.InvariantCulture);
+                digits = separator == -1
+                    ? 0
+                    : formatted.Length - (separator + 1);
+            }
+            catch (Exception e)
+            {
+                Logger.Send(LogType.Error,"","{0} is not a valid format specifier",Definition.Format);
+            }
         }
         return digits;
     }

+ 2 - 2
inabox.wpf/DynamicGrid/DynamicDocumentGrid.cs

@@ -19,7 +19,7 @@ using InABox.Wpf;
 namespace InABox.DynamicGrid
 {
 
-    public class DocumentConverter : UtilityConverter<object, object>
+    public class DocumentConverter : AbstractConverter<object, object>
     {
         public override object Convert(object value)
         {
@@ -27,7 +27,7 @@ namespace InABox.DynamicGrid
         }
     }
     
-    public class TimeStampToBrushConverter : UtilityConverter<DateTime, System.Windows.Media.Brush?>
+    public class TimeStampToBrushConverter : AbstractConverter<DateTime, System.Windows.Media.Brush?>
     {
         public System.Windows.Media.Brush? Empty { get; init; }
         public System.Windows.Media.Brush? Set { get; init; }

+ 108 - 1
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -144,7 +144,7 @@ public class DynamicGridSummaryStyleSelector : StyleSelector
 
 // Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
 // This should show nothing for false, and a tick in a box for true
-public class BoolToImageConverter : UtilityConverter<bool,ImageSource>
+public class BoolToImageConverter : AbstractConverter<bool,ImageSource>
 {
 
     public ImageSource TrueValue { get; set; }
@@ -180,8 +180,115 @@ public class StringToColorImageConverter : IValueConverter
 
     public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
     {
+<<<<<<< HEAD
         var str = value?.ToString();
         if (str is null)
+=======
+        public GridSelectionControllerExt(SfDataGrid datagrid)
+            : base(datagrid)
+        {
+        }
+
+        protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle)
+        {
+            base.ProcessSelectedItemChanged(handle);
+            if (handle.NewValue != null)
+            {
+                //this.DataGrid.ScrollInView(this.CurrentCellManager.CurrentRowColumnIndex);
+                //int rowIndex = this.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
+                var columnIndex = CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
+                var scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
+                DataGrid.ScrollInView(new RowColumnIndex(scrollRowIndex, columnIndex));
+            }
+        }
+    }
+
+    public class DynamicGridSummaryStyleSelector : StyleSelector
+    {
+        private readonly IDynamicGrid _grid;
+
+        public DynamicGridSummaryStyleSelector(IDynamicGrid grid)
+        {
+            _grid = grid;
+        }
+
+        public override Style SelectStyle(object item, DependencyObject container)
+        {
+            var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex;
+            var col = vcol > -1 && vcol < _grid.VisibleColumns.Count ? _grid.VisibleColumns[vcol] : null;
+
+            var style = new Style(typeof(GridTableSummaryCell));
+            style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+            style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+            style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty,
+                col != null ? col.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right));
+            style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray)));
+            style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0)));
+            style.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+            style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold));
+            return style;
+        }
+    }
+
+    // Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
+    // This should show nothing for false, and a tick in a box for true
+    public class BoolToImageConverter : AbstractConverter<bool,ImageSource>
+    {
+
+        public ImageSource TrueValue { get; set; }
+        public ImageSource FalseValue { get; set; }
+
+        public BoolToImageConverter()
+        {
+            TrueValue = Wpf.Resources.Bullet_Tick.AsBitmapImage();
+        }
+        
+        public override ImageSource Convert(bool value)
+        {
+            return value ? TrueValue : FalseValue;
+        }
+
+        public override bool Deconvert(ImageSource value)
+        {
+            return ImageUtils.IsEqual(value as BitmapImage,TrueValue as BitmapImage);
+        }
+    }
+
+    public class StringToColorImageConverter : IValueConverter
+    {
+        private readonly int _height = 50;
+        private readonly int _width = 25;
+        private readonly Dictionary<string, BitmapImage> cache = new();
+
+        public StringToColorImageConverter(int width, int height)
+        {
+            _width = width;
+            _height = height;
+        }
+
+        public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var str = value?.ToString();
+            if (str is null)
+                return null;
+
+            var colorcode = str.TrimStart('#');
+
+            if (!cache.ContainsKey(colorcode))
+            {
+                var col = ImageUtils.StringToColor(colorcode);
+                var bmp = ImageUtils.BitmapFromColor(col, _width, _height, Color.Black);
+                cache[colorcode] = bmp.AsBitmapImage();
+            }
+
+            var result = cache[colorcode];
+            return result;
+        }
+
+
+        public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+>>>>>>> origin/frank
             return null;
 
         var colorcode = str.TrimStart('#');

+ 1 - 1
inabox.wpf/DynamicGrid/Editors/RichTextEditor/RichTextEditor.xaml.cs

@@ -14,7 +14,7 @@ namespace InABox.DynamicGrid
 {
     public delegate void RichTextEditorChanged(object sender);
     
-    public class ButtonVisibleConverter : UtilityConverter<RichTextEditorButtons, Visibility>
+    public class ButtonVisibleConverter : AbstractConverter<RichTextEditorButtons, Visibility>
     {
 
         public Visibility Set { get; set; } = Visibility.Visible;

+ 74 - 67
inabox.wpf/DynamicGrid/PDF/PDFEditorControl.xaml.cs

@@ -340,82 +340,89 @@ namespace InABox.DynamicGrid
                     pdfdocument = File.ReadAllBytes(cachefile);
                 }
 
-                var doc = new PdfLoadedDocument(pdfdocument);
+                if (pdfdocument.Any())
+                {
 
-                currentAnnotations.Clear();
+                    var doc = new PdfLoadedDocument(pdfdocument);
 
-                var watermark = !_document.Superceded.IsEmpty() ? "SUPERCEDED" : Watermark;
-                if (!string.IsNullOrWhiteSpace(watermark))
-                    foreach (PdfPageBase page in doc.Pages)
-                    {
-                        var rect = new RectangleF(0, 0, page.Size.Width, page.Size.Height);
-                        var graphics = page.Graphics;
-                        PdfFont font = new PdfStandardFont(PdfFontFamily.Helvetica, page.Size.Width / 10, PdfFontStyle.Bold);
-                        var state = graphics.Save();
-                        graphics.SetTransparency(0.2f);
-                        var format = new PdfStringFormat();
-                        format.Alignment = PdfTextAlignment.Center;
-                        format.LineAlignment = PdfVerticalAlignment.Middle;
-
-                        var lineheight = (int)Math.Round(font.Height * 2.5);
-                        var lines = new List<string>();
-                        while (lines.Count * lineheight < page.Size.Height)
-                            lines.Add(watermark);
-                        graphics.DrawString(string.Join("\n\n", lines), font, PdfBrushes.Red, rect, format);
-                    }
+                    currentAnnotations.Clear();
 
-                //foreach (PdfPageBase page in doc.Pages)
-                //{
-                //    RectangleF rect = new RectangleF(0, 0, page.Size.Width, page.Size.Height);
-                //    PdfGraphics graphics = page.Graphics;
-                //    PdfFont font = new PdfStandardFont(PdfFontFamily.Helvetica, page.Size.Width / 10, PdfFontStyle.Bold);
-                //    PdfGraphicsState state = graphics.Save();
-                //    graphics.SetTransparency(0.2f);
-                //    PdfStringFormat format = new PdfStringFormat();
-                //    format.Alignment = PdfTextAlignment.Center;
-                //    format.LineAlignment = PdfVerticalAlignment.Middle;
-
-                //    int lineheight = (int)Math.Round(font.Height * 2.5);
-                //    List<String> lines = new List<string>();
-                //    while (lines.Count * lineheight < page.Size.Height)
-                //        lines.Add("SUPERCEDED");
-                //    graphics.DrawString(String.Join("\n\n", lines), font, PdfBrushes.Red, rect, format);
-                //}
-
-
-                var annotations = new Client<EntityDocumentAnnotation>()
-                    .Load(new Filter<EntityDocumentAnnotation>(x => x.EntityDocument).IsEqualTo(_document.ID)).ToList();
-                foreach (var annotation in annotations)
-                    try
-                    {
-                        currentAnnotations.Add(annotation.ID);
-                        if (!string.IsNullOrWhiteSpace(annotation.Data))
+                    var watermark = !_document.Superceded.IsEmpty() ? "SUPERCEDED" : Watermark;
+                    if (!string.IsNullOrWhiteSpace(watermark))
+                        foreach (PdfPageBase page in doc.Pages)
                         {
-                            if (annotation.Data.Contains("<freetext") && !annotation.Data.Contains("date=\""))
+                            var rect = new RectangleF(0, 0, page.Size.Width, page.Size.Height);
+                            var graphics = page.Graphics;
+                            PdfFont font = new PdfStandardFont(PdfFontFamily.Helvetica, page.Size.Width / 10,
+                                PdfFontStyle.Bold);
+                            var state = graphics.Save();
+                            graphics.SetTransparency(0.2f);
+                            var format = new PdfStringFormat();
+                            format.Alignment = PdfTextAlignment.Center;
+                            format.LineAlignment = PdfVerticalAlignment.Middle;
+
+                            var lineheight = (int)Math.Round(font.Height * 2.5);
+                            var lines = new List<string>();
+                            while (lines.Count * lineheight < page.Size.Height)
+                                lines.Add(watermark);
+                            graphics.DrawString(string.Join("\n\n", lines), font, PdfBrushes.Red, rect, format);
+                        }
+
+                    //foreach (PdfPageBase page in doc.Pages)
+                    //{
+                    //    RectangleF rect = new RectangleF(0, 0, page.Size.Width, page.Size.Height);
+                    //    PdfGraphics graphics = page.Graphics;
+                    //    PdfFont font = new PdfStandardFont(PdfFontFamily.Helvetica, page.Size.Width / 10, PdfFontStyle.Bold);
+                    //    PdfGraphicsState state = graphics.Save();
+                    //    graphics.SetTransparency(0.2f);
+                    //    PdfStringFormat format = new PdfStringFormat();
+                    //    format.Alignment = PdfTextAlignment.Center;
+                    //    format.LineAlignment = PdfVerticalAlignment.Middle;
+
+                    //    int lineheight = (int)Math.Round(font.Height * 2.5);
+                    //    List<String> lines = new List<string>();
+                    //    while (lines.Count * lineheight < page.Size.Height)
+                    //        lines.Add("SUPERCEDED");
+                    //    graphics.DrawString(String.Join("\n\n", lines), font, PdfBrushes.Red, rect, format);
+                    //}
+
+
+                    var annotations = new Client<EntityDocumentAnnotation>()
+                        .Load(new Filter<EntityDocumentAnnotation>(x => x.EntityDocument).IsEqualTo(_document.ID))
+                        .ToList();
+                    foreach (var annotation in annotations)
+                        try
+                        {
+                            currentAnnotations.Add(annotation.ID);
+                            if (!string.IsNullOrWhiteSpace(annotation.Data))
                             {
-                                Logger.Send(LogType.Information, ClientFactory.UserID,
-                                    string.Format("Annotation #{0} has no date - inserting now..", annotation.ID));
-                                annotation.Data = annotation.Data.Replace("<freetext",
-                                    string.Format("<freetext date=\"D:{0:yyyyMMddHHmmss}+08\'00\'\"", DateTime.Now));
-                                //annotation.Data = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">\r\n  <annots>\r\n    <freetext page=\"0\" rect=\"635.782,231.8206,812.7407,282.311\" rotation=\"0\" color=\"#FFFFFF\" title=\"ffffffff-ffff-ffff-ffff-ffffffffffff\" subject=\"Free Text\" date=\"D:20220715001146+08'00'\" name=\"800eae13-14f4-4db9-a60e-6d5d2d2adb55\" fringe=\"0,0,0,0\" border=\"0,0,1\" q=\"0\" IT=\"FreeTextCallout\" head=\"None\">\r\n      <defaultappearance>0.2901961 0.5647059 0.8862745 rg </defaultappearance>\r\n      <defaultstyle>font:Arial 8pt;style:Regular;color:#FF0000</defaultstyle>\r\n      <contents-richtext><body xmlns=\"http://www.w3.org/1999/xhtml\"><p dir=\"ltr\">MC'd by BW\r\nTrolley H18\r\n8 July 2022\r\nNOTE: 3032 Doorleaf is also on Trolley H18</p></body></contents-richtext>\r\n      <contents>MC'd by BW\r\nTrolley H18\r\n8 July 2022\r\nNOTE: 3032 Doorleaf is also on Trolley H18</contents>\r\n    </freetext>\r\n  </annots>\r\n  <f href=\"\" />\r\n</xfdf>";
-                                //annotation.Data = "<?xml version =\"1.0\" encoding=\"utf-8\"?>\r\n<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">\r\n  <annots>\r\n    <freetext page=\"0\" rect=\"768.7103,558.5799,898.9603,642.3299\" rotation=\"0\" color=\"#FFFFFF\" title=\"ffffffff-ffff-ffff-ffff-ffffffffffff\" subject=\"Free Text\" date=\"D:20220715001146+08'00'\" flags=\"print\" name=\"690a16b1-647b-45a9-80a5-17f83f63bc93\" fringe=\"18,2,20,16.5\" border=\"0,0,1.5\" justification=\"0\" IT=\"FreeTextCallout\" head=\"OpenArrow\" callout=\"786.7103,625.8299,768.7103,593.2049,786.7103,593.2049\">\r\n      <defaultappearance>0.2901961 0.5647059 0.8862745 rg </defaultappearance>\r\n      <defaultstyle>font:Arial 12pt;style:Regular;color:#FF0000</defaultstyle>\r\n      <contents-richtext><body xmlns=\"http://www.w3.org/1999/xhtml\"><p dir=\"ltr\">Hi Thjere\r\n\r\nawd.flae\r\nsdfgljsdlkfg</p></body></contents-richtext>\r\n      <contents>Hi Thjere\r\n\r\nawd.flae\r\nsdfgljsdlkfg</contents>\r\n    </freetext>\r\n  </annots>\r\n  <f href=\"\" />\r\n</xfdf>";
-                            }
+                                if (annotation.Data.Contains("<freetext") && !annotation.Data.Contains("date=\""))
+                                {
+                                    Logger.Send(LogType.Information, ClientFactory.UserID,
+                                        string.Format("Annotation #{0} has no date - inserting now..", annotation.ID));
+                                    annotation.Data = annotation.Data.Replace("<freetext",
+                                        string.Format("<freetext date=\"D:{0:yyyyMMddHHmmss}+08\'00\'\"",
+                                            DateTime.Now));
+                                    //annotation.Data = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">\r\n  <annots>\r\n    <freetext page=\"0\" rect=\"635.782,231.8206,812.7407,282.311\" rotation=\"0\" color=\"#FFFFFF\" title=\"ffffffff-ffff-ffff-ffff-ffffffffffff\" subject=\"Free Text\" date=\"D:20220715001146+08'00'\" name=\"800eae13-14f4-4db9-a60e-6d5d2d2adb55\" fringe=\"0,0,0,0\" border=\"0,0,1\" q=\"0\" IT=\"FreeTextCallout\" head=\"None\">\r\n      <defaultappearance>0.2901961 0.5647059 0.8862745 rg </defaultappearance>\r\n      <defaultstyle>font:Arial 8pt;style:Regular;color:#FF0000</defaultstyle>\r\n      <contents-richtext><body xmlns=\"http://www.w3.org/1999/xhtml\"><p dir=\"ltr\">MC'd by BW\r\nTrolley H18\r\n8 July 2022\r\nNOTE: 3032 Doorleaf is also on Trolley H18</p></body></contents-richtext>\r\n      <contents>MC'd by BW\r\nTrolley H18\r\n8 July 2022\r\nNOTE: 3032 Doorleaf is also on Trolley H18</contents>\r\n    </freetext>\r\n  </annots>\r\n  <f href=\"\" />\r\n</xfdf>";
+                                    //annotation.Data = "<?xml version =\"1.0\" encoding=\"utf-8\"?>\r\n<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">\r\n  <annots>\r\n    <freetext page=\"0\" rect=\"768.7103,558.5799,898.9603,642.3299\" rotation=\"0\" color=\"#FFFFFF\" title=\"ffffffff-ffff-ffff-ffff-ffffffffffff\" subject=\"Free Text\" date=\"D:20220715001146+08'00'\" flags=\"print\" name=\"690a16b1-647b-45a9-80a5-17f83f63bc93\" fringe=\"18,2,20,16.5\" border=\"0,0,1.5\" justification=\"0\" IT=\"FreeTextCallout\" head=\"OpenArrow\" callout=\"786.7103,625.8299,768.7103,593.2049,786.7103,593.2049\">\r\n      <defaultappearance>0.2901961 0.5647059 0.8862745 rg </defaultappearance>\r\n      <defaultstyle>font:Arial 12pt;style:Regular;color:#FF0000</defaultstyle>\r\n      <contents-richtext><body xmlns=\"http://www.w3.org/1999/xhtml\"><p dir=\"ltr\">Hi Thjere\r\n\r\nawd.flae\r\nsdfgljsdlkfg</p></body></contents-richtext>\r\n      <contents>Hi Thjere\r\n\r\nawd.flae\r\nsdfgljsdlkfg</contents>\r\n    </freetext>\r\n  </annots>\r\n  <f href=\"\" />\r\n</xfdf>";
+                                }
 
-                            LoadAnnotation(doc, annotation.Data.Replace("&", "+"), annotation.ID);
+                                LoadAnnotation(doc, annotation.Data.Replace("&", "+"), annotation.ID);
+                            }
+                        }
+                        catch (Exception e)
+                        {
+                            Logger.Send(LogType.Error, "",
+                                string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                         }
-                    }
-                    catch (Exception e)
-                    {
-                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-                    }
-
-                var ms = new MemoryStream();
-                doc.Save(ms);
 
-                PdfViewer.ZoomMode = ZoomMode.FitWidth;
-                PdfViewer.Load(ms);
+                    var ms = new MemoryStream();
+                    doc.Save(ms);
+                    PdfViewer.ZoomMode = ZoomMode.FitWidth;
+                    PdfViewer.Load(ms);
+                    CurrentAnnotations = AnnotationsToString();
+                }
                 UpdateButtons(PdfNoneImage);
-                CurrentAnnotations = AnnotationsToString();
             }
         }
 

+ 55 - 1
inabox.wpf/DynamicGrid/UIComponent/DynamicGridTreeUIComponent.cs

@@ -23,6 +23,21 @@ using System.Windows.Media.Imaging;
 
 namespace InABox.DynamicGrid;
 
+public enum DynamicTreeGridLines
+{
+    Both,
+    Horizontal,
+    Vertical,
+    None
+}
+
+public enum DynamicTreeGridExpandMode
+{
+    All,
+    Root,
+    None
+}
+
 public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynamicGridGridUIComponent<T>
     where T : BaseObject, new()
 {
@@ -69,6 +84,45 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         } 
     }
 
+    private DynamicTreeGridLines _gridLines = DynamicTreeGridLines.Both;
+    public DynamicTreeGridLines GridLines
+    {
+        get => _gridLines;
+        set
+        {
+            _gridLines = value;
+            _tree.GridLinesVisibility = value switch
+            {
+                DynamicTreeGridLines.Both => GridLinesVisibility.Both,
+                DynamicTreeGridLines.Vertical => GridLinesVisibility.Vertical,
+                DynamicTreeGridLines.Horizontal => GridLinesVisibility.Horizontal,
+                _ => GridLinesVisibility.None,
+            };
+        }
+    }
+
+    public DynamicTreeGridExpandMode ExpandMode
+    {
+        get
+        {
+            return _tree.AutoExpandMode switch
+            {
+                AutoExpandMode.AllNodesExpanded => DynamicTreeGridExpandMode.All,
+                AutoExpandMode.RootNodesExpanded => DynamicTreeGridExpandMode.Root,
+                _ => DynamicTreeGridExpandMode.None,
+            };
+        }
+        set
+        {
+            _tree.AutoExpandMode = value switch
+            {
+                DynamicTreeGridExpandMode.All => AutoExpandMode.AllNodesExpanded,
+                DynamicTreeGridExpandMode.Root => AutoExpandMode.RootNodesExpanded,
+                _ => AutoExpandMode.None
+            };
+        }
+    }
+
     private double minRowHeight = 30D;
     private double maxRowHeight = 30D;
 
@@ -125,7 +179,7 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         _tree.ChildPropertyName = "Children";
         //_tree.ParentPropertyName = "Parent";
         _tree.AutoGenerateColumns = false;
-        _tree.AutoExpandMode = AutoExpandMode.AllNodesExpanded;
+        ExpandMode = ExpandMode.All;
         //_tree.NodeCollapsing += (o, e) => { e.Cancel = true; };
         //_tree.HeaderRowHeight = 0D;
         _tree.HeaderRowHeight = 30;

+ 38 - 7
inabox.wpf/Panel/IPanel.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Drawing;
+using System.Windows;
 using System.Windows.Controls;
 using InABox.Configuration;
 using InABox.Core;
@@ -13,15 +14,45 @@ public interface IPanelActionItem
 
 }
 
-public class PanelAction : IPanelActionItem
+public class PanelAction : DependencyObject, IPanelActionItem
 {
-    public Action<PanelAction> OnExecute { get; set; }
-
-    public string Caption { get; set; }
-
-    public Bitmap Image { get; set; }
+    public Action<PanelAction>? OnExecute { get; set; }
+
+    public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(
+        nameof(Caption),
+        typeof(string),
+        typeof(PanelAction),
+        new PropertyMetadata("")
+    );
+    
+    public string Caption 
+    { 
+        get => (string)GetValue(CaptionProperty);
+        set => SetValue(CaptionProperty, value);
+    }
 
-    public ContextMenu? Menu { get; set; }
+    public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
+        nameof(Image),
+        typeof(Bitmap),
+        typeof(PanelAction)
+    );    
+    public Bitmap? Image
+    { 
+        get => GetValue(ImageProperty) as Bitmap;
+        set => SetValue(ImageProperty, value);
+    }
+    
+    public static readonly DependencyProperty MenuProperty = DependencyProperty.Register(
+        nameof(Menu),
+        typeof(ContextMenu),
+        typeof(PanelAction)
+    );   
+    
+    public ContextMenu? Menu
+    { 
+        get => GetValue(MenuProperty) as ContextMenu;
+        set => SetValue(MenuProperty, value);
+    }
 
     public PanelAction()
     {

+ 3 - 1
inabox.wpf/Reports/PreviewWindow.xaml

@@ -9,13 +9,15 @@
         xmlns:preview="clr-namespace:FastReport.Preview;assembly=FastReport.WPF"
         xmlns:design="clr-namespace:FastReport.Design;assembly=FastReport.WPF"
         mc:Ignorable="d"
-        Title="PreviewWindow" Height="450" Width="800">
+        Title="PreviewWindow" Height="450" Width="800"
+        Closing="PreviewWindow_OnClosing">
     <Grid x:Name="MainGrid">
         <Grid.RowDefinitions>
             <RowDefinition Height="*"/>
             <RowDefinition Height="0"/>
             <RowDefinition Height="0"/>
         </Grid.RowDefinitions>
+        
         <Grid Grid.Row="0" Name="PreviewGrid">
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="*" />

+ 27 - 4
inabox.wpf/Reports/PreviewWindow.xaml.cs

@@ -22,6 +22,7 @@ using InABox.Core.Reports;
 using ICSharpCode.AvalonEdit.Highlighting;
 using System.Drawing;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Windows.Threading;
@@ -87,7 +88,8 @@ namespace InABox.Wpf.Reports
             Designer.InnerDesigner.cmdSave.CustomAction += (sender, args) => SaveReport();
             Designer.InnerDesigner.cmdSaveAs.CustomAction += (sender, args) => SaveReportAs();
             Designer.InnerDesigner.cmdPreview.CustomAction += (sender, args) => ShowPreview();
-            
+            Designer.InnerDesigner.AskSave = false;
+
 
         }
 
@@ -198,7 +200,7 @@ namespace InABox.Wpf.Reports
                             }
                             catch (Exception e)
                             {
-                                MessageWindow.ShowMessage(e.Message,"Error in Report");
+                                MessageWindow.ShowError("Some errors were found in this report.", e.Message, "Errors in report");
                                 CloseLoading();
                                 ShowDesigner();
                             }
@@ -386,6 +388,7 @@ namespace InABox.Wpf.Reports
             _template.RDL = _report.SaveToString();
             DoSave(_template);
             Designer.Report = _report;
+            Designer.InnerDesigner.Modified = false;
         }
 
         private void DesignButton_Click(object sender, RoutedEventArgs e)
@@ -408,7 +411,12 @@ namespace InABox.Wpf.Reports
         private void DoSave(ReportTemplate template)
         {
             if (SaveTemplate is null)
-                new Client<ReportTemplate>().Save(template, "Updated by Designer");
+            {
+                Progress.ShowModal("Saving", p =>
+                {
+                    new Client<ReportTemplate>().Save(template, "Updated by Designer");
+                });
+            }
             else
                 SaveTemplate?.Invoke(template);
         }
@@ -419,6 +427,21 @@ namespace InABox.Wpf.Reports
             DoSave(_template);
             _report = null;
             ShowPreview();
-        }       
+        }
+
+        private void PreviewWindow_OnClosing(object? sender, CancelEventArgs e)
+        {
+            if (Designer.InnerDesigner.Modified)
+            {
+                var result = MessageWindow.ShowYesNoCancel("There are unsaved changes in this report!\nDo you wish to save them now?","Save Changes");
+                if (result == MessageWindowResult.Cancel)
+                {
+                    e.Cancel = true;
+                    return;
+                }
+                if (result == MessageWindowResult.Yes)
+                    SaveReport();
+            }
+        }
     }
 }

+ 2 - 2
inabox.wpf/Themes/Generic.cs

@@ -10,7 +10,7 @@ using System.Windows.Input;
 
 namespace InABox.WPF
 {
-    public class ObjectToGridLengthConverter : UtilityConverter<FrameworkElement?, GridLength>
+    public class ObjectToGridLengthConverter : AbstractConverter<FrameworkElement?, GridLength>
     {
         public GridLength NotNull { get; set; } = new GridLength(0, GridUnitType.Pixel);
         public GridLength IsNull { get; set; } = new GridLength(1, GridUnitType.Auto);
@@ -23,7 +23,7 @@ namespace InABox.WPF
         }
     }
     
-    public class ObjectToVisibilityConverter : UtilityConverter<FrameworkElement?, Visibility>
+    public class ObjectToVisibilityConverter : AbstractConverter<FrameworkElement?, Visibility>
     {
         public Visibility NotNull { get; set; } = Visibility.Visible;
         public Visibility IsNull { get; set; } = Visibility.Collapsed;

+ 22 - 2
inabox.wpf/WPFUtils.cs

@@ -1,18 +1,38 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
-using System.Globalization;
+using System.Linq.Expressions;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Data;
 using System.Windows.Media;
 using InABox.Core;
-using FontStyle = System.Windows.FontStyle;
 using Image = System.Windows.Controls.Image;
 
 namespace InABox.WPF
 {
     public static class WPFUtils
     {
+        public static void Bind<T, TProperty>(
+            this FrameworkElement element, 
+            DependencyProperty property, 
+            Expression<Func<T, TProperty>> expression, 
+            IValueConverter? converter = null,
+            BindingMode mode = BindingMode.Default,
+            string? format = null )
+        {
+            element.SetBinding(
+                property,
+                new Binding(CoreUtils.GetFullPropertyName(expression, "_"))
+                {
+                    Converter = converter,
+                    StringFormat = format,
+                    Mode = mode
+                }
+            );
+        }
+        
+        
         public static T? FindLogicalParent<T>(this DependencyObject dependencyObject)
             where T : DependencyObject
         {