diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs
index 6d90981..8091907 100644
--- a/imagecatalog/DataModel.cs
+++ b/imagecatalog/DataModel.cs
@@ -1132,13 +1132,26 @@ namespace ImageCatalog_2
{
try
{
- await MainToken?.CancelAsync();
+ var tokenSource = MainToken;
+ if (tokenSource is not null)
+ {
+ // Cancel synchronously and return to caller. Some CTSource implementations
+ // may provide async helpers but cancelling is immediate.
+ try
+ {
+ tokenSource.Cancel();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Exception while cancelling token");
+ }
+ }
UiEnabled = true;
}
catch (Exception e)
{
- _logger.LogError(e.Message, "Error canceling the token");
+ _logger.LogError(e, "Error canceling the token");
_logger.LogInformation("Ignora questo errore");
}
}
diff --git a/imagecatalog/ImageCatalog 2.csproj b/imagecatalog/ImageCatalog 2.csproj
index e0acb4f..4563b31 100644
--- a/imagecatalog/ImageCatalog 2.csproj
+++ b/imagecatalog/ImageCatalog 2.csproj
@@ -5,6 +5,7 @@
enable
enable
true
+ true
False
3.1.2.0
3.1.2.0
diff --git a/imagecatalog/MainWindow.xaml b/imagecatalog/MainWindow.xaml
new file mode 100644
index 0000000..b2bd652
--- /dev/null
+++ b/imagecatalog/MainWindow.xaml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/imagecatalog/MainWindow.xaml.cs b/imagecatalog/MainWindow.xaml.cs
new file mode 100644
index 0000000..3128c0c
--- /dev/null
+++ b/imagecatalog/MainWindow.xaml.cs
@@ -0,0 +1,193 @@
+using System.Windows;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.IO;
+using System.Windows.Media.Imaging;
+using Microsoft.Win32;
+using System.Windows.Forms;
+
+namespace ImageCatalog_2
+{
+ public partial class MainWindow : Window
+ {
+ private readonly DataModel _model;
+ public MainWindow(DataModel model)
+ {
+ InitializeComponent();
+ _model = model;
+ DataContext = _model;
+ // Subscribe to DataModel events that require UI dialogs
+ _model.SelectSourceFolderRequested += Model_SelectSourceFolderRequested;
+ _model.SelectDestinationFolderRequested += Model_SelectDestinationFolderRequested;
+ _model.SelectLogoFileRequested += Model_SelectLogoFileRequested;
+ _model.SaveSettingsRequested += Model_SaveSettingsRequested;
+ _model.LoadSettingsRequested += Model_LoadSettingsRequested;
+ _model.SelectColorRequested += Model_SelectColorRequested;
+ _model.SelectTransparentColorRequested += Model_SelectTransparentColorRequested;
+
+ // Watch for logo changes to update preview
+ _model.PropertyChanged += Model_PropertyChanged;
+ }
+
+ private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e is null || string.IsNullOrWhiteSpace(e.PropertyName)) return;
+ if (e.PropertyName == nameof(_model.LogoFile))
+ {
+ UpdateLogoPreview(_model.LogoFile);
+ }
+ }
+
+ private void Model_SelectSourceFolderRequested(object? sender, EventArgs e)
+ {
+ var dlg = new System.Windows.Forms.FolderBrowserDialog();
+ var starting = string.IsNullOrWhiteSpace(_model.SourcePath) ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : _model.SourcePath;
+ dlg.SelectedPath = starting;
+ if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ _model.SourcePath = dlg.SelectedPath + Path.DirectorySeparatorChar;
+ }
+ }
+
+ private void OpenSourceFolder_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ var path = _model.SourcePath;
+ if (string.IsNullOrWhiteSpace(path)) return;
+ path = path.Trim().Trim('"');
+ if (File.Exists(path))
+ {
+ System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{path}\"");
+ return;
+ }
+ if (Directory.Exists(path))
+ {
+ System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = path, UseShellExecute = true });
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ // ignore for now, or could show a message
+ }
+ }
+
+ private void Model_SelectDestinationFolderRequested(object? sender, EventArgs e)
+ {
+ var dlg = new System.Windows.Forms.FolderBrowserDialog();
+ var starting = string.IsNullOrWhiteSpace(_model.DestinationPath) ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : _model.DestinationPath;
+ dlg.SelectedPath = starting;
+ if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ _model.DestinationPath = dlg.SelectedPath + Path.DirectorySeparatorChar;
+ }
+
+ }
+
+ private void OpenDestinationFolder_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ var path = _model.DestinationPath;
+ if (string.IsNullOrWhiteSpace(path)) return;
+ path = path.Trim().Trim('"');
+ if (File.Exists(path))
+ {
+ System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{path}\"");
+ return;
+ }
+ if (Directory.Exists(path))
+ {
+ System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = path, UseShellExecute = true });
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ // ignore for now
+ }
+ }
+
+ private void Model_SelectLogoFileRequested(object? sender, EventArgs e)
+ {
+ var dlg = new Microsoft.Win32.OpenFileDialog();
+ dlg.Filter = "Image Files|*.jpg;*.jpeg;*.png;*.bmp;*.gif";
+ if (!string.IsNullOrWhiteSpace(_model.LogoFile)) dlg.FileName = _model.LogoFile;
+ var result = dlg.ShowDialog(this);
+ if (result == true)
+ {
+ _model.LogoFile = dlg.FileName;
+ }
+ }
+
+ private async void Model_SaveSettingsRequested(object? sender, string filePath)
+ {
+ var dlg = new Microsoft.Win32.SaveFileDialog();
+ dlg.Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*";
+ var result = dlg.ShowDialog(this);
+ if (result == true)
+ {
+ await _model.SaveSettingsToFileAsync(dlg.FileName);
+ }
+ }
+
+ private async void Model_LoadSettingsRequested(object? sender, string filePath)
+ {
+ var dlg = new Microsoft.Win32.OpenFileDialog();
+ dlg.Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*";
+ var result = dlg.ShowDialog(this);
+ if (result == true)
+ {
+ await _model.LoadSettingsFromFileAsync(dlg.FileName);
+ }
+ }
+
+ private void Model_SelectColorRequested(object? sender, EventArgs e)
+ {
+ var dlg = new System.Windows.Forms.ColorDialog { AllowFullOpen = true };
+ if (!string.IsNullOrWhiteSpace(_model.TextColorRGB))
+ {
+ try { dlg.Color = System.Drawing.ColorTranslator.FromHtml(_model.TextColorRGB); } catch { }
+ }
+ if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ _model.TextColorRGB = System.Drawing.ColorTranslator.ToHtml(dlg.Color);
+ }
+ }
+
+ private void Model_SelectTransparentColorRequested(object? sender, EventArgs e)
+ {
+ var dlg = new System.Windows.Forms.ColorDialog { AllowFullOpen = true };
+ try { dlg.Color = System.Drawing.ColorTranslator.FromHtml(_model.TransparentColor); } catch { }
+ if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ _model.TransparentColor = System.Drawing.ColorTranslator.ToHtml(dlg.Color);
+ }
+ }
+
+ private void UpdateLogoPreview(string? path)
+ {
+ if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
+ {
+ LogoPreview.Source = null;
+ return;
+ }
+
+ try
+ {
+ var bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.CacheOption = BitmapCacheOption.OnLoad;
+ bitmap.UriSource = new Uri(path);
+ bitmap.EndInit();
+ LogoPreview.Source = bitmap;
+ }
+ catch
+ {
+ LogoPreview.Source = null;
+ }
+ }
+ }
+}
+
diff --git a/imagecatalog/Program.cs b/imagecatalog/Program.cs
index c14bca6..067acb5 100644
--- a/imagecatalog/Program.cs
+++ b/imagecatalog/Program.cs
@@ -7,6 +7,7 @@ using AutoMapper;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
+using System.IO;
using Microsoft.Extensions.Options;
namespace ImageCatalog_2;
@@ -70,12 +71,32 @@ static class Program
ServiceProvider = serviceCollection.BuildServiceProvider();
- var mainForm = ServiceProvider.GetRequiredService();
- //var mainViewModel = ServiceProvider.GetRequiredService();
+ // Resolve WPF MainWindow when available, otherwise fall back to WinForms MainForm
+ var serviceProvider = ServiceProvider;
- //mainForm.Model = mainViewModel;
+ // NOTE: By default the app will attempt to run the WPF window when available and fall back
+ // to the WinForms MainForm. If you want to force the WinForms UI for testing, uncomment
+ // the block below and comment out the WPF branch.
+ // -----------------------------------------------------------------------------
+ // // Force WinForms UI (uncomment to enable)
+ // var mainForm = serviceProvider.GetRequiredService();
+ // // If you want to set the DataModel explicitly on the WinForms form use the lines below
+ // // var mainViewModel = serviceProvider.GetRequiredService();
+ // // mainForm.Model = mainViewModel;
+ // Application.Run(mainForm);
+ // -----------------------------------------------------------------------------
- Application.Run(mainForm);
+ if (serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) is ImageCatalog_2.MainWindow wpfMain)
+ {
+ // Start WPF app
+ var app = new System.Windows.Application();
+ app.Run(wpfMain);
+ }
+ else
+ {
+ var mainForm = serviceProvider.GetRequiredService();
+ Application.Run(mainForm);
+ }
}
private static void ConfigureServices(ServiceCollection services)
@@ -117,6 +138,8 @@ static class Program
// Register your forms
services.AddTransient();
+ // Register WPF MainWindow so it can be resolved with the existing DataModel
+ services.AddTransient();
// Version provider for UI and logging
services.AddSingleton();
@@ -153,8 +176,8 @@ public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable
public override void Write(
in LogEntry logEntry,
- IExternalScopeProvider scopeProvider,
- TextWriter textWriter)
+ IExternalScopeProvider? scopeProvider,
+ TextWriter? textWriter)
{
string? message =
logEntry.Formatter?.Invoke(