Просмотр исходного кода

Renamed UtilityConverter to AbstractConverter for consistency
Added WPFUtils.Bind() function

frogsoftware 1 год назад
Родитель
Сommit
cbdc664513

+ 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)
     {

+ 2 - 1
inabox.wpf/DynamicGrid/BaseDynamicGrid.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Linq.Expressions;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Data;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using FastReport.Editor;
@@ -195,7 +196,7 @@ namespace InABox.DynamicGrid
         {
             DoCustomiseColumnsEvent(this,columns);
         }
-
+        
         public abstract CoreRow[] SelectedRows { get; set; }
 
         public abstract void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);

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

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

@@ -146,7 +146,7 @@ namespace InABox.DynamicGrid
 
     // 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; }

+ 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();
             }
         }
 

+ 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()
     {

+ 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
         {