Files
EngineeringSync/EngineeringSync.TrayApp/App.xaml.cs
EngineeringSync 04ae8a0aae Initial commit: EngineeringSync v1.0.0
Vollständige Implementierung des EngineeringSync-Middleware-Tools:
- Windows Service (Kestrel :5050) mit FileSystemWatcher + SignalR
- WPF Tray-App mit PendingChanges- und Projektverwaltungs-Fenster
- Setup-Wizard (8-Schritte-Installer)
- SQLite/EF Core Datenschicht (WAL-Modus)
- SHA-256-basiertes Debouncing (2s Fenster)
- Backup-System mit konfigurierbarer Aufbewahrung

Bugfixes & Verbesserungen:
- BUG-1: AppDbContext OnConfiguring invertierte Bedingung behoben
- BUG-2: Event-Handler-Leak in TrayApp (Fenster-Singleton-Pattern)
- BUG-3: ProjectConfigChanged SignalR-Signal in allen CRUD-Endpoints
- BUG-5: Rename-Sync löscht alte Datei im Simulations-Ordner
- BUG-6: Doppeltes Dispose von SignalR verhindert
- BUG-7: Registry-Deinstallation nur EngineeringSync-Eintrag entfernt
- S1: Path-Traversal-Schutz via SafeCombine() im SyncManager
- E1: FSW Buffer 64KB + automatischer Re-Scan bei Overflow
- E2: Retry-Logik (3x) für gesperrte Dateien mit exponentiellem Backoff
- E4: Channel.Writer.TryComplete() beim Shutdown
- C2: HubMethodNames-Konstanten statt Magic Strings
- E3: Pagination in Changes-API (page/pageSize Query-Parameter)
- A1: Fire-and-Forget mit try/catch + Logging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 21:52:26 +01:00

140 lines
4.6 KiB
C#

using System.Windows;
using System.Windows.Controls;
using EngineeringSync.TrayApp.Services;
using EngineeringSync.TrayApp.ViewModels;
using EngineeringSync.TrayApp.Views;
using H.NotifyIcon;
namespace EngineeringSync.TrayApp;
public partial class App : Application
{
private TaskbarIcon? _trayIcon;
private SignalRService? _signalR;
private ApiClient? _apiClient;
private PendingChangesWindow? _pendingChangesWindow;
private PendingChangesViewModel? _pendingChangesViewModel;
protected override async void OnStartup(StartupEventArgs e)
{
try
{
base.OnStartup(e);
_apiClient = new ApiClient(new System.Net.Http.HttpClient());
_signalR = new SignalRService();
// TaskbarIcon aus XAML-Resource laden (wird dadurch korrekt im Shell registriert)
_trayIcon = (TaskbarIcon)FindResource("TrayIcon");
_trayIcon.ContextMenu = BuildContextMenu();
// ForceCreate() aufrufen, um sicherzustellen dass das Icon im System Tray erscheint
_trayIcon.ForceCreate(enablesEfficiencyMode: false);
_signalR.ChangeNotificationReceived += OnChangeNotificationReceived;
_signalR.ChangeNotificationReceived += OnPendingChangesWindowNotification;
try { await _signalR.ConnectAsync(); }
catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[TrayApp] SignalR Connect Fehler: {ex}"); }
}
catch (Exception ex)
{
// Debug-Fehlerbehandlung für Icon-Initialisierung
MessageBox.Show(
$"Fehler bei der Initialisierung des Tray Icons:\n\n{ex.Message}\n\n{ex.StackTrace}",
"EngineeringSync Startup Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
throw;
}
}
private ContextMenu BuildContextMenu()
{
var menu = new ContextMenu();
var showChanges = new MenuItem { Header = "Änderungen anzeigen" };
showChanges.Click += (_, _) => OpenChangesWindow();
menu.Items.Add(showChanges);
var showProjects = new MenuItem { Header = "Projekte verwalten" };
showProjects.Click += (_, _) => OpenProjectsWindow();
menu.Items.Add(showProjects);
menu.Items.Add(new Separator());
var exit = new MenuItem { Header = "Beenden" };
exit.Click += (_, _) => ExitApp();
menu.Items.Add(exit);
return menu;
}
private void OpenChangesWindow()
{
// Fenster beim ersten Mal erstellen
if (_pendingChangesWindow is null)
{
_pendingChangesViewModel = new PendingChangesViewModel(_apiClient!);
_pendingChangesWindow = new PendingChangesWindow(_pendingChangesViewModel);
}
// Fenster anzeigen/aktivieren
_pendingChangesWindow.Show();
_pendingChangesWindow.Activate();
_ = _pendingChangesViewModel!.LoadProjectsCommand.ExecuteAsync(null);
}
private void OpenProjectsWindow()
{
var existing = Windows.OfType<ProjectManagementWindow>().FirstOrDefault();
if (existing is not null) { existing.Activate(); return; }
var vm = new ProjectManagementViewModel(_apiClient!);
var window = new ProjectManagementWindow(vm);
window.Show();
_ = vm.LoadCommand.ExecuteAsync(null);
}
private void OnChangeNotificationReceived(Guid projectId, string projectName, int count)
{
Dispatcher.Invoke(() =>
{
_trayIcon?.ShowNotification(
title: "Neue Engineering-Daten",
message: $"Projekt \"{projectName}\": {count} ausstehende Änderung(en).");
});
}
private void OnPendingChangesWindowNotification(Guid projectId, string projectName, int count)
{
// Handler für PendingChangesWindow: nur aktualisieren wenn Fenster sichtbar ist
if (_pendingChangesWindow?.IsVisible == true && _pendingChangesViewModel is not null)
{
Dispatcher.Invoke(() => _ = _pendingChangesViewModel.LoadChangesCommand.ExecuteAsync(null));
}
}
private async void ExitApp()
{
if (_signalR is not null)
{
await _signalR.DisposeAsync();
_signalR = null;
}
_trayIcon?.Dispose();
Shutdown();
}
protected override async void OnExit(ExitEventArgs e)
{
if (_signalR is not null)
{
await _signalR.DisposeAsync();
_signalR = null;
}
_trayIcon?.Dispose();
base.OnExit(e);
}
}