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.
This commit is contained in:
MaddoScientisto 2026-02-21 16:49:13 +01:00
commit 9007a27fb2
4 changed files with 207 additions and 54 deletions

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))