From ba965e82666ce0baf6a8221c32b6f781333dbedc Mon Sep 17 00:00:00 2001 From: MaddoScientisto Date: Wed, 4 Feb 2026 23:16:06 +0100 Subject: [PATCH] Fixes and mapping --- MaddoShared/ImageCreationStuff.cs | 2 +- MaddoShared/MaddoShared.csproj | 1 + imagecatalog/Commands/ButtonCommandBinder.cs | 53 +++ imagecatalog/DataModel.cs | 193 +++++++++- imagecatalog/ImageCatalog 2.csproj | 1 + imagecatalog/MainForm.Designer.cs | 10 - imagecatalog/MainForm.cs | 329 +++--------------- .../Mappings/DataModelMappingProfile.cs | 94 +++++ imagecatalog/Program.cs | 4 + imagecatalog/ViewModelBase.cs | 48 ++- 10 files changed, 450 insertions(+), 285 deletions(-) create mode 100644 imagecatalog/Commands/ButtonCommandBinder.cs create mode 100644 imagecatalog/Mappings/DataModelMappingProfile.cs diff --git a/MaddoShared/ImageCreationStuff.cs b/MaddoShared/ImageCreationStuff.cs index ac78603..778a7e8 100644 --- a/MaddoShared/ImageCreationStuff.cs +++ b/MaddoShared/ImageCreationStuff.cs @@ -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 diff --git a/MaddoShared/MaddoShared.csproj b/MaddoShared/MaddoShared.csproj index 4753af8..502345d 100644 --- a/MaddoShared/MaddoShared.csproj +++ b/MaddoShared/MaddoShared.csproj @@ -9,6 +9,7 @@ + all diff --git a/imagecatalog/Commands/ButtonCommandBinder.cs b/imagecatalog/Commands/ButtonCommandBinder.cs new file mode 100644 index 0000000..2fc301c --- /dev/null +++ b/imagecatalog/Commands/ButtonCommandBinder.cs @@ -0,0 +1,53 @@ +using System; +using System.Windows.Forms; +using System.Windows.Input; + +namespace ImageCatalog_2.Commands +{ + /// + /// Helper class to bind WinForms Button controls to ICommand implementations + /// + public static class ButtonCommandBinder + { + /// + /// Binds a Button's Click event to an ICommand + /// + /// The button to bind + /// The command to execute + /// Optional parameter to pass to the command + 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); + } + + /// + /// Binds multiple buttons to commands at once + /// + public static void BindCommands(params (Button button, ICommand command, object parameter)[] bindings) + { + foreach (var (button, command, parameter) in bindings) + { + button.BindCommand(command, parameter); + } + } + } +} diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs index ce02440..49a715d 100644 --- a/imagecatalog/DataModel.cs +++ b/imagecatalog/DataModel.cs @@ -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 _logger; private readonly ISettingsService _settingsService; + private readonly ImageCreationStuff _imageCreationService; + private readonly PicSettings _picSettings; + private readonly IMapper _mapper; // ComboBox collections public List AvailableFonts { get; } public List VerticalPositions { get; } = new() { "Alto", "Centro", "Basso" }; public List HorizontalAlignments { get; } = new() { "Sinistra", "Centro", "Destra" }; - public DataModel(ITestService testService, ISettingsService settingsService, ILogger logger) + public DataModel(ITestService testService, ISettingsService settingsService, + ImageCreationStuff imageCreationService, PicSettings picSettings, + IMapper mapper, ILogger 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 _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(); + _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 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() diff --git a/imagecatalog/ImageCatalog 2.csproj b/imagecatalog/ImageCatalog 2.csproj index 6473071..aed7099 100644 --- a/imagecatalog/ImageCatalog 2.csproj +++ b/imagecatalog/ImageCatalog 2.csproj @@ -39,6 +39,7 @@ + diff --git a/imagecatalog/MainForm.Designer.cs b/imagecatalog/MainForm.Designer.cs index fdee7ba..266fa4f 100644 --- a/imagecatalog/MainForm.Designer.cs +++ b/imagecatalog/MainForm.Designer.cs @@ -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; - } } } } diff --git a/imagecatalog/MainForm.cs b/imagecatalog/MainForm.cs index f18ab89..c5e7b12 100644 --- a/imagecatalog/MainForm.cs +++ b/imagecatalog/MainForm.cs @@ -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 _logger; - private readonly ImageCreationStuff _imageCreationService; - private readonly ParametriSetup _parametriSetup; private readonly PicSettings _picSettings; @@ -34,7 +34,6 @@ public partial class MainForm ParametriSetup parametriSetup, ILogger logger) { Model = model; - _imageCreationService = imageCreationStuff; _parametriSetup = parametriSetup; _picSettings = picSettings; _logger = logger; @@ -42,77 +41,37 @@ public partial class MainForm _logger.LogDebug("Start"); InitializeComponent(); - BindControls(); - // Subscribe to DataModel events + // Set this form as the control for thread marshalling in the DataModel + Model.SetControl(this); + + BindControls(); + + 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> 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 args) - { - SetProgress(ProgressBar1, _results.Count, args.Item2); - SetText(Label18, _results.Count.ToString()); - SetText(Label10, args.Item1); - SetText(lblFotoTotaliNum, args.Item2.ToString()); - } - - private ConcurrentBag _results; - private void SetDefaults() { // Bind ComboBoxes to Model using proper data binding @@ -135,6 +94,19 @@ public partial class MainForm ComboBox5.DataSource = new List { "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 diff --git a/imagecatalog/Mappings/DataModelMappingProfile.cs b/imagecatalog/Mappings/DataModelMappingProfile.cs new file mode 100644 index 0000000..fd33654 --- /dev/null +++ b/imagecatalog/Mappings/DataModelMappingProfile.cs @@ -0,0 +1,94 @@ +using System.Drawing; +using AutoMapper; +using MaddoShared; + +namespace ImageCatalog_2.Mappings; + +/// +/// AutoMapper profile for mapping between DataModel and PicSettings +/// +public class DataModelMappingProfile : Profile +{ + public DataModelMappingProfile() + { + CreateMap() + // 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()); + } +} diff --git a/imagecatalog/Program.cs b/imagecatalog/Program.cs index 24ac1ab..2a5e2b2 100644 --- a/imagecatalog/Program.cs +++ b/imagecatalog/Program.cs @@ -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(); services.AddTransient(); diff --git a/imagecatalog/ViewModelBase.cs b/imagecatalog/ViewModelBase.cs index bedbb78..9807106 100644 --- a/imagecatalog/ViewModelBase.cs +++ b/imagecatalog/ViewModelBase.cs @@ -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; + } + + /// + /// Set a Control to use for thread marshalling in WinForms applications. + /// This is required for proper cross-thread handling with data binding. + /// + 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)); } }