develop #1
4 changed files with 207 additions and 54 deletions
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.
commit
9007a27fb2
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue