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
13
.github/copilot-instructions.md
vendored
13
.github/copilot-instructions.md
vendored
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Build
|
# Build
|
||||||
dotnet build Catalog.sln
|
dotnet build Catalog.slnx
|
||||||
|
|
||||||
# Run all tests
|
# Run all tests
|
||||||
dotnet test MaddoShared.Tests
|
dotnet test MaddoShared.Tests
|
||||||
|
|
@ -21,13 +21,13 @@ dotnet publish "imagecatalog\ImageCatalog 2.csproj" -c Release -r win-x64 --self
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
This is a WinForms/WPF image cataloging application targeting .NET 10.0-windows.
|
This is an Avalonia image cataloging application targeting .NET 10.0-windows.
|
||||||
|
|
||||||
### Projects
|
### Projects
|
||||||
|
|
||||||
| Project | Purpose |
|
| Project | Purpose |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
| **imagecatalog** | Main desktop application — WinForms (default), WPF (`--wpf`), or Avalonia (`--avalonia`) |
|
| **imagecatalog** | Main desktop application — Avalonia with Fluent theme (`AvaloniaMainWindow`) |
|
||||||
| **MaddoShared** | Shared image processing library (the core) |
|
| **MaddoShared** | Shared image processing library (the core) |
|
||||||
| **MaddoShared.Tests** | Unit tests for MaddoShared |
|
| **MaddoShared.Tests** | Unit tests for MaddoShared |
|
||||||
| **MaddoShared.Benchmarks** | BenchmarkDotNet performance benchmarks |
|
| **MaddoShared.Benchmarks** | BenchmarkDotNet performance benchmarks |
|
||||||
|
|
@ -35,12 +35,7 @@ This is a WinForms/WPF image cataloging application targeting .NET 10.0-windows.
|
||||||
| **ImageCatalogCS / ImageCatalogParallel** | Legacy/experimental variants |
|
| **ImageCatalogCS / ImageCatalogParallel** | Legacy/experimental variants |
|
||||||
| **CatalogLib / CatalogLibVb / CatalogVbLib** | Legacy VB.NET libraries |
|
| **CatalogLib / CatalogLibVb / CatalogVbLib** | Legacy VB.NET libraries |
|
||||||
|
|
||||||
The main app selects its UI at startup via command-line flag:
|
The main app launches Avalonia directly. Dialog events (`SelectSourceFolderRequested`, etc.) are subscribed in `AvaloniaMainWindow` code-behind. `DataModel.UiInvoker` must be set by the active UI to enable cross-thread UI updates (Avalonia sets this to `Dispatcher.UIThread.Invoke`).
|
||||||
- *(default)* — WinForms (`MainForm`)
|
|
||||||
- `--wpf` — WPF with MahApps.Metro (`MainWindow`)
|
|
||||||
- `--avalonia` — Avalonia with Fluent theme (`AvaloniaMainWindow`)
|
|
||||||
|
|
||||||
All three UIs bind to the same `DataModel`. Dialog events (`SelectSourceFolderRequested`, etc.) are subscribed in each window's code-behind. `DataModel.UiInvoker` must be set by the active UI to enable cross-thread UI updates (Avalonia sets this to `Dispatcher.UIThread.Invoke`; WPF uses `Application.Current.Dispatcher`).
|
|
||||||
|
|
||||||
### Core Flow
|
### Core Flow
|
||||||
|
|
||||||
|
|
|
||||||
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "ImageCatalog Avalonia",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build ImageCatalog Avalonia",
|
||||||
|
"program": "${workspaceFolder:Catalog}/imagecatalog/bin/Debug/net10.0-windows/win-x64/ImageCatalog.exe",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder:Catalog}/imagecatalog",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"console": "internalConsole"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
.vscode/tasks.json
vendored
Normal file
18
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build ImageCatalog Avalonia",
|
||||||
|
"type": "process",
|
||||||
|
"command": "dotnet",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder:Catalog}/imagecatalog/ImageCatalog 2.csproj",
|
||||||
|
"--configuration",
|
||||||
|
"Debug"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
116
Catalog.sln
116
Catalog.sln
|
|
@ -1,116 +0,0 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 18
|
|
||||||
VisualStudioVersion = 18.2.11415.280
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageCatalog 2", "imagecatalog\ImageCatalog 2.csproj", "{3F1E23DB-435E-0590-1EF5-735E898DBA3C}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{A3D50937-74F6-4DC8-8D89-B534B484C0F9}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaddoShared", "MaddoShared\MaddoShared.csproj", "{AEBFE9E3-277C-4A7B-8448-145D1B11998B}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5F0BEF23-B1EA-4100-A772-DC455D40B1C1}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaddoShared.Tests", "MaddoShared.Tests\MaddoShared.Tests.csproj", "{59952BE8-20B4-4BF2-9367-705F41395265}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaddoShared.Benchmarks", "MaddoShared.Benchmarks\MaddoShared.Benchmarks.csproj", "{07499348-8C15-4DCC-8316-4AD121A43C38}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catalog.Communication", "Catalog.Communication\Catalog.Communication.csproj", "{EF5D3B7E-F380-4976-A0A9-085FEA157F79}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaddoShared.ImageSharpTests", "MaddoShared.ImageSharpTests\MaddoShared.ImageSharpTests.csproj", "{1528903F-3BF9-599C-2DD0-0AF7B5706675}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Debug|x86 = Debug|x86
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Debug|x86.ActiveCfg = Debug|x86
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Debug|x86.Build.0 = Debug|x86
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Release|x64.Build.0 = Release|x64
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Release|x86.ActiveCfg = Release|x86
|
|
||||||
{3F1E23DB-435E-0590-1EF5-735E898DBA3C}.Release|x86.Build.0 = Release|x86
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{07499348-8C15-4DCC-8316-4AD121A43C38}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B} = {A3D50937-74F6-4DC8-8D89-B534B484C0F9}
|
|
||||||
{59952BE8-20B4-4BF2-9367-705F41395265} = {5F0BEF23-B1EA-4100-A772-DC455D40B1C1}
|
|
||||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79} = {A3D50937-74F6-4DC8-8D89-B534B484C0F9}
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {0E3ABC63-8601-4DAC-AFEA-33F3E8E36757}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
20
Catalog.slnx
Normal file
20
Catalog.slnx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Solution>
|
||||||
|
<Configurations>
|
||||||
|
<Platform Name="Any CPU" />
|
||||||
|
<Platform Name="x64" />
|
||||||
|
<Platform Name="x86" />
|
||||||
|
</Configurations>
|
||||||
|
<Folder Name="/Libraries/">
|
||||||
|
<Project Path="Catalog.Communication/Catalog.Communication.csproj" />
|
||||||
|
<Project Path="MaddoShared/MaddoShared.csproj" />
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/Tests/">
|
||||||
|
<Project Path="MaddoShared.Tests/MaddoShared.Tests.csproj" />
|
||||||
|
</Folder>
|
||||||
|
<Project Path="imagecatalog/ImageCatalog 2.csproj">
|
||||||
|
<Platform Solution="*|x86" Project="x86" />
|
||||||
|
<Platform Solution="Release|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj" />
|
||||||
|
<Project Path="MaddoShared.ImageSharpTests/MaddoShared.ImageSharpTests.csproj" />
|
||||||
|
</Solution>
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="10.0.3" />
|
<PackageReference Include="System.Drawing.Common" Version="10.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
#if WINDOWS
|
|
||||||
using System.Windows.Forms;
|
|
||||||
#endif
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using MaddoShared;
|
using MaddoShared;
|
||||||
|
|
@ -167,8 +164,7 @@ namespace ImageCatalog_2
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optional UI-thread invoker set by the active UI layer (WPF, Avalonia, etc.).
|
/// Optional UI-thread invoker set by the active UI layer.
|
||||||
/// When set, <see cref="InvokeOnUiThreadAsync"/> uses this delegate instead of the WPF dispatcher.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<Action>? UiInvoker { get; set; }
|
public Action<Action>? UiInvoker { get; set; }
|
||||||
|
|
||||||
|
|
@ -179,7 +175,7 @@ namespace ImageCatalog_2
|
||||||
if (UiInvoker != null)
|
if (UiInvoker != null)
|
||||||
UiInvoker(action);
|
UiInvoker(action);
|
||||||
else
|
else
|
||||||
System.Windows.Application.Current?.Dispatcher.Invoke(action);
|
action();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1886,11 +1882,7 @@ namespace ImageCatalog_2
|
||||||
public event EventHandler<string?>? LoadSettingsRequested;
|
public event EventHandler<string?>? LoadSettingsRequested;
|
||||||
public event EventHandler? SelectColorRequested;
|
public event EventHandler? SelectColorRequested;
|
||||||
// Request that the View shows a message to the user (message, caption, icon)
|
// 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;
|
public event EventHandler<Tuple<string, string, int>>? ShowMessageRequested;
|
||||||
#endif
|
|
||||||
public event EventHandler? SelectTransparentColorRequested;
|
public event EventHandler? SelectTransparentColorRequested;
|
||||||
|
|
||||||
private void SelectSourceFolder(object parameter)
|
private void SelectSourceFolder(object parameter)
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@
|
||||||
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))">
|
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))">
|
||||||
<TargetFramework>net10.0-windows</TargetFramework>
|
<TargetFramework>net10.0-windows</TargetFramework>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
|
||||||
<UseWPF>true</UseWPF>
|
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
@ -31,9 +29,7 @@
|
||||||
<PublishReadyToRun Condition="'$(PublishReadyToRun)' == ''">false</PublishReadyToRun>
|
<PublishReadyToRun Condition="'$(PublishReadyToRun)' == ''">false</PublishReadyToRun>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Keep MinVer package enabled but do NOT let it overwrite AssemblyVersion/FileVersion used at build-time.
|
<!-- 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. -->
|
|
||||||
<UpdateVersionProperties>true</UpdateVersionProperties>
|
<UpdateVersionProperties>true</UpdateVersionProperties>
|
||||||
<!-- Skip MinVer execution during local builds to avoid environment/runtime-specific failures. -->
|
<!-- Skip MinVer execution during local builds to avoid environment/runtime-specific failures. -->
|
||||||
<MinVerSkip>true</MinVerSkip>
|
<MinVerSkip>true</MinVerSkip>
|
||||||
|
|
@ -65,11 +61,9 @@
|
||||||
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.1" />
|
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.1" />
|
||||||
<PackageReference Include="AutoMapper" Version="16.1.0" />
|
<PackageReference Include="AutoMapper" Version="16.1.0" />
|
||||||
<PackageReference Include="IconPacks.Avalonia" Version="1.3.1" />
|
<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.DependencyInjection" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" 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="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="MinVer" Version="7.0.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="Avalonia" Version="11.3.12" />
|
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||||
|
|
@ -81,8 +75,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- MinVer provides a computed 'Version' property. Do not automatically override AssemblyVersion/FileVersion
|
<!-- 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
|
with MinVer's computed Version. The explicit AssemblyVersion/FileVersion at the top of this file will be used for runtime identity. -->
|
||||||
AssemblyVersion/FileVersion at the top of this file will be used for runtime identity. -->
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Properties\Settings.Designer.cs">
|
<Compile Update="Properties\Settings.Designer.cs">
|
||||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
|
@ -90,11 +83,6 @@
|
||||||
<DependentUpon>Settings.settings</DependentUpon>
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Update="MainForm.resx">
|
|
||||||
<DependentUpon>MainForm.cs</DependentUpon>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup />
|
<PropertyGroup />
|
||||||
|
|
||||||
<!-- No Visual Studio fallback required for MinVer; MinVer integrates with MSBuild during build. -->
|
<!-- 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 System.IO;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
|
||||||
|
|
||||||
namespace ImageCatalog_2;
|
namespace ImageCatalog_2;
|
||||||
|
|
||||||
|
|
@ -115,10 +114,6 @@ static class Program
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
System.Windows.Forms.Application.SetHighDpiMode(System.Windows.Forms.HighDpiMode.SystemAware);
|
|
||||||
System.Windows.Forms.Application.EnableVisualStyles();
|
|
||||||
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
|
|
||||||
|
|
||||||
AllocConsole();
|
AllocConsole();
|
||||||
RedirectConsoleOutput();
|
RedirectConsoleOutput();
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -128,59 +123,7 @@ static class Program
|
||||||
|
|
||||||
ServiceProvider = serviceCollection.BuildServiceProvider();
|
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>());
|
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)
|
private static void ConfigureServices(ServiceCollection services)
|
||||||
|
|
@ -233,11 +176,6 @@ static class Program
|
||||||
|
|
||||||
services.AddTransient<AvaloniaMainWindow>();
|
services.AddTransient<AvaloniaMainWindow>();
|
||||||
|
|
||||||
#if WINDOWS
|
|
||||||
services.AddTransient<MainForm>();
|
|
||||||
services.AddTransient<ImageCatalog_2.MainWindow>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
services.AddSingleton<MaddoShared.IVersionProvider, MaddoShared.VersionProvider>();
|
services.AddSingleton<MaddoShared.IVersionProvider, MaddoShared.VersionProvider>();
|
||||||
|
|
||||||
services.AddLogging(configure =>
|
services.AddLogging(configure =>
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,12 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
#if WINDOWS
|
|
||||||
using System.Windows.Forms;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ImageCatalog_2
|
namespace ImageCatalog_2
|
||||||
{
|
{
|
||||||
public class ViewModelBase : INotifyPropertyChanged
|
public class ViewModelBase : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private readonly SynchronizationContext? _synchronizationContext;
|
private readonly SynchronizationContext? _synchronizationContext;
|
||||||
#if WINDOWS
|
|
||||||
private Control? _control;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected ViewModelBase()
|
protected ViewModelBase()
|
||||||
{
|
{
|
||||||
|
|
@ -25,17 +19,6 @@ namespace ImageCatalog_2
|
||||||
_synchronizationContext = SynchronizationContext.Current;
|
_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;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
// This method is called by the Set accessor of each property.
|
// This method is called by the Set accessor of each property.
|
||||||
|
|
@ -46,22 +29,6 @@ namespace ImageCatalog_2
|
||||||
if (PropertyChanged == null)
|
if (PropertyChanged == null)
|
||||||
return;
|
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)
|
if (_synchronizationContext != null && SynchronizationContext.Current != _synchronizationContext)
|
||||||
{
|
{
|
||||||
// We're on a different thread, marshal to the UI thread
|
// We're on a different thread, marshal to the UI thread
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue