Refactored Program.cs to accept command-line arguments and select between WinForms (default) and WPF UI based on the presence of --wpf. Removed old commented-out UI selection code. Updated launchSettings.json to provide separate profiles for WinForms and WPF launches. If WPF is requested but unavailable, the app falls back to WinForms.
215 lines
No EOL
8.8 KiB
C#
215 lines
No EOL
8.8 KiB
C#
using System.Runtime.InteropServices;
|
|
using ImageCatalog;
|
|
using ImageCatalog_2.Services;
|
|
using MaddoShared;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using AutoMapper;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Logging.Console;
|
|
using System.IO;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace ImageCatalog_2;
|
|
|
|
static class Program
|
|
{
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool AllocConsole();
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern IntPtr GetStdHandle(int nStdHandle);
|
|
|
|
private const int STD_OUTPUT_HANDLE = -11;
|
|
private const int STD_ERROR_HANDLE = -12;
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern bool SetStdHandle(int nStdHandle, IntPtr handle);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern IntPtr GetConsoleWindow();
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern bool AttachConsole(int dwProcessId);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern IntPtr CreateFile(
|
|
string lpFileName,
|
|
uint dwDesiredAccess,
|
|
uint dwShareMode,
|
|
IntPtr lpSecurityAttributes,
|
|
uint dwCreationDisposition,
|
|
uint dwFlagsAndAttributes,
|
|
IntPtr hTemplateFile);
|
|
|
|
private const uint GENERIC_WRITE = 0x40000000;
|
|
private const uint OPEN_EXISTING = 3;
|
|
|
|
private static void RedirectConsoleOutput()
|
|
{
|
|
var stdOutHandle = CreateFile("CONOUT$", GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
|
|
var safeFileHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(stdOutHandle, true);
|
|
var fileStream = new FileStream(safeFileHandle, FileAccess.Write);
|
|
var standardOutput = new StreamWriter(fileStream) { AutoFlush = true };
|
|
Console.SetOut(standardOutput);
|
|
Console.SetError(standardOutput);
|
|
}
|
|
|
|
public static IServiceProvider ServiceProvider { get; private set; }
|
|
[STAThread]
|
|
static void Main(string[] args)
|
|
{
|
|
Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
|
Application.EnableVisualStyles();
|
|
Application.SetCompatibleTextRenderingDefault(false);
|
|
|
|
AllocConsole();
|
|
RedirectConsoleOutput();
|
|
|
|
var serviceCollection = new ServiceCollection();
|
|
ConfigureServices(serviceCollection);
|
|
|
|
ServiceProvider = serviceCollection.BuildServiceProvider();
|
|
|
|
// Resolve WPF MainWindow when available, otherwise fall back to WinForms MainForm
|
|
var serviceProvider = ServiceProvider;
|
|
|
|
// Determine UI based on command line. Default: WinForms. Use --wpf to explicitly request WPF.
|
|
bool useWpf = args is not null && Array.Exists(args, a => string.Equals(a, "--wpf", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (useWpf)
|
|
{
|
|
// 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)
|
|
{
|
|
wpfApp.Run(wpfMain);
|
|
return;
|
|
}
|
|
|
|
// If WPF was requested but not available, fall back to WinForms.
|
|
}
|
|
|
|
// Default / fallback to WinForms UI
|
|
var mainForm = serviceProvider.GetRequiredService<MainForm>();
|
|
System.Windows.Forms.Application.Run(mainForm);
|
|
}
|
|
|
|
private static void ConfigureServices(ServiceCollection services)
|
|
{
|
|
// Register AutoMapper (new AddAutoMapper overload — provide config and marker types)
|
|
services.AddAutoMapper(cfg => { }, typeof(Program));
|
|
|
|
// Register your services here
|
|
services.AddTransient<ITestService, TestService>();
|
|
services.AddTransient<ISettingsService, SettingsService>();
|
|
|
|
services.AddTransient<DataModel>(sp =>
|
|
{
|
|
// Resolve optional version provider and pass to DataModel
|
|
var testService = sp.GetRequiredService<ITestService>();
|
|
var settingsService = sp.GetRequiredService<ISettingsService>();
|
|
var imageCreation = sp.GetRequiredService<ImageCreationService>();
|
|
var picSettings = sp.GetRequiredService<PicSettings>();
|
|
var mapper = sp.GetRequiredService<IMapper>();
|
|
var logger = sp.GetRequiredService<ILogger<DataModel>>();
|
|
var versionProvider = sp.GetService<MaddoShared.IVersionProvider>();
|
|
|
|
return new DataModel(testService, settingsService, imageCreation, picSettings, mapper, logger, versionProvider);
|
|
});
|
|
|
|
services.AddTransient<ImageCreationService>();
|
|
services.AddTransient<ImageCreatorGDI>();
|
|
services.AddTransient<ImageCreatorImageSharp>();
|
|
services.AddTransient<ImageCreatorMapper>();
|
|
|
|
// Register IImageCreator to be resolved via ImageCreatorMapper which selects concrete implementation at call time
|
|
services.AddTransient<IImageCreator>(sp => sp.GetRequiredService<ImageCreatorMapper>());
|
|
|
|
// Register a ParametriSetup singleton that persists user preferences in LocalApplicationData
|
|
var userPrefsPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"ImageCatalog", "userprefs.xml");
|
|
services.AddSingleton(new ParametriSetup(userPrefsPath));
|
|
services.AddSingleton<PicSettings>();
|
|
|
|
// Register your forms
|
|
services.AddTransient<MainForm>();
|
|
// Register WPF MainWindow so it can be resolved with the existing DataModel
|
|
services.AddTransient<ImageCatalog_2.MainWindow>();
|
|
|
|
// Version provider for UI and logging
|
|
services.AddSingleton<MaddoShared.IVersionProvider, MaddoShared.VersionProvider>();
|
|
|
|
services.AddLogging(configure =>
|
|
{
|
|
configure.AddCustomFormatter();
|
|
configure.AddConsole();
|
|
configure.SetMinimumLevel(LogLevel.Debug);
|
|
});
|
|
}
|
|
}
|
|
|
|
public static class ConsoleLoggerExtensions
|
|
{
|
|
public static ILoggingBuilder AddCustomFormatter(
|
|
this ILoggingBuilder builder) =>
|
|
builder
|
|
.AddConsole(options => options.FormatterName = nameof(CustomLoggingFormatter))
|
|
.AddConsoleFormatter<CustomLoggingFormatter, ConsoleFormatterOptions>()
|
|
.AddFilter("LuckyPennySoftware.AutoMapper.License", LogLevel.None);
|
|
}
|
|
public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable
|
|
{
|
|
private readonly IDisposable _optionsReloadToken;
|
|
private ConsoleFormatterOptions _formatterOptions;
|
|
public CustomLoggingFormatter(IOptionsMonitor<ConsoleFormatterOptions> options)
|
|
// Case insensitive
|
|
: base(nameof(CustomLoggingFormatter)) =>
|
|
(_optionsReloadToken, _formatterOptions) =
|
|
(options.OnChange(ReloadLoggerOptions), options.CurrentValue);
|
|
private void ReloadLoggerOptions(ConsoleFormatterOptions options) =>
|
|
_formatterOptions = options;
|
|
|
|
public override void Write<TState>(
|
|
in LogEntry<TState> logEntry,
|
|
IExternalScopeProvider? scopeProvider,
|
|
TextWriter? textWriter)
|
|
{
|
|
string? message =
|
|
logEntry.Formatter?.Invoke(
|
|
logEntry.State, logEntry.Exception);
|
|
|
|
if (message is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
textWriter.WriteLine($"{message}");
|
|
}
|
|
public void Dispose() => _optionsReloadToken?.Dispose();
|
|
} |