< Summary - PropertyGridHelpers Code Coverage

Information
Class: PropertyGridHelpers.UIEditors.ImageTextUIEditor
Assembly: PropertyGridHelpers
File(s): c:\agent\_work\9\s\Code\PropertyGridHelpers\UIEditors\ImageTextUIEditor.cs
Tag: PropertyGridHelpers Build_2025.7.15.1_#485
Line coverage
100%
Covered lines: 160
Uncovered lines: 0
Coverable lines: 160
Total lines: 501
Line coverage: 100%
Branch coverage
100%
Covered branches: 56
Total branches: 56
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

c:\agent\_work\9\s\Code\PropertyGridHelpers\UIEditors\ImageTextUIEditor.cs

#LineLine coverage
 1using PropertyGridHelpers.Attributes;
 2using PropertyGridHelpers.Enums;
 3using System;
 4using System.ComponentModel;
 5using System.Drawing;
 6using System.Drawing.Design;
 7using System.Drawing.Drawing2D;
 8using System.Globalization;
 9using System.IO;
 10using System.Linq;
 11using System.Resources;
 12
 13namespace PropertyGridHelpers.UIEditors
 14{
 15    /// <summary>
 16    /// Provides a <see cref="UITypeEditor"/> for editing enumeration values that have associated images.
 17    /// </summary>
 18    /// <remarks>
 19    /// This editor is designed to display an enumeration in a UI with each value optionally associated with an image.
 20    /// The editor can be customized to include additional functionality or presentation enhancements.
 21    /// </remarks>
 22    /// <example>
 23    /// To use this editor, apply the <see cref="ImageTextUIEditor"/> to an enum property:
 24    /// <code>
 25    /// [Editor(typeof(ImageTextUIEditor), typeof(UITypeEditor))]
 26    /// public TestEnum EnumWithImages { get; set; }
 27    /// </code>
 28    /// Ensure that the enum has descriptions or resources set up using the <see cref="EnumImageAttribute"/> to provide 
 29    /// </example>
 30    /// <seealso cref="UITypeEditor" />
 31    public partial class ImageTextUIEditor : UITypeEditor, IDisposable
 32    {
 33        #region Fields ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 34
 35        /// <summary>
 36        /// The object is disposed
 37        /// </summary>
 38        private bool disposedValue;
 39
 40        /// <summary>
 41        /// The enum type
 42        /// </summary>
 43        protected Type EnumType
 44        {
 5645            get;
 46        }
 47
 48        /// <summary>
 49        /// The path to the resources where the images are stored
 50        /// </summary>
 51        protected string ResourcePath
 52        {
 11653            get; private set;
 54        }
 55
 56        /// <summary>
 57        /// Gets the file extension.
 58        /// </summary>
 59        /// <value>
 60        /// The file extension.
 61        /// </value>
 62        protected string FileExtension
 63        {
 4464            get; private set;
 65        }
 66
 67        #endregion
 68
 69        #region Constructors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 70
 71        /// <summary>
 72        /// Initializes a new instance of the <see cref="ImageTextUIEditor"/> class.
 73        /// </summary>
 74        /// <param name="type">Type of enum that is used in the process</param>
 3275        public ImageTextUIEditor(Type type)
 2476        {
 3277            EnumType = type;
 3278            ResourcePath = Support.Support.GetResourcePath(null, type, ResourceUsage.Images);
 3279        }
 80
 81        /// <summary>
 82        /// Initializes a new instance of the <see cref="ImageTextUIEditor"/> class.
 83        /// </summary>
 84        /// <param name="type">Type of enum that is used in the process</param>
 85        /// <param name="ResourcePath">The path to the resources where the images are stored</param>
 6486        public ImageTextUIEditor(Type type, string ResourcePath)
 5687        {
 6488            EnumType = type;
 6489            this.ResourcePath = ResourcePath;
 6490        }
 91
 92        #endregion
 93
 94        #region PaintValue Routines ^^^^^^^^^^^^^^^^^^^^^^^
 95
 96        /// <summary>
 97        /// return that the editor will paint the items in the drop-down
 98        /// </summary>
 99        /// <param name="context">The Type Descriptor Context</param>
 100        public override bool GetPaintValueSupported(ITypeDescriptorContext context)
 12101        {
 20102            ResourcePath = Support.Support.GetResourcePath(context, EnumType, ResourceUsage.Images);
 20103            FileExtension = Support.Support.GetFileExtension(context);
 20104            return true;
 12105        }
 106
 107        /// <summary>
 108        /// Paint the value in the drop-down list
 109        /// </summary>
 110        /// <param name="e">Paint Value Event Arguments</param>
 111        public override void PaintValue(PaintValueEventArgs e)
 52112        {
 113#if NET5_0_OR_GREATER
 44114            ArgumentNullException.ThrowIfNull(e);
 115#else
 16116            if (e is null) throw new ArgumentNullException(nameof(e));
 117#endif
 56118            var newImage = GetImageFromResource(e.Value, EnumType, ResourcePath, FileExtension, e.Bounds);
 48119            if (newImage != null)
 44120                e.Graphics.DrawImage(newImage, e.Bounds);
 48121        }
 122
 123        #endregion
 124
 125        #region Static Methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 126
 127        /// <summary>
 128        /// Gets the image from resource.
 129        /// </summary>
 130        /// <param name="Value">The value.</param>
 131        /// <param name="enumType">Type of the enum.</param>
 132        /// <param name="ResourcePath">The resource path.</param>
 133        /// <param name="fileExtension"></param>
 134        /// <returns></returns>
 135        /// <exception cref="System.InvalidOperationException">Resource file '{ResourceName}.resources' not found in ass
 136        /// $"Available resources: {string.Join(", ", resourceNames)}
 137        /// or
 138        /// Resource {enumImage} is not a valid image or byte array.</exception>
 139        /// <exception cref="InvalidOperationException">Resource file not found in the assembly.
 140        /// or
 141        /// Resource is not a valid image or byte array.</exception>
 142        /// <param name="bounds">The bounds of the generated image.</param>
 143        public static Bitmap GetImageFromResource(
 144            object Value,
 145            Type enumType,
 146            string ResourcePath,
 147            string fileExtension,
 148            Rectangle bounds)
 64149        {
 150#if NET5_0_OR_GREATER
 56151            ArgumentNullException.ThrowIfNull(Value);
 52152            ArgumentNullException.ThrowIfNull(enumType);
 153#else
 16154            if (Value is null) throw new ArgumentNullException(nameof(Value));
 16155            if (enumType is null) throw new ArgumentNullException(nameof(enumType));
 156#endif
 157            // get the EnumImageAttribute for the field
 64158            var dna = EnumImageAttribute.Get((Enum)Value);
 159
 64160            if (dna != null)
 52161            {
 60162                var m = GetModuleName(Value);
 60163                var ei = EnumImageAttribute.GetEnumImage((Enum)Value);
 60164                Bitmap originalImage = null;
 165                string ResourceName;
 60166                switch (dna.ImageLocation)
 167                {
 168                    case ImageLocation.Embedded:
 28169                        ResourceName = $"{m}{(string.IsNullOrEmpty(ResourcePath) ? "" : $".{ResourcePath}")}";
 28170                        originalImage = GetImageFromEmbeddedResource(Value, ei, ResourceName, fileExtension);
 28171                        break;
 172                    case ImageLocation.Resource:
 173                        // Create a resource manager to access the resources
 44174                        ResourceName = $"{m}{(string.IsNullOrEmpty(ResourcePath) ? "" : $".{ResourcePath}")}";
 44175                        originalImage = GetImageFromResourceFile(Value, ei, ResourceName, fileExtension, LicenseManager.
 36176                        break;
 177                    case ImageLocation.File:
 20178                        originalImage = GetImageFromFile(Value, ei, ResourcePath, fileExtension);
 12179                        break;
 180                }
 181
 56182                if (originalImage == null) return null;
 183
 184                // Create a new bitmap for the scaled image
 48185                var scaledImage = new Bitmap(bounds.Width, bounds.Height);
 186
 48187                using (var g = Graphics.FromImage(scaledImage))
 40188                {
 48189                    g.Clear(Color.Transparent); // Optional: set background to transparent
 48190                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
 191
 48192                    var ts = GetTargetSizes(originalImage, bounds);
 193
 194                    // Draw the scaled image centered in the bounds
 48195                    g.DrawImage(originalImage, ts.OffsetX, ts.OffsetY, ts.TargetWidth, ts.TargetHeight);
 48196                }
 197
 48198                return scaledImage;
 199            }
 200
 20201            return null;
 48202        }
 203
 204        /// <summary>
 205        /// Gets the image from embedded resource.
 206        /// </summary>
 207        /// <param name="Value">The value.</param>
 208        /// <param name="ResourceItem">The resource entry to retrieve.</param>
 209        /// <param name="ResourcePath">Name of the resource.</param>
 210        /// <param name="fileExtension">The file extension.</param>
 211        /// <returns></returns>
 212        /// <exception cref="System.ArgumentNullException">Value</exception>
 213        /// <exception cref="System.ArgumentException">'{nameof(enumImage)}' cannot be null or empty. - enumImage
 214        /// or
 215        /// '{nameof(ResourceName)}' cannot be null or empty. - ResourceName</exception>
 216        public static Bitmap GetImageFromEmbeddedResource(
 217            object Value,
 218            string ResourceItem,
 219            string ResourcePath,
 220            string fileExtension)
 36221        {
 222#if NET5_0_OR_GREATER
 28223            ArgumentNullException.ThrowIfNull(Value);
 224#else
 16225            if (Value is null) throw new ArgumentNullException(nameof(Value));
 226#endif
 44227            if (string.IsNullOrEmpty(ResourceItem)) throw new ArgumentException($"'{nameof(ResourceItem)}' cannot be nul
 40228            if (string.IsNullOrEmpty(ResourcePath)) throw new ArgumentException($"'{nameof(ResourcePath)}' cannot be nul
 32229            ResourcePath = $"{ResourcePath}.{ResourceItem}";
 32230            if (!string.IsNullOrEmpty(fileExtension))
 20231                ResourcePath = $"{ResourcePath}.{fileExtension}";
 232
 32233            Bitmap newImage = null;
 234
 32235            using (var stream = Value.GetType().Assembly.GetManifestResourceStream(ResourcePath))
 236#if NET5_0_OR_GREATER
 16237                if (stream is not null)
 238#else
 16239                if (!(stream is null))
 240#endif
 32241                    newImage = (Bitmap)Image.FromStream(stream);
 242
 32243            return newImage;
 24244        }
 245
 246        /// <summary>
 247        /// Gets the image from resource file.
 248        /// </summary>
 249        /// <param name="Value">The value.</param>
 250        /// <param name="ResourceItem">The resource entry to retrieve.</param>
 251        /// <param name="ResourcePath">Name of the resource.</param>
 252        /// <param name="fileExtension">The file extension.</param>
 253        /// <param name="IsInDesignMode">if set to <c>true</c> is in design mode.</param>
 254        /// <returns></returns>
 255        /// <exception cref="System.ArgumentNullException">Value</exception>
 256        /// <exception cref="System.ArgumentException">'{nameof(ResourceItem)}' cannot be null or empty. - ResourceItem
 257        /// or
 258        /// '{nameof(ResourcePath)}' cannot be null or empty. - ResourcePath</exception>
 259        /// <exception cref="System.InvalidOperationException">Resource file '{ResourcePath}.resources' not found in ass
 260        /// $"Available resources: {string.Join(", ", resourceNames)}
 261        /// or
 262        /// Error retrieving resource '{ResourceItem}{(string.IsNullOrEmpty(fileExtension) ? "" : $".{fileExtension}")}'
 263        /// or
 264        /// Resource '{ResourcePath}.resources.{ResourceItem}' is not a valid image or byte array.</exception>
 265        /// <exception cref="ArgumentNullException">Value</exception>
 266        /// <exception cref="ArgumentException">'{nameof(ResourceItem)}' cannot be null or empty. - ResourceItem
 267        /// or
 268        /// '{nameof(ResourcePath)}' cannot be null or empty. - ResourcePath</exception>
 269        /// <exception cref="InvalidOperationException">Resource file '{ResourcePath}.resources' not found in assembly '
 270        /// $"Available resources: {string.Join(", ", resourceNames)}
 271        /// or
 272        /// Resource '{ResourcePath}.resources.{ResourceItem}' is not a valid image or byte array.</exception>
 273        /// <exception cref="InvalidOperationException">Resource file '{ResourcePath}.resources' not found in assembly '
 274        /// $"Available resources: {string.Join(", ", resourceNames)}
 275        /// or
 276        /// Resource '{ResourcePath}.resources.{ResourceItem}' is not a valid image or byte array.</exception>
 277        public static Bitmap GetImageFromResourceFile(
 278            object Value,
 279            string ResourceItem,
 280            string ResourcePath,
 281            string fileExtension,
 282            bool IsInDesignMode)
 68283        {
 284#if NET5_0_OR_GREATER
 60285            ArgumentNullException.ThrowIfNull(Value);
 286#else
 16287            if (Value is null) throw new ArgumentNullException(nameof(Value));
 288#endif
 76289            if (string.IsNullOrEmpty(ResourceItem)) throw new ArgumentException($"'{nameof(ResourceItem)}' cannot be nul
 72290            if (string.IsNullOrEmpty(ResourcePath)) throw new ArgumentException($"'{nameof(ResourcePath)}' cannot be nul
 291
 292            // Check if the resource file exists in the assembly
 64293            var resourceNames = Value.GetType().Assembly.GetManifestResourceNames();
 168294            if (!resourceNames.Any(r => r.EndsWith($"{ResourcePath}.resources", StringComparison.CurrentCulture)))
 12295            {
 20296                var m = GetModuleName(Value);
 20297                throw new InvalidOperationException(
 20298                    $"Resource file '{ResourcePath}.resources' not found in assembly '{m}'. \n" +
 20299                    $"Available resources: {string.Join(", ", resourceNames)}");
 300            }
 301
 302            object resource;
 303
 304            // Get the resource object
 305            try
 52306            {
 60307                if (IsInDesignMode)
 16308                {
 309                    // If in design mode, use the ResourceManager to get the resource
 24310                    var rm = new ComponentResourceManager(Value.GetType());
 24311                    resource = rm.GetObject($"{ResourceItem}{(string.IsNullOrEmpty(fileExtension) ? "" : $".{fileExtensi
 16312                }
 313                else
 44314                {
 315                    // If in runtime, use the ResourceManager to get the resource
 52316                    var rm = new ResourceManager(ResourcePath, Value.GetType().Assembly);
 52317                    resource = rm.GetObject($"{ResourceItem}{(string.IsNullOrEmpty(fileExtension) ? "" : $".{fileExtensi
 36318                }
 52319            }
 24320            catch (Exception ex)
 16321            {
 24322                throw new InvalidOperationException($"Error retrieving resource '{ResourceItem}{(string.IsNullOrEmpty(fi
 323            }
 324
 52325            Bitmap newImage = null;
 52326            if (resource != null)
 40327            {
 48328                if (resource is Bitmap bitmap)
 329                    // If the resource is a Bitmap, use it directly
 28330                    newImage = bitmap;
 36331                else if (resource is byte[] byteArray)
 332                    // If the resource is a byte array, convert it to an image
 32333                    using (var ms = new MemoryStream(byteArray))
 32334                        newImage = new Bitmap(ms);
 335                else
 20336                    throw new InvalidOperationException($"Resource '{ResourcePath}.resources.{ResourceItem}' is not a va
 36337            }
 338
 48339            return newImage;
 40340        }
 341
 342        /// <summary>
 343        /// Gets the image from file.
 344        /// </summary>
 345        /// <param name="Value">The value.</param>
 346        /// <param name="ResourceItem">The resource entry to retrieve.</param>
 347        /// <param name="ResourcePath">The resource path.</param>
 348        /// <param name="fileExtension">The file extension.</param>
 349        /// <returns></returns>
 350        /// <exception cref="System.ArgumentNullException">Value</exception>
 351        /// <exception cref="System.ArgumentException">
 352        /// '{nameof(ResourceItem)}' cannot be null or empty. - ResourceItem
 353        /// or
 354        /// '{nameof(ResourcePath)}' cannot be null or empty. - ResourcePath
 355        /// </exception>
 356        /// <exception cref="ArgumentNullException">Value</exception>
 357        /// <exception cref="ArgumentException">'{nameof(ResourceItem)}' cannot be null or empty. - ResourceItem
 358        /// or
 359        /// '{nameof(ResourcePath)}' cannot be null or empty. - ResourcePath</exception>
 360        public static Bitmap GetImageFromFile(
 361            object Value,
 362            string ResourceItem,
 363            string ResourcePath,
 364            string fileExtension)
 32365        {
 366#if NET5_0_OR_GREATER
 24367            ArgumentNullException.ThrowIfNull(Value);
 368#else
 16369            if (Value is null) throw new ArgumentNullException(nameof(Value));
 370#endif
 40371            if (string.IsNullOrEmpty(ResourceItem)) throw new ArgumentException($"'{nameof(ResourceItem)}' cannot be nul
 36372            if (string.IsNullOrEmpty(ResourcePath)) throw new ArgumentException($"'{nameof(ResourcePath)}' cannot be nul
 373            string assemblyPath;
 374            // Get the directory of the assembly containing _enumType
 375#pragma warning disable SYSLIB0012 // The class is obsolete
 28376            var uri = new UriBuilder(Value.GetType().Assembly.CodeBase);
 377#pragma warning restore SYSLIB0012 // The class is obsolete
 28378            assemblyPath = Path.GetDirectoryName(Uri.UnescapeDataString(uri.Path));
 379            // Construct the full file path
 28380            if (!string.IsNullOrEmpty(ResourcePath))
 28381                assemblyPath = Path.Combine(assemblyPath, ResourcePath);
 28382            if (!string.IsNullOrEmpty(fileExtension))
 20383                ResourceItem = $"{ResourceItem}.{fileExtension}";
 28384            var ResourceName = Path.Combine(assemblyPath, ResourceItem);
 385
 386            // Load the image from the file path
 28387            var newImage = new Bitmap(ResourceName);
 388
 28389            return newImage;
 20390        }
 391
 392        /// <summary>
 393        /// Gets the name of the module.
 394        /// </summary>
 395        /// <param name="Value">The value.</param>
 396        /// <returns></returns>
 397        public static string GetModuleName(object Value)
 56398        {
 399            // Get the name of the DLL or EXE where the reference object is declared
 64400            var m = Value.GetType().Module.Name;
 401            // Remove the file extension from the name
 402#if NET5_0_OR_GREATER
 48403            m = m[0..^4];
 404#else
 16405            m = m.Substring(0, m.Length - 4);
 406#endif
 64407            return m;
 56408        }
 409
 410        /// <summary>
 411        /// Gets the target sizes.
 412        /// </summary>
 413        /// <param name="originalImage">The original image.</param>
 414        /// <param name="bounds">The bounds.</param>
 415        /// <returns></returns>
 416        public static TargetSizes GetTargetSizes(
 417            Bitmap originalImage,
 418            Rectangle bounds)
 48419        {
 56420            var ts = new TargetSizes();
 56421            var aspectRatio = (double)originalImage.Width / originalImage.Height;
 422
 56423            if (bounds.Width / (double)bounds.Height > aspectRatio)
 12424            {
 20425                ts.TargetHeight = bounds.Height;
 20426                ts.TargetWidth = (int)(ts.TargetHeight * aspectRatio);
 12427            }
 428            else
 44429            {
 52430                ts.TargetWidth = bounds.Width;
 52431                ts.TargetHeight = (int)(ts.TargetWidth / aspectRatio);
 44432            }
 433
 56434            ts.OffsetX = (bounds.Width - ts.TargetWidth) / 2;
 56435            ts.OffsetY = (bounds.Height - ts.TargetHeight) / 2;
 56436            return ts;
 48437        }
 438
 439        /// <summary>
 440        /// Target sizes for the image
 441        /// </summary>
 442        public struct TargetSizes
 443        {
 444            /// <summary>
 445            /// The target width
 446            /// </summary>
 447            public int TargetWidth;
 448            /// <summary>
 449            /// The target height
 450            /// </summary>
 451            public int TargetHeight;
 452            /// <summary>
 453            /// The offset x
 454            /// </summary>
 455            public int OffsetX;
 456            /// <summary>
 457            /// The offset y
 458            /// </summary>
 459            public int OffsetY;
 460        }
 461
 462        #endregion
 463
 464        #region Disposal routines ^^^^^^^^^^^^^^^^^^^^^^^^^
 465
 466        /// <summary>
 467        /// Releases unmanaged and - optionally - managed resources.
 468        /// </summary>
 469        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 470        protected virtual void Dispose(bool disposing)
 12471        {
 20472            if (!disposedValue)
 12473            {
 20474                if (disposing)
 12475                {
 12476                }
 477
 20478                disposedValue = true;
 12479            }
 20480        }
 481
 482        // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
 483        // ~FlagEnumUIEditor()
 484        // {
 485        //     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 486        //     Dispose(disposing: false);
 487        // }
 488
 489        /// <summary>
 490        /// Releases unmanaged and - optionally - managed resources.
 491        /// </summary>
 492        public void Dispose()
 12493        {
 494            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 20495            Dispose(disposing: true);
 20496            GC.SuppressFinalize(this);
 20497        }
 498
 499        #endregion
 500    }
 501}

Methods/Properties

get_EnumType()
get_ResourcePath()
get_FileExtension()
ImageTextUIEditor(System.Type)
.ctor(System.Type)
ImageTextUIEditor(System.Type, string)
.ctor(System.Type,System.String)
GetPaintValueSupported(System.ComponentModel.ITypeDescriptorContext)
GetPaintValueSupported(System.ComponentModel.ITypeDescriptorContext)
PaintValue(System.Drawing.Design.PaintValueEventArgs)
PaintValue(System.Drawing.Design.PaintValueEventArgs)
GetImageFromResource(object, System.Type, string, string, System.Drawing.Rectangle)
GetImageFromResource(System.Object,System.Type,System.String,System.String,System.Drawing.Rectangle)
GetImageFromResource(object, System.Type, string, string, System.Drawing.Rectangle)
GetImageFromEmbeddedResource(object, string, string, string)
GetImageFromEmbeddedResource(System.Object,System.String,System.String,System.String)
GetImageFromEmbeddedResource(object, string, string, string)
GetImageFromResourceFile(object, string, string, string, bool)
GetImageFromResourceFile(System.Object,System.String,System.String,System.String,System.Boolean)
GetImageFromResourceFile(object, string, string, string, bool)
GetImageFromFile(object, string, string, string)
GetImageFromFile(System.Object,System.String,System.String,System.String)
GetImageFromFile(object, string, string, string)
GetModuleName(object)
GetModuleName(System.Object)
GetModuleName(object)
GetTargetSizes(System.Drawing.Bitmap, System.Drawing.Rectangle)
GetTargetSizes(System.Drawing.Bitmap,System.Drawing.Rectangle)
GetTargetSizes(System.Drawing.Bitmap, System.Drawing.Rectangle)
Dispose(bool)
Dispose(System.Boolean)
Dispose(bool)
Dispose()
Dispose()