From 961cbd198ab5e114f094da2373dd3ed5637bb66c Mon Sep 17 00:00:00 2001 From: OliverT87 Date: Tue, 24 Jun 2025 14:48:42 +0200 Subject: [PATCH] themes verbessert, inkl about page --- .../PeriodensystemController.cs | 187 ++++++++++++++++- Project_Periodensystem.Model/Logger.cs | 136 +++++++++++++ Project_Periodensystem.Model/Theme.cs | 9 - Project_Periodensystem.View/AboutPage.axaml | 151 +++++++++++--- .../AboutPage.axaml.cs | 57 ++++-- Project_Periodensystem.View/App.axaml | 10 +- Project_Periodensystem.View/App.axaml.cs | 5 + Project_Periodensystem.View/LandingPage.axaml | 75 +++---- .../LandingPage.axaml.cs | 112 ++++++----- .../MainWindow.axaml.cs | 139 +++---------- .../PeriodicTablePage.axaml | 19 +- .../PeriodicTablePage.axaml.cs | 188 ++++++++++++------ 12 files changed, 763 insertions(+), 325 deletions(-) create mode 100644 Project_Periodensystem.Model/Logger.cs delete mode 100644 Project_Periodensystem.Model/Theme.cs diff --git a/Project_Periodensystem.Controller/PeriodensystemController.cs b/Project_Periodensystem.Controller/PeriodensystemController.cs index 3c363d7..49e70ec 100644 --- a/Project_Periodensystem.Controller/PeriodensystemController.cs +++ b/Project_Periodensystem.Controller/PeriodensystemController.cs @@ -1,17 +1,196 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.Generic; +using System.Linq; using Project_Periodensystem.Model; using Project_Periodensystem.Persistence; namespace Project_Periodensystem.Controller { + /// + /// Controller für das Periodensystem - verwaltet die Geschäftslogik + /// und trennt View von Model gemäß MVC-Pattern + /// public class PeriodensystemController { - public ObservableCollection Elements { get; } + // Nullable Field um Warning zu vermeiden + private List _elements = new List(); + /// + /// Konstruktor - initialisiert den Controller + /// public PeriodensystemController() { - // Gießt die statische List<> in eine ObservableCollection - Elements = new ObservableCollection(PeriodicTableData.Elements); + LoadElements(); + } + + /// + /// Lädt alle Elemente über den Persistence-Layer + /// + private void LoadElements() + { + try + { + // Verwende die korrekte statische Methode aus PeriodicTableData + _elements = PeriodicTableData.Elements.ToList(); + Logger.Log($"Controller: {_elements.Count} Elemente erfolgreich geladen"); + } + catch (Exception ex) + { + Logger.Log($"EXCEPTION in LoadElements: {ex.Message}"); + _elements = new List(); + } + } + + /// + /// Gibt alle verfügbaren Elemente zurück + /// + /// Liste aller Elemente + public List GetAllElements() + { + return _elements; + } + + /// + /// Sucht ein Element nach Atomnummer + /// + /// Atomnummer des gesuchten Elements + /// Element oder null wenn nicht gefunden + public Element? GetElementByAtomicNumber(int atomicNumber) + { + if (atomicNumber <= 0) + { + Logger.Log($"Ungültige Atomnummer: {atomicNumber}"); + return null; + } + + return _elements.FirstOrDefault(e => e.AtomicNumber == atomicNumber); + } + + /// + /// Sucht ein Element nach Symbol + /// + /// Symbol des gesuchten Elements + /// Element oder null wenn nicht gefunden + public Element? GetElementBySymbol(string symbol) + { + if (string.IsNullOrWhiteSpace(symbol)) + { + Logger.Log("Leeres Symbol übergeben"); + return null; + } + + return _elements.FirstOrDefault(e => + string.Equals(e.Symbol, symbol, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Filtert Elemente nach Serie + /// + /// Gewünschte Elementserie + /// Liste der Elemente der angegebenen Serie + public List GetElementsBySeries(string series) + { + if (string.IsNullOrWhiteSpace(series)) + { + return new List(); + } + + return _elements.Where(e => + string.Equals(e.Series, series, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + /// + /// Validiert Grid-Position eines Elements + /// + /// Zu validierendes Element + /// True wenn Position gültig ist + public bool ValidateElementPosition(Element element) + { + if (element == null) + { + Logger.Log("Null-Element kann nicht validiert werden"); + return false; + } + + // Periodensystem hat 7 Perioden (0-6) und 18 Gruppen (0-17) + // Plus Lanthanoid/Actinoid-Reihen bei Zeile 8 und 9 + bool validRow = element.Row >= 0 && element.Row <= 9; + bool validColumn = element.Column >= 0 && element.Column <= 17; + + if (!validRow || !validColumn) + { + Logger.Log($"Ungültige Position für {element.Symbol}: ({element.Row},{element.Column})"); + return false; + } + + return true; + } + + /// + /// Gibt alle verfügbaren Element-Serien zurück + /// + /// Liste aller Serien + public List GetAllSeries() + { + return _elements.Select(e => e.Series) + .Distinct() + .Where(s => !string.IsNullOrWhiteSpace(s)) + .OrderBy(s => s) + .ToList(); + } + + /// + /// Überprüft ob alle Elemente korrekt geladen wurden + /// + /// True wenn Daten vollständig sind + public bool ValidateData() + { + if (_elements.Count == 0) + { + Logger.Log("Keine Elemente geladen"); + return false; + } + + // Überprüfe auf Duplikate bei Atomnummern + var duplicateNumbers = _elements.GroupBy(e => e.AtomicNumber) + .Where(g => g.Count() > 1) + .Select(g => g.Key); + + if (duplicateNumbers.Any()) + { + Logger.Log($"Doppelte Atomnummern gefunden: {string.Join(", ", duplicateNumbers)}"); + return false; + } + + // Überprüfe auf leere Symbole + var emptySymbols = _elements.Where(e => string.IsNullOrWhiteSpace(e.Symbol)); + if (emptySymbols.Any()) + { + Logger.Log("Elemente mit leeren Symbolen gefunden"); + return false; + } + + Logger.Log($"Datenvalidierung erfolgreich - {_elements.Count} Elemente validiert"); + return true; + } + + /// + /// Lädt Daten neu (für Refresh-Funktionalität) + /// + public void RefreshData() + { + Logger.Log("Daten werden neu geladen..."); + LoadElements(); + } + + /// + /// Gibt die Anzahl der geladenen Elemente zurück + /// + /// Anzahl der Elemente + public int GetElementCount() + { + return _elements.Count; } } } diff --git a/Project_Periodensystem.Model/Logger.cs b/Project_Periodensystem.Model/Logger.cs new file mode 100644 index 0000000..559d22e --- /dev/null +++ b/Project_Periodensystem.Model/Logger.cs @@ -0,0 +1,136 @@ +using System; +using System.IO; + +namespace Project_Periodensystem.Model +{ + /// + /// Einfacher Logger für Debug-Ausgaben und Fehlerprotokollierung + /// + public static class Logger + { + private static readonly string LogFilePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Periodensystem", "app.log"); + + /// + /// Statischer Initializer - erstellt Log-Verzeichnis + /// + static Logger() + { + try + { + var logDirectory = Path.GetDirectoryName(LogFilePath); + if (logDirectory != null && !Directory.Exists(logDirectory)) + { + Directory.CreateDirectory(logDirectory); + } + } + catch + { + // Falls Log-Datei nicht erstellt werden kann, nur Konsole verwenden + } + } + + /// + /// Protokolliert eine Nachricht sowohl in Konsole als auch Datei + /// + /// Zu protokollierende Nachricht + public static void Log(string message) + { + if (string.IsNullOrWhiteSpace(message)) + return; + + var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var logEntry = $"[{timestamp}] {message}"; + + // Konsolen-Ausgabe (für Debug) + Console.WriteLine(logEntry); + + // Datei-Ausgabe (für Persistenz) + try + { + File.AppendAllText(LogFilePath, logEntry + Environment.NewLine); + } + catch + { + // Fehler beim Schreiben ignorieren um App nicht zum Absturz zu bringen + } + } + + /// + /// Protokolliert eine Exception mit Stack-Trace + /// + /// Exception die protokolliert werden soll + /// Zusätzlicher Kontext + public static void LogException(Exception ex, string context = "") + { + if (ex == null) + { + Log("Null-Exception übergeben"); + return; + } + + var message = string.IsNullOrWhiteSpace(context) + ? $"EXCEPTION: {ex.Message}\nStack: {ex.StackTrace}" + : $"EXCEPTION in {context}: {ex.Message}\nStack: {ex.StackTrace}"; + + Log(message); + } + + /// + /// Protokolliert eine Warnung + /// + /// Warnung + public static void LogWarning(string message) + { + Log($"WARNING: {message}"); + } + + /// + /// Protokolliert einen Fehler + /// + /// Fehlermeldung + public static void LogError(string message) + { + Log($"ERROR: {message}"); + } + + /// + /// Protokolliert Debug-Informationen (nur in Debug-Build) + /// + /// Debug-Nachricht + [System.Diagnostics.Conditional("DEBUG")] + public static void LogDebug(string message) + { + Log($"DEBUG: {message}"); + } + + /// + /// Löscht die Log-Datei (für Cleanup) + /// + public static void ClearLog() + { + try + { + if (File.Exists(LogFilePath)) + { + File.Delete(LogFilePath); + Log("Log-Datei gelöscht"); + } + } + catch (Exception ex) + { + Log($"Fehler beim Löschen der Log-Datei: {ex.Message}"); + } + } + + /// + /// Gibt den Pfad zur Log-Datei zurück + /// + /// Vollständiger Pfad zur Log-Datei + public static string GetLogFilePath() + { + return LogFilePath; + } + } +} \ No newline at end of file diff --git a/Project_Periodensystem.Model/Theme.cs b/Project_Periodensystem.Model/Theme.cs deleted file mode 100644 index b54600b..0000000 --- a/Project_Periodensystem.Model/Theme.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Project_Periodensystem.Model -{ - public enum AppTheme - { - Dark, - Light, - Classic - } -} \ No newline at end of file diff --git a/Project_Periodensystem.View/AboutPage.axaml b/Project_Periodensystem.View/AboutPage.axaml index 5dfe90a..fd586d8 100644 --- a/Project_Periodensystem.View/AboutPage.axaml +++ b/Project_Periodensystem.View/AboutPage.axaml @@ -1,19 +1,19 @@ - + + + + + + + + + + + + + + + + + + - + + + + - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - \ No newline at end of file diff --git a/Project_Periodensystem.View/AboutPage.axaml.cs b/Project_Periodensystem.View/AboutPage.axaml.cs index fee854f..79a2ef0 100644 --- a/Project_Periodensystem.View/AboutPage.axaml.cs +++ b/Project_Periodensystem.View/AboutPage.axaml.cs @@ -8,44 +8,67 @@ using Project_Periodensystem.Model; namespace Project_Periodensystem.View { + /// + /// Code-Behind für die About-Seite + /// public partial class AboutPage : UserControl { + /// + /// Konstruktor + /// public AboutPage() { InitializeComponent(); } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - if (this.GetVisualRoot() is MainWindow mainWindow) - { - mainWindow.UpdateTheme(MainWindow.CurrentTheme); - } - } - private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + /// + /// Zurück-Button - Navigation zur Landing Page + /// private void BackButton_Click(object? sender, RoutedEventArgs e) { - if (MainWindow.Instance != null) + try { - MainWindow.Instance.ShowLandingPage(); + var mainWindow = TopLevel.GetTopLevel(this) as Window; + if (mainWindow != null) + { + var landingPage = new LandingPage(); + mainWindow.Content = landingPage; + Logger.Log("Navigation zurück zur Landing Page"); + } + } + catch (System.Exception ex) + { + Logger.Log($"Fehler bei Navigation zurück: {ex.Message}"); } } + /// + /// Event-Handler für Theme-Button - VEREINHEITLICHT mit anderen Seiten + /// private void ThemeButton_Click(object? sender, RoutedEventArgs e) { - var themes = Enum.GetValues(); - MainWindow.CurrentTheme = themes[(Array.IndexOf(themes, MainWindow.CurrentTheme) + 1) % themes.Length]; - - if (MainWindow.Instance != null) + try { - MainWindow.Instance.UpdateTheme(MainWindow.CurrentTheme); + // GLEICHE LOGIK wie auf LandingPage und PeriodicTablePage + var app = Application.Current; + if (app != null) + { + var currentTheme = app.ActualThemeVariant; + app.RequestedThemeVariant = currentTheme == Avalonia.Styling.ThemeVariant.Dark + ? Avalonia.Styling.ThemeVariant.Light + : Avalonia.Styling.ThemeVariant.Dark; + + Logger.Log($"Theme gewechselt zu: {app.RequestedThemeVariant}"); + } + } + catch (Exception ex) + { + Logger.Log($"Fehler beim Theme-Wechsel: {ex.Message}"); } } } diff --git a/Project_Periodensystem.View/App.axaml b/Project_Periodensystem.View/App.axaml index 09e0eda..aae0356 100644 --- a/Project_Periodensystem.View/App.axaml +++ b/Project_Periodensystem.View/App.axaml @@ -1,7 +1,9 @@ - - - + x:Class="Project_Periodensystem.View.App" + RequestedThemeVariant="Dark"> + + + + diff --git a/Project_Periodensystem.View/App.axaml.cs b/Project_Periodensystem.View/App.axaml.cs index 1b535e1..af02b32 100644 --- a/Project_Periodensystem.View/App.axaml.cs +++ b/Project_Periodensystem.View/App.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Project_Periodensystem.Model; namespace Project_Periodensystem.View { @@ -15,7 +16,11 @@ namespace Project_Periodensystem.View { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + // Standard Dark Theme beim Start + RequestedThemeVariant = Avalonia.Styling.ThemeVariant.Dark; + desktop.MainWindow = new MainWindow(); + Logger.Log("Application initialized with Dark theme"); } base.OnFrameworkInitializationCompleted(); diff --git a/Project_Periodensystem.View/LandingPage.axaml b/Project_Periodensystem.View/LandingPage.axaml index 695ac68..500552b 100644 --- a/Project_Periodensystem.View/LandingPage.axaml +++ b/Project_Periodensystem.View/LandingPage.axaml @@ -1,13 +1,15 @@ - + @@ -17,50 +19,53 @@ + + + + + - - - - - - - - - - + - - + + + + diff --git a/Project_Periodensystem.View/LandingPage.axaml.cs b/Project_Periodensystem.View/LandingPage.axaml.cs index 0329116..4c43f1e 100644 --- a/Project_Periodensystem.View/LandingPage.axaml.cs +++ b/Project_Periodensystem.View/LandingPage.axaml.cs @@ -1,79 +1,95 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; -using Avalonia.Media; -using Avalonia.VisualTree; using Project_Periodensystem.Model; -using System; -using System.Collections.Generic; namespace Project_Periodensystem.View { + /// + /// Code-Behind für die Landing Page + /// public partial class LandingPage : UserControl { - private readonly Random random = new(); - private readonly Dictionary themeColors; - private DateTime lastClickTime = DateTime.MinValue; - private const int DEBOUNCE_MS = 500; - + /// + /// Konstruktor + /// public LandingPage() { - Logger.Log($"=== LandingPage wird initialisiert am {DateTime.Now:dd.MM.yyyy HH:mm:ss} ==="); InitializeComponent(); - - themeColors = new Dictionary - { - { AppTheme.Dark, "#5C5144" }, - { AppTheme.Light, "#E8DFD8" }, - { AppTheme.Classic, "#7B8B6F" } - }; - - Logger.Log("LandingPage initialisiert"); } - private void InitializeComponent() + /// + /// Event-Handler für Periodensystem-Button + /// + private void PeriodicTableButton_Click(object? sender, RoutedEventArgs e) { - AvaloniaXamlLoader.Load(this); - } - - private void ThemeButton_Click(object? sender, RoutedEventArgs e) - { - var themes = Enum.GetValues(); - MainWindow.CurrentTheme = themes[(Array.IndexOf(themes, MainWindow.CurrentTheme) + 1) % themes.Length]; - - if (MainWindow.Instance != null) + try { - MainWindow.Instance.UpdateTheme(MainWindow.CurrentTheme); + var mainWindow = TopLevel.GetTopLevel(this) as Window; + if (mainWindow != null) + { + var periodicTablePage = new PeriodicTablePage(); + mainWindow.Content = periodicTablePage; + Logger.Log("Navigation zum Periodensystem"); + } + } + catch (System.Exception ex) + { + Logger.Log($"Fehler bei Navigation zum Periodensystem: {ex.Message}"); } } - private void StartButton_Click(object? sender, RoutedEventArgs e) + /// + /// Event-Handler für About-Button + /// + private void AboutButton_Click(object? sender, RoutedEventArgs e) { - // Add multiple ways to see if this method is called - System.Diagnostics.Debug.WriteLine("StartButton_Click CALLED!"); - Console.WriteLine("StartButton_Click CALLED!"); - // Remove the MessageBox line - it's not available - - Logger.Log("=== StartButton_Click CALLED ==="); - - try + try { - if (MainWindow.Instance != null) + var mainWindow = TopLevel.GetTopLevel(this) as Window; + if (mainWindow != null) { - Logger.Log("Found MainWindow instance - calling ShowPeriodicTable"); - MainWindow.Instance.ShowPeriodicTable(); - Logger.Log("ShowPeriodicTable called successfully"); + var aboutPage = new AboutPage(); + mainWindow.Content = aboutPage; + Logger.Log("Navigation zur About-Seite"); + } + } + catch (System.Exception ex) + { + Logger.Log($"Fehler bei Navigation zu About: {ex.Message}"); + } + } + + /// + /// Event-Handler für Theme-Button + /// + private void ThemeButton_Click(object? sender, RoutedEventArgs e) + { + try + { + Logger.Log("Theme-Button geklickt"); + + var app = Application.Current; + if (app != null) + { + var currentTheme = app.ActualThemeVariant; + Logger.Log($"Aktuelles Theme: {currentTheme}"); + + var newTheme = currentTheme == Avalonia.Styling.ThemeVariant.Dark + ? Avalonia.Styling.ThemeVariant.Light + : Avalonia.Styling.ThemeVariant.Dark; + + app.RequestedThemeVariant = newTheme; + Logger.Log($"Theme gewechselt zu: {newTheme}"); } else { - Logger.Log("MainWindow.Instance is null!"); + Logger.Log("Application.Current ist null!"); } } - catch (Exception ex) + catch (System.Exception ex) { - Logger.Log($"ERROR in StartButton_Click: {ex.Message}"); + Logger.Log($"Fehler beim Theme-Wechsel: {ex.Message}"); } } } diff --git a/Project_Periodensystem.View/MainWindow.axaml.cs b/Project_Periodensystem.View/MainWindow.axaml.cs index 13df0ce..26e7050 100644 --- a/Project_Periodensystem.View/MainWindow.axaml.cs +++ b/Project_Periodensystem.View/MainWindow.axaml.cs @@ -1,152 +1,65 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Media; -using Avalonia.VisualTree; -using Avalonia.Threading; using Project_Periodensystem.Model; -using System; -using System.Collections.Generic; -using System.Linq; -using static Project_Periodensystem.Model.AppTheme; namespace Project_Periodensystem.View { + /// + /// Vereinfachtes MainWindow - nur noch Container für Content + /// public partial class MainWindow : Window { private ContentControl? mainContent; - public static AppTheme CurrentTheme { get; set; } = AppTheme.Dark; - public static MainWindow? Instance { get; private set; } // Add this line - - public static Dictionary ThemeColors { get; } = new() - { - { AppTheme.Dark, ("#2F2F2F", "#FFFFFF") }, - { AppTheme.Light, ("#FFFFFF", "#000000") }, - { AppTheme.Classic, ("#E8E8E8", "#000000") } - }; public MainWindow() { InitializeComponent(); - Instance = this; // Set the static reference mainContent = this.FindControl("MainContent"); ShowLandingPage(); } + /// + /// Zeigt die Landing Page an + /// public void ShowLandingPage() { - mainContent!.Content = new LandingPage(); - Dispatcher.UIThread.Post(() => UpdateTheme(CurrentTheme), DispatcherPriority.Loaded); + if (mainContent != null) + { + mainContent.Content = new LandingPage(); + Logger.Log("Landing Page angezeigt"); + } } + /// + /// Zeigt das Periodensystem an + /// public void ShowPeriodicTable() { Logger.Log("ShowPeriodicTable called"); try { - mainContent!.Content = new PeriodicTablePage(); - Logger.Log("PeriodicTablePage created and set"); - Dispatcher.UIThread.Post(() => UpdateTheme(CurrentTheme), DispatcherPriority.Loaded); - Logger.Log("Theme update posted"); + if (mainContent != null) + { + mainContent.Content = new PeriodicTablePage(); + Logger.Log("PeriodicTablePage created and set"); + } } - catch (Exception ex) + catch (System.Exception ex) { Logger.Log($"Error in ShowPeriodicTable: {ex.Message}"); } } + /// + /// Zeigt die About Page an + /// public void ShowAboutPage() { - mainContent!.Content = new AboutPage(); - Dispatcher.UIThread.Post(() => UpdateTheme(CurrentTheme), DispatcherPriority.Loaded); - } - - public void UpdateTheme(AppTheme theme) - { - CurrentTheme = theme; - if (mainContent?.Content is UserControl control) + if (mainContent != null) { - var (background, foreground) = ThemeColors[theme]; - control.Background = new SolidColorBrush(Color.Parse(background)); - - // For PeriodicTablePage, use a timer to ensure visual tree is ready - if (control is PeriodicTablePage) - { - // Wait a bit longer for the visual tree to be fully constructed - var timer = new System.Timers.Timer(100); // 100ms delay - timer.Elapsed += (sender, e) => - { - timer.Stop(); - timer.Dispose(); - - Dispatcher.UIThread.Post(() => - { - try - { - var allButtons = control.GetVisualDescendants().OfType + +