Refactor application to remove Windows Forms dependencies and transition to Avalonia UI
- Deleted MainWindow.xaml.cs, which contained the WPF implementation of the main window. - Updated Program.cs to remove Windows Forms initialization and support only Avalonia UI. - Removed Windows Forms specific code from ViewModelBase, including control marshalling logic.
This commit is contained in:
parent
988a3d94e1
commit
d6b778a648
16 changed files with 64 additions and 4415 deletions
|
|
@ -1,53 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,6 @@ using System.Runtime.InteropServices;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#if WINDOWS
|
||||
using System.Windows.Forms;
|
||||
#endif
|
||||
using System.Windows.Input;
|
||||
using AutoMapper;
|
||||
using MaddoShared;
|
||||
|
|
@ -167,8 +164,7 @@ namespace ImageCatalog_2
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional UI-thread invoker set by the active UI layer (WPF, Avalonia, etc.).
|
||||
/// When set, <see cref="InvokeOnUiThreadAsync"/> uses this delegate instead of the WPF dispatcher.
|
||||
/// Optional UI-thread invoker set by the active UI layer.
|
||||
/// </summary>
|
||||
public Action<Action>? UiInvoker { get; set; }
|
||||
|
||||
|
|
@ -179,7 +175,7 @@ namespace ImageCatalog_2
|
|||
if (UiInvoker != null)
|
||||
UiInvoker(action);
|
||||
else
|
||||
System.Windows.Application.Current?.Dispatcher.Invoke(action);
|
||||
action();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1886,11 +1882,7 @@ namespace ImageCatalog_2
|
|||
public event EventHandler<string?>? LoadSettingsRequested;
|
||||
public event EventHandler? SelectColorRequested;
|
||||
// Request that the View shows a message to the user (message, caption, icon)
|
||||
#if WINDOWS
|
||||
public event EventHandler<Tuple<string, string, MessageBoxIcon>>? ShowMessageRequested;
|
||||
#else
|
||||
public event EventHandler<Tuple<string, string, int>>? ShowMessageRequested;
|
||||
#endif
|
||||
public event EventHandler? SelectTransparentColorRequested;
|
||||
|
||||
private void SelectSourceFolder(object parameter)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@
|
|||
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))">
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWPF>true</UseWPF>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
|
@ -31,9 +29,7 @@
|
|||
<PublishReadyToRun Condition="'$(PublishReadyToRun)' == ''">false</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Keep MinVer package enabled but do NOT let it overwrite AssemblyVersion/FileVersion used at build-time.
|
||||
This prevents MinVer from injecting a computed version into generated BAML/pack URIs which can cause
|
||||
WPF to try loading a mismatched assembly identity at runtime. Do a full clean rebuild after this change. -->
|
||||
<!-- Keep MinVer package enabled but do NOT let it overwrite AssemblyVersion/FileVersion used at build-time. -->
|
||||
<UpdateVersionProperties>true</UpdateVersionProperties>
|
||||
<!-- Skip MinVer execution during local builds to avoid environment/runtime-specific failures. -->
|
||||
<MinVerSkip>true</MinVerSkip>
|
||||
|
|
@ -65,11 +61,9 @@
|
|||
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.1" />
|
||||
<PackageReference Include="AutoMapper" Version="16.1.0" />
|
||||
<PackageReference Include="IconPacks.Avalonia" Version="1.3.1" />
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.11" Condition="$([MSBuild]::IsOsPlatform('Windows'))" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
|
||||
<PackageReference Include="MahApps.Metro.IconPacks" Version="6.2.1" Condition="$([MSBuild]::IsOsPlatform('Windows'))" />
|
||||
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||
|
|
@ -81,8 +75,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<!-- MinVer provides a computed 'Version' property. Do not automatically override AssemblyVersion/FileVersion
|
||||
with MinVer's computed Version to avoid mismatches embedded into generated XAML/BAML. The explicit
|
||||
AssemblyVersion/FileVersion at the top of this file will be used for runtime identity. -->
|
||||
with MinVer's computed Version. The explicit AssemblyVersion/FileVersion at the top of this file will be used for runtime identity. -->
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
|
|
@ -90,11 +83,6 @@
|
|||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<PropertyGroup />
|
||||
|
||||
<!-- No Visual Studio fallback required for MinVer; MinVer integrates with MSBuild during build. -->
|
||||
|
|
|
|||
2246
imagecatalog/MainForm.Designer.cs
generated
2246
imagecatalog/MainForm.Designer.cs
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,842 +0,0 @@
|
|||
#if WINDOWS
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Text;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
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;
|
||||
|
||||
namespace ImageCatalog;
|
||||
|
||||
public partial class MainForm
|
||||
{
|
||||
private readonly DataModel Model;
|
||||
|
||||
private readonly ILogger<MainForm> _logger;
|
||||
|
||||
private readonly ParametriSetup _parametriSetup;
|
||||
private readonly PicSettings _picSettings;
|
||||
// Prevent re-entrant updates between UI events and model PropertyChanged handling
|
||||
private bool _suppressRadioUpdates = false;
|
||||
private bool _transparentDialogOpen = false;
|
||||
|
||||
public MainForm(DataModel model, ImageCreationService imageCreationStuff, PicSettings picSettings,
|
||||
ParametriSetup parametriSetup, ILogger<MainForm> logger)
|
||||
{
|
||||
Model = model;
|
||||
_parametriSetup = parametriSetup;
|
||||
_picSettings = picSettings;
|
||||
_logger = logger;
|
||||
|
||||
_logger.LogDebug("Start");
|
||||
|
||||
InitializeComponent();
|
||||
// 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;
|
||||
|
||||
// Thumbnail options moved to ComboBox to avoid conflicting bindings with multiple radio buttons
|
||||
|
||||
// Initialize ComboBox with Italian descriptions
|
||||
comboThumbnailOption.Items.Clear();
|
||||
comboThumbnailOption.Items.AddRange(new object[] {
|
||||
"Nessuna",
|
||||
"Aggiungi scritta",
|
||||
"Nome file",
|
||||
"Aggiungi orario",
|
||||
"Nome+Orario",
|
||||
"Tempo gara"
|
||||
});
|
||||
|
||||
// Bind to model via helper index property ThumbnailOptionIndex
|
||||
comboThumbnailOption.DataBindings.Add(new Binding("SelectedIndex", bindingSource1, "ThumbnailOptionIndex", true, DataSourceUpdateMode.OnPropertyChanged));
|
||||
|
||||
// Save user preferences on form close instead of immediately when dialogs are used
|
||||
this.FormClosing += MainForm_FormClosing;
|
||||
|
||||
// Wire up 'Open folder in Explorer' buttons
|
||||
btnOpenSourceFolder.Click += BtnOpenSourceFolder_Click;
|
||||
btnOpenDestFolder.Click += BtnOpenDestFolder_Click;
|
||||
|
||||
// Show currently selected color in small PictureBox3
|
||||
PictureBox3.BackColor = ColorTranslator.FromHtml(Model.TransparentColor);
|
||||
|
||||
// Version label is data-bound to DataModel.AppVersion; DataModel is populated with the version via DI
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void RdbLibrary_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnail mode changes - reflect back to combo box index
|
||||
if (e.PropertyName == nameof(Model.ThumbnailMode) ||
|
||||
e.PropertyName == nameof(Model.ThumbnailOption) ||
|
||||
e.PropertyName == nameof(Model.ThumbnailOptionIndex))
|
||||
{
|
||||
try
|
||||
{
|
||||
_suppressRadioUpdates = true;
|
||||
comboThumbnailOption.SelectedIndex = Model.ThumbnailOptionIndex;
|
||||
}
|
||||
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);
|
||||
// Bind the transparency chooser button/command
|
||||
btnSetTransparency.BindCommand(Model.SelectTransparentColorCommand);
|
||||
|
||||
// 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;
|
||||
Model.SelectTransparentColorRequested += OnSelectTransparentColorRequested;
|
||||
// Show message requests (from ViewModel validation)
|
||||
Model.ShowMessageRequested += OnShowMessageRequested;
|
||||
}
|
||||
|
||||
private void OnSelectTransparentColorRequested(object? sender, EventArgs e)
|
||||
{
|
||||
// Ensure UI thread
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action<object, EventArgs>(OnSelectTransparentColorRequested), sender, e as EventArgs ?? EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
// Prevent re-entrancy: if the dialog is already open, ignore subsequent requests
|
||||
if (_transparentDialogOpen) return;
|
||||
|
||||
_transparentDialogOpen = true;
|
||||
var dlg = new ColorDialog { AllowFullOpen = true };
|
||||
try
|
||||
{
|
||||
dlg.Color = ColorTranslator.FromHtml(Model.TransparentColor);
|
||||
}
|
||||
catch { }
|
||||
try
|
||||
{
|
||||
if (dlg.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
Model.TransparentColor = ColorTranslator.ToHtml(dlg.Color);
|
||||
PictureBox3.BackColor = dlg.Color;
|
||||
|
||||
// Update preview if logo exists
|
||||
if (!string.IsNullOrWhiteSpace(Model.LogoFile) && File.Exists(Model.LogoFile))
|
||||
{
|
||||
UpdateLogoPictureBox(Model.LogoFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_transparentDialogOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnSetTransparency_Click(object? sender, EventArgs e)
|
||||
{
|
||||
Model.SelectTransparentColorCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void OnShowMessageRequested(object? sender, Tuple<string, string, MessageBoxIcon> args)
|
||||
{
|
||||
if (args is null) return;
|
||||
// Ensure call on UI thread
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => OnShowMessageRequested(sender, args)));
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBox.Show(this, args.Item1, args.Item2, MessageBoxButtons.OK, args.Item3);
|
||||
}
|
||||
|
||||
private void SetDefaults()
|
||||
{
|
||||
// Bind ComboBoxes to Model using proper data binding
|
||||
ComboBox1.DataSource = new List<string>(Model.VerticalPositions);
|
||||
ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition),
|
||||
false, DataSourceUpdateMode.OnPropertyChanged));
|
||||
|
||||
ComboBox2.DataSource = new List<string>(Model.HorizontalAlignments);
|
||||
ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment),
|
||||
false, DataSourceUpdateMode.OnPropertyChanged));
|
||||
|
||||
ComboBox3.DataSource = new List<string>(Model.AvailableFonts);
|
||||
ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName),
|
||||
false, DataSourceUpdateMode.OnPropertyChanged));
|
||||
|
||||
ComboBox4.DataSource = new List<string>(Model.HorizontalAlignments);
|
||||
ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition),
|
||||
false, DataSourceUpdateMode.OnPropertyChanged));
|
||||
|
||||
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));
|
||||
|
||||
// Bind transparency model properties to UI
|
||||
chkUseTransparentColor.DataBindings.Add(new Binding("Checked", bindingSource1, nameof(Model.UseTransparentColor), false, DataSourceUpdateMode.OnPropertyChanged));
|
||||
// Show currently selected color in PictureBox3
|
||||
PictureBox3.Visible = false;
|
||||
if (!string.IsNullOrWhiteSpace(Model.TransparentColor))
|
||||
{
|
||||
try
|
||||
{
|
||||
PictureBox3.BackColor = ColorTranslator.FromHtml(Model.TransparentColor);
|
||||
PictureBox3.Visible = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
PictureBox3.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// When logo file changes, update preview
|
||||
Model.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Model.LogoFile) || e.PropertyName == nameof(Model.UseTransparentColor) || e.PropertyName == nameof(Model.TransparentColor))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Model.LogoFile) && System.IO.File.Exists(Model.LogoFile))
|
||||
{
|
||||
UpdateLogoPictureBox(Model.LogoFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Bind transparent color hex and show color in PictureBox3
|
||||
// Bind UseTransparentColor checkbox (designer control named CheckBox5 used for AddLogo earlier, add new binding control exists in designer)
|
||||
// Use PictureBox3 to display color value
|
||||
var colorBinding = new Binding("BackColor", bindingSource1, nameof(Model.TransparentColor), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
colorBinding.Format += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
e.Value = ColorTranslator.FromHtml(e.Value?.ToString() ?? "#FFFFFF");
|
||||
}
|
||||
catch
|
||||
{
|
||||
e.Value = Color.White;
|
||||
}
|
||||
};
|
||||
PictureBox3.DataBindings.Add(colorBinding);
|
||||
|
||||
// Bind checkbox for using color key transparency if such control exists (CheckBox5 was repurposed as AddLogo); create binding if available
|
||||
try
|
||||
{
|
||||
// The designer has CheckBox5 for 'AddLogo'. We'll add a separate binding to a new control named CheckBoxUseTransparentColor if present.
|
||||
var chk = this.Controls.Find("chkUseTransparentColor", true).FirstOrDefault() as CheckBox;
|
||||
if (chk != null)
|
||||
{
|
||||
chk.DataBindings.Add(new Binding("Checked", bindingSource1, nameof(Model.UseTransparentColor), false, DataSourceUpdateMode.OnPropertyChanged));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
|
||||
private void Form1_Load(object sender, EventArgs e)
|
||||
{
|
||||
bindingSource1.DataSource = Model;
|
||||
Application.EnableVisualStyles();
|
||||
SetDefaults();
|
||||
|
||||
_logger.LogInformation("Programma Avviato");
|
||||
// If settings were loaded before the form was shown, ensure the logo preview is updated
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Model.LogoFile) && File.Exists(Model.LogoFile))
|
||||
{
|
||||
UpdateLogoPictureBox(Model.LogoFile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to load logo during form load");
|
||||
}
|
||||
}
|
||||
|
||||
private string CalcTime(DateTime timeStart, DateTime timeStop, int numFoto)
|
||||
{
|
||||
long timediffH, timediffS;
|
||||
long timediffM;
|
||||
|
||||
TimeSpan timeDiff = timeStop - timeStart;
|
||||
|
||||
timediffM = (int)timeDiff.TotalMinutes;
|
||||
timediffS = (int)timeDiff.TotalSeconds;
|
||||
timediffH = (int)timeDiff.TotalHours;
|
||||
|
||||
// timediffM = DateAndTime.DateDiff(DateInterval.Minute, timeStart, timeStop);
|
||||
// timediffS = DateAndTime.DateDiff(DateInterval.Second, timeStart, timeStop);
|
||||
// timediffH = DateAndTime.DateDiff(DateInterval.Hour, timeStart, timeStop);
|
||||
|
||||
double fotoSec = numFoto / (double)timediffS;
|
||||
double fotoMin = numFoto / (double)timediffM;
|
||||
double fotoOra = numFoto / (double)timediffH;
|
||||
string s = "S: " + timediffS.ToString() + "; F/s: " +
|
||||
fotoSec.ToString(
|
||||
"0.000"); // + " F/m: " + fotoMin.ToString("0.00") + " F/h: " + fotoOra.ToString("0.00")
|
||||
return s;
|
||||
}
|
||||
|
||||
private string SelectFolder(string startingFolder)
|
||||
{
|
||||
var dialog = new FolderBrowserDialog();
|
||||
dialog.InitialDirectory = startingFolder;
|
||||
if (dialog.ShowDialog() != DialogResult.OK) return string.Empty;
|
||||
|
||||
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)
|
||||
{
|
||||
// Prefer model value; if empty fall back to last-used value stored in user prefs
|
||||
var starting = !string.IsNullOrWhiteSpace(Model.SourcePath)
|
||||
? Model.SourcePath
|
||||
: _parametriSetup.LeggiParametroString("LastSourceFolder");
|
||||
|
||||
var dialogResult = SelectFolder(starting);
|
||||
if (!string.IsNullOrWhiteSpace(dialogResult))
|
||||
{
|
||||
Model.SourcePath = dialogResult;
|
||||
_parametriSetup.AggiornaParametro("LastSourceFolder", dialogResult);
|
||||
_parametriSetup.SalvaParametriSetup();
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnOpenSourceFolder_Click(object? sender, EventArgs e)
|
||||
{
|
||||
// Prefer the model value but fall back to the textbox if needed
|
||||
var path = string.IsNullOrWhiteSpace(Model.SourcePath) ? txtSorgente.Text : Model.SourcePath;
|
||||
OpenFolder(path);
|
||||
}
|
||||
|
||||
private void BtnOpenDestFolder_Click(object? sender, EventArgs e)
|
||||
{
|
||||
var path = string.IsNullOrWhiteSpace(Model.DestinationPath) ? txtDestinazione.Text : Model.DestinationPath;
|
||||
OpenFolder(path);
|
||||
}
|
||||
|
||||
private void OpenFolder(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
MessageBox.Show(this, "Folder path is empty.", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
path = path.Trim().Trim('"');
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
// If a file was provided, open its folder and select it
|
||||
Process.Start("explorer.exe", $"/select,\"{path}\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBox.Show(this, $"Folder does not exist: {path}", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to open folder {Path}", path);
|
||||
MessageBox.Show(this, $"Failed to open folder: {ex.Message}", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectDestinationFolderRequested(object sender, EventArgs e)
|
||||
{
|
||||
var starting = !string.IsNullOrWhiteSpace(Model.DestinationPath)
|
||||
? Model.DestinationPath
|
||||
: _parametriSetup.LeggiParametroString("LastDestinationFolder");
|
||||
|
||||
var dialogResult = SelectFolder(starting);
|
||||
if (!string.IsNullOrWhiteSpace(dialogResult))
|
||||
{
|
||||
Model.DestinationPath = dialogResult;
|
||||
_parametriSetup.AggiornaParametro("LastDestinationFolder", dialogResult);
|
||||
_parametriSetup.SalvaParametriSetup();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectLogoFileRequested(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog();
|
||||
dialog.Filter = "Image Files|*.jpg;*.jpeg;*.png;*.bmp;*.gif";
|
||||
if (!string.IsNullOrWhiteSpace(Model.LogoFile))
|
||||
{
|
||||
dialog.FileName = Model.LogoFile;
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastLogoFolder = _parametriSetup.LeggiParametroString("LastLogoFolder");
|
||||
if (!string.IsNullOrWhiteSpace(lastLogoFolder) && Directory.Exists(lastLogoFolder))
|
||||
{
|
||||
dialog.InitialDirectory = lastLogoFolder;
|
||||
}
|
||||
}
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
Model.LogoFile = dialog.FileName;
|
||||
UpdateLogoPictureBox(Model.LogoFile);
|
||||
try
|
||||
{
|
||||
var folder = Path.GetDirectoryName(dialog.FileName) ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
_parametriSetup.AggiornaParametro("LastLogoFolder", folder);
|
||||
_parametriSetup.SalvaParametriSetup();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore preferences save failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSaveSettingsRequested(object sender, string e)
|
||||
{
|
||||
var saveDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*",
|
||||
FilterIndex = 0,
|
||||
RestoreDirectory = true
|
||||
};
|
||||
var lastSettings = _parametriSetup.LeggiParametroString("LastSettingsFolder");
|
||||
if (!string.IsNullOrWhiteSpace(lastSettings) && Directory.Exists(lastSettings))
|
||||
saveDialog.InitialDirectory = lastSettings;
|
||||
|
||||
if (saveDialog.ShowDialog() != DialogResult.OK) return;
|
||||
|
||||
await Model.SaveSettingsToFileAsync(saveDialog.FileName);
|
||||
Text = "Image Catalog - " + Path.GetFileName(saveDialog.FileName);
|
||||
|
||||
try
|
||||
{
|
||||
var folder = Path.GetDirectoryName(saveDialog.FileName) ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
_parametriSetup.AggiornaParametro("LastSettingsFolder", folder);
|
||||
_parametriSetup.SalvaParametriSetup();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnLoadSettingsRequested(object sender, string e)
|
||||
{
|
||||
var openDialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*",
|
||||
FilterIndex = 0,
|
||||
RestoreDirectory = true
|
||||
};
|
||||
|
||||
var lastSettings = _parametriSetup.LeggiParametroString("LastSettingsFolder");
|
||||
if (!string.IsNullOrWhiteSpace(lastSettings) && Directory.Exists(lastSettings))
|
||||
openDialog.InitialDirectory = lastSettings;
|
||||
|
||||
if (openDialog.ShowDialog() != DialogResult.OK) return;
|
||||
|
||||
try
|
||||
{
|
||||
await Model.LoadSettingsFromFileAsync(openDialog.FileName);
|
||||
|
||||
// Explicitly ensure UI is enabled after loading
|
||||
Model.UiEnabled = true;
|
||||
|
||||
// If a logo path was stored in the settings, try to resolve and display it.
|
||||
// The stored path may be absolute or relative to the settings file location.
|
||||
try
|
||||
{
|
||||
var storedLogo = Model.LogoFile;
|
||||
// Trim whitespace and surrounding quotes which may be present in saved settings
|
||||
if (!string.IsNullOrWhiteSpace(storedLogo))
|
||||
{
|
||||
storedLogo = storedLogo.Trim();
|
||||
storedLogo = storedLogo.Trim('"');
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(storedLogo))
|
||||
{
|
||||
string resolved = storedLogo;
|
||||
// If not rooted, try to resolve relative to the settings file folder
|
||||
var settingsFolder = Path.GetDirectoryName(openDialog.FileName) ?? string.Empty;
|
||||
if (!Path.IsPathRooted(resolved) && !string.IsNullOrWhiteSpace(settingsFolder))
|
||||
{
|
||||
var candidate = Path.Combine(settingsFolder, resolved);
|
||||
if (File.Exists(candidate)) resolved = candidate;
|
||||
}
|
||||
|
||||
// If rooted but file doesn't exist, try filename near settings file
|
||||
if (!File.Exists(resolved) && !string.IsNullOrWhiteSpace(settingsFolder))
|
||||
{
|
||||
var candidate2 = Path.Combine(settingsFolder, Path.GetFileName(resolved));
|
||||
if (File.Exists(candidate2)) resolved = candidate2;
|
||||
}
|
||||
|
||||
if (File.Exists(resolved))
|
||||
{
|
||||
// Update the model so data-bound controls reflect the resolved absolute path
|
||||
Model.LogoFile = resolved;
|
||||
UpdateLogoPictureBox(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Error resolving logo path after loading settings");
|
||||
}
|
||||
|
||||
Text = "Image Catalog - " + Path.GetFileName(openDialog.FileName);
|
||||
|
||||
try
|
||||
{
|
||||
var folder = Path.GetDirectoryName(openDialog.FileName) ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
_parametriSetup.AggiornaParametro("LastSettingsFolder", folder);
|
||||
_parametriSetup.SalvaParametriSetup();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore preferences save failures
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Settings loaded successfully from {openDialog.FileName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading settings");
|
||||
MessageBox.Show($"Error loading settings: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object? sender, FormClosingEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Persist last-used dialogs paths (user preferences)
|
||||
// These keys are managed independently from settings files
|
||||
// and must be saved when the form closes.
|
||||
_parametriSetup.AggiornaParametro("LastSourceFolder", Model.SourcePath ?? string.Empty);
|
||||
_parametriSetup.AggiornaParametro("LastDestinationFolder", Model.DestinationPath ?? string.Empty);
|
||||
_parametriSetup.AggiornaParametro("LastLogoFolder", Path.GetDirectoryName(Model.LogoFile ?? string.Empty) ?? string.Empty);
|
||||
_parametriSetup.SalvaParametriSetup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Failed to save user preferences on exit");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectColorRequested(object sender, EventArgs e)
|
||||
{
|
||||
var colorDialog = new ColorDialog
|
||||
{
|
||||
AllowFullOpen = true
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Model.TextColorRGB))
|
||||
{
|
||||
try
|
||||
{
|
||||
colorDialog.Color = ColorTranslator.FromHtml(Model.TextColorRGB);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid color, use default
|
||||
}
|
||||
}
|
||||
|
||||
if (colorDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
Model.TextColorRGB = ColorTranslator.ToHtml(colorDialog.Color);
|
||||
TextBox34.BackColor = colorDialog.Color;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLogoPictureBox(string logoPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load image via System.Drawing for preview so we can use MakeTransparent when requested
|
||||
using var img = System.Drawing.Image.FromFile(logoPath);
|
||||
|
||||
System.Drawing.Bitmap previewBmp;
|
||||
// If using color-key transparency and a color is selected, apply MakeTransparent for preview
|
||||
if (Model.UseTransparentColor && !string.IsNullOrWhiteSpace(Model.TransparentColor))
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = ColorTranslator.FromHtml(Model.TransparentColor);
|
||||
previewBmp = new System.Drawing.Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using (var g = System.Drawing.Graphics.FromImage(previewBmp))
|
||||
{
|
||||
g.Clear(System.Drawing.Color.Transparent);
|
||||
g.DrawImage(img, 0, 0, img.Width, img.Height);
|
||||
}
|
||||
// Apply exact color-key transparency
|
||||
previewBmp.MakeTransparent(key);
|
||||
PictureBox3.BackColor = key;
|
||||
PictureBox3.Visible = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
previewBmp = new System.Drawing.Bitmap(img);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
previewBmp = new System.Drawing.Bitmap(img);
|
||||
}
|
||||
|
||||
// Resize preview to fit into PictureBox1 while preserving aspect ratio
|
||||
// Resize preview to fit into PictureBox1 while preserving aspect ratio
|
||||
var boxW = PictureBox1.ClientSize.Width > 0 ? PictureBox1.ClientSize.Width : 449;
|
||||
var boxH = PictureBox1.ClientSize.Height > 0 ? PictureBox1.ClientSize.Height : 369;
|
||||
var ratio = Math.Min((double)boxW / previewBmp.Width, (double)boxH / previewBmp.Height);
|
||||
var destW = Math.Max(1, (int)(previewBmp.Width * ratio));
|
||||
var destH = Math.Max(1, (int)(previewBmp.Height * ratio));
|
||||
var scaled = new System.Drawing.Bitmap(destW, destH, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using (var g = System.Drawing.Graphics.FromImage(scaled))
|
||||
{
|
||||
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
g.Clear(System.Drawing.Color.Transparent);
|
||||
// Center the image in the PictureBox
|
||||
var offsetX = Math.Max(0, (boxW - destW) / 2);
|
||||
var offsetY = Math.Max(0, (boxH - destH) / 2);
|
||||
using var canvas = new System.Drawing.Bitmap(boxW, boxH, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using (var cg = System.Drawing.Graphics.FromImage(canvas))
|
||||
{
|
||||
cg.Clear(System.Drawing.Color.Transparent);
|
||||
cg.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
cg.DrawImage(previewBmp, offsetX, offsetY, destW, destH);
|
||||
}
|
||||
g.DrawImage(canvas, 0, 0);
|
||||
}
|
||||
|
||||
// Set PictureBox1 image (dispose previous)
|
||||
var old = PictureBox1.Image;
|
||||
PictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
|
||||
PictureBox1.Image = scaled;
|
||||
old?.Dispose();
|
||||
previewBmp.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Image loading failed, ignore
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLogoPreviewWithColorKey(string logoPath, Color keyColor)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var img = (Bitmap)Image.FromFile(logoPath);
|
||||
// Create ARGB copy
|
||||
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.DrawImage(img, 0, 0, img.Width, img.Height);
|
||||
}
|
||||
|
||||
bmp.MakeTransparent(keyColor);
|
||||
|
||||
// Resize to PictureBox1 size similar to previous logic
|
||||
Bitmap finalBmp;
|
||||
if (bmp.Height >= bmp.Width)
|
||||
{
|
||||
finalBmp = new Bitmap(bmp, new Size((int)(160 * bmp.Width / (double)bmp.Height), 160));
|
||||
}
|
||||
else
|
||||
{
|
||||
finalBmp = new Bitmap(bmp, new Size(160, (int)(160 * bmp.Height / (double)bmp.Width)));
|
||||
}
|
||||
|
||||
PictureBox1.Image = finalBmp;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore preview failures
|
||||
}
|
||||
}
|
||||
|
||||
private void setLabel18Text(string text)
|
||||
{
|
||||
if (Label18.InvokeRequired)
|
||||
{
|
||||
Label18.Invoke(new Action<string>(setLabel18Text), text);
|
||||
}
|
||||
else
|
||||
{
|
||||
Label18.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PicInfo
|
||||
{
|
||||
public DirectoryInfo DirSource, DirDest, DirDestStart;
|
||||
public string NomeImmagine;
|
||||
|
||||
public PicInfo(DirectoryInfo Dir_Source, DirectoryInfo Dir_Dest, DirectoryInfo Dir_DestStart,
|
||||
string Nome_Immagine)
|
||||
{
|
||||
DirSource = Dir_Source;
|
||||
DirDest = Dir_Dest;
|
||||
DirDestStart = Dir_DestStart;
|
||||
NomeImmagine = Nome_Immagine;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="bindingSource1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>586, 17</value>
|
||||
</metadata>
|
||||
<metadata name="dataModelBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>120, 17</value>
|
||||
</metadata>
|
||||
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="dataModelBindingSource1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>349, 17</value>
|
||||
</metadata>
|
||||
<metadata name="colorDialog1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>802, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
|
|
@ -1,510 +0,0 @@
|
|||
<controls:MetroWindow x:Class="ImageCatalog_2.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
|
||||
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
mc:Ignorable="d"
|
||||
Title="Image Catalog - WPF" Height="540" Width="800"
|
||||
Background="{DynamicResource WindowBackgroundBrush}" Foreground="{DynamicResource ControlForegroundBrush}"
|
||||
GlowBrush="{DynamicResource AccentBrush}">
|
||||
<controls:MetroWindow.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- Default (Light) theme resources placed at top-level so DynamicResource lookups resolve -->
|
||||
<!-- style moved later to avoid early resource lookup -->
|
||||
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="White" />
|
||||
<SolidColorBrush x:Key="ControlBackgroundBrush" Color="White" />
|
||||
<SolidColorBrush x:Key="ControlForegroundBrush" Color="Black" />
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="#DDD" />
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="#0078D7" />
|
||||
<SolidColorBrush x:Key="DataGridBackgroundBrush" Color="White" />
|
||||
<SolidColorBrush x:Key="DataGridForegroundBrush" Color="Black" />
|
||||
|
||||
<!-- Also keep named theme dictionaries for future switching if needed -->
|
||||
<ResourceDictionary x:Key="LightTheme">
|
||||
<SolidColorBrush x:Key="WindowBackgroundBrush.Light" Color="White" />
|
||||
<SolidColorBrush x:Key="ControlBackgroundBrush.Light" Color="White" />
|
||||
<SolidColorBrush x:Key="ControlForegroundBrush.Light" Color="Black" />
|
||||
<SolidColorBrush x:Key="BorderBrush.Light" Color="#DDD" />
|
||||
<SolidColorBrush x:Key="AccentBrush.Light" Color="#0078D7" />
|
||||
<SolidColorBrush x:Key="DataGridBackgroundBrush.Light" Color="White" />
|
||||
<SolidColorBrush x:Key="DataGridForegroundBrush.Light" Color="Black" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="DarkTheme">
|
||||
<SolidColorBrush x:Key="WindowBackgroundBrush.Dark" Color="#1E1E1E" />
|
||||
<SolidColorBrush x:Key="ControlBackgroundBrush.Dark" Color="#252526" />
|
||||
<SolidColorBrush x:Key="ControlForegroundBrush.Dark" Color="#E6E6E6" />
|
||||
<SolidColorBrush x:Key="BorderBrush.Dark" Color="#3A3A3A" />
|
||||
<SolidColorBrush x:Key="AccentBrush.Dark" Color="#0A84FF" />
|
||||
<SolidColorBrush x:Key="DataGridBackgroundBrush.Dark" Color="#252526" />
|
||||
<SolidColorBrush x:Key="DataGridForegroundBrush.Dark" Color="#E6E6E6" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<!-- Improve tab header visuals so selected tab and boundaries are clear -->
|
||||
<Style TargetType="controls:MetroTabItem">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource ControlForegroundBrush}" />
|
||||
<Setter Property="Margin" Value="0,0,4,0" />
|
||||
<Setter Property="Padding" Value="6,4" />
|
||||
<Setter Property="MinWidth" Value="56" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:MetroTabItem">
|
||||
<Border x:Name="Bd"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
BorderThickness="0"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter ContentSource="Header" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource ControlForegroundBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource BorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</controls:MetroWindow.Resources>
|
||||
<controls:MetroWindow.RightWindowCommands>
|
||||
<controls:WindowCommands>
|
||||
<!-- Show version in title area; theme toggle moved into window content -->
|
||||
<TextBlock Name="VersionTextBlock" Text="" VerticalAlignment="Center" Margin="8,0,0,0" />
|
||||
</controls:WindowCommands>
|
||||
</controls:MetroWindow.RightWindowCommands>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" />
|
||||
<!-- Make the live view/right side narrower -->
|
||||
<ColumnDefinition Width="0.8*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left: Tabs -->
|
||||
<controls:MetroTabControl Grid.Column="0" Margin="0,0,10,0">
|
||||
<controls:MetroTabItem>
|
||||
<controls:MetroTabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="CogOutline" Width="16" Height="16" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Generale" />
|
||||
</StackPanel>
|
||||
</controls:MetroTabItem.Header>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Percorsi" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<!-- Source path row: textbox with pick and open buttons aligned to the end -->
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Sorgente:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding SourcePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectSourceFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="56" Margin="8,0,0,0" Click="OpenSourceFolder_Click" Grid.Column="3">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Destination path row -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Destinazione:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding DestinationPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectDestinationFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="56" Margin="8,0,0,0" Click="OpenDestinationFolder_Click" Grid.Column="3">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Opzioni" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Vertical" Margin="0,6,0,0">
|
||||
<CheckBox Content="Forza JPEG" IsChecked="{Binding ForceJpeg}" />
|
||||
<CheckBox Content="Aggiorna sottodirectory" IsChecked="{Binding UpdateSubdirectories}" />
|
||||
<CheckBox Content="Crea sottocartelle" IsChecked="{Binding CreateSubfolders}" />
|
||||
<CheckBox Content="Sovrascrivi immagini" IsChecked="{Binding OverwriteImages}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Elaborazione" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Threads:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding ThreadsCount, Mode=TwoWay}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Chunk:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding ChunkSize, Mode=TwoWay}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Divisione cartelle" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="File per cartella:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding FilesPerFolder}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Suffisso:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding FolderSuffix}" Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Numerazione" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<RadioButton Content="Progressiva" IsChecked="{Binding UseProgressiveNumbering}" GroupName="Num" />
|
||||
<RadioButton Content="Per file" IsChecked="{Binding UseFileNumbering}" GroupName="Num" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Cifre:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding CounterDigits}" Width="40" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Libreria Immagini" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<RadioButton Content="System.Graphics" IsChecked="{Binding UseSystemGraphics}" GroupName="Lib" />
|
||||
<RadioButton Content="ImageSharp" IsChecked="{Binding UseImageSharp}" GroupName="Lib" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</controls:MetroTabItem>
|
||||
|
||||
<controls:MetroTabItem>
|
||||
<controls:MetroTabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="FormatLetterCase" Width="16" Height="16" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Testo" />
|
||||
</StackPanel>
|
||||
</controls:MetroTabItem.Header>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Testo Orizzontale" FontWeight="Bold" />
|
||||
<TextBox Text="{Binding HorizontalText, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Text="Testo Verticale" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBox Text="{Binding VerticalText, Mode=TwoWay}" AcceptsReturn="True" TextWrapping="Wrap" MinLines="4" VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<TextBlock Text="Font" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox ItemsSource="{Binding AvailableFonts}" SelectedItem="{Binding FontName}" Width="250" />
|
||||
<TextBox Text="{Binding FontSize}" Width="60" Margin="8,0,0,0" />
|
||||
<CheckBox Content="Grassetto" IsChecked="{Binding FontBold}" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Colore testo" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding TextColorRGB}" Width="120" />
|
||||
<Button Content="Seleziona colore" Command="{Binding SelectColorCommand}" Margin="8,0,0,0" />
|
||||
<!-- MahApps color picker for direct color selection -->
|
||||
<controls:ColorPicker SelectedColor="{Binding TextColor}" Width="160" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Dimensioni verticale" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Size:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding VerticalTextSize}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Margin:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding VerticalTextMargin}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Trasparenza testo:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding TextTransparency}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Margine testo:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding TextMargin}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<!-- Tempo Gara section moved from Foto tab -->
|
||||
<TextBlock Text="Tempo Gara" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<CheckBox Content="Aggiungi Orario" IsChecked="{Binding AddTime}" />
|
||||
<CheckBox Content="Aggiungi tempo gara" IsChecked="{Binding AddRaceTime}" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Partenza:" VerticalAlignment="Center" />
|
||||
<controls:DateTimePicker SelectedDateTime="{Binding RaceTime}" IsEnabled="{Binding AddRaceTime}" Margin="8,0,0,0" Width="200" />
|
||||
<TextBox Text="{Binding TimeLabel}" Width="220" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</controls:MetroTabItem>
|
||||
|
||||
<controls:MetroTabItem>
|
||||
<controls:MetroTabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="CameraFrontVariant" Width="16" Height="16" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Foto" />
|
||||
</StackPanel>
|
||||
</controls:MetroTabItem.Header>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Dimensioni foto grandi" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBox Text="{Binding PhotoBigWidth}" Width="80" />
|
||||
<TextBox Text="{Binding PhotoBigHeight}" Width="80" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Opzioni foto" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Vertical" Margin="0,6,0,0">
|
||||
<CheckBox Content="Mantieni dimensioni originali" IsChecked="{Binding KeepOriginalDimensions}" />
|
||||
<CheckBox Content="Rotazione automatica" IsChecked="{Binding AutomaticRotation}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="JPEG" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Qualità:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding JpegQuality}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Miniature Qualità:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding JpegQualityThumbnail}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</controls:MetroTabItem>
|
||||
|
||||
<controls:MetroTabItem>
|
||||
<controls:MetroTabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="Image" Width="16" Height="16" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Miniature" />
|
||||
</StackPanel>
|
||||
</controls:MetroTabItem.Header>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Miniature" FontWeight="Bold" />
|
||||
<CheckBox Content="Crea miniature" IsChecked="{Binding CreateThumbnails}" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Prefisso:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding ThumbnailPrefix}" Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBox Text="{Binding ThumbnailWidth}" Width="80" />
|
||||
<TextBox Text="{Binding ThumbnailHeight}" Width="80" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- New unified thumbnail mode selector (Italian labels) -->
|
||||
<StackPanel Orientation="Vertical" Margin="0,8,0,0">
|
||||
<TextBlock Text="Modalità miniature:" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding ThumbnailOptionIndex, Mode=TwoWay}" Width="220" Margin="0,6,0,0">
|
||||
<ComboBoxItem>Nessuna</ComboBoxItem>
|
||||
<ComboBoxItem>Aggiungi scritta</ComboBoxItem>
|
||||
<ComboBoxItem>Nome file</ComboBoxItem>
|
||||
<ComboBoxItem>Aggiungi orario</ComboBoxItem>
|
||||
<ComboBoxItem>Nome+Orario</ComboBoxItem>
|
||||
<ComboBoxItem>Tempo gara</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</controls:MetroTabItem>
|
||||
|
||||
<controls:MetroTabItem>
|
||||
<controls:MetroTabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="ImageFilterCenterFocus" Width="16" Height="16" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Logo" />
|
||||
</StackPanel>
|
||||
</controls:MetroTabItem.Header>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Logo" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<Button Command="{Binding SelectLogoFileCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="ImageOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Seleziona logo" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock Text="{Binding LogoFile}" Margin="8,0,0,0" VerticalAlignment="Center" Width="250" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<Image Name="LogoPreview" Width="160" Height="160" Stretch="Uniform" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBox Text="{Binding LogoWidth}" Width="80" />
|
||||
<TextBox Text="{Binding LogoHeight}" Width="80" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<CheckBox Content="Aggiungi logo" IsChecked="{Binding AddLogo}" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBlock Text="Margine:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding LogoMargin}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Trasparenza:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding LogoTransparency}" Width="60" Margin="8,0,0,0" />
|
||||
<Button Command="{Binding SelectTransparentColorCommand}" Margin="8,0,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="PaletteOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Seleziona trasparente" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBlock Text="Posizione:" VerticalAlignment="Center" />
|
||||
<ComboBox ItemsSource="{Binding HorizontalAlignments}" SelectedItem="{Binding LogoHorizontalPosition}" Width="120" Margin="8,0,0,0" />
|
||||
<ComboBox ItemsSource="{Binding VerticalPositions}" SelectedItem="{Binding LogoVerticalPosition}" Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</controls:MetroTabItem>
|
||||
|
||||
<controls:MetroTabItem>
|
||||
<controls:MetroTabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="Robot" Width="16" Height="16" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="AI" />
|
||||
</StackPanel>
|
||||
</controls:MetroTabItem.Header>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
||||
<CheckBox Content="Estrai numeri dalle immagini" IsChecked="{Binding ExtractNumbers}" Margin="0,8,0,0" />
|
||||
|
||||
<TextBlock Text="Modelli" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<Grid Margin="0,6,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Cartella modelli:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding ModelsFolderPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectModelsFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="FolderSearchOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="56" Margin="8,0,0,0" Click="OpenModelsFolder_Click" Grid.Column="3">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<Grid Margin="0,6,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Percorso CSV:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding CsvOutputPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectCsvOutputCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="FileFindOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="56" Margin="8,0,0,0" Click="OpenCsvOutputFolder_Click" Grid.Column="3">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Anteprima risultati" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<DataGrid ItemsSource="{Binding PreviewResults}" IsReadOnly="True" AutoGenerateColumns="False" Height="200" Margin="0,6,0,0">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Path" Binding="{Binding Path}" Width="*" />
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="2*" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</controls:MetroTabItem>
|
||||
</controls:MetroTabControl>
|
||||
|
||||
<!-- Right: Controls and live info -->
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical">
|
||||
<!-- Compact theme toggle panel (icon-only) aligned right -->
|
||||
<StackPanel HorizontalAlignment="Stretch" Margin="0,0,0,12">
|
||||
<Button Width="24" Height="24" Click="ToggleTheme_Click" ToolTip="Cambia tema" HorizontalAlignment="Right" Padding="2">
|
||||
<iconPacks:PackIconMaterial Kind="ThemeLightDark" Width="12" Height="12" Foreground="{StaticResource AccentBrush}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Border BorderBrush="#DDD" BorderThickness="1" Padding="8" MaxWidth="280">
|
||||
<!-- Buttons and status live info inside the bordered panel -->
|
||||
<StackPanel>
|
||||
<!-- Buttons stacked vertically as requested -->
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
|
||||
<Button Width="120" Margin="0,0,0,8" Command="{Binding LoadSettingsCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="FolderUploadOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Carica" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="120" Margin="0,0,0,8" Command="{Binding SaveSettingsCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="ContentSaveOutline" Width="14" Height="14" Foreground="{StaticResource AccentBrush}" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Salva" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="120" Height="36" Margin="0,6,0,8" Command="{Binding ProcessImagesCommand}" IsEnabled="{Binding UiEnabled}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="PlayCircleOutline" Width="14" Height="14" Foreground="Green" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Avvia" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="120" Height="36" Command="{Binding AsyncCancelOperationCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="StopCircleOutline" Width="14" Height="14" Foreground="Red" Margin="0,0,6,0" />
|
||||
<TextBlock Text="Stop" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Separator Margin="0,12,0,12" />
|
||||
|
||||
<TextBlock Text="Stato" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding ProcessingStatus}" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Text="Progresso" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<ProgressBar Minimum="0" Maximum="{Binding ProgressBarMaximum}" Value="{Binding ProgressBarValue}" Height="20" />
|
||||
<TextBlock Margin="0,6,0,0">
|
||||
<Run Text="{Binding ProcessedImagesCount}" />
|
||||
<Run Text=" / " />
|
||||
<Run Text="{Binding TotalImagesCount}" />
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Text="Velocità" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="{Binding SpeedCounter}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Status bar removed; version now shown in the title commands area -->
|
||||
</Grid>
|
||||
</controls:MetroWindow>
|
||||
|
|
@ -1,383 +0,0 @@
|
|||
#if WINDOWS
|
||||
using System.Windows;
|
||||
using MahApps.Metro.Controls;
|
||||
using ControlzEx.Theming;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Win32;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ImageCatalog_2
|
||||
{
|
||||
public partial class MainWindow : MetroWindow
|
||||
{
|
||||
private readonly DataModel _model;
|
||||
private bool _isDarkTheme = false;
|
||||
public MainWindow(DataModel model)
|
||||
{
|
||||
InitializeComponent();
|
||||
_model = model;
|
||||
DataContext = _model;
|
||||
// Set product version in status bar (use ProductVersion rather than AssemblyVersion)
|
||||
try
|
||||
{
|
||||
var entry = System.Reflection.Assembly.GetEntryAssembly();
|
||||
string version = string.Empty;
|
||||
if (entry is not null && !string.IsNullOrEmpty(entry.Location))
|
||||
{
|
||||
try
|
||||
{
|
||||
version = FileVersionInfo.GetVersionInfo(entry.Location).ProductVersion ?? string.Empty;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
// fallback to assembly version
|
||||
version = entry?.GetName().Version?.ToString() ?? string.Empty;
|
||||
}
|
||||
VersionTextBlock.Text = string.IsNullOrWhiteSpace(version) ? string.Empty : $"v{version}";
|
||||
}
|
||||
catch { }
|
||||
// Ensure MahApps resource dictionaries are loaded so chrome/styles are available
|
||||
EnsureMahAppsResourcesLoaded();
|
||||
|
||||
// Apply theme based on user preference or system setting (default to light)
|
||||
ApplyTheme(isDark: false);
|
||||
// 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;
|
||||
_model.SelectModelsFolderRequested += Model_SelectModelsFolderRequested;
|
||||
_model.SelectCsvOutputRequested += Model_SelectCsvOutputRequested;
|
||||
|
||||
// Watch for logo changes to update preview
|
||||
_model.PropertyChanged += Model_PropertyChanged;
|
||||
}
|
||||
|
||||
private void ApplyTheme(bool isDark)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rd = isDark ? (ResourceDictionary)Resources["DarkTheme"] : (ResourceDictionary)Resources["LightTheme"];
|
||||
foreach (var key in rd.Keys)
|
||||
{
|
||||
// If the theme dictionary uses suffixed keys (e.g. "WindowBackgroundBrush.Dark"),
|
||||
// map them to the base key ("WindowBackgroundBrush") so existing DynamicResource lookups update.
|
||||
string outKey = key?.ToString() ?? string.Empty;
|
||||
if (outKey.EndsWith(".Light", StringComparison.OrdinalIgnoreCase))
|
||||
outKey = outKey.Substring(0, outKey.Length - ".Light".Length);
|
||||
else if (outKey.EndsWith(".Dark", StringComparison.OrdinalIgnoreCase))
|
||||
outKey = outKey.Substring(0, outKey.Length - ".Dark".Length);
|
||||
|
||||
Resources[outKey] = rd[key];
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore theme failures
|
||||
}
|
||||
}
|
||||
|
||||
private void Model_SelectModelsFolderRequested(object? sender, EventArgs e)
|
||||
{
|
||||
var dlg = new System.Windows.Forms.FolderBrowserDialog();
|
||||
var starting = string.IsNullOrWhiteSpace(_model.ModelsFolderPath) ? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) : _model.ModelsFolderPath;
|
||||
dlg.SelectedPath = starting;
|
||||
if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
_model.ModelsFolderPath = dlg.SelectedPath + Path.DirectorySeparatorChar;
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenModelsFolder_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = _model.ModelsFolderPath;
|
||||
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 { }
|
||||
}
|
||||
|
||||
private void Model_SelectCsvOutputRequested(object? sender, EventArgs e)
|
||||
{
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog();
|
||||
dlg.Filter = "CSV file (*.csv)|*.csv|All files (*.*)|*.*";
|
||||
if (!string.IsNullOrWhiteSpace(_model.CsvOutputPath)) dlg.FileName = _model.CsvOutputPath;
|
||||
var result = dlg.ShowDialog(this);
|
||||
if (result == true)
|
||||
{
|
||||
_model.CsvOutputPath = dlg.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenCsvOutputFolder_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = _model.CsvOutputPath;
|
||||
if (string.IsNullOrWhiteSpace(path)) return;
|
||||
path = path.Trim().Trim('"');
|
||||
if (File.Exists(path))
|
||||
{
|
||||
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{path}\"");
|
||||
return;
|
||||
}
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir))
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = dir, UseShellExecute = true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
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 ToggleTheme_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleTheme();
|
||||
}
|
||||
|
||||
private void ToggleTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isDarkTheme = !_isDarkTheme;
|
||||
|
||||
// Use MahApps ThemeManager to change the application theme (handles chrome and brushes)
|
||||
try
|
||||
{
|
||||
var themeName = _isDarkTheme ? "Dark.Blue" : "Light.Blue";
|
||||
ThemeManager.Current.ChangeTheme(System.Windows.Application.Current, themeName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fall back silently if ThemeManager isn't available
|
||||
}
|
||||
|
||||
// Still apply local resource overrides so any app-specific keys update
|
||||
ApplyTheme(_isDarkTheme);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore toggle failures
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureMahAppsResourcesLoaded()
|
||||
{
|
||||
try
|
||||
{
|
||||
var app = System.Windows.Application.Current;
|
||||
if (app is null)
|
||||
return;
|
||||
|
||||
var mds = app.Resources.MergedDictionaries;
|
||||
|
||||
// Helper to add if missing
|
||||
void AddIfMissing(string uriString)
|
||||
{
|
||||
if (!mds.Any(d => d.Source is not null && d.Source.OriginalString.Equals(uriString, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
mds.Add(new ResourceDictionary { Source = new Uri(uriString) });
|
||||
}
|
||||
}
|
||||
|
||||
AddIfMissing("pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml");
|
||||
AddIfMissing("pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml");
|
||||
// Ensure a default theme is present
|
||||
if (!mds.Any(d => d.Source is not null && d.Source.OriginalString.IndexOf("/MahApps.Metro;component/Styles/Themes/", StringComparison.OrdinalIgnoreCase) >= 0))
|
||||
{
|
||||
AddIfMissing("pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml");
|
||||
_isDarkTheme = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore; styling will fallback to local resources
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -11,7 +11,6 @@ using Microsoft.Extensions.Logging.Console;
|
|||
using System.IO;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
|
||||
namespace ImageCatalog_2;
|
||||
|
||||
|
|
@ -115,10 +114,6 @@ static class Program
|
|||
static void Main(string[] args)
|
||||
{
|
||||
#if WINDOWS
|
||||
System.Windows.Forms.Application.SetHighDpiMode(System.Windows.Forms.HighDpiMode.SystemAware);
|
||||
System.Windows.Forms.Application.EnableVisualStyles();
|
||||
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
AllocConsole();
|
||||
RedirectConsoleOutput();
|
||||
#endif
|
||||
|
|
@ -128,59 +123,7 @@ static class Program
|
|||
|
||||
ServiceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var serviceProvider = ServiceProvider;
|
||||
|
||||
// Determine UI based on command line. Default: WinForms. Use --wpf for WPF, --avalonia for Avalonia.
|
||||
bool useWpf = args is not null && Array.Exists(args, a => string.Equals(a, "--wpf", StringComparison.OrdinalIgnoreCase));
|
||||
bool useAvalonia = args is not null && Array.Exists(args, a => string.Equals(a, "--avalonia", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (useAvalonia)
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args ?? Array.Empty<string>());
|
||||
return;
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
if (useWpf)
|
||||
{
|
||||
var wpfApp = new System.Windows.Application();
|
||||
try
|
||||
{
|
||||
wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml") });
|
||||
wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml") });
|
||||
wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml") });
|
||||
|
||||
try
|
||||
{
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(wpfApp, "Light.Blue");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore if ThemeManager API isn't present
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If resources fail to load, continue silently
|
||||
}
|
||||
|
||||
var wpfMain = serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) as ImageCatalog_2.MainWindow;
|
||||
if (wpfMain is not null)
|
||||
{
|
||||
wpfApp.Run(wpfMain);
|
||||
return;
|
||||
}
|
||||
|
||||
// If WPF was requested but not available, fall through to WinForms.
|
||||
}
|
||||
|
||||
// Default / fallback to WinForms UI
|
||||
var mainForm = serviceProvider.GetRequiredService<MainForm>();
|
||||
System.Windows.Forms.Application.Run(mainForm);
|
||||
#else
|
||||
// On non-Windows, Avalonia is the only available UI
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args ?? Array.Empty<string>());
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void ConfigureServices(ServiceCollection services)
|
||||
|
|
@ -233,11 +176,6 @@ static class Program
|
|||
|
||||
services.AddTransient<AvaloniaMainWindow>();
|
||||
|
||||
#if WINDOWS
|
||||
services.AddTransient<MainForm>();
|
||||
services.AddTransient<ImageCatalog_2.MainWindow>();
|
||||
#endif
|
||||
|
||||
services.AddSingleton<MaddoShared.IVersionProvider, MaddoShared.VersionProvider>();
|
||||
|
||||
services.AddLogging(configure =>
|
||||
|
|
|
|||
|
|
@ -6,18 +6,12 @@ using System.Runtime.CompilerServices;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#if WINDOWS
|
||||
using System.Windows.Forms;
|
||||
#endif
|
||||
|
||||
namespace ImageCatalog_2
|
||||
{
|
||||
public class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
private readonly SynchronizationContext? _synchronizationContext;
|
||||
#if WINDOWS
|
||||
private Control? _control;
|
||||
#endif
|
||||
|
||||
protected ViewModelBase()
|
||||
{
|
||||
|
|
@ -25,19 +19,8 @@ namespace ImageCatalog_2
|
|||
_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>
|
||||
#if WINDOWS
|
||||
public void SetControl(Control control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
#endif
|
||||
|
||||
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.
|
||||
|
|
@ -46,22 +29,6 @@ namespace ImageCatalog_2
|
|||
if (PropertyChanged == null)
|
||||
return;
|
||||
|
||||
#if WINDOWS
|
||||
// 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
|
||||
#endif
|
||||
if (_synchronizationContext != null && SynchronizationContext.Current != _synchronizationContext)
|
||||
{
|
||||
// We're on a different thread, marshal to the UI thread
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue