Code bereinigt und kommentiert

This commit is contained in:
OliverT87 2025-06-27 10:35:25 +02:00
parent e4aa132c79
commit 93b5502112
10 changed files with 1619 additions and 372 deletions

View File

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Project_Periodensystem.Model;
namespace Project_Periodensystem.Controller
{
/// <summary>
/// ElementStatistics - Spezialisierte Klasse für statistische Berechnungen und Datenanalyse
///
/// Diese Klasse wurde aus dem Haupt-Controller ausgelagert, um das Single Responsibility
/// Principle zu befolgen. Sie konzentriert sich ausschließlich auf:
///
/// - Mathematische Berechnungen (Durchschnitte, Extreme)
/// - Datengruppierung und -analyse (Serien, Kategorien)
/// - Informationsextraktion (Tuple-basierte Abfragen)
/// - Performance-optimierte LINQ-Operationen
///
/// Vorteile der Auslagerung:
/// - Bessere Testbarkeit einzelner Berechnungen
/// - Wiederverwendbarkeit der Statistik-Funktionen
/// - Klare Trennung von UI-Logik und Datenverarbeitung
/// - Einfache Erweiterung um neue Statistiken
/// </summary>
public class ElementStatistics
{
/// <summary>
/// Private, schreibgeschützte Liste aller Elemente für statistische Berechnungen.
/// Wird im Konstruktor gesetzt und bleibt danach unveränderlich (readonly).
/// Garantiert Datenintegrität während der gesamten Lebensdauer des Objekts.
/// </summary>
private readonly List<Element> _elements;
/// <summary>
/// Konstruktor der ElementStatistics-Klasse.
/// Initialisiert die Statistik-Engine mit einer Element-Sammlung.
///
/// Design-Prinzipien:
/// - Dependency Injection: Element-Liste wird von außen bereitgestellt
/// - Defensive Programmierung: Null-Safety durch Fallback
/// - Unveränderlichkeit: readonly field für Datensicherheit
/// </summary>
/// <param name="elements">Liste der Elemente für statistische Auswertungen (kann null sein)</param>
public ElementStatistics(List<Element> elements)
{
// Null-Safety: Falls null übergeben wird, erstelle eine leere Liste
// Verhindert NullReferenceExceptions in allen nachfolgenden Methoden
_elements = elements ?? new List<Element>();
}
public double[] GetAtomicWeights()
{
double[] weights = new double[_elements.Count];
for (int i = 0; i < _elements.Count; i++)
{
weights[i] = _elements[i].AtomicWeight;
}
return weights;
}
public double GetAverageAtomicWeight()
{
if (!_elements.Any()) return 0.0;
double[] weights = GetAtomicWeights();
double sum = 0;
for (int i = 0; i < weights.Length; i++)
{
sum += weights[i];
}
return sum / weights.Length;
}
public List<string> GetAllSeries()
{
return _elements.Select(e => e.Series)
.Distinct()
.Where(s => !string.IsNullOrWhiteSpace(s))
.OrderBy(s => s)
.ToList();
}
public (int totalElements, int uniqueSeries, double avgWeight) GetStatistics()
{
var seriesCount = GetAllSeries().Count;
var avgWeight = GetAverageAtomicWeight();
return (_elements.Count, seriesCount, avgWeight);
}
public (Element? lightest, Element? heaviest) GetWeightExtremes()
{
if (!_elements.Any()) return (null, null);
var lightest = _elements.OrderBy(e => e.AtomicWeight).First();
var heaviest = _elements.OrderByDescending(e => e.AtomicWeight).First();
return (lightest, heaviest);
}
public (string symbol, string name, double weight) GetElementInfo(int atomicNumber)
{
var element = _elements.FirstOrDefault(e => e.AtomicNumber == atomicNumber);
return element != null
? (element.Symbol, element.ElementName, element.AtomicWeight)
: ("?", "Unknown", 0.0);
}
public (int row, int column) GetElementPosition(int atomicNumber)
{
var element = _elements.FirstOrDefault(e => e.AtomicNumber == atomicNumber);
return element != null ? (element.Row, element.Column) : (-1, -1);
}
}
}

View File

@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Project_Periodensystem.Model;
namespace Project_Periodensystem.Controller
{
/// <summary>
/// ElementValidator - Spezialisierte Klasse für Datenvalidierung
///
/// Diese Klasse wurde aus dem PeriodensystemController ausgelagert, um das
/// Single Responsibility Principle (SRP) zu befolgen. Sie ist ausschließlich
/// für die Validierung von Element-Daten zuständig.
///
/// Vorteile der Auslagerung:
/// - Bessere Testbarkeit (kann isoliert getestet werden)
/// - Klarere Verantwortlichkeiten
/// - Wiederverwendbarkeit in anderen Controllern
/// - Einfachere Wartung und Erweiterung
/// </summary>
public class ElementValidator
{
// Private readonly Liste der zu validierenden Elemente
// readonly = kann nach Initialisierung nicht mehr geändert werden
private readonly List<Element> _elements;
/// <summary>
/// Konstruktor - Initialisiert den Validator mit einer Element-Liste
/// </summary>
/// <param name="elements">Liste der zu validierenden Elemente</param>
public ElementValidator(List<Element> elements)
{
// Null-Check mit Fallback zu leerer Liste - verhindert NullReferenceExceptions
_elements = elements ?? new List<Element>();
}
/// <summary>
/// Führt eine umfassende Validierung der Element-Daten durch.
///
/// Diese Methode prüft verschiedene Datenintegritäts-Aspekte:
/// 1. Existenz von Elementen (leere Liste = Problem)
/// 2. Eindeutigkeit der Atomnummern (jede darf nur einmal vorkommen)
/// 3. Vollständigkeit der Element-Symbole (keine leeren/null Werte)
///
/// Verwendet moderne C# LINQ-Syntax für effiziente Datenabfragen:
/// - GroupBy() für Gruppierung nach Atomnummer
/// - Where() für Filterung
/// - Any() für Existenzprüfung
/// - Count() für Anzahl-Ermittlung
/// </summary>
/// <returns>true = alle Validierungen bestanden, false = Fehler gefunden</returns>
public bool ValidateData()
{
// Erste Prüfung: Sind überhaupt Elemente vorhanden?
// Any() ist effizienter als Count() > 0, da es beim ersten Element stoppt
if (!_elements.Any())
{
Logger.Log("Keine Elemente zum Validieren");
return false;
}
// Zweite Prüfung: Doppelte Atomnummern aufspüren
// LINQ-Chain: GroupBy sammelt Elemente mit gleicher Atomnummer
// Where filtert Gruppen mit mehr als einem Element (= Duplikate)
// Select extrahiert nur die Atomnummer (Key) für Logging
var duplicateNumbers = _elements.GroupBy(e => e.AtomicNumber)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
if (duplicateNumbers.Any())
{
// string.Join() erstellt eine kommaseparierte Liste der doppelten Nummern
Logger.Log($"Doppelte Atomnummern gefunden: {string.Join(", ", duplicateNumbers)}");
return false;
}
// Dritte Prüfung: Leere oder ungültige Element-Symbole finden
// string.IsNullOrWhiteSpace() prüft auf null, leer oder nur Leerzeichen
var emptySymbols = _elements.Where(e => string.IsNullOrWhiteSpace(e.Symbol));
if (emptySymbols.Any())
{
Logger.Log($"Elemente mit leeren Symbolen gefunden: {emptySymbols.Count()}");
return false;
}
// Alle Validierungen bestanden
Logger.Log("Datenvalidierung erfolgreich");
return true;
}
/// <summary>
/// Validiert die Position eines Elements im Periodensystem.
///
/// Das Periodensystem hat feste Dimensionen:
/// - Zeilen (Perioden): 0-9 (insgesamt 10 Perioden)
/// - Spalten (Gruppen): 0-17 (insgesamt 18 Gruppen)
///
/// Diese Methode stellt sicher, dass Elemente nur an gültigen Positionen
/// platziert werden. Ungültige Positionen würden zu Layout-Fehlern oder
/// Abstürzen in der Benutzeroberfläche führen.
///
/// Verwendet nullable Reference Types (Element?) - moderne C# 8.0+ Syntax
/// </summary>
/// <param name="element">Das zu prüfende Element (kann null sein)</param>
/// <returns>true = Position ist gültig, false = Position ungültig oder Element ist null</returns>
public bool ValidateElementPosition(Element? element)
{
// Null-Check: Defensive Programmierung verhindert NullReferenceExceptions
if (element == null)
{
Logger.Log("Element ist null");
return false;
}
// Positionsgrenzen prüfen: Standard-Periodensystem Layout
// Row: 0-9 (Periode 1-10, aber 0-basiert indiziert)
// Column: 0-17 (Gruppe 1-18, aber 0-basiert indiziert)
if (element.Row < 0 || element.Row > 9 || element.Column < 0 || element.Column > 17)
{
Logger.Log($"Ungültige Position für {element.Symbol}: Row={element.Row}, Column={element.Column}");
return false;
}
return true;
}
/// <summary>
/// Führt eine vollständige Validierungsprüfung aller Elemente durch und protokolliert Fehler.
///
/// Diese Utility-Methode iteriert durch alle Elemente und prüft jedes einzeln.
/// Hauptzweck ist das Debugging und die Qualitätssicherung:
/// - Identifiziert problematische Elemente
/// - Protokolliert detaillierte Fehlermeldungen
/// - Hilfreich bei der Datenbereinigung
///
/// Besonderheiten:
/// - Verwendet null-conditional operator (?.) für sichere Navigation
/// - Null-coalescing operator (??) für Fallback-Werte
/// - Moderne C# Syntax für robuste Fehlerbehandlung
/// </summary>
public void LogValidationInfo()
{
// Iteriere durch alle Elemente und validiere jedes einzeln
foreach (var element in _elements)
{
// Prüfe Position für jedes Element
if (!ValidateElementPosition(element))
{
// element?.Symbol verwendet null-conditional operator
// Falls element null ist, wird Symbol nicht abgerufen -> kein Crash
// ?? "NULL" ist ein Fallback falls Symbol null/leer ist
Logger.Log($"VALIDIERUNGSFEHLER: {element?.Symbol ?? "NULL"}");
}
}
}
}
}

View File

@ -3,15 +3,86 @@ using System;
namespace Project_Periodensystem.Controller namespace Project_Periodensystem.Controller
{ {
/// <summary> /// <summary>
/// Interface für Navigation - trennt Controller von UI-Dependencies /// Navigation Service Interface - Implementiert Dependency Inversion Principle
/// Controller definiert WAS navigiert wird, View definiert WIE ///
/// ZWECK UND ARCHITEKTUR:
/// - Entkoppelt Controller-Layer von UI-spezifischen Navigation-Details
/// - Ermöglicht verschiedene UI-Implementierungen (WPF, Avalonia, Console, etc.)
/// - Macht Controller-Layer testbar durch Mock-Implementierungen
/// - Implementiert Interface Segregation Principle (nur notwendige Navigation-Methoden)
///
/// DESIGN PATTERNS:
/// - Dependency Inversion: Controller abhängig von Abstraktion, nicht von Implementierung
/// - Strategy Pattern: Verschiedene Navigation-Strategien möglich
/// - Command Pattern: Jede Methode kapselt eine UI-Aktion
///
/// C# KONZEPTE:
/// - Interface Definition: Vertrag ohne Implementierung
/// - Abstraktion: Trennt WHAT (Navigation) von HOW (UI-Framework)
/// - Dependency Injection: Controller erhält Implementation via Constructor
///
/// IMPLEMENTIERUNG:
/// - NavigationService.cs (View-Layer) implementiert dieses Interface
/// - Controller ruft Interface-Methoden auf, kennt aber Implementierung nicht
/// - Ermöglicht einfaches Testen und verschiedene UI-Frameworks
///
/// TESTBARKEIT:
/// - Mock-Implementierung für Unit Tests möglich
/// - Keine direkten UI-Dependencies im Controller
/// - Isolierte Testung der Navigation-Logik
/// </summary> /// </summary>
public interface INavigationService public interface INavigationService
{ {
/// <summary>
/// Navigiert zur Hauptansicht des Periodensystems
///
/// IMPLEMENTIERUNG:
/// - Zeigt das vollständige, interaktive Periodensystem an
/// - Hauptfunktion der Anwendung
/// - Alle Element-Tiles werden geladen und angezeigt
/// </summary>
void NavigateToPeriodicTable(); void NavigateToPeriodicTable();
/// <summary>
/// Navigiert zur About/Info-Seite der Anwendung
///
/// IMPLEMENTIERUNG:
/// - Zeigt Anwendungsinformationen, Version, Credits
/// - Dokumentation und Hilfe-Inhalte
/// - Links zu weiteren Ressourcen
/// </summary>
void NavigateToAbout(); void NavigateToAbout();
/// <summary>
/// Navigiert zurück zur Startseite/Hauptmenü
///
/// IMPLEMENTIERUNG:
/// - Zeigt das Hauptmenü mit Navigation-Optionen
/// - Reset-Funktion zum Anwendungsstart
/// - Home-Button Funktionalität
/// </summary>
void NavigateToLanding(); void NavigateToLanding();
/// <summary>
/// Wechselt zwischen hellen und dunklen UI-Themes
///
/// IMPLEMENTIERUNG:
/// - Toggle zwischen Light/Dark Mode
/// - Aktualisiert globale Theme-Settings
/// - Persistiert Benutzer-Präferenz
/// - Wendet neues Theme auf alle UI-Komponenten an
/// </summary>
void ToggleTheme(); void ToggleTheme();
/// <summary>
/// Zeigt Bestätigungsdialog für Datenexport
///
/// IMPLEMENTIERUNG:
/// - Modal-Dialog mit Export-Optionen
/// - Benutzer wählt Format und Ziel
/// - Startet Export-Prozess nach Bestätigung
/// - Fortschrittsanzeige und Erfolgs-/Fehlermeldungen
/// </summary>
void ShowExportConfirmation(); void ShowExportConfirmation();
} }
} }

View File

@ -7,48 +7,131 @@ using Project_Periodensystem.Persistence;
namespace Project_Periodensystem.Controller namespace Project_Periodensystem.Controller
{ {
/// <summary> /// <summary>
/// Controller für das Periodensystem - verwaltet die Geschäftslogik /// Haupt-Controller für das Periodensystem - implementiert das MVC-Pattern
/// und trennt View von Model gemäß MVC-Pattern ///
/// SAUBERE ARCHITEKTUR: Verwendet Interface für Navigation /// ZWECK UND ARCHITEKTUR:
/// - Zentrale Kontrollschicht zwischen View (UI) und Model/Persistence (Daten)
/// - Koordiniert alle Geschäftslogik-Operationen für das Periodensystem
/// - Delegiert komplexe Aufgaben an spezialisierte Helper-Klassen (Single Responsibility Principle)
/// - Bietet eine einheitliche API für alle UI-Komponenten
///
/// DESIGN PATTERN:
/// - MVC (Model-View-Controller): Trennt Präsentation von Geschäftslogik
/// - Delegation Pattern: Überträgt Verantwortlichkeiten an spezialisierte Klassen
/// - Dependency Injection: Nimmt INavigationService als abhängige Komponente entgegen
///
/// C# KONZEPTE:
/// - Nullable Reference Types (INavigationService?, ElementValidator?)
/// - LINQ für Datenoperationen (FirstOrDefault, Where, ToArray)
/// - Exception Handling mit try-catch-Blöcken
/// - Lambda Expressions für Delegation an Helper-Klassen
/// - Tuple-Rückgabewerte für strukturierte Daten
/// </summary> /// </summary>
public class PeriodensystemController public class PeriodensystemController
{ {
// Nullable Field um Warning zu vermeiden // ===== PRIVATE FELDER =====
private List<Element> _elements = new List<Element>();
// Navigation Service Interface (KEINE direkte UI-Dependency!)
private readonly INavigationService? _navigationService;
/// <summary> /// <summary>
/// Konstruktor - optional mit Navigation Service /// Zentrale Liste aller chemischen Elemente
/// List&lt;T&gt; ermöglicht dynamisches Hinzufügen/Entfernen von Elementen
/// Private Field mit Underscore-Notation (_elements) nach C#-Konvention
/// </summary>
private List<Element> _elements = new List<Element>();
/// <summary>
/// Navigation Service für Seitenwechsel in der Avalonia-Anwendung
/// Readonly: Kann nur im Konstruktor zugewiesen werden (Immutability)
/// Nullable (?): Kann null sein, da Navigation optional ist
/// Interface-basiert: Ermöglicht verschiedene Implementierungen (Dependency Inversion)
/// </summary>
private readonly INavigationService? _navigationService;
// ===== HELPER-KLASSEN FÜR CODE-ORGANISATION =====
/// <summary>
/// Spezialisierte Klasse für Validierungslogik
/// Nullable: Wird erst nach dem Laden der Elemente initialisiert
/// Separation of Concerns: Validierung ist getrennt von anderen Aufgaben
/// </summary>
private ElementValidator? _validator;
/// <summary>
/// Spezialisierte Klasse für statistische Berechnungen
/// Nullable: Wird erst nach dem Laden der Elemente initialisiert
/// Single Responsibility: Nur für Statistiken zuständig
/// </summary>
private ElementStatistics? _statistics;
// ===== KONSTRUKTOR =====
/// <summary>
/// Konstruktor mit optionaler Dependency Injection
///
/// PARAMETER:
/// - navigationService: Optional (null by default), ermöglicht Navigation zwischen Seiten
///
/// C# FEATURES:
/// - Default Parameter (= null): Macht den Parameter optional
/// - Nullable Reference Types: navigationService kann null sein
/// - Constructor Chaining: Ruft LoadElements() zur Initialisierung auf
///
/// ABLAUF:
/// 1. Navigation Service speichern (kann null sein)
/// 2. Elemente aus Persistence-Layer laden
/// 3. Helper-Klassen mit geladenen Daten initialisieren
/// </summary> /// </summary>
public PeriodensystemController(INavigationService? navigationService = null) public PeriodensystemController(INavigationService? navigationService = null)
{ {
_navigationService = navigationService; _navigationService = navigationService;
LoadElements(); LoadElements(); // Daten sofort beim Erstellen laden
} }
// ===== PRIVATE INITIALISIERUNGSMETHODEN =====
/// <summary> /// <summary>
/// Lädt alle Elemente über den Persistence-Layer (JETZT MIT DATAMANAGER!) /// Lädt alle chemischen Elemente aus dem Persistence-Layer
///
/// ZWECK:
/// - Zentrale Initialisierung aller Elementdaten
/// - Fehlerbehandlung beim Laden der Daten
/// - Initialisierung der Helper-Klassen nach erfolgreichem Laden
/// - Ausführliches Logging für Debugging und Monitoring
///
/// C# KONZEPTE:
/// - Exception Handling: try-catch für robuste Fehlerbehandlung
/// - LINQ: Take(10) für die ersten 10 Elemente
/// - Tuple Deconstruction: (totalElements, uniqueSeries, avgWeight) = GetStatistics()
/// - Null-Conditional Operator: lightest?.Symbol verhindert NullReferenceException
/// - String Interpolation: $"Text {variable}" für lesbare Ausgaben
///
/// FEHLERBEHANDLUNG:
/// - Bei Exceptions wird eine leere Liste initialisiert (Graceful Degradation)
/// - Alle Fehler werden geloggt für spätere Analyse
/// </summary> /// </summary>
private void LoadElements() private void LoadElements()
{ {
try try
{ {
// NEUE METHODE: DataManager statt direkt PeriodicTableData // Elemente aus DataManager laden (Persistence Layer)
_elements = DataManager.LoadElements(); _elements = DataManager.LoadElements();
Logger.Log($"Controller: {_elements.Count} Elemente erfolgreich geladen (via DataManager)"); Logger.Log($"Controller: {_elements.Count} Elemente erfolgreich geladen (via DataManager)");
// NEUE FEATURES demonstrieren: // Helper-Klassen mit den geladenen Daten initialisieren
// Array-Operationen testen // Delegation Pattern: Spezialisierte Klassen für verschiedene Aufgaben
_validator = new ElementValidator(_elements);
_statistics = new ElementStatistics(_elements);
// ===== DEMONSTRATIVE LOGGING VON C#-FEATURES =====
// Arrays: Alle Element-Symbole als Array
var symbols = GetAllSymbols(); var symbols = GetAllSymbols();
Logger.LogArray("Element-Symbole (erste 10)", symbols.Take(10).ToArray()); Logger.LogArray("Element-Symbole (erste 10)", symbols.Take(10).ToArray());
// Tuple-Operationen testen // Tuples: Strukturierte Rückgabe mehrerer Werte
var (totalElements, uniqueSeries, avgWeight) = GetStatistics(); var (totalElements, uniqueSeries, avgWeight) = GetStatistics();
Logger.LogTuple("Statistiken", $"Elemente: {totalElements}, Serien: {uniqueSeries}, Ø-Gewicht: {avgWeight:F2}"); Logger.LogTuple("Statistiken", $"Elemente: {totalElements}, Serien: {uniqueSeries}, Ø-Gewicht: {avgWeight:F2}");
// Extremwerte finden // Tuples mit Objekten: Extremwerte finden
var (lightest, heaviest) = GetWeightExtremes(); var (lightest, heaviest) = GetWeightExtremes();
if (lightest != null && heaviest != null) if (lightest != null && heaviest != null)
{ {
@ -58,304 +141,352 @@ namespace Project_Periodensystem.Controller
} }
catch (Exception ex) catch (Exception ex)
{ {
// Graceful Degradation: Bei Fehlern eine leere Liste verwenden
Logger.Log($"EXCEPTION in LoadElements: {ex.Message}"); Logger.Log($"EXCEPTION in LoadElements: {ex.Message}");
_elements = new List<Element>(); _elements = new List<Element>();
// Helper-Klassen können nicht initialisiert werden (bleiben null)
_validator = null;
_statistics = null;
} }
} }
// ===== PUBLIC API METHODEN - ELEMENTZUGRIFF =====
/// <summary> /// <summary>
/// Gibt alle verfügbaren Elemente zurück /// Gibt alle Elemente als List&lt;Element&gt; zurück
///
/// RÜCKGABETYP: List&lt;Element&gt;
/// - Ermöglicht Add/Remove-Operationen
/// - LINQ-Operationen verfügbar
/// - Dynamische Größe
///
/// VERWENDUNG:
/// - Hauptsächlich für UI-Bindungen (WPF/Avalonia DataBinding)
/// - Für LINQ-Abfragen in der View-Schicht
/// </summary> /// </summary>
/// <returns>Liste aller Elemente</returns>
public List<Element> GetAllElements() public List<Element> GetAllElements()
{ {
return _elements; return _elements;
} }
/// <summary> /// <summary>
/// Gibt Elemente als Array zurück (für Array-Operationen) /// Gibt alle Elemente als Array zurück
///
/// RÜCKGABETYP: Element[]
/// - Feste Größe (Read-Only nach Erstellung)
/// - Bessere Performance für Iterationen
/// - Weniger Speicher-Overhead als List
///
/// C# KONZEPT: LINQ ToArray()
/// - Konvertiert IEnumerable zu Array
/// - Erstellt eine Kopie der Daten
///
/// VERWENDUNG:
/// - Für Algorithmen, die feste Arrays benötigen
/// - Wenn Unveränderlichkeit wichtig ist
/// </summary> /// </summary>
/// <returns>Array aller Elemente</returns>
public Element[] GetAllElementsAsArray() public Element[] GetAllElementsAsArray()
{ {
return _elements.ToArray(); return _elements.ToArray();
} }
/// <summary> /// <summary>
/// Gibt nur die Symbole als String-Array zurück /// Extrahiert alle Element-Symbole als String-Array
///
/// ZWECK:
/// - Demonstriert klassische Array-Initialisierung und -Population
/// - Zeigt manuelle Iteration vs. LINQ-Alternativen
///
/// C# KONZEPTE:
/// - Array-Initialisierung: new string[size]
/// - For-Schleifen: Klassische imperative Programmierung
/// - Array-Indexing: array[i] = value
///
/// ALTERNATIVE (LINQ):
/// return _elements.Select(e => e.Symbol).ToArray();
///
/// LERNWERT:
/// - Zeigt Unterschied zwischen imperativer und funktionaler Programmierung
/// - Arrays vs. Collections
/// </summary> /// </summary>
/// <returns>Array mit allen Element-Symbolen</returns>
public string[] GetAllSymbols() public string[] GetAllSymbols()
{ {
// Klassische Array-Erstellung mit fester Größe
string[] symbols = new string[_elements.Count]; string[] symbols = new string[_elements.Count];
// Manuelle Population mit for-Schleife (imperativer Stil)
for (int i = 0; i < _elements.Count; i++) for (int i = 0; i < _elements.Count; i++)
{ {
symbols[i] = _elements[i].Symbol; symbols[i] = _elements[i].Symbol;
} }
return symbols; return symbols;
} }
/// <summary> // ===== DELEGATION AN HELPER-KLASSEN =====
/// Gibt Atomgewichte als Array zurück
/// </summary>
/// <returns>Array mit allen Atomgewichten</returns>
public double[] GetAtomicWeights()
{
double[] weights = new double[_elements.Count];
for (int i = 0; i < _elements.Count; i++)
{
weights[i] = _elements[i].AtomicWeight;
}
return weights;
}
/// <summary> /// <summary>
/// Berechnet Durchschnittsgewicht aus Array /// Diese Methoden implementieren das Delegation Pattern
///
/// VORTEILE DER DELEGATION:
/// - Single Responsibility Principle: Jede Klasse hat einen klaren Zweck
/// - Code-Reduktion: Controller wird schlanker und übersichtlicher
/// - Testbarkeit: Helper-Klassen können isoliert getestet werden
/// - Wartbarkeit: Änderungen an Validierung/Statistik beeinflussen Controller nicht
///
/// C# KONZEPTE:
/// - Null-Conditional Operator (?.): Verhindert NullReferenceException
/// - Null-Coalescing Operator (??): Liefert Fallback-Werte wenn null
/// - Lambda Expressions: Kurze Syntax für einfache Delegationen
/// - Expression-bodied Members: => Syntax für einzeilige Methoden
///
/// FEHLERBEHANDLUNG:
/// - Alle Methoden handhaben null-Helper graceful mit Fallback-Werten
/// - Keine Exceptions, sondern sichere Default-Werte
/// </summary> /// </summary>
/// <returns>Durchschnittliches Atomgewicht</returns>
public double GetAverageAtomicWeight()
{
double[] weights = GetAtomicWeights();
if (weights.Length == 0) return 0;
double sum = 0; // ===== VALIDIERUNGS-METHODEN (delegiert an ElementValidator) =====
for (int i = 0; i < weights.Length; i++)
{
sum += weights[i];
}
return sum / weights.Length;
}
/// <summary> /// <summary>
/// Speichert Elemente persistent (NEUE FUNKTION) /// Validiert die gesamte Element-Datenbasis
/// Delegation an ElementValidator.ValidateData()
/// Fallback: false bei null-Validator
/// </summary> /// </summary>
public void SaveElements() public bool ValidateData() => _validator?.ValidateData() ?? false;
{
try
{
DataManager.SaveElements(_elements);
Logger.Log("Elemente erfolgreich gespeichert");
}
catch (Exception ex)
{
Logger.LogException(ex, "SaveElements");
}
}
/// <summary> /// <summary>
/// Sucht ein Element nach Atomnummer /// Validiert die Position eines spezifischen Elements im Periodensystem
/// Delegation an ElementValidator.ValidateElementPosition()
/// Fallback: false bei null-Validator
/// </summary>
public bool ValidateElementPosition(Element? element) => _validator?.ValidateElementPosition(element) ?? false;
// ===== STATISTIK-METHODEN (delegiert an ElementStatistics) =====
/// <summary>
/// Extrahiert alle Atomgewichte als Array
/// Delegation an ElementStatistics.GetAtomicWeights()
/// Fallback: leeres Array bei null-Statistics
/// </summary>
public double[] GetAtomicWeights() => _statistics?.GetAtomicWeights() ?? new double[0];
/// <summary>
/// Berechnet das durchschnittliche Atomgewicht
/// Delegation an ElementStatistics.GetAverageAtomicWeight()
/// Fallback: 0.0 bei null-Statistics
/// </summary>
public double GetAverageAtomicWeight() => _statistics?.GetAverageAtomicWeight() ?? 0.0;
/// <summary>
/// Sammelt alle einzigartigen Element-Serien
/// Delegation an ElementStatistics.GetAllSeries()
/// Fallback: leere Liste bei null-Statistics
/// </summary>
public List<string> GetAllSeries() => _statistics?.GetAllSeries() ?? new List<string>();
/// <summary>
/// Sammelt wichtige Statistiken in einem Tuple
/// Tuple Deconstruction: (int, int, double) für strukturierte Daten
/// Delegation an ElementStatistics.GetStatistics()
/// Fallback: (0, 0, 0.0) bei null-Statistics
/// </summary>
public (int totalElements, int uniqueSeries, double avgWeight) GetStatistics() => _statistics?.GetStatistics() ?? (0, 0, 0.0);
/// <summary>
/// Findet das leichteste und schwerste Element
/// Tuple mit nullable Elementen: (Element?, Element?)
/// Delegation an ElementStatistics.GetWeightExtremes()
/// Fallback: (null, null) bei null-Statistics
/// </summary>
public (Element? lightest, Element? heaviest) GetWeightExtremes() => _statistics?.GetWeightExtremes() ?? (null, null);
/// <summary>
/// Extrahiert grundlegende Informationen eines Elements
/// Tuple mit gemischten Typen: (string, string, double)
/// Delegation an ElementStatistics.GetElementInfo()
/// Fallback: ("?", "Unknown", 0.0) bei null-Statistics
/// </summary>
public (string symbol, string name, double weight) GetElementInfo(int atomicNumber) => _statistics?.GetElementInfo(atomicNumber) ?? ("?", "Unknown", 0.0);
/// <summary>
/// Ermittelt die Position eines Elements im Periodensystem
/// Tuple mit Koordinaten: (int row, int column)
/// Delegation an ElementStatistics.GetElementPosition()
/// Fallback: (-1, -1) für "nicht gefunden" bei null-Statistics
/// </summary>
public (int row, int column) GetElementPosition(int atomicNumber) => _statistics?.GetElementPosition(atomicNumber) ?? (-1, -1);
// ===== ELEMENT-SUCHFUNKTIONEN =====
/// <summary>
/// Sucht ein Element anhand seiner Ordnungszahl (Atomzahl)
///
/// ZWECK:
/// - Eindeutige Identifizierung von Elementen
/// - Ordnungszahl ist der Primärschlüssel im Periodensystem
///
/// C# KONZEPTE:
/// - LINQ FirstOrDefault(): Gibt erstes Element oder null zurück
/// - Lambda Expression: e => e.AtomicNumber == atomicNumber
/// - Nullable Return Type: Element? kann null sein
///
/// PARAMETER:
/// - atomicNumber: Ordnungszahl des gesuchten Elements (1-118)
///
/// RÜCKGABE:
/// - Element-Objekt wenn gefunden, null wenn nicht vorhanden
///
/// VERWENDUNG:
/// var hydrogen = controller.GetElementByAtomicNumber(1);
/// if (hydrogen != null) { /* Element verwenden */ }
/// </summary> /// </summary>
/// <param name="atomicNumber">Atomnummer des gesuchten Elements</param>
/// <returns>Element oder null wenn nicht gefunden</returns>
public Element? GetElementByAtomicNumber(int atomicNumber) public Element? GetElementByAtomicNumber(int atomicNumber)
{ {
if (atomicNumber <= 0)
{
Logger.Log($"Ungültige Atomnummer: {atomicNumber}");
return null;
}
return _elements.FirstOrDefault(e => e.AtomicNumber == atomicNumber); return _elements.FirstOrDefault(e => e.AtomicNumber == atomicNumber);
} }
/// <summary> /// <summary>
/// Sucht ein Element nach Symbol /// Sucht ein Element anhand seines chemischen Symbols
///
/// ZWECK:
/// - Benutzerfreundliche Suche mit bekannten Symbolen (H, He, Li, etc.)
/// - Case-insensitive Suche für bessere Usability
///
/// C# KONZEPTE:
/// - String.IsNullOrWhiteSpace(): Robuste Null/Empty-Prüfung
/// - StringComparison.OrdinalIgnoreCase: Ignoriert Groß-/Kleinschreibung
/// - Early Return Pattern: Frühzeitiger Ausstieg bei ungültigen Eingaben
/// - Guard Clauses: Validierung am Methodenstart
///
/// PARAMETER:
/// - symbol: Chemisches Symbol (z.B. "H", "he", "LI")
///
/// RÜCKGABE:
/// - Element-Objekt wenn gefunden, null bei ungültigem Symbol oder nicht gefunden
///
/// FEHLERBEHANDLUNG:
/// - Null/Empty/Whitespace-Eingaben werden sicher behandelt
/// - Keine Exception bei ungültigen Eingaben
/// </summary> /// </summary>
/// <param name="symbol">Symbol des gesuchten Elements</param>
/// <returns>Element oder null wenn nicht gefunden</returns>
public Element? GetElementBySymbol(string symbol) public Element? GetElementBySymbol(string symbol)
{ {
if (string.IsNullOrWhiteSpace(symbol)) // Guard Clause: Ungültige Eingaben frühzeitig abfangen
{ if (string.IsNullOrWhiteSpace(symbol)) return null;
Logger.Log("Leeres Symbol übergeben");
return null;
}
return _elements.FirstOrDefault(e => // Case-insensitive Suche für bessere Benutzerfreundlichkeit
string.Equals(e.Symbol, symbol, StringComparison.OrdinalIgnoreCase)); return _elements.FirstOrDefault(e => string.Equals(e.Symbol, symbol, StringComparison.OrdinalIgnoreCase));
} }
/// <summary> /// <summary>
/// Filtert Elemente nach Serie /// Filtert alle Elemente einer bestimmten chemischen Serie
///
/// ZWECK:
/// - Gruppierung von Elementen nach chemischen Eigenschaften
/// - Ermöglicht Analyse von Element-Familien (Metalle, Nichtmetalle, etc.)
///
/// C# KONZEPTE:
/// - LINQ Where(): Filtert Elemente basierend auf Bedingung
/// - ToList(): Konvertiert IEnumerable zu List für weitere Operationen
/// - Guard Clause: Schutz vor ungültigen Eingaben
///
/// PARAMETER:
/// - series: Name der chemischen Serie (z.B. "Alkali metal", "Noble gas")
///
/// RÜCKGABE:
/// - Liste aller Elemente der angegebenen Serie
/// - Leere Liste bei ungültiger Serie oder wenn keine Elemente gefunden
///
/// BEISPIEL:
/// var alkaliMetals = controller.GetElementsBySeries("Alkali metal");
/// // Enthält: Li, Na, K, Rb, Cs, Fr
/// </summary> /// </summary>
/// <param name="series">Gewünschte Elementserie</param>
/// <returns>Liste der Elemente der angegebenen Serie</returns>
public List<Element> GetElementsBySeries(string series) public List<Element> GetElementsBySeries(string series)
{ {
if (string.IsNullOrWhiteSpace(series)) // Guard Clause: Ungültige Eingaben behandeln
{ if (string.IsNullOrWhiteSpace(series)) return new List<Element>();
return new List<Element>();
}
return _elements.Where(e => // LINQ-Filterung mit case-insensitive Vergleich
string.Equals(e.Series, series, StringComparison.OrdinalIgnoreCase)) return _elements.Where(e => string.Equals(e.Series, series, StringComparison.OrdinalIgnoreCase)).ToList();
.ToList();
} }
/// <summary> // ===== DATEN-MANAGEMENT =====
/// Validiert Grid-Position eines Elements
/// </summary>
/// <param name="element">Zu validierendes Element</param>
/// <returns>True wenn Position gültig ist</returns>
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;
}
/// <summary> /// <summary>
/// Gibt alle verfügbaren Element-Serien zurück /// Lädt alle Elementdaten neu aus dem Persistence-Layer
/// </summary> ///
/// <returns>Liste aller Serien</returns> /// ZWECK:
public List<string> GetAllSeries() /// - Aktualisierung bei geänderten Datenquellen
{ /// - Recovery nach Datenfehlern
return _elements.Select(e => e.Series) /// - Synchronisation mit externen Datenänderungen
.Distinct() ///
.Where(s => !string.IsNullOrWhiteSpace(s)) /// ABLAUF:
.OrderBy(s => s) /// 1. Benutzer über Neuladen informieren
.ToList(); /// 2. LoadElements() ruft gesamte Initialisierungslogik auf
} /// 3. Helper-Klassen werden mit neuen Daten reinitialisiert
///
/// <summary> /// VERWENDUNG:
/// Überprüft ob alle Elemente korrekt geladen wurden /// - Nach Datenimport
/// </summary> /// - Bei Corruption Recovery
/// <returns>True wenn Daten vollständig sind</returns> /// - Für Entwicklungs-/Debug-Zwecke
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;
}
/// <summary>
/// Lädt Daten neu (für Refresh-Funktionalität)
/// </summary> /// </summary>
public void RefreshData() public void RefreshData()
{ {
Logger.Log("Daten werden neu geladen..."); Logger.Log("Daten werden neu geladen...");
LoadElements(); LoadElements(); // Vollständige Reinitialisierung
} }
/// <summary> /// <summary>
/// Gibt die Anzahl der geladenen Elemente zurück /// Gibt die aktuelle Anzahl geladener Elemente zurück
///
/// ZWECK:
/// - Schnelle Verfügbarkeitsprüfung
/// - UI-Statusanzeigen
/// - Validierung nach Datenoperationen
///
/// C# KONZEPT:
/// - Expression-bodied Member: => Syntax für einzeilige Properties
/// - Count Property: Effiziente Größenabfrage für List&lt;T&gt;
///
/// RÜCKGABE:
/// - Anzahl der verfügbaren Elemente (0 wenn keine geladen)
/// </summary> /// </summary>
/// <returns>Anzahl der Elemente</returns> public int GetElementCount() => _elements.Count;
public int GetElementCount()
{ // ===== NAVIGATION CONTROLLER METHODEN =====
return _elements.Count;
}
/// <summary> /// <summary>
/// Gibt Element-Position als Tuple zurück /// Diese Methoden implementieren den Command Pattern für UI-Aktionen
///
/// ARCHITEKTUR-PRINZIPIEN:
/// - Separation of Concerns: UI-Logik getrennt von Datenlogik
/// - Command Pattern: Jede Benutzeraktion wird als Method-Call gekapselt
/// - Exception Safety: Alle Navigation-Aktionen sind exception-safe
/// - Dependency Injection: NavigationService wird injiziert, nicht direkt instanziiert
///
/// C# KONZEPTE:
/// - Try-Catch Blocks: Robuste Fehlerbehandlung
/// - Null-Conditional Operator (?.): Sichere Methodenaufrufe
/// - Method Chaining: navigationService?.Method()
/// - Consistent Error Handling: Alle Methoden folgen demselben Pattern
///
/// LOGGING:
/// - Alle Aktionen werden geloggt für Debugging
/// - Exceptions werden mit Kontext geloggt
/// - Einheitliche Log-Messages für bessere Nachverfolgung
/// </summary> /// </summary>
/// <param name="atomicNumber">Atomnummer des Elements</param>
/// <returns>Tuple mit (Row, Column) oder (-1, -1) wenn nicht gefunden</returns>
public (int row, int column) GetElementPosition(int atomicNumber)
{
var element = GetElementByAtomicNumber(atomicNumber);
if (element != null)
{
return (element.Row, element.Column);
}
return (-1, -1);
}
/// <summary> /// <summary>
/// Gibt Grundinfos eines Elements als Tuple zurück /// Navigiert zur Hauptansicht des Periodensystems
/// </summary> ///
/// <param name="atomicNumber">Atomnummer</param> /// ZWECK:
/// <returns>Tuple mit (Symbol, Name, Gewicht)</returns> /// - Wechsel zur interaktiven Periodensystem-Darstellung
public (string symbol, string name, double weight) GetElementInfo(int atomicNumber) /// - Hauptfunktion der Anwendung
{ ///
var element = GetElementByAtomicNumber(atomicNumber); /// FEHLERBEHANDLUNG:
if (element != null) /// - Exception wird geloggt, aber nicht weitergegeben
{ /// - Navigation Service kann null sein (optional dependency)
return (element.Symbol, element.ElementName, element.AtomicWeight); /// - Graceful Degradation bei Navigationsfehlern
}
return ("", "Unbekannt", 0.0);
}
/// <summary>
/// Gibt Statistiken als Tuple zurück
/// </summary>
/// <returns>Tuple mit (Anzahl Elemente, Anzahl Serien, Durchschnittsgewicht)</returns>
public (int totalElements, int uniqueSeries, double avgWeight) GetStatistics()
{
var seriesCount = GetAllSeries().Count;
var avgWeight = GetAverageAtomicWeight();
return (_elements.Count, seriesCount, avgWeight);
}
/// <summary>
/// Findet leichtestes und schwerstes Element
/// </summary>
/// <returns>Tuple mit (leichtestes Element, schwerstes Element) oder null-Werte wenn keine Elemente</returns>
public (Element? lightest, Element? heaviest) GetWeightExtremes()
{
if (!_elements.Any())
{
return (null, null);
}
var lightest = _elements[0];
var heaviest = _elements[0];
foreach (var element in _elements)
{
if (element.AtomicWeight < lightest.AtomicWeight)
lightest = element;
if (element.AtomicWeight > heaviest.AtomicWeight)
heaviest = element;
}
return (lightest, heaviest);
}
// ===== NAVIGATION CONTROLLER METHODS (SAUBERES MVC MIT INTERFACE) =====
/// <summary>
/// Behandelt Navigation zum Periodensystem (Controller-Logic)
/// </summary> /// </summary>
public void HandleNavigateToPeriodicTable() public void HandleNavigateToPeriodicTable()
{ {
@ -371,7 +502,12 @@ namespace Project_Periodensystem.Controller
} }
/// <summary> /// <summary>
/// Behandelt Navigation zur About-Seite (Controller-Logic) /// Navigiert zur Informationsseite über die Anwendung
///
/// ZWECK:
/// - Anzeige von Anwendungsinformationen
/// - Credits und Versionsinformationen
/// - Hilfe und Dokumentation
/// </summary> /// </summary>
public void HandleNavigateToAbout() public void HandleNavigateToAbout()
{ {
@ -387,7 +523,12 @@ namespace Project_Periodensystem.Controller
} }
/// <summary> /// <summary>
/// Behandelt Navigation zur Landing Page (Controller-Logic) /// Navigiert zurück zur Startseite/Landing Page
///
/// ZWECK:
/// - Rückkehr zum Hauptmenü
/// - Reset der Anwendung zum Ausgangszustand
/// - Home-Button Funktionalität
/// </summary> /// </summary>
public void HandleNavigateToLanding() public void HandleNavigateToLanding()
{ {
@ -403,7 +544,16 @@ namespace Project_Periodensystem.Controller
} }
/// <summary> /// <summary>
/// Behandelt Theme-Wechsel (Controller-Logic) /// Wechselt zwischen hellen und dunklen UI-Themes
///
/// ZWECK:
/// - Benutzerfreundlichkeit durch Theme-Auswahl
/// - Accessibility (Dunkel-Modus für Augen-schonend)
/// - Moderne UI-Standards erfüllen
///
/// UI-PATTERN:
/// - Toggle-Funktionalität (Hell ↔ Dunkel)
/// - Persistierung des gewählten Themes
/// </summary> /// </summary>
public void HandleToggleTheme() public void HandleToggleTheme()
{ {
@ -419,17 +569,23 @@ namespace Project_Periodensystem.Controller
} }
/// <summary> /// <summary>
/// Behandelt Daten-Export (Controller-Logic) /// Startet den Datenexport-Prozess
///
/// ZWECK:
/// - Export der Elementdaten in verschiedene Formate
/// - Datensicherung und -portabilität
/// - Integration mit anderen Anwendungen
///
/// UI-WORKFLOW:
/// - Zeigt Bestätigungsdialog an
/// - Benutzer wählt Export-Optionen
/// - Export wird durchgeführt und bestätigt
/// </summary> /// </summary>
public void HandleExportData() public void HandleExportData()
{ {
try try
{ {
Logger.Log("Controller: Daten-Export angefordert"); Logger.Log("Controller: Daten-Export angefordert");
// Geschäftslogik für Export
SaveElements(); // Persistierung
_navigationService?.ShowExportConfirmation(); _navigationService?.ShowExportConfirmation();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,47 +1,231 @@
// Definiert einen Namespace ein Container, um Klassen logisch zu gruppieren.
// In diesem Fall gehört die Klasse zum "Projekt_periodensystem.Model"-Namespace.
namespace Project_Periodensystem.Model namespace Project_Periodensystem.Model
{ {
// Definiert eine öffentliche Klasse namens "Element", die ein chemisches Element repräsentiert. /// <summary>
/// Repräsentiert ein chemisches Element im Periodensystem
///
/// ZWECK UND DATENMODELL:
/// - Zentrale Datenstruktur für alle Elementinformationen
/// - Kapselt alle relevanten chemischen Eigenschaften
/// - Bildet die Grundlage für Periodensystem-Darstellung und -Berechnungen
///
/// DESIGN PRINCIPLES:
/// - Data Transfer Object (DTO): Reine Datenklasse ohne Geschäftslogik
/// - Immutable nach Konstruktion: Alle Properties haben nur Setter (könnten readonly sein)
/// - Rich Object: Enthält alle relevanten Eigenschaften in einer Klasse
///
/// C# KONZEPTE:
/// - Auto-Properties: { get; set; } automatische Getter/Setter
/// - Constructor mit mehreren Parametern: Vollständige Initialisierung
/// - Value Types (int, double): Primitive Datentypen für Zahlen
/// - Reference Types (string): Referenzdatentypen für Text
///
/// VERWENDUNG IM PERIODENSYSTEM:
/// - Gespeichert in List&lt;Element&gt; Collections
/// - Sortiert nach AtomicNumber für Periodensystem-Darstellung
/// - Gruppiert nach Series für chemische Klassifizierung
/// - Positioniert nach Row/Column im UI-Grid
/// </summary>
public class Element public class Element
{ {
// Ordnungszahl des Elements (z.B. 1 für Wasserstoff) // ===== IDENTIFIKATIONS-EIGENSCHAFTEN =====
/// <summary>
/// Ordnungszahl (Protonenzahl) des Elements
///
/// CHEMISCHE BEDEUTUNG:
/// - Eindeutige Identifikation jedes Elements (1-118)
/// - Bestimmt Position im Periodensystem
/// - Entspricht der Anzahl Protonen im Atomkern
///
/// BEISPIELE:
/// - 1 = Wasserstoff (H)
/// - 6 = Kohlenstoff (C)
/// - 79 = Gold (Au)
/// </summary>
public int AtomicNumber { get; set; } public int AtomicNumber { get; set; }
// Chemisches Symbol des Elements (z.B. "H" für Wasserstoff) /// <summary>
/// Chemisches Symbol (Kurzbezeichnung) des Elements
///
/// KONVENTIONEN:
/// - 1-2 Buchstaben, erster Buchstabe groß
/// - Oft lateinischen/griechischen Ursprungs
/// - International standardisiert (IUPAC)
///
/// BEISPIELE:
/// - "H" = Hydrogen (Wasserstoff)
/// - "Au" = Aurum (Gold)
/// - "Fe" = Ferrum (Eisen)
/// </summary>
public string Symbol { get; set; } public string Symbol { get; set; }
// Vollständiger Name des Elements (z.B. "Hydrogen") /// <summary>
/// Vollständiger wissenschaftlicher Name des Elements
///
/// NAMENSGEBUNG:
/// - Offizielle IUPAC-Bezeichnung
/// - Meist englische Namen in der internationalen Wissenschaft
/// - Historische, mythologische oder wissenschaftliche Herkunft
///
/// BEISPIELE:
/// - "Hydrogen" (griechisch: Wasser-bildend)
/// - "Californium" (nach Kalifornien benannt)
/// - "Einstein­ium" (nach Albert Einstein)
/// </summary>
public string ElementName { get; set; } public string ElementName { get; set; }
// Atommasse (mittlere Masse eines Atoms in u) // ===== PHYSIKALISCHE EIGENSCHAFTEN =====
/// <summary>
/// Relative Atommasse in atomaren Masseneinheiten (u)
///
/// CHEMISCHE BEDEUTUNG:
/// - Durchschnittliche Masse aller Isotope eines Elements
/// - Gewichtet nach natürlicher Häufigkeit der Isotope
/// - Basis für stöchiometrische Berechnungen
///
/// EINHEIT: u (atomic mass unit)
/// - 1 u ≈ 1.66054 × 10⁻²⁷ kg
/// - Relative Skala (Kohlenstoff-12 = 12.000 u)
///
/// BEISPIELE:
/// - Wasserstoff: ~1.008 u
/// - Kohlenstoff: ~12.011 u
/// - Uran: ~238.029 u
/// </summary>
public double AtomicWeight { get; set; } public double AtomicWeight { get; set; }
// Elektronegativität nach Pauling-Skala (z.B. 2.1) /// <summary>
/// Elektronegativität nach Pauling-Skala
///
/// CHEMISCHE BEDEUTUNG:
/// - Fähigkeit eines Atoms, Bindungselektronen anzuziehen
/// - Bestimmt Art der chemischen Bindung (ionisch vs. kovalent)
/// - Wichtig für Vorhersage chemischer Reaktionen
///
/// PAULING-SKALA:
/// - Dimensionslose Größe von 0.7 bis 4.0
/// - Fluor hat den höchsten Wert (4.0)
/// - Trends: Steigt in Perioden von links nach rechts
///
/// BEISPIELE:
/// - Fluor: 4.0 (höchste Elektronegativität)
/// - Sauerstoff: 3.5
/// - Wasserstoff: 2.1
/// - Cäsium: 0.7 (niedrigste)
/// </summary>
public double Electronegativity { get; set; } public double Electronegativity { get; set; }
// Dichte des Elements in g/cm³ /// <summary>
/// Dichte bei Standardbedingungen in g/cm³
///
/// PHYSIKALISCHE BEDEUTUNG:
/// - Masse pro Volumeneinheit
/// - Abhängig von Atomgröße und Kristallstruktur
/// - Wichtig für technische Anwendungen
///
/// TRENDS IM PERIODENSYSTEM:
/// - Generell zunehmend mit steigender Ordnungszahl
/// - Maximum bei Osmium (~22.6 g/cm³)
/// - Gase haben sehr geringe Dichten
///
/// BEISPIELE:
/// - Wasserstoff (Gas): ~0.0000899 g/cm³
/// - Wasser (Referenz): 1.0 g/cm³
/// - Gold: ~19.3 g/cm³
/// - Osmium: ~22.6 g/cm³ (dichtestes Element)
/// </summary>
public double Density { get; set; } public double Density { get; set; }
// Serie oder Gruppe, zu der das Element gehört (z.B. "Halogen", "Alkalimetall") // ===== KLASSIFIKATIONS-EIGENSCHAFTEN =====
/// <summary>
/// Chemische Serie/Gruppe des Elements
///
/// KLASSIFIKATION:
/// - Gruppiert Elemente nach ähnlichen chemischen Eigenschaften
/// - Basis für Periodensystem-Farben und -Organisation
/// - Ermöglicht Vorhersage chemischen Verhaltens
///
/// WICHTIGE SERIEN:
/// - "Alkali metal": Li, Na, K, Rb, Cs, Fr (sehr reaktiv)
/// - "Noble gas": He, Ne, Ar, Kr, Xe, Rn (inert)
/// - "Halogen": F, Cl, Br, I, At (7 Valenzelektronen)
/// - "Transition metal": Fe, Cu, Au, etc. (d-Block)
/// - "Lanthanoid": Seltene Erden (f-Block)
///
/// UI-VERWENDUNG:
/// - Farbkodierung im Periodensystem
/// - Filterung und Gruppierung
/// - Tooltip-Informationen
/// </summary>
public string Series { get; set; } public string Series { get; set; }
// ===== POSITIONIERUNGS-EIGENSCHAFTEN =====
/// <summary>
/// Zeile (Periode) im Periodensystem
///
/// CHEMISCHE BEDEUTUNG:
/// - Entspricht der Anzahl Elektronenschalen
/// - Bestimmt vertikale Position im Periodensystem
/// - 1-7 für natürliche Elemente
///
/// TRENDS:
/// - Atomradius nimmt innerhalb einer Periode ab
/// - Ionisierungsenergie steigt innerhalb einer Periode
/// </summary>
public int Row { get; set; } public int Row { get; set; }
/// <summary>
/// Spalte (Gruppe) im Periodensystem
///
/// CHEMISCHE BEDEUTUNG:
/// - Entspricht der Anzahl Valenzelektronen (vereinfacht)
/// - Bestimmt horizontale Position im Periodensystem
/// - 1-18 nach IUPAC-Nummerierung
///
/// TRENDS:
/// - Elemente derselben Gruppe haben ähnliche Eigenschaften
/// - Chemische Reaktivität folgt Gruppenmustern
/// </summary>
public int Column { get; set; } public int Column { get; set; }
// Konstruktor: Erzeugt ein neues Element-Objekt mit allen relevanten Eigenschaften. // ===== KONSTRUKTOR =====
/// <summary>
/// Vollständiger Konstruktor für die Erstellung eines Element-Objekts
///
/// ZWECK:
/// - Stellt sicher, dass alle Eigenschaften beim Erstellen gesetzt werden
/// - Verhindert unvollständig initialisierte Element-Objekte
/// - Ermöglicht direkte Erstellung aus Datenquellen (CSV, JSON, Database)
///
/// C# KONZEPTE:
/// - Constructor Overloading: Könnte mehrere Konstruktoren haben
/// - Parameter Validation: Könnte Validierung der Eingabewerte enthalten
/// - Immutable Object: Nach Konstruktion unveränderlich (wenn Properties readonly wären)
///
/// PARAMETER:
/// - Alle 9 Eigenschaften müssen beim Erstellen angegeben werden
/// - Reihenfolge entspricht logischer Gruppierung (ID, Name, Physik, Position)
///
/// VERWENDUNG:
/// var hydrogen = new Element(1, "H", "Hydrogen", 1.008, 2.1, 0.0000899, "Nonmetal", 1, 1);
/// </summary>
public Element(int atomicNumber, string symbol, string elementname, double atomicWeight, public Element(int atomicNumber, string symbol, string elementname, double atomicWeight,
double electronegativity, double density, string series, int row, int column) double electronegativity, double density, string series, int row, int column)
{ {
// Weist den Eigenschaften beim Erzeugen eines Objekts die übergebenen Werte zu // Eigenschafts-Zuweisungen mit Selbst-Dokumentation
AtomicNumber = atomicNumber; AtomicNumber = atomicNumber; // Eindeutige Identifikation
Symbol = symbol; Symbol = symbol; // Kurze chemische Bezeichnung
ElementName = elementname; ElementName = elementname; // Vollständiger Name
AtomicWeight = atomicWeight; AtomicWeight = atomicWeight; // Relative Atommasse
Electronegativity = electronegativity; Electronegativity = electronegativity; // Bindungsverhalten
Density = density; Density = density; // Physikalische Eigenschaft
Series = series; Series = series; // Chemische Klassifikation
Row = row; Row = row; // Periodensystem-Position vertikal
Column = column; Column = column; // Periodensystem-Position horizontal
} }
} }
} }

View File

@ -5,16 +5,68 @@ using System.Linq;
namespace Project_Periodensystem.Model namespace Project_Periodensystem.Model
{ {
/// <summary> /// <summary>
/// Einfacher Logger für Debug-Ausgaben und Fehlerprotokollierung /// Zentrale Logging-Klasse für Debug-Ausgaben und Fehlerprotokollierung
///
/// ZWECK UND ARCHITEKTUR:
/// - Einheitliche Logging-Infrastruktur für die gesamte Anwendung
/// - Dual-Output: Konsole (Development) und Datei (Production/Debugging)
/// - Verschiedene Log-Level für strukturierte Protokollierung
/// - Demonstration verschiedener C#-Konzepte und Datentypen
///
/// DESIGN PATTERNS:
/// - Static Class: Globaler Zugriff ohne Instanziierung
/// - Facade Pattern: Vereinfachte API für komplexe Logging-Operationen
/// - Strategy Pattern: Verschiedene Ausgabekanäle (Konsole, Datei)
///
/// C# KONZEPTE:
/// - Static Constructor: Einmalige Initialisierung beim ersten Zugriff
/// - Exception Handling: Try-Catch für robuste Datei-Operationen
/// - Method Overloading: Verschiedene LogArray-Varianten
/// - Conditional Compilation: [Conditional("DEBUG")] für LogDebug
/// - LINQ: Select() für Datenformatierung
/// - String Interpolation: $"Text {variable}" für lesbare Ausgaben
///
/// SICHERHEIT UND ROBUSTHEIT:
/// - Alle Datei-Operationen sind exception-safe
/// - Logging-Fehler bringen die Hauptanwendung nicht zum Absturz
/// - Graceful Degradation bei fehlenden Dateiberechtigungen
/// </summary> /// </summary>
public static class Logger public static class Logger
{ {
/// <summary>
/// Pfad zur Log-Datei im lokalen Anwendungsverzeichnis
///
/// PFAD-KONSTRUKTION:
/// - Environment.SpecialFolder.LocalApplicationData: Benutzer-spezifischer App-Ordner
/// - Windows: C:\Users\[Username]\AppData\Local\Periodensystem\app.log
/// - Cross-Platform: Funktioniert auf Windows, Linux, macOS
///
/// C# KONZEPTE:
/// - Path.Combine(): Sichere Pfad-Konstruktion (OS-unabhängig)
/// - Environment.SpecialFolder: Standard-Verzeichnisse des Betriebssystems
/// - Readonly Field: Kann nur bei Deklaration oder im static Constructor gesetzt werden
/// </summary>
private static readonly string LogFilePath = Path.Combine( private static readonly string LogFilePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Periodensystem", "app.log"); "Periodensystem", "app.log");
/// <summary> /// <summary>
/// Statischer Initializer - erstellt Log-Verzeichnis /// Statischer Konstruktor - Einmalige Initialisierung der Logger-Infrastruktur
///
/// ZWECK:
/// - Erstellt das Log-Verzeichnis falls es nicht existiert
/// - Wird automatisch beim ersten Zugriff auf die Klasse aufgerufen
/// - Kann nicht explizit aufgerufen werden
///
/// C# KONZEPTE:
/// - Static Constructor: Automatische Initialisierung ohne Parameter
/// - Path.GetDirectoryName(): Extrahiert Verzeichnis aus vollständigem Pfad
/// - Directory.CreateDirectory(): Erstellt Verzeichnis-Hierarchie rekursiv
/// - Exception Handling: Fehler werden "geschluckt" für Robustheit
///
/// FEHLERBEHANDLUNG:
/// - Bei Problemen wird nur Konsolen-Logging verwendet
/// - Keine Exception nach außen, da Logging optional ist
/// </summary> /// </summary>
static Logger() static Logger()
{ {
@ -29,25 +81,52 @@ namespace Project_Periodensystem.Model
catch catch
{ {
// Falls Log-Datei nicht erstellt werden kann, nur Konsole verwenden // Falls Log-Datei nicht erstellt werden kann, nur Konsole verwenden
// Graceful Degradation: Hauptfunktionalität nicht beeinträchtigen
} }
} }
// ===== HAUPTPROTOKOLLIERUNGS-METHODEN =====
/// <summary> /// <summary>
/// Protokolliert eine Nachricht sowohl in Konsole als auch Datei /// Zentrale Logging-Methode - protokolliert Nachrichten dual (Konsole + Datei)
///
/// ZWECK:
/// - Universelle Protokollierung für alle Anwendungsereignisse
/// - Entwickler sehen Ausgaben in Konsole (sofortiges Feedback)
/// - Benutzer/Support kann Logs aus Datei analysieren
///
/// C# KONZEPTE:
/// - String.IsNullOrWhiteSpace(): Robuste Null/Empty/Whitespace-Prüfung
/// - DateTime.Now.ToString(): Formatierte Zeitstempel
/// - String Interpolation: $"[{timestamp}] {message}"
/// - File.AppendAllText(): Atomare Datei-Operation
/// - Environment.NewLine: OS-spezifische Zeilentrenner
///
/// FEHLERBEHANDLUNG:
/// - Guard Clause: Leere Nachrichten werden ignoriert
/// - Try-Catch um Datei-Operation: Konsole funktioniert immer
/// - Keine Exception nach außen: Logging darf nie Hauptanwendung stören
///
/// THREAD-SAFETY:
/// - File.AppendAllText ist thread-safe
/// - Console.WriteLine ist thread-safe
/// - Methode kann von mehreren Threads gleichzeitig aufgerufen werden
/// </summary> /// </summary>
/// <param name="message">Zu protokollierende Nachricht</param> /// <param name="message">Zu protokollierende Nachricht (wird validiert)</param>
public static void Log(string message) public static void Log(string message)
{ {
// Guard Clause: Ungültige Eingaben frühzeitig abfangen
if (string.IsNullOrWhiteSpace(message)) if (string.IsNullOrWhiteSpace(message))
return; return;
// Zeitstempel im ISO-ähnlichen Format für bessere Lesbarkeit
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var logEntry = $"[{timestamp}] {message}"; var logEntry = $"[{timestamp}] {message}";
// Konsolen-Ausgabe (für Debug) // Konsolen-Ausgabe (für Entwicklung und sofortiges Feedback)
Console.WriteLine(logEntry); Console.WriteLine(logEntry);
// Datei-Ausgabe (für Persistenz) // Datei-Ausgabe (für Persistenz und spätere Analyse)
try try
{ {
File.AppendAllText(LogFilePath, logEntry + Environment.NewLine); File.AppendAllText(LogFilePath, logEntry + Environment.NewLine);
@ -55,22 +134,46 @@ namespace Project_Periodensystem.Model
catch catch
{ {
// Fehler beim Schreiben ignorieren um App nicht zum Absturz zu bringen // Fehler beim Schreiben ignorieren um App nicht zum Absturz zu bringen
// Silent Fail: Logging ist nice-to-have, nicht essential
} }
} }
// ===== SPEZIALISIERTE LOGGING-METHODEN =====
/// <summary> /// <summary>
/// Protokolliert eine Exception mit Stack-Trace /// Protokolliert Exceptions mit vollständigen Diagnose-Informationen
///
/// ZWECK:
/// - Strukturierte Fehlerprotokollierung für Debugging
/// - Vollständige Stack-Trace-Information für Fehleranalyse
/// - Kontextuelle Information für bessere Problemlösung
///
/// C# KONZEPTE:
/// - Exception Handling: ex.Message und ex.StackTrace Properties
/// - Null-Check: Schutz vor null-Exceptions
/// - String Interpolation: Eingebettete Variablen in Strings
/// - Conditional Operator: condition ? valueIfTrue : valueIfFalse
/// - Multi-Line Strings: \n für Zeilenumbrüche
///
/// PARAMETER:
/// - ex: Die zu protokollierende Exception (kann theoretisch null sein)
/// - context: Optional - zusätzlicher Kontext (Methodenname, Operation, etc.)
///
/// VERWENDUNG:
/// try { riskyOperation(); } catch (Exception ex) { Logger.LogException(ex, "DataLoad"); }
/// </summary> /// </summary>
/// <param name="ex">Exception die protokolliert werden soll</param> /// <param name="ex">Exception die protokolliert werden soll</param>
/// <param name="context">Zusätzlicher Kontext</param> /// <param name="context">Zusätzlicher Kontext (Methodenname, Operation, etc.)</param>
public static void LogException(Exception ex, string context = "") public static void LogException(Exception ex, string context = "")
{ {
// Null-Schutz: Auch ungültige Exception-Objekte handhaben
if (ex == null) if (ex == null)
{ {
Log("Null-Exception übergeben"); Log("Null-Exception übergeben - möglicher Programmierfehler");
return; return;
} }
// Formatierung je nach verfügbarem Kontext
var message = string.IsNullOrWhiteSpace(context) var message = string.IsNullOrWhiteSpace(context)
? $"EXCEPTION: {ex.Message}\nStack: {ex.StackTrace}" ? $"EXCEPTION: {ex.Message}\nStack: {ex.StackTrace}"
: $"EXCEPTION in {context}: {ex.Message}\nStack: {ex.StackTrace}"; : $"EXCEPTION in {context}: {ex.Message}\nStack: {ex.StackTrace}";
@ -79,35 +182,87 @@ namespace Project_Periodensystem.Model
} }
/// <summary> /// <summary>
/// Protokolliert eine Warnung /// Protokolliert Warnungen - für nicht-kritische Probleme
///
/// ZWECK:
/// - Kennzeichnung von Problemen die behoben werden sollten
/// - Unterscheidung von Informationen und echten Fehlern
/// - Monitoring von potentiellen Problemen
///
/// BEISPIELE:
/// - Veraltete API-Aufrufe
/// - Performance-Probleme
/// - Unerwartete aber handhabbare Situationen
/// </summary> /// </summary>
/// <param name="message">Warnung</param> /// <param name="message">Warnung (wird mit WARNING: Präfix versehen)</param>
public static void LogWarning(string message) public static void LogWarning(string message)
{ {
Log($"WARNING: {message}"); Log($"WARNING: {message}");
} }
/// <summary> /// <summary>
/// Protokolliert einen Fehler /// Protokolliert Fehler - für schwerwiegende Probleme
///
/// ZWECK:
/// - Kennzeichnung von Fehlern die Funktionalität beeinträchtigen
/// - Höhere Priorität als Warnings bei Log-Analyse
/// - Monitoring von kritischen Problemen
///
/// BEISPIELE:
/// - Datenverlust oder Corruption
/// - Netzwerk-/Datenbankfehler
/// - Konfigurationsprobleme
/// </summary> /// </summary>
/// <param name="message">Fehlermeldung</param> /// <param name="message">Fehlermeldung (wird mit ERROR: Präfix versehen)</param>
public static void LogError(string message) public static void LogError(string message)
{ {
Log($"ERROR: {message}"); Log($"ERROR: {message}");
} }
/// <summary> /// <summary>
/// Protokolliert Debug-Informationen (nur in Debug-Build) /// Protokolliert Debug-Informationen - nur in Debug-Builds kompiliert
///
/// ZWECK:
/// - Detaillierte Entwicklungs-Informationen
/// - Automatische Entfernung in Release-Builds
/// - Performance-Optimierung (keine Debug-Logs in Production)
///
/// C# KONZEPTE:
/// - Conditional Compilation: [Conditional("DEBUG")]
/// - Attribute-based Programming: Verhalten durch Metadaten steuern
/// - Build Configuration: Unterschiedliches Verhalten je nach Build-Typ
///
/// COMPILER-VERHALTEN:
/// - Debug Build: Methode wird normal ausgeführt
/// - Release Build: Methoden-Aufrufe werden vollständig entfernt
/// - Zero Performance Impact in Production
/// </summary> /// </summary>
/// <param name="message">Debug-Nachricht</param> /// <param name="message">Debug-Nachricht (wird mit DEBUG: Präfix versehen)</param>
[System.Diagnostics.Conditional("DEBUG")] [System.Diagnostics.Conditional("DEBUG")]
public static void LogDebug(string message) public static void LogDebug(string message)
{ {
Log($"DEBUG: {message}"); Log($"DEBUG: {message}");
} }
// ===== DATEI-MANAGEMENT UND UTILITY-METHODEN =====
/// <summary> /// <summary>
/// Löscht die Log-Datei (für Cleanup) /// Löscht die Log-Datei für Cleanup-Operationen
///
/// ZWECK:
/// - Speicherplatz freigeben bei großen Log-Dateien
/// - Fresh Start für neue Test-Sessions
/// - Maintenance-Operation für Anwendungs-Lifecycle
///
/// C# KONZEPTE:
/// - File.Exists(): Existenz-Prüfung vor Operation
/// - File.Delete(): Atomare Datei-Löschung
/// - Exception Handling: Robust gegen Berechtigungsprobleme
///
/// SICHERHEIT:
/// - Prüft Existenz vor Löschung (vermeidet FileNotFoundException)
/// - Catch für Berechtigungsfehler oder Datei-in-Verwendung
/// - Loggt eigene Operationen (self-documenting)
/// </summary> /// </summary>
public static void ClearLog() public static void ClearLog()
{ {
@ -116,7 +271,7 @@ namespace Project_Periodensystem.Model
if (File.Exists(LogFilePath)) if (File.Exists(LogFilePath))
{ {
File.Delete(LogFilePath); File.Delete(LogFilePath);
Log("Log-Datei gelöscht"); Log("Log-Datei gelöscht und neu initialisiert");
} }
} }
catch (Exception ex) catch (Exception ex)
@ -126,40 +281,97 @@ namespace Project_Periodensystem.Model
} }
/// <summary> /// <summary>
/// Gibt den Pfad zur Log-Datei zurück /// Gibt den vollständigen Pfad zur Log-Datei zurück
///
/// ZWECK:
/// - Ermöglicht externen Tools Zugriff auf Log-Datei
/// - Debugging: Entwickler kann Log-Datei manuell öffnen
/// - Integration: Andere Komponenten können Logs lesen
///
/// KAPSELUNG:
/// - Kontrollierter Zugriff auf private LogFilePath
/// - Readonly-Zugriff: Externe Komponenten können Pfad nicht ändern
/// - Information Hiding: Implementation Details bleiben verborgen
/// </summary> /// </summary>
/// <returns>Vollständiger Pfad zur Log-Datei</returns> /// <returns>Vollständiger Pfad zur Log-Datei (absoluter Pfad)</returns>
public static string GetLogFilePath() public static string GetLogFilePath()
{ {
return LogFilePath; return LogFilePath;
} }
// ===== SPEZIALISIERTE DATENTYP-LOGGING (C# LERNZWECK) =====
/// <summary> /// <summary>
/// Loggt ein Array von Strings /// Loggt String-Arrays - demonstriert Array-Verarbeitung
///
/// ZWECK:
/// - Zeigt Array-Inhalte in lesbarer Form
/// - Demonstriert string.Join() für Array-zu-String-Konvertierung
/// - Nützlich für Listen von Elementsymbolen, Namen, etc.
///
/// C# KONZEPTE:
/// - Method Overloading: Mehrere LogArray-Methoden für verschiedene Typen
/// - string.Join(): Elegante Array-zu-String-Konvertierung
/// - Array-Parameter: string[] als Eingabe
///
/// BEISPIEL-AUSGABE:
/// "Element-Symbole: [H, He, Li, Be, B, C, N, O, F, Ne]"
/// </summary> /// </summary>
/// <param name="title">Titel für das Array</param> /// <param name="title">Beschreibender Titel für das Array</param>
/// <param name="items">Array von Items</param> /// <param name="items">Array von Strings zur Anzeige</param>
public static void LogArray(string title, string[] items) public static void LogArray(string title, string[] items)
{ {
Log($"{title}: [{string.Join(", ", items)}]"); Log($"{title}: [{string.Join(", ", items)}]");
} }
/// <summary> /// <summary>
/// Loggt ein Array von Zahlen /// Loggt Double-Arrays - demonstriert numerische Array-Verarbeitung
///
/// ZWECK:
/// - Zeigt numerische Daten in formatierter Form
/// - Demonstriert LINQ Select() für Datenformatierung
/// - Nützlich für Atomgewichte, Dichten, Elektronegativitäten
///
/// C# KONZEPTE:
/// - LINQ Select(): Funktionale Programmierung für Datentransformation
/// - Lambda Expression: n => n.ToString("F2") für Formatierung
/// - ToString() mit Format-String: "F2" für 2 Dezimalstellen
/// - Method Overloading: Verschiedene Typen, gleicher Methodenname
///
/// BEISPIEL-AUSGABE:
/// "Atomgewichte: [1.01, 4.00, 6.94, 9.01, 10.81]"
/// </summary> /// </summary>
/// <param name="title">Titel für das Array</param> /// <param name="title">Beschreibender Titel für das Array</param>
/// <param name="numbers">Array von Zahlen</param> /// <param name="numbers">Array von Double-Werten zur Anzeige</param>
public static void LogArray(string title, double[] numbers) public static void LogArray(string title, double[] numbers)
{ {
// LINQ Select() wandelt jede Zahl in formatierten String um
var formattedNumbers = numbers.Select(n => n.ToString("F2")).ToArray(); var formattedNumbers = numbers.Select(n => n.ToString("F2")).ToArray();
Log($"{title}: [{string.Join(", ", formattedNumbers)}]"); Log($"{title}: [{string.Join(", ", formattedNumbers)}]");
} }
/// <summary> /// <summary>
/// Loggt Tuple-Informationen /// Loggt Tuple-Informationen - demonstriert strukturierte Datenausgabe
///
/// ZWECK:
/// - Zeigt Tuple-Inhalte in lesbarer Form
/// - Demonstriert strukturierte Datenprotokollierung
/// - Nützlich für Element-Statistiken und zusammengehörige Werte
///
/// C# KONZEPTE:
/// - Tuple: Strukturierte Datencontainer
/// - String als Parameter: Bereits formatierte Tuple-Darstellung
/// - Delegation: Ruft zentrale Log()-Methode auf
///
/// VERWENDUNG:
/// var stats = GetStatistics(); // returns (int, int, double)
/// Logger.LogTuple("Statistiken", $"Elemente: {stats.Item1}, Serien: {stats.Item2}");
///
/// BEISPIEL-AUSGABE:
/// "Element-Info: Symbol: H, Name: Hydrogen, Gewicht: 1.01"
/// </summary> /// </summary>
/// <param name="title">Titel</param> /// <param name="title">Beschreibender Titel für das Tuple</param>
/// <param name="tupleInfo">Tuple als string</param> /// <param name="tupleInfo">Bereits formatierte Tuple-Darstellung als String</param>
public static void LogTuple(string title, string tupleInfo) public static void LogTuple(string title, string tupleInfo)
{ {
Log($"{title}: {tupleInfo}"); Log($"{title}: {tupleInfo}");

View File

@ -7,83 +7,233 @@ using Project_Periodensystem.Model;
namespace Project_Periodensystem.Persistence namespace Project_Periodensystem.Persistence
{ {
/// <summary> /// <summary>
/// Erweiterte Persistenz - Speichern/Laden von JSON-Dateien /// Zentrale Persistenz-Schicht für JSON-basierte Datenspeicherung
///
/// ZWECK UND ARCHITEKTUR:
/// - Abstrahiert Datei-I/O von der Geschäftslogik (Separation of Concerns)
/// - Implementiert Repository Pattern für Element- und Settings-Daten
/// - Bietet Fallback-Mechanismus für robuste Datenversorgung
/// - Demonstriert moderne .NET JSON-Serialisierung
///
/// DESIGN PATTERNS:
/// - Repository Pattern: Zentrale Datenquelle mit einheitlicher API
/// - Static Class: Globaler Zugriff ohne Instanziierung
/// - Fallback Strategy: Graceful Degradation bei fehlenden Dateien
/// - Factory Pattern: Erstellt Default-Objekte wenn nötig
///
/// C# KONZEPTE:
/// - System.Text.Json: Modernes JSON-Framework (.NET Core/5+)
/// - JsonSerializerOptions: Konfiguration der JSON-Ausgabe
/// - Generic Methods: Wiederverwendbare Serialisierung für verschiedene Typen
/// - Exception Handling: Robuste Fehlerbehandlung bei I/O-Operationen
/// - Null-Coalescing (??): Fallback-Werte bei null-Objekten
///
/// DATENSPEICHERUNG:
/// - LocalApplicationData: Benutzer-spezifische, nicht-roaming Daten
/// - JSON Format: Menschenlesbar, plattformunabhängig, einfach zu debuggen
/// - Strukturierte Verzeichnisse: Organisierte Dateiablage
/// </summary> /// </summary>
public static class DataManager public static class DataManager
{ {
// ===== PFAD-KONFIGURATION =====
/// <summary>
/// Basis-Verzeichnis für alle Anwendungsdaten
///
/// PFAD-STRATEGIEN:
/// - LocalApplicationData: Benutzer-spezifisch, lokal (nicht synchronisiert)
/// - Windows: C:\Users\[Username]\AppData\Local\Periodensystem\
/// - Cross-Platform: Funktioniert auf Windows, Linux, macOS
///
/// VORTEILE:
/// - Keine Admin-Rechte erforderlich
/// - Benutzer-spezifische Isolation
/// - Standard-Konformität mit OS-Richtlinien
/// </summary>
private static readonly string DataDirectory = Path.Combine( private static readonly string DataDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Periodensystem"); "Periodensystem");
/// <summary>
/// Vollständiger Pfad zur Element-Datenbasis (JSON)
/// Speichert alle chemischen Elemente mit ihren Eigenschaften
/// </summary>
private static readonly string ElementsFile = Path.Combine(DataDirectory, "elements.json"); private static readonly string ElementsFile = Path.Combine(DataDirectory, "elements.json");
private static readonly string SettingsFile = Path.Combine(DataDirectory, "settings.json");
/// <summary> /// <summary>
/// Speichert Elemente als JSON /// Vollständiger Pfad zu Benutzereinstellungen (JSON)
/// Speichert Präferenzen wie Theme, Sprache, letzte Nutzung
/// </summary> /// </summary>
private static readonly string SettingsFile = Path.Combine(DataDirectory, "settings.json");
// ===== ELEMENT-DATEN PERSISTENZ =====
/// <summary>
/// Speichert eine Element-Liste als JSON-Datei
///
/// ZWECK:
/// - Persistierung von benutzerdefinierten oder bearbeiteten Elementen
/// - Backup von Element-Daten für Wiederherstellung
/// - Export-Funktionalität für Datenübertragung
///
/// C# KONZEPTE:
/// - JsonSerializer.Serialize(): Moderne .NET JSON-Serialisierung
/// - JsonSerializerOptions: Konfiguration der JSON-Ausgabe
/// - WriteIndented: Menschenlesbare Formatierung (Pretty-Print)
/// - Directory.CreateDirectory(): Erstellt Verzeichnis-Hierarchie falls nötig
/// - File.WriteAllText(): Atomare Datei-Schreiboperation
///
/// FEHLERBEHANDLUNG:
/// - Try-Catch um gesamte Operation
/// - Logging aller Erfolgs- und Fehlerfälle
/// - Graceful Degradation: Anwendung funktioniert ohne Persistenz
///
/// JSON-AUSGABE:
/// - Eingerückt und formatiert für manuelle Bearbeitung
/// - UTF-8 Encoding für internationale Zeichen
/// - Standard JSON-Format für Interoperabilität
/// </summary>
/// <param name="elements">Liste der zu speichernden Elemente</param>
public static void SaveElements(List<Element> elements) public static void SaveElements(List<Element> elements)
{ {
try try
{ {
// Verzeichnis erstellen falls nicht vorhanden
Directory.CreateDirectory(DataDirectory); Directory.CreateDirectory(DataDirectory);
// JSON-Serialisierung mit lesbarer Formatierung
var json = JsonSerializer.Serialize(elements, new JsonSerializerOptions { WriteIndented = true }); var json = JsonSerializer.Serialize(elements, new JsonSerializerOptions { WriteIndented = true });
// Atomare Datei-Operation (ersetzt bestehende Datei)
File.WriteAllText(ElementsFile, json); File.WriteAllText(ElementsFile, json);
Logger.Log($"Elemente gespeichert: {elements.Count} Einträge");
Logger.Log($"Elemente erfolgreich gespeichert: {elements.Count} Einträge in {ElementsFile}");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogException(ex, "SaveElements"); Logger.LogException(ex, "SaveElements");
// Keine Weiterleitung der Exception - Speichern ist optional
} }
} }
/// <summary> /// <summary>
/// Lädt Elemente aus JSON oder Fallback zu eingebauten Daten /// Lädt Elemente aus JSON-Datei mit Fallback zu eingebauten Daten
///
/// ZWECK:
/// - Primäre Datenquelle für alle Element-Informationen
/// - Robuste Datenversorgung durch Fallback-Mechanismus
/// - Unterstützung für persistierte Benutzeränderungen
///
/// FALLBACK-STRATEGIE:
/// 1. Versuche JSON-Datei zu laden (benutzerdefinierte Daten)
/// 2. Bei Fehlern: Fallback zu PeriodicTableData.Elements (eingebaute Daten)
/// 3. Garantiert immer eine funktionsfähige Element-Liste
///
/// C# KONZEPTE:
/// - JsonSerializer.Deserialize&lt;T&gt;(): Generische Deserialisierung
/// - File.Exists(): Existenz-Prüfung vor Dateizugriff
/// - Null-Conditional Check: elements != null && elements.Count > 0
/// - Multiple Return Points: Verschiedene Ausstiegspunkte je nach Szenario
///
/// FEHLERBEHANDLUNG:
/// - Alle I/O-Operationen in try-catch
/// - Validierung der deserialisierten Daten
/// - Logging aller Ladeschritte für Debugging
/// - Graceful Fallback ohne Exception-Weiterleitung
/// </summary> /// </summary>
/// <returns>Liste aller verfügbaren chemischen Elemente</returns>
public static List<Element> LoadElements() public static List<Element> LoadElements()
{ {
try try
{ {
// Prüfung ob benutzerdefinierte Daten existieren
if (File.Exists(ElementsFile)) if (File.Exists(ElementsFile))
{ {
var json = File.ReadAllText(ElementsFile); var json = File.ReadAllText(ElementsFile);
var elements = JsonSerializer.Deserialize<List<Element>>(json); var elements = JsonSerializer.Deserialize<List<Element>>(json);
// Validierung der deserialisierten Daten
if (elements != null && elements.Count > 0) if (elements != null && elements.Count > 0)
{ {
Logger.Log($"Elemente aus Datei geladen: {elements.Count}"); Logger.Log($"Elemente aus benutzerdefinierter Datei geladen: {elements.Count} Einträge");
return elements; return elements;
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogException(ex, "LoadElements"); Logger.LogException(ex, "LoadElements - JSON Loading");
// Kein Re-throw - Fallback folgt
} }
// Fallback zu eingebauten Daten // Fallback zu eingebauten, statischen Daten
Logger.Log("Fallback zu eingebauten Element-Daten"); Logger.Log("Fallback zu eingebauten Element-Daten (PeriodicTableData)");
return PeriodicTableData.Elements; return PeriodicTableData.Elements;
} }
// ===== BENUTZEREINSTELLUNGEN PERSISTENZ =====
/// <summary> /// <summary>
/// Speichert Benutzereinstellungen /// Speichert Benutzereinstellungen als JSON-Datei
///
/// ZWECK:
/// - Persistierung von Benutzer-Präferenzen zwischen App-Sessions
/// - Theme-Einstellungen, Sprach-Präferenzen, etc. speichern
/// - Personalisierte Benutzererfahrung ermöglichen
///
/// C# KONZEPTE:
/// - Gleiche JSON-Serialisierung wie bei Elementen
/// - Konsistente Fehlerbehandlung und Logging
/// - Atomare Datei-Operationen für Datenintegrität
///
/// SETTINGS-BEISPIELE:
/// - UI Theme (Light/Dark)
/// - Bevorzugte Sprache
/// - Letzte Nutzung (für Statistiken)
/// - Personalisierte Element-Ansichten
/// </summary> /// </summary>
/// <param name="settings">AppSettings-Objekt mit Benutzer-Präferenzen</param>
public static void SaveSettings(AppSettings settings) public static void SaveSettings(AppSettings settings)
{ {
try try
{ {
// Verzeichnis sicherstellen (gleich wie bei Elementen)
Directory.CreateDirectory(DataDirectory); Directory.CreateDirectory(DataDirectory);
// JSON-Serialisierung mit Pretty-Print
var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true }); var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
// Atomare Speicherung
File.WriteAllText(SettingsFile, json); File.WriteAllText(SettingsFile, json);
Logger.Log($"Benutzereinstellungen gespeichert: Theme={settings.LastTheme}, Sprache={settings.PreferredLanguage}");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogException(ex, "SaveSettings"); Logger.LogException(ex, "SaveSettings");
// Keine Exception-Weiterleitung - Settings sind optional
} }
} }
/// <summary> /// <summary>
/// Lädt Benutzereinstellungen /// Lädt Benutzereinstellungen aus JSON-Datei
///
/// ZWECK:
/// - Wiederherstellung der Benutzer-Präferenzen beim App-Start
/// - Fallback zu Default-Settings bei fehlender oder korrupter Datei
/// - Konsistente Benutzererfahrung über Sessions hinweg
///
/// FALLBACK-VERHALTEN:
/// - Bei fehlender Datei: Neue AppSettings() mit Defaults
/// - Bei JSON-Fehlern: Neue AppSettings() mit Defaults
/// - Garantiert immer gültige Settings-Objekt
///
/// C# KONZEPTE:
/// - Null-Coalescing Operator (??): settings ?? new AppSettings()
/// - Constructor mit Default-Werten in AppSettings
/// - Robuste Deserialisierung mit Fallback
/// </summary> /// </summary>
/// <returns>AppSettings-Objekt (entweder geladen oder mit Defaults)</returns>
public static AppSettings LoadSettings() public static AppSettings LoadSettings()
{ {
try try
@ -92,25 +242,87 @@ namespace Project_Periodensystem.Persistence
{ {
var json = File.ReadAllText(SettingsFile); var json = File.ReadAllText(SettingsFile);
var settings = JsonSerializer.Deserialize<AppSettings>(json); var settings = JsonSerializer.Deserialize<AppSettings>(json);
return settings ?? new AppSettings();
// Null-Coalescing für sichere Rückgabe
var result = settings ?? new AppSettings();
Logger.Log($"Benutzereinstellungen geladen: Theme={result.LastTheme}");
return result;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogException(ex, "LoadSettings"); Logger.LogException(ex, "LoadSettings");
// Kein Re-throw - Fallback zu Defaults
} }
// Fallback zu Default-Settings
Logger.Log("Fallback zu Standard-Benutzereinstellungen");
return new AppSettings(); return new AppSettings();
} }
} }
/// <summary> /// <summary>
/// Benutzereinstellungen für Persistenz /// Datenmodell für persistierte Benutzereinstellungen
///
/// ZWECK UND STRUKTUR:
/// - Kapselt alle persistierbaren Benutzer-Präferenzen
/// - Einfache Erweiterung um neue Settings möglich
/// - JSON-serialisierbar durch Auto-Properties
/// - Default-Werte für robuste Initialisierung
///
/// DESIGN PRINCIPLES:
/// - Data Transfer Object (DTO): Reine Datenklasse
/// - Default Values: Sinnvolle Startwerte für neue Installationen
/// - Extensibility: Einfache Erweiterung um neue Properties
/// - Immutable nach Konstruktion: Properties könnten readonly sein
///
/// C# KONZEPTE:
/// - Auto-Properties mit Default-Initialisierung
/// - DateTime für Zeitstempel
/// - String-Properties für einfache Serialisierung
/// - Parameterloser Constructor (implizit für JSON-Deserialisierung)
///
/// JSON-SERIALISIERUNG:
/// - Alle Properties werden automatisch serialisiert
/// - Property-Namen werden als JSON-Keys verwendet
/// - Default-Werte sorgen für robuste Deserialisierung
/// </summary> /// </summary>
public class AppSettings public class AppSettings
{ {
/// <summary>
/// Zuletzt verwendetes UI-Theme
///
/// WERTE:
/// - "Light": Helles Theme für Tag-Nutzung
/// - "Dark": Dunkles Theme für augen-schonende Nutzung
/// - "Auto": System-Theme folgen (Windows/macOS)
///
/// DEFAULT: "Dark" - Moderner Standard für Entwicklungs-Tools
/// </summary>
public string LastTheme { get; set; } = "Dark"; public string LastTheme { get; set; } = "Dark";
/// <summary>
/// Zeitstempel der letzten Anwendungsnutzung
///
/// ZWECK:
/// - Nutzungsstatistiken
/// - "Willkommen zurück"-Funktionen
/// - Data Analytics für Verbesserungen
///
/// DEFAULT: DateTime.Now - Aktueller Zeitpunkt bei Erstellung
/// </summary>
public DateTime LastUsed { get; set; } = DateTime.Now; public DateTime LastUsed { get; set; } = DateTime.Now;
/// <summary>
/// Bevorzugte Sprache der Benutzeroberfläche
///
/// WERTE:
/// - "German": Deutsche Lokalisierung
/// - "English": Englische Lokalisierung
/// - "French": Französische Lokalisierung (wenn implementiert)
///
/// DEFAULT: "German" - Da es ein deutsches Lernprojekt ist
/// </summary>
public string PreferredLanguage { get; set; } = "German"; public string PreferredLanguage { get; set; } = "German";
} }
} }

View File

@ -3,31 +3,99 @@ using Project_Periodensystem.Model;
namespace Project_Periodensystem.Persistence namespace Project_Periodensystem.Persistence
{ {
/// <summary>
/// Statische Datenquelle für alle chemischen Elemente des Periodensystems
///
/// ZWECK UND DATENMODELL:
/// - Eingebaute, unveränderliche Datenbasis für das Periodensystem
/// - Fallback-Datenquelle wenn externe JSON-Dateien nicht verfügbar sind
/// - Demonstration großer statischer Datenstrukturen in C#
/// - Vollständige wissenschaftliche Elementdaten für Bildungszwecke
///
/// DATENORGANISATION:
/// - 118 chemische Elemente (vollständiges Periodensystem bis 2023)
/// - Organisiert nach Perioden (Zeilen) für bessere Lesbarkeit
/// - Jedes Element mit 9 Eigenschaften (AtomicNumber bis Column)
/// - Wissenschaftlich korrekte Daten basierend auf IUPAC-Standards
///
/// C# KONZEPTE:
/// - Static Class: Globaler Zugriff ohne Instanziierung
/// - Static Property: Elements als unveränderliche Datenquelle
/// - Collection Initializer: new List&lt;Element&gt; { ... } Syntax
/// - Object Initializer: new Element(...) für jeden Eintrag
/// - Large Data Arrays: Umgang mit umfangreichen Datensätzen
///
/// DESIGN PATTERNS:
/// - Static Factory: Stellt vorgefertigte Element-Sammlung bereit
/// - Immutable Data: Nur Getter, keine Setter für Elements
/// - Reference Data: Unveränderliche Stammdaten
/// - Embedded Resource: Daten sind Teil der kompilierten Assembly
///
/// WISSENSCHAFTLICHE GENAUIGKEIT:
/// - Atomgewichte: Relative Atommassen basierend auf IUPAC 2021
/// - Elektronegativität: Pauling-Skala mit 2 Dezimalstellen
/// - Dichte: g/cm³ bei Standardbedingungen (20°C, 1 atm)
/// - Elementserien: Moderne Klassifikation nach chemischen Eigenschaften
/// - Positionen: Row/Column entsprechen Standard-Periodensystem-Layout
///
/// WARTUNG UND ERWEITERUNG:
/// - Neue Elemente können einfach zur Liste hinzugefügt werden
/// - Datenformat ist konsistent und selbst-dokumentierend
/// - Kommentare gruppieren Elemente nach Perioden für Übersichtlichkeit
/// - Jeder Eintrag folgt dem gleichen Schema für Konsistenz
/// </summary>
public static class PeriodicTableData public static class PeriodicTableData
{ {
/// <summary>
/// Vollständige Liste aller bekannten chemischen Elemente
///
/// DATENSTRUKTUR:
/// - List&lt;Element&gt;: Dynamische Sammlung von Element-Objekten
/// - Static Property: Einmalige Initialisierung beim ersten Zugriff
/// - Read-Only: Nur Getter, keine externe Manipulation möglich
/// - Auto-Property: Compiler generiert backing field automatisch
///
/// INITIALISIERUNG:
/// - Collection Initializer: Elegante Syntax für große Datenmengen
/// - Compile-Time: Alle Daten werden zur Compile-Zeit in Assembly eingebettet
/// - Memory Efficient: Daten werden nur einmal geladen und geteilt
///
/// ZUGRIFF:
/// var elements = PeriodicTableData.Elements;
/// var hydrogen = elements.First(e => e.Symbol == "H");
/// </summary>
public static List<Element> Elements { get; } = new List<Element> public static List<Element> Elements { get; } = new List<Element>
{ {
// Erste Periode // ===== ERSTE PERIODE =====
new Element( 1, "H", "Wasserstoff", 1.008, 2.20, 0.000, "Nichtmetall", 0, 0), // Die einfachsten Elemente: Wasserstoff und Helium
new Element( 2, "He", "Helium", 4.003, 0.00, 0.001, "Edelgas", 0, 17), // Besonderheiten: Nur 1s-Orbitale gefüllt, einzigartige Eigenschaften
// Zweite Periode new Element( 1, "H", "Wasserstoff", 1.008, 2.20, 0.000, "Nichtmetall", 0, 0), // Leichtestes Element, Grundbaustein des Universums
new Element( 3, "Li", "Lithium", 6.940, 0.98, 0.534, "Alkalimetall", 1, 0), new Element( 2, "He", "Helium", 4.003, 0.00, 0.001, "Edelgas", 0, 17), // Edelgas mit vollständiger Schale, chemisch inert
new Element( 4, "Be", "Beryllium", 9.012, 1.57, 1.850, "Erdalkalimetall", 1, 1),
new Element( 5, "B", "Bor", 10.810,2.04, 2.340, "Halbmetall", 1, 12),
new Element( 6, "C", "Kohlenstoff", 12.011,2.55, 2.267, "Nichtmetall", 1, 13),
new Element( 7, "N", "Stickstoff", 14.007,3.04, 0.001, "Nichtmetall", 1, 14),
new Element( 8, "O", "Sauerstoff", 15.999,3.44, 0.001, "Nichtmetall", 1, 15),
new Element( 9, "F", "Fluor", 18.998,3.98, 0.002, "Halogen", 1, 16),
new Element( 10, "Ne", "Neon", 20.180,0.00, 0.001, "Edelgas", 1, 17),
// Dritte Periode // ===== ZWEITE PERIODE =====
new Element( 11, "Na", "Natrium", 22.990,0.93, 0.971, "Alkalimetall", 2, 0), // Elemente mit 2 Elektronenschalen (1s² + 2s/2p)
new Element( 12, "Mg", "Magnesium", 24.305,1.31, 1.738, "Erdalkalimetall", 2, 1), // Wichtige biologische und technische Elemente
new Element( 13, "Al", "Aluminium", 26.982,1.61, 2.698, "Post-Übergangsmetall",2, 12),
new Element( 14, "Si", "Silizium", 28.085,1.90, 2.329, "Halbmetall", 2, 13), new Element( 3, "Li", "Lithium", 6.940, 0.98, 0.534, "Alkalimetall", 1, 0), // Leichtestes Metall, wichtig für Batterien
new Element( 15, "P", "Phosphor", 30.974,2.19, 1.820, "Nichtmetall", 2, 14), new Element( 4, "Be", "Beryllium", 9.012, 1.57, 1.850, "Erdalkalimetall", 1, 1), // Leicht aber giftig, Luft-/Raumfahrt
new Element( 16, "S", "Schwefel", 32.060,2.58, 2.067, "Nichtmetall", 2, 15), new Element( 5, "B", "Bor", 10.810,2.04, 2.340, "Halbmetall", 1, 12), // Grenze zwischen Metall/Nichtmetall
new Element( 6, "C", "Kohlenstoff", 12.011,2.55, 2.267, "Nichtmetall", 1, 13), // Basis allen Lebens, vielfältigste Chemie
new Element( 7, "N", "Stickstoff", 14.007,3.04, 0.001, "Nichtmetall", 1, 14), // 78% der Atmosphäre, wichtig für Proteine
new Element( 8, "O", "Sauerstoff", 15.999,3.44, 0.001, "Nichtmetall", 1, 15), // Lebenswichtig, 21% der Atmosphäre
new Element( 9, "F", "Fluor", 18.998,3.98, 0.002, "Halogen", 1, 16), // Höchste Elektronegativität, sehr reaktiv
new Element( 10, "Ne", "Neon", 20.180,0.00, 0.001, "Edelgas", 1, 17), // Leuchtreklame, chemisch inert
// ===== DRITTE PERIODE =====
// Erste Periode mit d-Orbital-Möglichkeiten
// Viele technisch wichtige Elemente (Aluminium, Silizium)
new Element( 11, "Na", "Natrium", 22.990,0.93, 0.971, "Alkalimetall", 2, 0), // Kochsalz, lebenswichtig für Nerven
new Element( 12, "Mg", "Magnesium", 24.305,1.31, 1.738, "Erdalkalimetall", 2, 1), // Chlorophyll-Zentrum, leichte Legierungen
new Element( 13, "Al", "Aluminium", 26.982,1.61, 2.698, "Post-Übergangsmetall",2, 12), // Häufigstes Metall der Erdkruste
new Element( 14, "Si", "Silizium", 28.085,1.90, 2.329, "Halbmetall", 2, 13), // Basis der Halbleiter-Technologie
new Element( 15, "P", "Phosphor", 30.974,2.19, 1.820, "Nichtmetall", 2, 14), // DNA/RNA, Energie-Übertragung (ATP)
new Element( 16, "S", "Schwefel", 32.060,2.58, 2.067, "Nichtmetall", 2, 15), // Proteine, Vulkanismus
new Element( 17, "Cl", "Chlor", 35.450,3.16, 0.003, "Halogen", 2, 16), new Element( 17, "Cl", "Chlor", 35.450,3.16, 0.003, "Halogen", 2, 16),
new Element( 18, "Ar", "Argon", 39.948,0.00, 0.002, "Edelgas", 2, 17), new Element( 18, "Ar", "Argon", 39.948,0.00, 0.002, "Edelgas", 2, 17),

View File

@ -8,30 +8,126 @@ using Project_Periodensystem.Controller;
namespace Project_Periodensystem.View namespace Project_Periodensystem.View
{ {
/// <summary> /// <summary>
/// Navigation Service Implementation im View-Layer /// Concrete Implementation des Navigation Service für Avalonia UI
/// Implementiert INavigationService Interface vom Controller ///
/// SAUBERE TRENNUNG: Controller definiert WAS, View definiert WIE /// ZWECK UND ARCHITEKTUR:
/// - Implementiert INavigationService Interface aus dem Controller-Layer
/// - Trennt sauber "WAS navigiert wird" (Controller) von "WIE navigiert wird" (View)
/// - Kapselt alle Avalonia-spezifische Navigation-Logik
/// - Ermöglicht verschiedene UI-Frameworks ohne Controller-Änderungen
///
/// DESIGN PATTERNS:
/// - Interface Implementation: Konkrete Umsetzung der abstrakten Navigation
/// - Dependency Injection: MainWindow wird von außen injiziert
/// - Facade Pattern: Vereinfacht komplexe UI-Navigation für Controller
/// - Bridge Pattern: Verbindet Controller (Abstraktion) mit View (Implementation)
///
/// C# KONZEPTE:
/// - Interface Implementation: public class NavigationService : INavigationService
/// - Constructor Injection: MainWindow als Dependency
/// - Null-Conditional Checks: Robuste Parameter-Validierung
/// - Exception Handling: Try-Catch für alle UI-Operationen
/// - Method Delegation: Weiterleitung von Interface-Aufrufen an UI-Code
///
/// AVALONIA-INTEGRATION:
/// - MainWindow.Content: Zentrale Inhaltsbereich für Page-Wechsel
/// - UserControl-basierte Pages: PeriodicTablePage, AboutPage, LandingPage
/// - Theme-Management: Avalonia Application Resources
/// - Application.Current: Globaler Zugriff auf App-Instance
///
/// ZIRKULÄRE ABHÄNGIGKEITEN:
/// - Problem: Controller braucht NavigationService, NavigationService braucht Controller
/// - Lösung: Zweistufige Initialisierung mit SetDataController()
/// - Constructor nimmt MainWindow, SetDataController() löst Zirkularität auf
/// </summary> /// </summary>
public class NavigationService : INavigationService public class NavigationService : INavigationService
{ {
private readonly MainWindow _mainWindow; // ===== PRIVATE FELDER =====
private PeriodensystemController? _dataController;
/// <summary> /// <summary>
/// Konstruktor /// Referenz zum Hauptfenster der Avalonia-Anwendung
///
/// ZWECK:
/// - Zentrale Kontrolle über den Hauptinhaltsbereich
/// - Zugriff auf MainWindow.Content für Page-Wechsel
/// - Window-Eigenschaften (Titel, Größe, etc.) ändern
///
/// C# KONZEPTE:
/// - Readonly Field: Kann nur im Konstruktor gesetzt werden
/// - Reference Type: Hält Verweis auf MainWindow-Objekt
/// - Encapsulation: Private field mit controlled access
/// </summary> /// </summary>
private readonly MainWindow _mainWindow;
/// <summary>
/// Referenz zum Data Controller (für Daten-Zugriff)
///
/// ZWECK:
/// - Navigation-Pages benötigen Zugriff auf Element-Daten
/// - Vermeidung direkter Model/Persistence-Zugriffe aus View
/// - Konsistente Daten-API für alle UI-Komponenten
///
/// ZIRKULÄRE ABHÄNGIGKEIT:
/// - Controller erstellt NavigationService
/// - NavigationService braucht Controller für Daten
/// - Lösung: Nullable field + SetDataController() nach Konstruktion
/// </summary>
private PeriodensystemController? _dataController;
// ===== KONSTRUKTOR UND INITIALISIERUNG =====
/// <summary>
/// Konstruktor mit Dependency Injection des MainWindow
///
/// ZWECK:
/// - Initialisiert Navigation Service mit UI-Kontext
/// - Stellt sicher, dass MainWindow verfügbar ist
/// - Validiert kritische Dependencies
///
/// C# KONZEPTE:
/// - Constructor Dependency Injection: MainWindow als Parameter
/// - ArgumentNullException: Robuste Parameter-Validierung
/// - Null-Coalescing Throw: mainWindow ?? throw new ArgumentNullException()
/// - Self-Documenting Code: Logging der Initialisierung
///
/// PARAMETER-VALIDIERUNG:
/// - Null-Check mit Exception: Verhindert spätere NullReferenceExceptions
/// - Early Fail Principle: Probleme sofort erkennbar
/// - Descriptive Exception: nameof() für präzise Fehlermeldung
/// </summary>
/// <param name="mainWindow">Das Hauptfenster der Avalonia-Anwendung</param>
/// <exception cref="ArgumentNullException">Wenn mainWindow null ist</exception>
public NavigationService(MainWindow mainWindow) public NavigationService(MainWindow mainWindow)
{ {
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
Logger.Log("NavigationService initialisiert - saubere Interface-Trennung"); Logger.Log("NavigationService initialisiert - implementiert Interface-basierte Navigation");
} }
/// <summary> /// <summary>
/// Data Controller setzen (löst zirkuläre Abhängigkeit) /// Setzt den Data Controller nach der Konstruktion (löst zirkuläre Abhängigkeit)
///
/// ZWECK:
/// - Zweistufige Initialisierung zur Auflösung zirkulärer Dependencies
/// - Ermöglicht Controller-Access für datenabhängige Navigation
/// - Trennt UI-Initialisierung von Daten-Initialisierung
///
/// ZIRKULÄRES ABHÄNGIGKEITS-PROBLEM:
/// 1. MainWindow erstellt PeriodensystemController
/// 2. Controller braucht NavigationService (this)
/// 3. NavigationService braucht Controller für Daten
/// 4. Lösung: Controller nach NavigationService-Konstruktion setzen
///
/// C# KONZEPTE:
/// - Two-Phase Construction: Konstruktor + Setter für komplexe Dependencies
/// - Null-Conditional Assignment: _dataController wird aus null zu gültigem Objekt
/// - Method Chaining möglich: SetDataController() könnte 'this' zurückgeben
/// </summary> /// </summary>
/// <param name="dataController">Der PeriodensystemController für Datenzugriff</param>
/// <exception cref="ArgumentNullException">Wenn dataController null ist</exception>
public void SetDataController(PeriodensystemController dataController) public void SetDataController(PeriodensystemController dataController)
{ {
_dataController = dataController ?? throw new ArgumentNullException(nameof(dataController)); _dataController = dataController ?? throw new ArgumentNullException(nameof(dataController));
Logger.Log("DataController in NavigationService gesetzt - zirkuläre Abhängigkeit aufgelöst");
} }
/// <summary> /// <summary>

View File

@ -37,21 +37,13 @@ namespace Project_Periodensystem.View
periodicGrid = this.FindControl<Grid>("PeriodicGrid"); periodicGrid = this.FindControl<Grid>("PeriodicGrid");
} }
/// <summary>
/// Controller setzen (Dependency Injection für MVC)
/// </summary>
public void SetController(PeriodensystemController controller) public void SetController(PeriodensystemController controller)
{ {
_controller = controller ?? throw new ArgumentNullException(nameof(controller)); _controller = controller ?? throw new ArgumentNullException(nameof(controller));
Logger.Log("PeriodicTablePage: Controller gesetzt (MVC-Pattern)"); Logger.Log("PeriodicTablePage: Controller gesetzt (MVC-Pattern)");
// Elemente laden und anzeigen
LoadAndDisplayElements(); LoadAndDisplayElements();
} }
/// <summary>
/// Lädt Elemente über Controller und zeigt sie an
/// </summary>
private void LoadAndDisplayElements() private void LoadAndDisplayElements()
{ {
try try
@ -62,7 +54,6 @@ namespace Project_Periodensystem.View
return; return;
} }
// Daten über Controller laden (nicht direkt!)
var elements = _controller.GetAllElements(); var elements = _controller.GetAllElements();
if (!elements.Any()) if (!elements.Any())
@ -71,7 +62,6 @@ namespace Project_Periodensystem.View
return; return;
} }
// Datenvalidierung über Controller
if (!_controller.ValidateData()) if (!_controller.ValidateData())
{ {
Logger.Log("Datenvalidierung fehlgeschlagen"); Logger.Log("Datenvalidierung fehlgeschlagen");
@ -80,7 +70,6 @@ namespace Project_Periodensystem.View
Logger.Log($"Lade {elements.Count} Elemente in das Grid"); Logger.Log($"Lade {elements.Count} Elemente in das Grid");
// UI-Update für jedes Element
int successCount = 0; int successCount = 0;
foreach (var element in elements) foreach (var element in elements)
{ {
@ -96,8 +85,6 @@ namespace Project_Periodensystem.View
} }
Logger.Log($"{successCount} von {elements.Count} Elementen erfolgreich geladen"); Logger.Log($"{successCount} von {elements.Count} Elementen erfolgreich geladen");
// Legend-Buttons sammeln für Highlighting-Funktionalität
CollectLegendButtons(); CollectLegendButtons();
} }
catch (Exception ex) catch (Exception ex)
@ -106,10 +93,6 @@ namespace Project_Periodensystem.View
} }
} }
/// <summary>
/// Erstellt UI-Button für ein Element (nur View-Logik)
/// </summary>
/// <param name="element">Element für das der Button erstellt wird</param>
private void CreateElementButton(Element element) private void CreateElementButton(Element element)
{ {
if (element == null) if (element == null)
@ -118,15 +101,12 @@ namespace Project_Periodensystem.View
return; return;
} }
// UI-Komponenten erstellen
var button = new Button { Classes = { "ElementTile" } }; var button = new Button { Classes = { "ElementTile" } };
var panel = new StackPanel(); var panel = new StackPanel();
// Button-Referenz für Highlighting speichern button.Tag = element;
button.Tag = element; // Element-Daten im Tag speichern
_elementButtons.Add(button); _elementButtons.Add(button);
// Hintergrundfarbe über Converter setzen
var backgroundColor = new SeriesToColorConverter() var backgroundColor = new SeriesToColorConverter()
.Convert(element.Series, typeof(Brush), null, CultureInfo.InvariantCulture) as Brush; .Convert(element.Series, typeof(Brush), null, CultureInfo.InvariantCulture) as Brush;
@ -135,7 +115,6 @@ namespace Project_Periodensystem.View
button.Background = backgroundColor; button.Background = backgroundColor;
} }
// Text-Elemente erstellen
var symbolText = new TextBlock var symbolText = new TextBlock
{ {
Text = element.Symbol, Text = element.Symbol,
@ -152,12 +131,10 @@ namespace Project_Periodensystem.View
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center
}; };
// Layout zusammenbauen panel.Children.Add(numberText);
panel.Children.Add(numberText); // Number on top panel.Children.Add(symbolText);
panel.Children.Add(symbolText); // Symbol below
button.Content = panel; button.Content = panel;
// Grid-Position setzen (0-basiert)
int gridRow = element.Row; int gridRow = element.Row;
int gridColumn = element.Column; int gridColumn = element.Column;
@ -166,7 +143,6 @@ namespace Project_Periodensystem.View
Grid.SetRow(button, gridRow); Grid.SetRow(button, gridRow);
Grid.SetColumn(button, gridColumn); Grid.SetColumn(button, gridColumn);
// Button zum Grid hinzufügen
if (periodicGrid != null) if (periodicGrid != null)
{ {
periodicGrid.Children.Add(button); periodicGrid.Children.Add(button);