develop #1
7 changed files with 474 additions and 62 deletions
Add selectable image library option and refactor processing
Introduce UI option to choose between System.Graphics and ImageSharp for image processing. Update DataModel and MainForm for robust binding and synchronization. Rewrite ImageCreatorAlternate to use ImageSharp for core operations and GDI+ for overlays. Remove test buttons, add radio group for library selection. Update project dependencies to support new features and modernize image handling.
commit
63751af18d
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.20.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,301 @@
|
|||
using System.Drawing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Drawing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Formats;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
||||
using System.Linq;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace MaddoShared;
|
||||
|
||||
// Minimal alternate adapter that currently delegates to ImageCreatorSharp.
|
||||
// Later this can be replaced with a different library implementation.
|
||||
/// <summary>
|
||||
/// Image creator implemented using SixLabors.ImageSharp for core image operations.
|
||||
/// This implementation focuses on loading, EXIF-orientation, resizing and saving.
|
||||
/// It intentionally implements a minimal subset of the original functionality to
|
||||
/// provide a safe and testable replacement. Additional features (text/logo drawing)
|
||||
/// can be added later using ImageSharp.Drawing.Common and SixLabors.Fonts.
|
||||
/// </summary>
|
||||
public class ImageCreatorAlternate : IImageCreator
|
||||
{
|
||||
private readonly ImageCreatorSharp _inner;
|
||||
private readonly PicSettings _picSettings;
|
||||
private readonly ILogger<ImageCreatorAlternate> _logger;
|
||||
|
||||
public ImageCreatorAlternate(ImageCreatorSharp inner, ILogger<ImageCreatorAlternate> logger)
|
||||
public ImageCreatorAlternate(PicSettings picSettings, ILogger<ImageCreatorAlternate> logger)
|
||||
{
|
||||
_inner = inner;
|
||||
_picSettings = picSettings ?? throw new ArgumentNullException(nameof(picSettings));
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task CreateImageAsync(ImageState imgState, Image logo)
|
||||
public async Task CreateImageAsync(ImageState imgState, System.Drawing.Image logo)
|
||||
{
|
||||
_logger.LogDebug("Using alternate image creator adapter");
|
||||
return _inner.CreateImageAsync(imgState, logo);
|
||||
ArgumentNullException.ThrowIfNull(imgState);
|
||||
|
||||
// Minimal preparation of names and settings normally done by ImageCreatorSharp.PrepareVariables
|
||||
PrepareVariablesMinimal(imgState);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("[Alternate] Processing {File} -> {Dest}", imgState.WorkFile?.FullName, imgState.DestDir?.FullName);
|
||||
|
||||
using var fs = File.OpenRead(imgState.WorkFile.FullName);
|
||||
|
||||
// Load as Rgba32 for general operations
|
||||
using var img = await SixLabors.ImageSharp.Image.LoadAsync<Rgba32>(fs).ConfigureAwait(false);
|
||||
|
||||
// Extract EXIF info (orientation and creation date)
|
||||
ExtractExif(img, imgState);
|
||||
|
||||
// Apply orientation
|
||||
ApplyExifOrientation(img, imgState);
|
||||
|
||||
// Determine output format
|
||||
var forceJpg = _picSettings.UsaForzaJpg;
|
||||
|
||||
// Compute big size
|
||||
var bigSize = ComputeBigSize(img.Width, img.Height);
|
||||
|
||||
// Resize big image if needed
|
||||
using var imgBig = img.Clone(ctx => ctx.Resize(bigSize.Width, bigSize.Height));
|
||||
|
||||
// Ensure destination exists
|
||||
imgState.DestDir?.Create();
|
||||
|
||||
var fileNameBig = System.IO.Path.Combine(imgState.DestDir.FullName, imgState.NomeFileBig);
|
||||
|
||||
// Draw overlays (text/logo) onto big image via GDI+ and save
|
||||
await DrawAndSaveWithGdiAsync(imgBig, fileNameBig, imgState, logo, _picSettings.JpegQuality, isThumbnail: false).ConfigureAwait(false);
|
||||
|
||||
// Save big image with quality if JPEG
|
||||
var extBig = System.IO.Path.GetExtension(imgState.NomeFileBig)?.ToLowerInvariant() ?? string.Empty;
|
||||
var encoderBig = GetEncoderForExtension(extBig, _picSettings.JpegQuality);
|
||||
await using (var outStream = System.IO.File.Open(fileNameBig, System.IO.FileMode.Create, System.IO.FileAccess.Write))
|
||||
{
|
||||
await imgBig.SaveAsync(outStream, encoderBig, default).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Create thumbnail if requested
|
||||
if (_picSettings.CreaMiniature)
|
||||
{
|
||||
var smallSize = ComputeSmallSize(img.Width, img.Height);
|
||||
using var imgSmall = img.Clone(ctx => ctx.Resize(smallSize.Width, smallSize.Height));
|
||||
|
||||
var fileNameSmall = System.IO.Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall);
|
||||
|
||||
// Draw overlays and save thumbnail via GDI+
|
||||
await DrawAndSaveWithGdiAsync(imgSmall, fileNameSmall, imgState, logo, _picSettings.JpegQualityMin, isThumbnail: true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[Alternate] Error processing image {File}", imgState.WorkFile?.Name);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Thumbnail overlays are rendered by the GDI+ pass in DrawAndSaveWithGdiAsync to match original rendering.
|
||||
|
||||
private static SixLabors.ImageSharp.Formats.IImageEncoder GetEncoderForExtension(string ext, long quality)
|
||||
{
|
||||
quality = Math.Clamp(quality, 1, 100);
|
||||
return ext switch
|
||||
{
|
||||
".png" => new SixLabors.ImageSharp.Formats.Png.PngEncoder(),
|
||||
".gif" => new SixLabors.ImageSharp.Formats.Gif.GifEncoder(),
|
||||
_ => new JpegEncoder { Quality = (int)quality },
|
||||
};
|
||||
}
|
||||
|
||||
private async Task DrawAndSaveWithGdiAsync(Image<Rgba32> imgSharp, string outputPath, ImageState imgState, System.Drawing.Image logo, long quality, bool isThumbnail)
|
||||
{
|
||||
// Convert ImageSharp image to System.Drawing.Bitmap via MemoryStream PNG to preserve alpha
|
||||
await using var ms = new MemoryStream();
|
||||
await imgSharp.SaveAsPngAsync(ms).ConfigureAwait(false);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using var bmp = new Bitmap(ms);
|
||||
using var g = Graphics.FromImage(bmp);
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||
|
||||
// Prepare text
|
||||
var text = isThumbnail ? imgState.TestoFirmaPiccola : imgState.TestoFirma;
|
||||
if (string.IsNullOrEmpty(text) && _picSettings.TestoNome)
|
||||
text = imgState.NomeFileBig;
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
var fontSize = isThumbnail ? imgState.DimensioneStandardMiniatura : imgState.DimensioneStandard;
|
||||
using var font = new System.Drawing.Font(_picSettings.IlFont ?? "Arial", Math.Max(6, fontSize));
|
||||
using var shadowBrush = new SolidBrush(System.Drawing.Color.FromArgb(imgState.AlphaScelta, 0, 0, 0));
|
||||
using var textBrush = new SolidBrush(System.Drawing.Color.FromArgb(imgState.AlphaScelta, _picSettings.FontColoreRGB));
|
||||
|
||||
var sf = new StringFormat { Alignment = StringAlignment.Center };
|
||||
var x = bmp.Width / 2f;
|
||||
var y = isThumbnail ? bmp.Height - fontSize - 4 : bmp.Height - fontSize - (_picSettings.Margine);
|
||||
|
||||
g.DrawString(text, font, shadowBrush, new System.Drawing.PointF(x + 1, y + 1), sf);
|
||||
g.DrawString(text, font, textBrush, new System.Drawing.PointF(x, y), sf);
|
||||
}
|
||||
|
||||
// Draw logo if provided
|
||||
if (logo != null && _picSettings.LogoAggiungi && File.Exists(_picSettings.LogoNomeFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var target = new System.Drawing.Size(_picSettings.LogoLarghezza, _picSettings.LogoAltezza);
|
||||
using var logoResized = new Bitmap(logo, target.Width, target.Height);
|
||||
|
||||
int xPos = _picSettings.LogoPosizioneH?.ToUpperInvariant() == "DESTRA" ? bmp.Width - target.Width - _picSettings.Margine : _picSettings.Margine;
|
||||
int yPos = _picSettings.LogoPosizioneV?.ToUpperInvariant() == "BASSO" ? bmp.Height - target.Height - _picSettings.Margine : _picSettings.Margine;
|
||||
|
||||
var cm = new System.Drawing.Imaging.ColorMatrix { Matrix33 = (float)Math.Clamp((int.TryParse(_picSettings.LogoTrasparenza, out var lt) ? lt : 100) / 100.0, 0.0, 1.0) };
|
||||
var ia = new System.Drawing.Imaging.ImageAttributes();
|
||||
ia.SetColorMatrix(cm, System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap);
|
||||
|
||||
g.DrawImage(logoResized, new System.Drawing.Rectangle(xPos, yPos, target.Width, target.Height), 0, 0, target.Width, target.Height, GraphicsUnit.Pixel, ia);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[Alternate] Error drawing logo in GDI pass");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure directory
|
||||
var dir = System.IO.Path.GetDirectoryName(outputPath);
|
||||
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
// Save with requested quality using GDI encoder
|
||||
var encoder = GetEncoder(ImageFormat.Jpeg);
|
||||
var myEncoder = System.Drawing.Imaging.Encoder.Quality;
|
||||
using var encoderParams = new System.Drawing.Imaging.EncoderParameters(1);
|
||||
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(myEncoder, quality);
|
||||
bmp.Save(outputPath, encoder, encoderParams);
|
||||
}
|
||||
|
||||
private static ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format)
|
||||
{
|
||||
var codecs = ImageCodecInfo.GetImageDecoders();
|
||||
foreach (var codec in codecs)
|
||||
{
|
||||
if (codec.FormatID == format.Guid) return codec;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void PrepareVariablesMinimal(ImageState imgState)
|
||||
{
|
||||
imgState.NomeFileBig = imgState.WorkFile.Name;
|
||||
imgState.NomeFileSmall = (_picSettings.Suffisso ?? string.Empty) + imgState.WorkFile.Name;
|
||||
imgState.DimensioneStandard = _picSettings.DimStandard;
|
||||
imgState.DimensioneStandardMiniatura = _picSettings.DimStandardMiniatura;
|
||||
|
||||
// sanitize
|
||||
imgState.NomeFileBig = SanitizeFileName(imgState.NomeFileBig);
|
||||
imgState.NomeFileSmall = SanitizeFileName(imgState.NomeFileSmall);
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName)) return fileName;
|
||||
var invalid = System.IO.Path.GetInvalidFileNameChars();
|
||||
var sb = new System.Text.StringBuilder(fileName.Length);
|
||||
foreach (var ch in fileName)
|
||||
sb.Append(Array.IndexOf(invalid, ch) >= 0 ? '_' : ch);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void ExtractExif(Image<Rgba32> img, ImageState imgState)
|
||||
{
|
||||
imgState.Orientation = Orientations.TopLeft;
|
||||
imgState.CreationDate = null;
|
||||
|
||||
var profile = img.Metadata?.ExifProfile;
|
||||
if (profile is null) return;
|
||||
|
||||
IExifValue<ushort> rotation = null;
|
||||
var found = profile.TryGetValue(ExifTag.Orientation, out rotation);
|
||||
if (found && rotation != null)
|
||||
{
|
||||
imgState.Orientation = (Orientations)Convert.ToInt32(rotation.Value);
|
||||
}
|
||||
|
||||
IExifValue<string> date = null;
|
||||
var creationFound = profile.TryGetValue(ExifTag.DateTimeOriginal, out date);
|
||||
if (creationFound && date != null)
|
||||
{
|
||||
if (DateTime.TryParseExact(date.Value, "yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var cr))
|
||||
{
|
||||
imgState.CreationDate = cr;
|
||||
}
|
||||
else
|
||||
{
|
||||
imgState.CreationDate = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
imgState.CreationDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyExifOrientation(Image<Rgba32> img, ImageState imgState)
|
||||
{
|
||||
// Common EXIF orientations: 1=TopLeft, 3=BottomRight (rotate 180), 6=RightTop (rotate 90 CW), 8=LeftBottom (rotate 270 CW)
|
||||
switch (imgState.Orientation)
|
||||
{
|
||||
case Orientations.RightTop: // 6
|
||||
img.Mutate(x => x.Rotate(RotateMode.Rotate90));
|
||||
break;
|
||||
case Orientations.BottomRight: // 3
|
||||
img.Mutate(x => x.Rotate(RotateMode.Rotate180));
|
||||
break;
|
||||
case Orientations.LftBottom: // 8
|
||||
img.Mutate(x => x.Rotate(RotateMode.Rotate270));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Drawing.Size ComputeBigSize(int width, int height)
|
||||
{
|
||||
// If original large size option requested, return original
|
||||
// otherwise compute based on width/height limits from settings
|
||||
return width > height
|
||||
? CalculateThumbnailSize(width, height, _picSettings.LarghezzaBig, "Larghezza")
|
||||
: CalculateThumbnailSize(width, height, _picSettings.AltezzaBig, "Altezza");
|
||||
}
|
||||
|
||||
private System.Drawing.Size ComputeSmallSize(int width, int height)
|
||||
{
|
||||
return width > height
|
||||
? CalculateThumbnailSize(width, height, _picSettings.LarghezzaSmall, "Larghezza")
|
||||
: CalculateThumbnailSize(width, height, _picSettings.AltezzaSmall, "Altezza");
|
||||
}
|
||||
|
||||
// Helper to access PicSettings values via instance _picSettings
|
||||
|
||||
private static System.Drawing.Size CalculateThumbnailSize(int currentwidth, int currentheight, int maxPixel, string tipoSize)
|
||||
{
|
||||
double tempMultiplier;
|
||||
if (string.Equals(tipoSize, "Larghezza", StringComparison.OrdinalIgnoreCase))
|
||||
tempMultiplier = maxPixel / (double)currentwidth;
|
||||
else if (string.Equals(tipoSize, "Altezza", StringComparison.OrdinalIgnoreCase))
|
||||
tempMultiplier = maxPixel / (double)currentheight;
|
||||
else if (currentheight > currentwidth)
|
||||
tempMultiplier = maxPixel / (double)currentheight;
|
||||
else
|
||||
tempMultiplier = maxPixel / (double)currentwidth;
|
||||
|
||||
var newSize = new System.Drawing.Size(Convert.ToInt32(currentwidth * tempMultiplier), Convert.ToInt32(currentheight * tempMultiplier));
|
||||
return newSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
|
||||
<PackageReference Include="SixLabors.Fonts" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="System.Buffers" Version="4.6.1" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="9.0.7" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="10.0.3" />
|
||||
<PackageReference Include="System.Memory" Version="4.6.3" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
|
|
@ -27,6 +29,6 @@
|
|||
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.421302">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -397,6 +397,50 @@ namespace ImageCatalog_2
|
|||
}
|
||||
}
|
||||
|
||||
// Image library selection (UI radio buttons bind to the boolean helpers)
|
||||
private string _imageLibrary = "System.Graphics";
|
||||
|
||||
/// <summary>
|
||||
/// The selected image processing library. Possible values: "System.Graphics" or "ImageSharp".
|
||||
/// This value is mirrored into PicSettings.ImageCreatorProvider so the runtime mapper picks the implementation.
|
||||
/// </summary>
|
||||
public string ImageLibrary
|
||||
{
|
||||
get => _imageLibrary;
|
||||
set
|
||||
{
|
||||
if (_imageLibrary == value) return;
|
||||
_imageLibrary = value;
|
||||
// Reflect selection into PicSettings so mapper can resolve at runtime
|
||||
_picSettings.ImageCreatorProvider = string.Equals(value, "ImageSharp", StringComparison.OrdinalIgnoreCase)
|
||||
? "ALTERNATE"
|
||||
: "Sharp";
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(UseSystemGraphics));
|
||||
NotifyPropertyChanged(nameof(UseImageSharp));
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseSystemGraphics
|
||||
{
|
||||
get => string.Equals(ImageLibrary, "System.Graphics", StringComparison.OrdinalIgnoreCase);
|
||||
set
|
||||
{
|
||||
if (value) ImageLibrary = "System.Graphics";
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseImageSharp
|
||||
{
|
||||
get => string.Equals(ImageLibrary, "ImageSharp", StringComparison.OrdinalIgnoreCase);
|
||||
set
|
||||
{
|
||||
if (value) ImageLibrary = "ImageSharp";
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Folder division settings
|
||||
private int _filesPerFolder = 99;
|
||||
public int FilesPerFolder
|
||||
|
|
|
|||
75
imagecatalog/MainForm.Designer.cs
generated
75
imagecatalog/MainForm.Designer.cs
generated
|
|
@ -45,8 +45,6 @@ namespace ImageCatalog
|
|||
Label43 = new Label();
|
||||
TabControl1 = new TabControl();
|
||||
TabPage5 = new TabPage();
|
||||
button1 = new Button();
|
||||
btnTest = new Button();
|
||||
GroupBox11 = new GroupBox();
|
||||
numericUpDown2 = new NumericUpDown();
|
||||
numericUpDown1 = new NumericUpDown();
|
||||
|
|
@ -183,6 +181,9 @@ namespace ImageCatalog
|
|||
_btnCreaCatalogoAsync = new Button();
|
||||
timer1 = new System.Windows.Forms.Timer(components);
|
||||
dataModelBindingSource1 = new BindingSource(components);
|
||||
groupBox12 = new GroupBox();
|
||||
rdbLibrary1 = new RadioButton();
|
||||
rdbLibrary2 = new RadioButton();
|
||||
((System.ComponentModel.ISupportInitialize)bindingSource1).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)dataModelBindingSource).BeginInit();
|
||||
TabControl1.SuspendLayout();
|
||||
|
|
@ -211,6 +212,7 @@ namespace ImageCatalog
|
|||
((System.ComponentModel.ISupportInitialize)_PictureBox1).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)PictureBox3).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)dataModelBindingSource1).BeginInit();
|
||||
groupBox12.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// ProgressBar1
|
||||
|
|
@ -270,8 +272,7 @@ namespace ImageCatalog
|
|||
//
|
||||
// TabPage5
|
||||
//
|
||||
TabPage5.Controls.Add(button1);
|
||||
TabPage5.Controls.Add(btnTest);
|
||||
TabPage5.Controls.Add(groupBox12);
|
||||
TabPage5.Controls.Add(GroupBox11);
|
||||
TabPage5.Controls.Add(GroupBox3);
|
||||
TabPage5.Controls.Add(GroupBox8);
|
||||
|
|
@ -285,28 +286,6 @@ namespace ImageCatalog
|
|||
TabPage5.Text = "Generale";
|
||||
TabPage5.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// button1
|
||||
//
|
||||
button1.DataBindings.Add(new Binding("Command", bindingSource1, "AsyncTestCommand", true));
|
||||
button1.Location = new Point(751, 720);
|
||||
button1.Margin = new Padding(5);
|
||||
button1.Name = "button1";
|
||||
button1.Size = new Size(141, 43);
|
||||
button1.TabIndex = 50;
|
||||
button1.Text = "Test Async";
|
||||
button1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnTest
|
||||
//
|
||||
btnTest.DataBindings.Add(new Binding("Command", bindingSource1, "TestCommand", true));
|
||||
btnTest.Location = new Point(487, 708);
|
||||
btnTest.Margin = new Padding(5);
|
||||
btnTest.Name = "btnTest";
|
||||
btnTest.Size = new Size(141, 43);
|
||||
btnTest.TabIndex = 49;
|
||||
btnTest.Text = "Test";
|
||||
btnTest.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// GroupBox11
|
||||
//
|
||||
GroupBox11.Controls.Add(numericUpDown2);
|
||||
|
|
@ -1750,6 +1729,7 @@ namespace ImageCatalog
|
|||
//
|
||||
// versionLabel
|
||||
//
|
||||
versionLabel.DataBindings.Add(new Binding("Text", bindingSource1, "AppVersion", true));
|
||||
versionLabel.Location = new Point(1182, 873);
|
||||
versionLabel.Margin = new Padding(6, 0, 6, 0);
|
||||
versionLabel.Name = "versionLabel";
|
||||
|
|
@ -1757,7 +1737,6 @@ namespace ImageCatalog
|
|||
versionLabel.TabIndex = 62;
|
||||
versionLabel.Text = "Versione 2.2 2021";
|
||||
versionLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
versionLabel.DataBindings.Add(new Binding("Text", bindingSource1, "AppVersion", true));
|
||||
//
|
||||
// _Button7
|
||||
//
|
||||
|
|
@ -1863,6 +1842,41 @@ namespace ImageCatalog
|
|||
//
|
||||
dataModelBindingSource1.DataSource = typeof(ImageCatalog_2.DataModel);
|
||||
//
|
||||
// groupBox12
|
||||
//
|
||||
groupBox12.Controls.Add(rdbLibrary2);
|
||||
groupBox12.Controls.Add(rdbLibrary1);
|
||||
groupBox12.Location = new Point(405, 625);
|
||||
groupBox12.Name = "groupBox12";
|
||||
groupBox12.Size = new Size(350, 175);
|
||||
groupBox12.TabIndex = 49;
|
||||
groupBox12.TabStop = false;
|
||||
groupBox12.Text = "Libreria Manipolazione Grafica";
|
||||
//
|
||||
// rdbLibrary1
|
||||
//
|
||||
rdbLibrary1.AutoSize = true;
|
||||
rdbLibrary1.Location = new Point(12, 37);
|
||||
rdbLibrary1.Name = "rdbLibrary1";
|
||||
rdbLibrary1.Size = new Size(188, 34);
|
||||
rdbLibrary1.TabIndex = 0;
|
||||
rdbLibrary1.TabStop = true;
|
||||
rdbLibrary1.Text = "System.Graphics";
|
||||
rdbLibrary1.DataBindings.Add(new Binding("Checked", bindingSource1, "UseSystemGraphics", true, DataSourceUpdateMode.OnPropertyChanged));
|
||||
rdbLibrary1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// rdbLibrary2
|
||||
//
|
||||
rdbLibrary2.AutoSize = true;
|
||||
rdbLibrary2.Location = new Point(12, 77);
|
||||
rdbLibrary2.Name = "rdbLibrary2";
|
||||
rdbLibrary2.Size = new Size(149, 34);
|
||||
rdbLibrary2.TabIndex = 1;
|
||||
rdbLibrary2.TabStop = true;
|
||||
rdbLibrary2.Text = "ImageSharp";
|
||||
rdbLibrary2.DataBindings.Add(new Binding("Checked", bindingSource1, "UseImageSharp", true, DataSourceUpdateMode.OnPropertyChanged));
|
||||
rdbLibrary2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(12F, 30F);
|
||||
|
|
@ -1929,6 +1943,8 @@ namespace ImageCatalog
|
|||
((System.ComponentModel.ISupportInitialize)_PictureBox1).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)PictureBox3).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)dataModelBindingSource1).EndInit();
|
||||
groupBox12.ResumeLayout(false);
|
||||
groupBox12.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
|
@ -2275,12 +2291,13 @@ namespace ImageCatalog
|
|||
private BindingSource dataModelBindingSource;
|
||||
private BindingSource dataModelBindingSource1;
|
||||
private BindingSource bindingSource1;
|
||||
private Button btnTest;
|
||||
private Button button1;
|
||||
private NumericUpDown numericUpDown1;
|
||||
private NumericUpDown numericUpDown2;
|
||||
private Button btnOpenDestFolder;
|
||||
private Button btnOpenSourceFolder;
|
||||
private GroupBox groupBox12;
|
||||
private RadioButton rdbLibrary2;
|
||||
private RadioButton rdbLibrary1;
|
||||
|
||||
internal Button btnCreaCatalogoAsync
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ using ImageCatalog_2.Commands;
|
|||
using ImageCatalog_2.Services;
|
||||
using MaddoShared;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ImageCatalog;
|
||||
|
||||
|
|
@ -30,6 +29,8 @@ public partial class MainForm
|
|||
|
||||
private readonly ParametriSetup _parametriSetup;
|
||||
private readonly PicSettings _picSettings;
|
||||
// Prevent re-entrant updates between UI events and model PropertyChanged handling
|
||||
private bool _suppressRadioUpdates = false;
|
||||
|
||||
public MainForm(DataModel model, ImageCreationStuff imageCreationStuff, PicSettings picSettings,
|
||||
ParametriSetup parametriSetup, ILogger<MainForm> logger)
|
||||
|
|
@ -42,11 +43,44 @@ public partial class MainForm
|
|||
_logger.LogDebug("Start");
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
// Set this form as the control for thread marshalling in the DataModel
|
||||
Model.SetControl(this);
|
||||
|
||||
BindControls();
|
||||
// Set this form as the control for thread marshalling in the DataModel
|
||||
Model.SetControl(this);
|
||||
|
||||
// Ensure the designer data bindings have a concrete DataSource immediately so
|
||||
// that UI controls (radio buttons) reflect the ViewModel state and propagate
|
||||
// user changes back to the ViewModel.
|
||||
bindingSource1.DataSource = Model;
|
||||
|
||||
BindControls();
|
||||
|
||||
// The designer originally bound the radio buttons to boolean helpers on the ViewModel.
|
||||
// Those two separate bindings can fight with each other. Clear designer bindings and
|
||||
// wire explicit Click handlers that update the single authoritative property
|
||||
// `Model.ImageLibrary`. Also keep a PropertyChanged listener to reflect external
|
||||
// changes back into the radio buttons.
|
||||
rdbLibrary1.DataBindings.Clear();
|
||||
rdbLibrary2.DataBindings.Clear();
|
||||
|
||||
// Initialize radio state from model
|
||||
rdbLibrary1.Checked = Model.UseSystemGraphics;
|
||||
rdbLibrary2.Checked = Model.UseImageSharp;
|
||||
|
||||
// Use Click handlers (not CheckedChanged) to avoid competing binding updates
|
||||
rdbLibrary1.Click += (_, _) =>
|
||||
{
|
||||
if (_suppressRadioUpdates) return;
|
||||
if (Model.ImageLibrary != "System.Graphics")
|
||||
Model.ImageLibrary = "System.Graphics";
|
||||
};
|
||||
rdbLibrary2.Click += (_, _) =>
|
||||
{
|
||||
if (_suppressRadioUpdates) return;
|
||||
if (Model.ImageLibrary != "ImageSharp")
|
||||
Model.ImageLibrary = "ImageSharp";
|
||||
};
|
||||
|
||||
// Watch for model changes so we can reflect external updates
|
||||
Model.PropertyChanged += Model_PropertyChanged;
|
||||
|
||||
// Save user preferences on form close instead of immediately when dialogs are used
|
||||
this.FormClosing += MainForm_FormClosing;
|
||||
|
|
@ -58,17 +92,56 @@ public partial class MainForm
|
|||
// Version label is data-bound to DataModel.AppVersion; DataModel is populated with the version via DI
|
||||
}
|
||||
|
||||
protected void BindControls()
|
||||
private void RdbLibrary_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
// Bind buttons to ViewModel commands using command binding
|
||||
_btnCreaCatalogoAsync.BindCommand(Model.ProcessImagesCommand);
|
||||
button1.BindCommand(Model.ProcessImagesCommand);
|
||||
_Button2.BindCommand(Model.SelectSourceFolderCommand);
|
||||
_Button3.BindCommand(Model.SelectDestinationFolderCommand);
|
||||
_Button4.BindCommand(Model.SelectLogoFileCommand);
|
||||
_Button5.BindCommand(Model.SaveSettingsCommand);
|
||||
_Button6.BindCommand(Model.LoadSettingsCommand);
|
||||
_Button8.BindCommand(Model.SelectColorCommand);
|
||||
// Keep behavior simple: when a radio button becomes checked, update the ViewModel
|
||||
// so that the designer binding and PicSettings stay in sync.
|
||||
if (sender is RadioButton rb && rb.Checked)
|
||||
{
|
||||
_logger?.LogDebug("Radio library changed: {RadioName}", rb.Name);
|
||||
if (rb == rdbLibrary2)
|
||||
{
|
||||
Model.ImageLibrary = "ImageSharp";
|
||||
}
|
||||
else if (rb == rdbLibrary1)
|
||||
{
|
||||
Model.ImageLibrary = "System.Graphics";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is null) return;
|
||||
if (e.PropertyName == nameof(Model.ImageLibrary) || e.PropertyName == nameof(Model.UseImageSharp) || e.PropertyName == nameof(Model.UseSystemGraphics))
|
||||
{
|
||||
_logger?.LogDebug("Model property changed: {Property} => ImageLibrary={ImageLibrary}, PicSettings.Provider={Provider}", e.PropertyName, Model.ImageLibrary, _picSettings.ImageCreatorProvider);
|
||||
|
||||
// Reflect authoritative model value into the radio buttons in a thread-safe, re-entrancy-safe way
|
||||
try
|
||||
{
|
||||
_suppressRadioUpdates = true;
|
||||
rdbLibrary1.Checked = Model.UseSystemGraphics;
|
||||
rdbLibrary2.Checked = Model.UseImageSharp;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressRadioUpdates = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void BindControls()
|
||||
{
|
||||
// Bind buttons to ViewModel commands using command binding
|
||||
_btnCreaCatalogoAsync.BindCommand(Model.ProcessImagesCommand);
|
||||
// Note: `button1` control does not exist in the designer. Use the primary create button only.
|
||||
_Button2.BindCommand(Model.SelectSourceFolderCommand);
|
||||
_Button3.BindCommand(Model.SelectDestinationFolderCommand);
|
||||
_Button4.BindCommand(Model.SelectLogoFileCommand);
|
||||
_Button5.BindCommand(Model.SaveSettingsCommand);
|
||||
_Button6.BindCommand(Model.LoadSettingsCommand);
|
||||
_Button8.BindCommand(Model.SelectColorCommand);
|
||||
|
||||
// Subscribe to ViewModel events for UI dialogs (these need UI context)
|
||||
Model.SelectSourceFolderRequested += OnSelectSourceFolderRequested;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue