Fixes and mapping

This commit is contained in:
MaddoScientisto 2026-02-04 23:16:06 +01:00
commit ba965e8266
10 changed files with 449 additions and 284 deletions

View file

@ -96,7 +96,7 @@ namespace MaddoShared
catch (Exception e)
{
logger.LogError(e, "Error in reporting update");
throw;
// Don't rethrow - continue processing other images
}
}
finally

View file

@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AsyncEnumerator" Version="4.0.2" />
<PackageReference Include="AutoMapper" Version="16.0.0" />
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="IDisposableAnalyzers" Version="4.0.8">
<PrivateAssets>all</PrivateAssets>

View file

@ -0,0 +1,53 @@
using System;
using System.Windows.Forms;
using System.Windows.Input;
namespace ImageCatalog_2.Commands
{
/// <summary>
/// Helper class to bind WinForms Button controls to ICommand implementations
/// </summary>
public static class ButtonCommandBinder
{
/// <summary>
/// Binds a Button's Click event to an ICommand
/// </summary>
/// <param name="button">The button to bind</param>
/// <param name="command">The command to execute</param>
/// <param name="commandParameter">Optional parameter to pass to the command</param>
public static void BindCommand(this Button button, ICommand command, object commandParameter = null)
{
if (button == null) throw new ArgumentNullException(nameof(button));
if (command == null) throw new ArgumentNullException(nameof(command));
// Wire up the Click event to execute the command
button.Click += (sender, e) =>
{
if (command.CanExecute(commandParameter))
{
command.Execute(commandParameter);
}
};
// Wire up CanExecuteChanged to enable/disable the button
command.CanExecuteChanged += (sender, e) =>
{
button.Enabled = command.CanExecute(commandParameter);
};
// Set initial enabled state
button.Enabled = command.CanExecute(commandParameter);
}
/// <summary>
/// Binds multiple buttons to commands at once
/// </summary>
public static void BindCommands(params (Button button, ICommand command, object parameter)[] bindings)
{
foreach (var (button, command, parameter) in bindings)
{
button.BindCommand(command, parameter);
}
}
}
}

View file

@ -1,14 +1,19 @@
using ImageCatalog_2.Commands;
using ImageCatalog_2.Services;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Input;
using AutoMapper;
using MaddoShared;
using Microsoft.Extensions.Logging;
namespace ImageCatalog_2
@ -29,17 +34,25 @@ namespace ImageCatalog_2
private readonly ITestService _service;
private readonly ILogger<DataModel> _logger;
private readonly ISettingsService _settingsService;
private readonly ImageCreationStuff _imageCreationService;
private readonly PicSettings _picSettings;
private readonly IMapper _mapper;
// ComboBox collections
public List<string> AvailableFonts { get; }
public List<string> VerticalPositions { get; } = new() { "Alto", "Centro", "Basso" };
public List<string> HorizontalAlignments { get; } = new() { "Sinistra", "Centro", "Destra" };
public DataModel(ITestService testService, ISettingsService settingsService, ILogger<DataModel> logger)
public DataModel(ITestService testService, ISettingsService settingsService,
ImageCreationStuff imageCreationService, PicSettings picSettings,
IMapper mapper, ILogger<DataModel> logger)
{
_service = testService;
_logger = logger;
_settingsService = settingsService;
_imageCreationService = imageCreationService;
_picSettings = picSettings;
_mapper = mapper;
TestCommand = new RelayCommand(Test);
AsyncTestCommand = new AsyncCommand(TestAsync);
@ -774,6 +787,67 @@ namespace ImageCatalog_2
}
}
// Image processing progress and status
private string _processingStatus = "";
public string ProcessingStatus
{
get => _processingStatus;
set
{
_processingStatus = value;
NotifyPropertyChanged();
}
}
private int _processedImagesCount = 0;
public int ProcessedImagesCount
{
get => _processedImagesCount;
set
{
_processedImagesCount = value;
NotifyPropertyChanged();
}
}
private int _totalImagesCount = 0;
public int TotalImagesCount
{
get => _totalImagesCount;
set
{
_totalImagesCount = value;
NotifyPropertyChanged();
}
}
private int _progressBarValue = 0;
public int ProgressBarValue
{
get => _progressBarValue;
set
{
_progressBarValue = value;
NotifyPropertyChanged();
}
}
private int _progressBarMaximum = 100;
public int ProgressBarMaximum
{
get => _progressBarMaximum;
set
{
_progressBarMaximum = value;
NotifyPropertyChanged();
}
}
private ConcurrentBag<string> _results = new();
private int _currentAmount = 0;
private int _previousAmount = 0;
private System.Threading.Timer? _speedTimer;
private void Test(object parameter)
{
Debug.WriteLine("Yep");
@ -787,6 +861,123 @@ namespace ImageCatalog_2
private async Task ProcessImages()
{
_logger.LogInformation("Avvio elaborazione...");
UiEnabled = false;
MainToken?.Dispose();
MainToken = new CancellationTokenSource();
var token = MainToken.Token;
// Fix paths
FixPaths();
// Reset counters
ProcessingStatus = "Elaborazione in corso...";
TotalImagesCount = 0;
ProcessedImagesCount = 0;
SpeedCounter = "-f/m";
ProgressBarValue = 0;
ProgressBarMaximum = 100;
// Update PicSettings from DataModel using AutoMapper
_mapper.Map(this, _picSettings);
var imageCreationOptions = new ImageCreationStuff.Options
{
AggiornaSottodirectory = UpdateSubdirectories,
CreaSottocartelle = CreateSubfolders,
FilePerCartella = FilesPerFolder,
SuffissoCartelle = FolderSuffix,
CifreContatore = CounterDigits,
NumerazioneType = UseProgressiveNumbering ? NumerazioneType.Progressiva : NumerazioneType.Files,
SourcePath = SourcePath,
DestinationPath = DestinationPath,
MaxThreads = ThreadsCount,
ChunksSize = ChunkSize,
LinearExecution = UseSequentialProcessing
};
try
{
_results = new ConcurrentBag<string>();
_currentAmount = 0;
_previousAmount = 0;
// Start speed timer (every minute)
_speedTimer = new System.Threading.Timer(UpdateSpeedCounter, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
var time = await _imageCreationService.CreaCatalogoParallel(
imageCreationOptions,
_results,
OnImageProcessed,
token);
SpeedCounter = time;
_speedTimer?.Dispose();
_speedTimer = null;
}
catch (OperationCanceledException)
{
_logger.LogInformation("Operazione Cancellata");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante l'elaborazione delle immagini");
ProcessingStatus = $"Errore: {ex.Message}";
}
finally
{
MainToken?.Dispose();
MainToken = null;
_speedTimer?.Dispose();
_speedTimer = null;
}
ProcessingStatus = "Finito";
UiEnabled = true;
}
private void UpdateSpeedCounter(object? state)
{
_previousAmount = _currentAmount;
_currentAmount = _results.Count;
int diff = _currentAmount - _previousAmount;
SpeedCounter = $"{diff} f/m";
}
private void OnImageProcessed(object? sender, Tuple<string, int> args)
{
ProcessedImagesCount = _results.Count;
TotalImagesCount = args.Item2;
ProgressBarMaximum = args.Item2;
ProgressBarValue = _results.Count;
ProcessingStatus = args.Item1;
}
private void FixPaths()
{
SourcePath = FixPath(SourcePath);
DestinationPath = FixPath(DestinationPath);
}
private string FixPath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return string.Empty;
}
// Trim leading/trailing quotes
path = path.Trim().Trim('"');
// Normalize directory separators
path = path.Replace('/', System.IO.Path.DirectorySeparatorChar)
.Replace('\\', System.IO.Path.DirectorySeparatorChar);
// Remove trailing separators then add one back
path = path.TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar;
return path;
}
private async Task CancelOperation()

View file

@ -39,6 +39,7 @@
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="16.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />

View file

@ -1835,7 +1835,6 @@ namespace ImageCatalog
_btnCreaCatalogoAsync.TabIndex = 68;
_btnCreaCatalogoAsync.Text = "CREA";
_btnCreaCatalogoAsync.UseVisualStyleBackColor = true;
_btnCreaCatalogoAsync.Click += Button1_Click;
//
// dataModelBindingSource1
//
@ -2268,16 +2267,7 @@ namespace ImageCatalog
[MethodImpl(MethodImplOptions.Synchronized)]
set
{
if (_btnCreaCatalogoAsync != null)
{
_btnCreaCatalogoAsync.Click -= Button1_Click;
}
_btnCreaCatalogoAsync = value;
if (_btnCreaCatalogoAsync != null)
{
_btnCreaCatalogoAsync.Click += Button1_Click;
}
}
}
}

View file

@ -13,9 +13,11 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using ImageCatalog_2;
using ImageCatalog_2.Commands;
using ImageCatalog_2.Services;
using MaddoShared;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
namespace ImageCatalog;
@ -25,8 +27,6 @@ public partial class MainForm
private readonly ILogger<MainForm> _logger;
private readonly ImageCreationStuff _imageCreationService;
private readonly ParametriSetup _parametriSetup;
private readonly PicSettings _picSettings;
@ -34,7 +34,6 @@ public partial class MainForm
ParametriSetup parametriSetup, ILogger<MainForm> logger)
{
Model = model;
_imageCreationService = imageCreationStuff;
_parametriSetup = parametriSetup;
_picSettings = picSettings;
_logger = logger;
@ -42,77 +41,37 @@ public partial class MainForm
_logger.LogDebug("Start");
InitializeComponent();
// Set this form as the control for thread marshalling in the DataModel
Model.SetControl(this);
BindControls();
// Subscribe to DataModel events
var version = Assembly.GetExecutingAssembly().GetName().Version;
_Label27.Text = $"Version: {version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
protected void BindControls()
{
// 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);
// Subscribe to ViewModel events for UI dialogs (these need UI context)
Model.SelectSourceFolderRequested += OnSelectSourceFolderRequested;
Model.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested;
Model.SelectLogoFileRequested += OnSelectLogoFileRequested;
Model.SaveSettingsRequested += OnSaveSettingsRequested;
Model.LoadSettingsRequested += OnLoadSettingsRequested;
Model.SelectColorRequested += OnSelectColorRequested;
var version = Assembly.GetExecutingAssembly().GetName().Version;
_Label27.Text = $"Version: {version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
_results = [];
UiUpdateEvent += OnUiUpdateEvent;
}
protected void BindControls()
{
// Wire up buttons to ViewModel commands
_Button2.Click += (s, e) => Model.SelectSourceFolderCommand.Execute(null);
_Button3.Click += (s, e) => Model.SelectDestinationFolderCommand.Execute(null);
_Button4.Click += (s, e) => Model.SelectLogoFileCommand.Execute(null);
_Button5.Click += (s, e) => Model.SaveSettingsCommand.Execute(null);
_Button6.Click += (s, e) => Model.LoadSettingsCommand.Execute(null);
_Button8.Click += (s, e) => Model.SelectColorCommand.Execute(null);
}
private event EventHandler<Tuple<string, int>> UiUpdateEvent;
delegate void SetTextCallback(Label target, string text);
private void SetText(Label target, string text)
{
if (InvokeRequired)
{
var d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { target, text });
}
else
{
target.Text = text;
}
}
private delegate void SetProgressCallback(ProgressBar target, int amount, int maximum);
private void SetProgress(ProgressBar target, int amount, int maximum)
{
if (InvokeRequired)
{
var d = new SetProgressCallback(SetProgress);
this.Invoke(d, new object[] { target, amount, maximum });
}
else
{
target.Maximum = maximum;
target.Value = amount;
}
}
private void OnUiUpdateEvent(object sender, Tuple<string, int> args)
{
SetProgress(ProgressBar1, _results.Count, args.Item2);
SetText(Label18, _results.Count.ToString());
SetText(Label10, args.Item1);
SetText(lblFotoTotaliNum, args.Item2.ToString());
}
private ConcurrentBag<string> _results;
private void SetDefaults()
{
// Bind ComboBoxes to Model using proper data binding
@ -135,6 +94,19 @@ public partial class MainForm
ComboBox5.DataSource = new List<string> { "Alto", "Centro", "Basso" };
ComboBox5.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoVerticalPosition),
false, DataSourceUpdateMode.OnPropertyChanged));
// Bind progress bar and status labels
ProgressBar1.DataBindings.Add(new Binding("Maximum", bindingSource1, nameof(Model.ProgressBarMaximum),
false, DataSourceUpdateMode.OnPropertyChanged));
ProgressBar1.DataBindings.Add(new Binding("Value", bindingSource1, nameof(Model.ProgressBarValue),
false, DataSourceUpdateMode.OnPropertyChanged));
Label18.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.ProcessedImagesCount),
false, DataSourceUpdateMode.OnPropertyChanged));
lblFotoTotaliNum.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.TotalImagesCount),
false, DataSourceUpdateMode.OnPropertyChanged));
Label10.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.ProcessingStatus),
false, DataSourceUpdateMode.OnPropertyChanged));
}
@ -147,50 +119,6 @@ public partial class MainForm
_logger.LogInformation("Programma Avviato");
}
private void FixPaths()
{
Model.SourcePath = FixPath(Model.SourcePath);
Model.DestinationPath = FixPath(Model.DestinationPath);
}
private string FixPath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return string.Empty;
}
// Trim leading/trailing quotes
path = path.Trim().Trim('"');
// Normalize directory separators
path = path.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
// Remove trailing separators then add one back
path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
return path;
}
private void lockUI()
{
Model.UiEnabled = false;
//TabControl1.Enabled = false;
//Button5.Enabled = false;
//Button6.Enabled = false;
//btnCreaCatalogoAsync.Enabled = false;
}
private void unlockUI()
{
Model.UiEnabled = true;
//TabControl1.Enabled = true;
//Button5.Enabled = true;
//Button6.Enabled = true;
//btnCreaCatalogoAsync.Enabled = true;
}
private string CalcTime(DateTime timeStart, DateTime timeStop, int numFoto)
{
long timediffH, timediffS;
@ -224,6 +152,26 @@ public partial class MainForm
return FixPath(dialog.SelectedPath);
}
private string FixPath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return string.Empty;
}
// Trim leading/trailing quotes
path = path.Trim().Trim('"');
// Normalize directory separators
path = path.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
// Remove trailing separators then add one back
path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
return path;
}
private void OnSelectSourceFolderRequested(object sender, EventArgs e)
{
var dialogResult = SelectFolder(Model.SourcePath);
@ -356,76 +304,6 @@ public partial class MainForm
}
}
private void SetPicSettings(string SourcePath, string DestPath)
{
_picSettings.DirectorySorgente = SourcePath;
_picSettings.DirectoryDestinazione = DestPath;
// Font and text settings from Model
_picSettings.DimStandard = Model.FontSize;
_picSettings.DimStandardMiniatura = Model.FontSizeThumbnail;
_picSettings.IlFont = Model.FontName;
_picSettings.Grassetto = Model.FontBold;
_picSettings.Posizione = Model.VerticalPosition;
_picSettings.Allineamento = Model.HorizontalAlignment;
_picSettings.Trasparenza = Model.TextTransparency;
_picSettings.Margine = Model.TextMargin;
_picSettings.FontColoreRGB = ColorTranslator.FromHtml(Model.TextColorRGB);
// Thumbnail settings from Model
_picSettings.AltezzaSmall = Model.ThumbnailHeight;
_picSettings.LarghezzaSmall = Model.ThumbnailWidth;
_picSettings.Suffisso = Model.ThumbnailPrefix;
_picSettings.CreaMiniature = Model.CreateThumbnails;
_picSettings.JpegQualityMin = Model.JpegQualityThumbnail;
_picSettings.DimMin = Model.FontSizeThumbnail;
// Big photo settings from Model
_picSettings.AltezzaBig = Model.PhotoBigHeight;
_picSettings.LarghezzaBig = Model.PhotoBigWidth;
_picSettings.FotoGrandeDimOrigina = Model.KeepOriginalDimensions;
_picSettings.JpegQuality = Model.JpegQuality;
_picSettings.Codice = Model.BigPhotoSuffix;
// Logo settings from Model
_picSettings.LogoAggiungi = Model.AddLogo;
_picSettings.LogoNomeFile = Model.LogoFile;
_picSettings.LogoAltezza = Model.LogoHeight;
_picSettings.LogoLarghezza = Model.LogoWidth;
_picSettings.LogoMargine = Model.LogoMargin.ToString();
_picSettings.LogoTrasparenza = Model.LogoTransparency.ToString();
_picSettings.LogoPosizioneH = Model.LogoHorizontalPosition;
_picSettings.LogoPosizioneV = Model.LogoVerticalPosition;
// Text content from Model
_picSettings.TestoFirmaStart = Model.HorizontalText;
_picSettings.TestoFirmaStartV = Model.VerticalText;
// Vertical text settings from Model
_picSettings.DimVert = Model.VerticalTextSize;
_picSettings.MargVert = Model.VerticalTextMargin;
// Boolean flags from Model
_picSettings.UsaRotazioneAutomatica = Model.AutomaticRotation;
_picSettings.UsaForzaJpg = Model.ForceJpeg;
_picSettings.TestoNome = Model.ShowPhotoNumber;
_picSettings.NomeData = Model.ShowDate;
_picSettings.UsaOrarioTestoApplicare = Model.AddTime;
_picSettings.UsaTempoGaraTestoApplicare = Model.AddRaceTime;
_picSettings.OverwriteFiles = Model.OverwriteImages;
// Additional settings from Model
_picSettings.UsaOrarioMiniatura = Model.AddTimeToThumbnails;
_picSettings.DataPartenza = Model.RaceStartDate;
_picSettings.TestoOrario = Model.TimeLabel;
_picSettings.TestoMin = Model.ShowFileNameOnThumbnails;
// Thumbnail text options from Model
_picSettings.AggiungiScritteMiniature = Model.AddTextToThumbnails;
_picSettings.AggTempoGaraMin = Model.AddRaceTimeToThumbnails;
_picSettings.AggNumTempMin = Model.AddNumberAndTimeToThumbnails;
}
private void setLabel18Text(string text)
{
if (Label18.InvokeRequired)
@ -437,99 +315,6 @@ public partial class MainForm
Label18.Text = text;
}
}
private NumerazioneType GetNumerazioneEnum()
{
NumerazioneType numerazioneType;
if (rdbNumProgressiva.Checked)
{
numerazioneType = NumerazioneType.Progressiva;
}
else
{
numerazioneType = NumerazioneType.Files;
}
return numerazioneType;
}
private async void Button1_Click(object sender, EventArgs e)
{
_logger.LogInformation("Avvio elaborazione...");
lockUI();
Model.MainToken?.Dispose();
Model.MainToken = new CancellationTokenSource();
var token = Model.MainToken.Token;
// timeStart = TimeOfDay
FixPaths();
Label10.Text = "Elaborazione in corso...";
lblFotoTotaliNum.Text = "0";
Label18.Text = "0";
Model.SpeedCounter = "-f/m";
SetPicSettings(Model.SourcePath, Model.DestinationPath);
ProgressBar1.Minimum = 0;
ProgressBar1.Step = 1;
ProgressBar1.Value = 0;
var imageCreationOptions = new ImageCreationStuff.Options
{
AggiornaSottodirectory = chkAggiornaSottodirectory.Checked,
CreaSottocartelle = chkCreaSottocartelle.Checked,
FilePerCartella = int.Parse(txtFilePerCartella.Text),
SuffissoCartelle = txtSuffissoCartelle.Text,
CifreContatore = int.Parse(txtCifreContatore.Text),
NumerazioneType = GetNumerazioneEnum(),
SourcePath = Model.SourcePath,
DestinationPath = Model.DestinationPath,
MaxThreads = Model.ThreadsCount,
ChunksSize = Model.ChunkSize,
LinearExecution = rdbVecchioMetodo.Checked
};
try
{
_results = [];
_currentAmount = 0;
_previousAmount = 0;
timer1.Tick += Timer1OnTick;
timer1.Interval = 1000 * 60;
timer1.Enabled = true;
var time =
await _imageCreationService.CreaCatalogoParallel(imageCreationOptions, _results, UiUpdateEvent, token);
Model.SpeedCounter = time;
timer1.Enabled = false;
}
catch (OperationCanceledException operationCanceledException)
{
_logger.LogInformation("Operazione Cancellata");
}
finally
{
Model.MainToken?.Dispose();
Model.MainToken = null;
timer1.Tick -= Timer1OnTick;
}
Label10.Text = "Finito";
unlockUI();
}
private int _currentAmount = 0;
private int _previousAmount = 0;
private void Timer1OnTick(object sender, EventArgs e)
{
_previousAmount = _currentAmount;
_currentAmount = _results.Count;
int diff = _currentAmount - _previousAmount;
Model.SpeedCounter = $"{diff} f/m";
}
}
public class PicInfo

View file

@ -0,0 +1,94 @@
using System.Drawing;
using AutoMapper;
using MaddoShared;
namespace ImageCatalog_2.Mappings;
/// <summary>
/// AutoMapper profile for mapping between DataModel and PicSettings
/// </summary>
public class DataModelMappingProfile : Profile
{
public DataModelMappingProfile()
{
CreateMap<DataModel, PicSettings>()
// Paths
.ForMember(dest => dest.DirectorySorgente, opt => opt.MapFrom(src => src.SourcePath))
.ForMember(dest => dest.DirectoryDestinazione, opt => opt.MapFrom(src => src.DestinationPath))
// Font and text settings
.ForMember(dest => dest.DimStandard, opt => opt.MapFrom(src => src.FontSize))
.ForMember(dest => dest.DimStandardMiniatura, opt => opt.MapFrom(src => src.FontSizeThumbnail))
.ForMember(dest => dest.IlFont, opt => opt.MapFrom(src => src.FontName))
.ForMember(dest => dest.Grassetto, opt => opt.MapFrom(src => src.FontBold))
.ForMember(dest => dest.Posizione, opt => opt.MapFrom(src => src.VerticalPosition))
.ForMember(dest => dest.Allineamento, opt => opt.MapFrom(src => src.HorizontalAlignment))
.ForMember(dest => dest.Trasparenza, opt => opt.MapFrom(src => src.TextTransparency))
.ForMember(dest => dest.Margine, opt => opt.MapFrom(src => src.TextMargin))
.ForMember(dest => dest.FontColoreRGB, opt => opt.MapFrom(src => ColorTranslator.FromHtml(src.TextColorRGB)))
// Thumbnail settings
.ForMember(dest => dest.AltezzaSmall, opt => opt.MapFrom(src => src.ThumbnailHeight))
.ForMember(dest => dest.LarghezzaSmall, opt => opt.MapFrom(src => src.ThumbnailWidth))
.ForMember(dest => dest.Suffisso, opt => opt.MapFrom(src => src.ThumbnailPrefix))
.ForMember(dest => dest.CreaMiniature, opt => opt.MapFrom(src => src.CreateThumbnails))
.ForMember(dest => dest.JpegQualityMin, opt => opt.MapFrom(src => src.JpegQualityThumbnail))
.ForMember(dest => dest.DimMin, opt => opt.MapFrom(src => src.FontSizeThumbnail))
// Big photo settings
.ForMember(dest => dest.AltezzaBig, opt => opt.MapFrom(src => src.PhotoBigHeight))
.ForMember(dest => dest.LarghezzaBig, opt => opt.MapFrom(src => src.PhotoBigWidth))
.ForMember(dest => dest.FotoGrandeDimOrigina, opt => opt.MapFrom(src => src.KeepOriginalDimensions))
.ForMember(dest => dest.JpegQuality, opt => opt.MapFrom(src => src.JpegQuality))
.ForMember(dest => dest.Codice, opt => opt.MapFrom(src => src.BigPhotoSuffix))
// Logo settings
.ForMember(dest => dest.LogoAggiungi, opt => opt.MapFrom(src => src.AddLogo))
.ForMember(dest => dest.LogoNomeFile, opt => opt.MapFrom(src => src.LogoFile))
.ForMember(dest => dest.LogoAltezza, opt => opt.MapFrom(src => src.LogoHeight))
.ForMember(dest => dest.LogoLarghezza, opt => opt.MapFrom(src => src.LogoWidth))
.ForMember(dest => dest.LogoMargine, opt => opt.MapFrom(src => src.LogoMargin.ToString()))
.ForMember(dest => dest.LogoTrasparenza, opt => opt.MapFrom(src => src.LogoTransparency.ToString()))
.ForMember(dest => dest.LogoPosizioneH, opt => opt.MapFrom(src => src.LogoHorizontalPosition))
.ForMember(dest => dest.LogoPosizioneV, opt => opt.MapFrom(src => src.LogoVerticalPosition))
// Text content
.ForMember(dest => dest.TestoFirmaStart, opt => opt.MapFrom(src => src.HorizontalText))
.ForMember(dest => dest.TestoFirmaStartV, opt => opt.MapFrom(src => src.VerticalText))
// Vertical text settings
.ForMember(dest => dest.DimVert, opt => opt.MapFrom(src => src.VerticalTextSize))
.ForMember(dest => dest.MargVert, opt => opt.MapFrom(src => src.VerticalTextMargin))
// Boolean flags
.ForMember(dest => dest.UsaRotazioneAutomatica, opt => opt.MapFrom(src => src.AutomaticRotation))
.ForMember(dest => dest.UsaForzaJpg, opt => opt.MapFrom(src => src.ForceJpeg))
.ForMember(dest => dest.TestoNome, opt => opt.MapFrom(src => src.ShowPhotoNumber))
.ForMember(dest => dest.NomeData, opt => opt.MapFrom(src => src.ShowDate))
.ForMember(dest => dest.UsaOrarioTestoApplicare, opt => opt.MapFrom(src => src.AddTime))
.ForMember(dest => dest.UsaTempoGaraTestoApplicare, opt => opt.MapFrom(src => src.AddRaceTime))
.ForMember(dest => dest.OverwriteFiles, opt => opt.MapFrom(src => src.OverwriteImages))
// Additional settings
.ForMember(dest => dest.UsaOrarioMiniatura, opt => opt.MapFrom(src => src.AddTimeToThumbnails))
.ForMember(dest => dest.DataPartenza, opt => opt.MapFrom(src => src.RaceStartDate))
.ForMember(dest => dest.TestoOrario, opt => opt.MapFrom(src => src.TimeLabel))
.ForMember(dest => dest.TestoMin, opt => opt.MapFrom(src => src.ShowFileNameOnThumbnails))
// Thumbnail text options
.ForMember(dest => dest.AggiungiScritteMiniature, opt => opt.MapFrom(src => src.AddTextToThumbnails))
.ForMember(dest => dest.AggTempoGaraMin, opt => opt.MapFrom(src => src.AddRaceTimeToThumbnails))
.ForMember(dest => dest.AggNumTempMin, opt => opt.MapFrom(src => src.AddNumberAndTimeToThumbnails))
// Ignore unmapped properties
.ForMember(dest => dest.DestDir, opt => opt.Ignore())
.ForMember(dest => dest.SecretDefault, opt => opt.Ignore())
.ForMember(dest => dest.SecretBig, opt => opt.Ignore())
.ForMember(dest => dest.SecretSmall, opt => opt.Ignore())
.ForMember(dest => dest.SecretPathSmall, opt => opt.Ignore())
.ForMember(dest => dest.SecretPathBig, opt => opt.Ignore())
.ForMember(dest => dest.FotoRuotaADestra, opt => opt.Ignore())
.ForMember(dest => dest.FotoRuotaASinistra, opt => opt.Ignore())
.ForMember(dest => dest.TempMinText, opt => opt.Ignore());
}
}

View file

@ -3,6 +3,7 @@ using ImageCatalog;
using ImageCatalog_2.Services;
using MaddoShared;
using Microsoft.Extensions.DependencyInjection;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
@ -79,6 +80,9 @@ static class Program
private static void ConfigureServices(ServiceCollection services)
{
// Register AutoMapper (new AddAutoMapper overload — provide config and marker types)
services.AddAutoMapper(cfg => { }, typeof(Program));
// Register your services here
services.AddTransient<ITestService, TestService>();
services.AddTransient<ISettingsService, SettingsService>();

View file

@ -4,20 +4,66 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ImageCatalog_2
{
public class ViewModelBase : INotifyPropertyChanged
{
private readonly SynchronizationContext? _synchronizationContext;
private Control? _control;
protected ViewModelBase()
{
// Capture the synchronization context (UI thread context)
_synchronizationContext = SynchronizationContext.Current;
}
/// <summary>
/// Set a Control to use for thread marshalling in WinForms applications.
/// This is required for proper cross-thread handling with data binding.
/// </summary>
public void SetControl(Control control)
{
_control = control;
}
public event PropertyChangedEventHandler? PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
if (PropertyChanged == null)
return;
// If we have a Control reference (WinForms), use Control.Invoke for proper marshalling
if (_control != null)
{
if (_control.InvokeRequired)
{
_control.Invoke(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)));
}
else
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// Fallback to SynchronizationContext if available
else if (_synchronizationContext != null && SynchronizationContext.Current != _synchronizationContext)
{
// We're on a different thread, marshal to the UI thread
_synchronizationContext.Send(_ =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}, null);
}
else
{
// We're already on the UI thread or no sync context available
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}