[feat:] first project version

This commit is contained in:
Elias Fierke
2024-12-23 23:03:20 +01:00
parent 8c56577de0
commit 54d25ac155
8 changed files with 705 additions and 8 deletions

View File

@@ -7,4 +7,8 @@
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.Resources>
<x:Double x:Key="DatePickerThemeMinWidth">10</x:Double>
<x:Double x:Key="DatePickerThemeMaxWidth">1000</x:Double>
</Application.Resources>
</Application>

7
Backup.cs Normal file
View File

@@ -0,0 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

20
Exam.cs Normal file
View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace PLG_Exam
{
public class Exam
{
public string Name { get; set; } = string.Empty;
public string Vorname { get; set; } = string.Empty;
public DateTime? Datum { get; set; }
public List<ExamTab> Tabs { get; set; } = new();
}
public class ExamTab
{
public string Aufgabennummer { get; set; } = string.Empty;
public string Überschrift { get; set; } = string.Empty;
public string Inhalt { get; set; } = string.Empty;
}
}

View File

@@ -4,6 +4,49 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PLG_Exam.MainWindow"
Title="PLG_Exam">
Welcome to Avalonia!
Title="PLG Exam">
<DockPanel Margin="10">
<!-- Unterer Teil: Dateiverwaltung -->
<Border DockPanel.Dock="Bottom" Background="#232327" Margin="0,10,0,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Center" Spacing="10">
<Button Content="Speichern" Click="OnSaveClick" />
<Button Content="Speichern unter..." Click="OnSaveAsClick" />
<Button Content="Öffnen" Click="OnOpenClick" />
<Button Content="Neu" Click="OnNewClick" />
</StackPanel>
</Border>
<!-- Oberer Teil: Name, Vorname, Datum -->
<Border DockPanel.Dock="Top" Background="#232327" Height="100" Margin="0,0,0,10">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="350" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="5" Spacing="5">
<TextBlock Text="Name" FontWeight="Bold" Foreground="White" />
<TextBox Name="NameField" Height="33"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="5" Spacing="5">
<TextBlock Text="Vorname" FontWeight="Bold" Foreground="White" />
<TextBox Name="VornameField" Height="33"/>
</StackPanel>
<StackPanel Grid.Column="2" Margin="5" Spacing="5">
<TextBlock Text="Datum" HorizontalAlignment="Stretch" FontWeight="Bold" Foreground="White" />
<DatePicker Name="DatumField" Width="340" Height="33" />
</StackPanel>
</Grid>
</Border>
<!-- Mittlerer Teil: Tabansicht -->
<Border Background="#232327" Margin="0,0,0,0">
<DockPanel>
<Button Content="Weitere Aufgabenlösung hinzufügen" DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10,10,10,0" Click="OnAddTabClick" />
<TabControl Name="TabView" Margin="10">
</TabControl>
</DockPanel>
</Border>
</DockPanel>
</Window>

View File

@@ -1,11 +1,515 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using System.Timers;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
namespace PLG_Exam;
public partial class MainWindow : Window
namespace PLG_Exam
{
public MainWindow()
public partial class MainWindow : Window
{
InitializeComponent();
private int _tabCounter = 0;
private bool _isSaved = true;
private string? _currentFilePath;
public static Exam _currentExam = new();
public MainWindow()
{
InitializeComponent();
InitializeBackupTimer();
AddNewTab();
this.KeyDown += OnKeyDown;
}
// Event für "Neuen Tab hinzufügen"
private void OnAddTabClick(object? sender, RoutedEventArgs e)
{
AddNewTab();
}
private async void OnKeyDown(object? sender, Avalonia.Input.KeyEventArgs e)
{
if (e.KeyModifiers.HasFlag(Avalonia.Input.KeyModifiers.Control))
{
switch (e.Key)
{
case Avalonia.Input.Key.S:
if (e.KeyModifiers.HasFlag(Avalonia.Input.KeyModifiers.Shift))
{
// Strg + Shift + S: Speichern Unter
OnSaveAsClick(sender, e);
}
else
{
// Strg + S: Speichern
OnSaveClick(sender, e);
}
break;
case Avalonia.Input.Key.O:
// Strg + O: Öffnen
OnOpenClick(sender, e);
break;
case Avalonia.Input.Key.T:
// Strg + T: Neuer Tab
OnAddTabClick(sender, e);
break;
case Avalonia.Input.Key.N:
// Strg + N: Neu
OnNewClick(sender, e);
break;
case Avalonia.Input.Key.R:
// Strg + R: Abgeben (coming soon)
await OnSubmitClick(sender, e);
break;
}
}
}
private async Task OnSubmitClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
await MessageBox.Show(this, "Abgeben-Funktion wird bald verfügbar sein!", "Abgeben", MessageBoxButton.Ok);
}
private void AddNewTab()
{
_tabCounter++;
var closeButton = new Button
{
Content = "×",
FontSize = 14,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center,
Width = 20,
Height = 20,
Background = new SolidColorBrush(Color.FromRgb(123,35,39)),
Foreground = new SolidColorBrush(Color.FromRgb(0,0,0)),
Padding = new Thickness(0),
Margin = new Thickness(5, 0, 0, 0),
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ToolTip.SetTip(closeButton, "Tab schließen");
var headerStackPanel = new StackPanel
{
Orientation = Avalonia.Layout.Orientation.Horizontal
};
var tabHeader = new TextBlock
{
Text = $"{_tabCounter} - Neu",
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
headerStackPanel.Children.Add(tabHeader);
headerStackPanel.Children.Add(closeButton);
var tabItem = new TabItem
{
Header = headerStackPanel,
Content = CreateTabContent()
};
// Schließen-Event hinzufügen
closeButton.Click += async (sender, e) =>
{
var result = await MessageBox.Show(this, "Diese Aktion schließt die Aufgabe unwiderruflich. Möchten Sie fortfahren?",
"Tab schließen", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
TabView.Items.Remove(tabItem);
_isSaved = false;
}
};
TabView.Items.Add(tabItem);
TabView.SelectedItem = tabItem;
_isSaved = false;
}
// Tab entfernen mit Sicherheitsabfrage
private async void OnRemoveTabClick(object? sender, RoutedEventArgs e)
{
if (TabView.SelectedItem is TabItem selectedTab)
{
var result = await MessageBox.Show(this, "Dieser Tab wird unwiderruflich gelöscht. Möchten Sie fortfahren?",
"Tab löschen", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
TabView.Items.Remove(selectedTab);
_isSaved = false;
}
}
}
// Speichern
private async void OnSaveClick(object? sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(_currentFilePath))
{
await SaveAs();
}
else
{
SaveToFile(_currentFilePath);
}
}
private async void OnSaveAsClick(object? sender, RoutedEventArgs e)
{
await SaveAs();
}
private async Task<bool> SaveAs()
{
var saveDialog = new SaveFileDialog
{
DefaultExtension = "exam",
Filters = { new FileDialogFilter { Name = "Exam files", Extensions = { "exam" } } }
};
var filePath = await saveDialog.ShowAsync(this);
if (!string.IsNullOrEmpty(filePath))
{
SaveToFile(filePath);
return true;
} else {
return false;
}
}
private void SaveToFile(string filePath)
{
// Update Exam-Daten
_currentExam.Name = NameField.Text;
_currentExam.Vorname = VornameField.Text;
_currentExam.Datum = DatumField.SelectedDate?.UtcDateTime;
_currentExam.Tabs = TabView.Items.OfType<TabItem>()
.Select(tab =>
{
var grid = tab.Content as Grid;
if (grid == null) return null;
var aufgabennummer = (grid.Children[0] as Grid)?.Children[0] as TextBox;
var ueberschrift = (grid.Children[0] as Grid)?.Children[1] as TextBox;
var beschreibung = grid.Children[1] as TextBox;
return new ExamTab
{
Aufgabennummer = aufgabennummer?.Text ?? "",
Überschrift = ueberschrift?.Text ?? "",
Inhalt = beschreibung?.Text ?? ""
};
})
.Where(tab => tab != null)
.ToList();
var json = JsonSerializer.Serialize(_currentExam, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(filePath, json);
_currentFilePath = filePath;
_isSaved = true;
}
// Öffnen
private async void OnOpenClick(object? sender, RoutedEventArgs e)
{
var openDialog = new OpenFileDialog
{
AllowMultiple = false,
Filters = { new FileDialogFilter { Name = "Exam files", Extensions = { "exam" } } }
};
var result = await openDialog.ShowAsync(this);
if (result != null && result.Length > 0)
{
LoadFromFile(result[0]);
}
}
private void LoadFromFile(string filePath)
{
var fileContent = File.ReadAllText(filePath);
var exam = JsonSerializer.Deserialize<Exam>(fileContent);
if (exam == null) return;
// Daten wiederherstellen
_currentExam = exam;
NameField.Text = exam.Name;
VornameField.Text = exam.Vorname;
DatumField.SelectedDate = exam.Datum;
TabView.Items.Clear();
_tabCounter = 0;
foreach (var tab in exam.Tabs)
{
_tabCounter++;
var tabItem = new TabItem
{
Header = $"{tab.Aufgabennummer} - {tab.Überschrift}",
Content = CreateTabContent(tab.Aufgabennummer, tab.Überschrift, tab.Inhalt)
};
TabView.Items.Add(tabItem);
}
_currentFilePath = filePath;
_isSaved = true;
}
// Neu
private async void OnNewClick(object? sender, RoutedEventArgs e)
{
if (!_isSaved)
{
var result = await MessageBox.Show(this, "Möchten Sie die aktuellen Änderungen speichern?",
"Nicht gespeicherte Änderungen", MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.Cancel) return;
if (result == MessageBoxResult.Yes) {
if((await SaveAs()) != true){
return;
}
}
}
_currentExam = new Exam();
NameField.Text = "";
VornameField.Text = "";
DatumField.SelectedDate = null;
TabView.Items.Clear();
_tabCounter = 0;
_currentFilePath = null;
_isSaved = true;
}
private Grid CreateTabContent(string? aufgabennummer = null, string? ueberschrift = null, string? beschreibung = null)
{
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(33) });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
var headerGrid = new Grid { Margin = new Thickness(0, 0, 0, 10) };
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(250) });
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var aufgabenNummerTextBox = new TextBox
{
Name = "Aufgabennummer",
Watermark = "Aufgabennummer",
Margin = new Thickness(0,10,0,0),
Text = aufgabennummer ?? ""
};
var ueberschriftTextBox = new TextBox
{
Name = "Überschrift",
Watermark = "Überschrift (optional)",
Margin = new Thickness(10,10,0,0),
Text = ueberschrift ?? ""
};
var beschreibungTextBox = new TextBox
{
Name = "Beschreibung",
AcceptsReturn = true,
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Stretch,
Watermark = "Beschreibung",
Text = beschreibung ?? "",
};
// Event für dynamische Tab-Umbenennung
aufgabenNummerTextBox.KeyUp += (s, e) => UpdateTabHeader(TabView.SelectedItem as TabItem, aufgabenNummerTextBox, ueberschriftTextBox);
ueberschriftTextBox.KeyUp += (s, e) => UpdateTabHeader(TabView.SelectedItem as TabItem, aufgabenNummerTextBox, ueberschriftTextBox);
headerGrid.Children.Add(aufgabenNummerTextBox);
headerGrid.Children.Add(ueberschriftTextBox);
Grid.SetColumn(aufgabenNummerTextBox, 0);
Grid.SetColumn(ueberschriftTextBox, 1);
grid.Children.Add(headerGrid);
grid.Children.Add(beschreibungTextBox);
Grid.SetRow(headerGrid, 0);
Grid.SetRow(beschreibungTextBox, 1);
return grid;
}
private void UpdateTabHeader(TabItem? tab, TextBox aufgabennummer, TextBox ueberschrift)
{
if (tab == null) return;
var aufgabeText = string.IsNullOrWhiteSpace(aufgabennummer.Text) ? "Neu" : aufgabennummer.Text;
var ueberschriftText = string.IsNullOrWhiteSpace(ueberschrift.Text) ? "" : ueberschrift.Text;
tab.Header = string.IsNullOrWhiteSpace(ueberschriftText)
? aufgabeText
: $"{aufgabeText} - {ueberschriftText}";
}
//
// Backups
//
private const int BackupInterval = 20000; // 20 Sekunden in Millisekunden
private DispatcherTimer _backupTimer;
private void InitializeBackupTimer()
{
_backupTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(20)
};
_backupTimer.Tick += (sender, e) => CreateBackup();
_backupTimer.Start();
}
private const int MaxBackupFiles = 5; // Maximal 5 Backups
private readonly string BackupFolderPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"PLG_Exam_Backup");
// Backup erstellen
private void CreateBackup()
{
try
{
// Ordner erstellen, falls nicht vorhanden
if (!Directory.Exists(BackupFolderPath))
{
Directory.CreateDirectory(BackupFolderPath);
}
// Erstelle Dateinamen mit Zeitstempel
var timestamp = DateTime.Now.ToString("yyMMdd_HHmmss");
var backupFileName = Path.Combine(BackupFolderPath, $"{timestamp}_backup.exam");
// Aktuelle Daten in JSON-Format speichern
var jsonData = GetCurrentExamDataAsJson();
File.WriteAllText(backupFileName, jsonData);
// Alte Backups löschen, wenn mehr als MaxBackupFiles vorhanden sind
CleanupOldBackups();
}
catch (Exception ex)
{
Console.WriteLine($"Backup konnte nicht erstellt werden: {ex.Message}");
}
}
// Löscht alte Backups, wenn die maximale Anzahl überschritten wird
private void CleanupOldBackups()
{
var backupFiles = Directory.GetFiles(BackupFolderPath, "*_backup.exam")
.OrderBy(File.GetCreationTime)
.ToList();
while (backupFiles.Count > MaxBackupFiles)
{
var oldestFile = backupFiles.First();
File.Delete(oldestFile);
backupFiles.RemoveAt(0);
}
}
// Löscht alle Backups unwiderruflich (mit Überschreiben)
private void DeleteAllBackups()
{
try
{
if (Directory.Exists(BackupFolderPath))
{
foreach (var file in Directory.GetFiles(BackupFolderPath, "*_backup.exam"))
{
OverwriteAndDeleteFile(file);
}
Directory.Delete(BackupFolderPath, true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Backups konnten nicht gelöscht werden: {ex.Message}");
}
}
// Überschreibt eine Datei mit Zufallsdaten und löscht sie anschließend
private void OverwriteAndDeleteFile(string filePath)
{
try
{
if (!File.Exists(filePath)) return;
var fileLength = new FileInfo(filePath).Length;
// Datei mit Zufallsdaten überschreiben
using (var stream = new FileStream(filePath, FileMode.Open))
{
var randomData = new byte[fileLength];
new Random().NextBytes(randomData);
stream.Write(randomData, 0, randomData.Length);
}
// Datei löschen
File.Delete(filePath);
}
catch (Exception ex)
{
Console.WriteLine($"Datei konnte nicht überschrieben und gelöscht werden: {ex.Message}");
}
}
// Holt die aktuelle Exam-Daten als JSON
private string GetCurrentExamDataAsJson()
{
_currentExam.Name = NameField.Text;
_currentExam.Vorname = VornameField.Text;
_currentExam.Datum = DatumField.SelectedDate?.UtcDateTime;
_currentExam.Tabs = TabView.Items.OfType<TabItem>()
.Select(tab =>
{
var grid = tab.Content as Grid;
if (grid == null) return null;
var aufgabennummer = (grid.Children[0] as Grid)?.Children[0] as TextBox;
var ueberschrift = (grid.Children[0] as Grid)?.Children[1] as TextBox;
var beschreibung = grid.Children[1] as TextBox;
return new ExamTab
{
Aufgabennummer = aufgabennummer?.Text ?? "",
Überschrift = ueberschrift?.Text ?? "",
Inhalt = beschreibung?.Text ?? ""
};
})
.Where(tab => tab != null)
.ToList();
return System.Text.Json.JsonSerializer.Serialize(_currentExam, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
}
}
}

18
MessageBox.axaml Normal file
View File

@@ -0,0 +1,18 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" SizeToContent="WidthAndHeight"
x:Class="PLG_Exam.MessageBox"
Title="MessageBox">
<StackPanel>
<TextBlock Name="Text" Margin="10" TextWrapping="Wrap"/>
<StackPanel HorizontalAlignment="Right" Margin="5" Orientation="Horizontal" Name="Buttons">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="5"/>
</Style>
</StackPanel.Styles>
</StackPanel>
</StackPanel>
</Window>

76
MessageBox.axaml.cs Normal file
View File

@@ -0,0 +1,76 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Threading.Tasks;
namespace PLG_Exam;
public partial class MessageBox : Window
{
public MessageBox()
{
AvaloniaXamlLoader.Load(this);
}
public static Task<MessageBoxResult> Show(Window? parent, string text, string title, MessageBoxButton buttons = MessageBoxButton.Ok)
{
var msgbox = new MessageBox()
{
Title = title
};
var tb = msgbox.FindControl<TextBlock>("Text");
if(tb != null) tb.Text = text;
var buttonPanel = msgbox.FindControl<StackPanel>("Buttons");
var res = MessageBoxResult.Ok;
void AddButton(string caption, MessageBoxResult r, bool def = false)
{
var btn = new Button { Content = caption };
btn.Click += (_, __) =>
{
res = r;
msgbox.Close();
};
if(buttonPanel == null) return;
buttonPanel.Children.Add(btn);
if (def)
res = r;
}
if (buttons == MessageBoxButton.Ok || buttons == MessageBoxButton.OkCancel)
AddButton("Ok", MessageBoxResult.Ok, true);
if (buttons == MessageBoxButton.YesNo || buttons == MessageBoxButton.YesNoCancel)
{
AddButton("Yes", MessageBoxResult.Yes);
AddButton("No", MessageBoxResult.No, true);
}
if (buttons == MessageBoxButton.OkCancel || buttons == MessageBoxButton.YesNoCancel)
AddButton("Cancel", MessageBoxResult.Cancel, true);
var tcs = new TaskCompletionSource<MessageBoxResult>();
msgbox.Closed += delegate { tcs.TrySetResult(res); };
if (parent != null)
msgbox.ShowDialog(parent);
else msgbox.Show();
return tcs.Task;
}
}
public enum MessageBoxButton
{
Ok,
OkCancel,
YesNo,
YesNoCancel
}
public enum MessageBoxResult
{
Ok,
Cancel,
Yes,
No
}

25
PLG-Exam.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PLG-Exam", "PLG-Exam.csproj", "{F120D7C1-1272-40DA-9CBB-0EE2DC779632}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {357C5A9A-CDAF-4061-ABC2-8F768DA586D2}
EndGlobalSection
EndGlobal