develop #1

Open
maddo wants to merge 128 commits from develop into master
4 changed files with 207 additions and 54 deletions
Showing only changes of commit 9007a27fb2 - Show all commits

Upgrade UI to MahApps.Metro with runtime theming

Modernize WPF UI using MahApps.Metro: switch MainWindow to MetroWindow, add MetroTabControl/MetroTabItem, custom tab styles, and icon-based theme toggle. Move version display to window commands. Integrate MahApps resource dictionaries and ThemeManager for runtime theme switching. Update startup logic for proper theming. WinForms fallback retained.
MaddoScientisto 2026-02-21 16:49:13 +01:00

View file

@ -42,6 +42,7 @@
<ItemGroup>
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.1" />
<PackageReference Include="AutoMapper" Version="16.0.0" />
<PackageReference Include="MahApps.Metro" Version="2.4.11" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />

View file

@ -1,15 +1,18 @@
<Window x:Class="ImageCatalog_2.MainWindow"
<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="490" Width="800"
Background="{DynamicResource WindowBackgroundBrush}" Foreground="{DynamicResource ControlForegroundBrush}">
<Window.Resources>
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" />
@ -38,8 +41,49 @@
<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>
</Window.Resources>
</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="*" />
@ -54,14 +98,14 @@
</Grid.ColumnDefinitions>
<!-- Left: Tabs -->
<TabControl Grid.Column="0" Margin="0,0,10,0">
<TabItem>
<TabItem.Header>
<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>
</TabItem.Header>
</controls:MetroTabItem.Header>
<ScrollViewer>
<StackPanel Margin="8">
<TextBlock Text="Percorsi" FontWeight="Bold" />
@ -155,15 +199,15 @@
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
</controls:MetroTabItem>
<TabItem>
<TabItem.Header>
<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>
</TabItem.Header>
</controls:MetroTabItem.Header>
<ScrollViewer>
<StackPanel Margin="8">
<TextBlock Text="Testo Orizzontale" FontWeight="Bold" />
@ -206,15 +250,15 @@
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
</controls:MetroTabItem>
<TabItem>
<TabItem.Header>
<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>
</TabItem.Header>
</controls:MetroTabItem.Header>
<ScrollViewer>
<StackPanel Margin="8">
<TextBlock Text="Dimensioni foto grandi" FontWeight="Bold" />
@ -240,15 +284,15 @@
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
</controls:MetroTabItem>
<TabItem>
<TabItem.Header>
<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>
</TabItem.Header>
</controls:MetroTabItem.Header>
<ScrollViewer>
<StackPanel Margin="8">
<TextBlock Text="Miniature" FontWeight="Bold" />
@ -275,15 +319,15 @@
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
</controls:MetroTabItem>
<TabItem>
<TabItem.Header>
<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>
</TabItem.Header>
</controls:MetroTabItem.Header>
<ScrollViewer>
<StackPanel Margin="8">
<TextBlock Text="Logo" FontWeight="Bold" />
@ -317,15 +361,15 @@
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
</controls:MetroTabItem>
<TabItem>
<TabItem.Header>
<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>
</TabItem.Header>
</controls:MetroTabItem.Header>
<ScrollViewer>
<StackPanel Margin="8">
<TextBlock Text="AI / OCR" FontWeight="Bold" />
@ -388,14 +432,22 @@
</DataGrid>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
</controls:MetroTabItem>
</controls:MetroTabControl>
<!-- Right: Controls and live info -->
<Border Grid.Column="1" BorderBrush="#DDD" BorderThickness="1" Padding="8" MaxWidth="280">
<StackPanel>
<!-- Buttons stacked vertically as requested -->
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<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" />
@ -438,14 +490,10 @@
<TextBlock Text="Velocità" FontWeight="Bold" Margin="0,8,0,0" />
<TextBlock Text="{Binding SpeedCounter}" TextWrapping="Wrap" />
</StackPanel>
</Border>
</Border>
</StackPanel>
</Grid>
<!-- Status bar at the bottom showing version -->
<StatusBar Grid.Row="1">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock Name="VersionTextBlock" Text="" />
</StatusBarItem>
</StatusBar>
<!-- Status bar removed; version now shown in the title commands area -->
</Grid>
</Window>
</controls:MetroWindow>

View file

@ -1,4 +1,6 @@
using System.Windows;
using MahApps.Metro.Controls;
using ControlzEx.Theming;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
@ -9,9 +11,10 @@ using System.Windows.Forms;
namespace ImageCatalog_2
{
public partial class MainWindow : Window
public partial class MainWindow : MetroWindow
{
private readonly DataModel _model;
private bool _isDarkTheme = false;
public MainWindow(DataModel model)
{
InitializeComponent();
@ -38,6 +41,9 @@ namespace ImageCatalog_2
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
@ -62,7 +68,15 @@ namespace ImageCatalog_2
var rd = isDark ? (ResourceDictionary)Resources["DarkTheme"] : (ResourceDictionary)Resources["LightTheme"];
foreach (var key in rd.Keys)
{
Resources[key] = rd[key];
// 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
@ -274,6 +288,71 @@ namespace ImageCatalog_2
}
}
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))

View file

@ -86,17 +86,42 @@ static class Program
//Application.Run(mainForm);
// -----------------------------------------------------------------------------
if (serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) is ImageCatalog_2.MainWindow wpfMain)
// Create the WPF Application and merge MahApps resources BEFORE constructing the MainWindow
// so InitializeComponent sees the theme resources on first render.
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") });
// Default Light theme (can be replaced at runtime)
wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml") });
// Also notify ThemeManager about initial theme so chrome and MahApps brushes are applied
try
{
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(wpfApp, "Light.Blue");
}
catch
{
// ignore if ThemeManager API isn't present
}
}
catch
{
// If resources fail to load (package not present at runtime), continue silently
}
// Now resolve the WPF MainWindow so its constructor runs with the application resources available
var wpfMain = serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) as ImageCatalog_2.MainWindow;
if (wpfMain is not null)
{
// Start WPF app
var app = new System.Windows.Application();
app.Run(wpfMain);
}
else
{
var mainForm = serviceProvider.GetRequiredService<MainForm>();
Application.Run(mainForm);
wpfApp.Run(wpfMain);
return;
}
// Fallback to WinForms UI
var mainForm = serviceProvider.GetRequiredService<MainForm>();
System.Windows.Forms.Application.Run(mainForm);
}
private static void ConfigureServices(ServiceCollection services)