QuestPDF-Vorlagensystem und Designer hinzufügen
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-08-30 15:37:22 +02:00
parent a088823b30
commit 2f2761caf8
40 changed files with 2177 additions and 738 deletions
@@ -328,15 +328,15 @@
</ScrollViewer>
</ContentPage>
<!-- Tab: Word-Briefvorlagen (7.1.4 / 11.5) -->
<!-- Tab: portable PDF-Briefvorlagen -->
<ContentPage Header="Briefvorlagen">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="16" MaxWidth="760">
<TextBlock Text="Word-Vorlagen bleiben vollständig in Word gestaltet. Die App befüllt Inhaltssteuerelemente anhand ihres Tags und prüft die Vorlage bereits beim Import."
<TextBlock Text="Portable .lavorlage-Pakete werden beim Import vollständig geprüft und lokal kopiert. Briefe entstehen anschließend als flache, durchsuchbare PDF-Dateien."
FontSize="12" Opacity="0.65" TextWrapping="Wrap"/>
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="12">
<Button Grid.Column="0" Content=" DOCX-Vorlage importieren" Click="OnImportLetterTemplateClick"/>
<Button Grid.Column="0" Content=" .lavorlage importieren" Click="OnImportLetterTemplateClick"/>
<TextBlock Grid.Column="1" Text="{Binding LetterTemplateStatus}" FontSize="12"
VerticalAlignment="Center" TextWrapping="Wrap"
IsVisible="{Binding LetterTemplateStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
@@ -355,7 +355,7 @@
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="14"/>
<TextBlock FontSize="11" Opacity="0.55">
<Run Text="{Binding OriginalFileName}"/><Run Text=" · "/>
<Run Text="{Binding PackageFileName}"/><Run Text=" · "/>
<Run Text="{Binding ValidationSummary}"/>
</TextBlock>
</StackPanel>
@@ -391,19 +391,8 @@
</ItemsControl>
<Separator/>
<TextBlock Text="Unterstützte Tags" FontSize="15" FontWeight="SemiBold"/>
<TextBlock Text="In Word: Entwicklertools → Rich-Text- oder Nur-Text-Inhaltssteuerelement → Eigenschaften → Tag. Der Titel ist frei wählbar; ausgewertet wird der Tag."
<TextBlock Text="Vorlagen werden mit dem separaten LehrerApp Vorlagen-Designer erstellt. Designer und Hauptapp verwenden exakt dieselbe QuestPDF-Renderingbibliothek."
FontSize="12" Opacity="0.65" TextWrapping="Wrap"/>
<ItemsControl ItemsSource="{Binding SupportedLetterPlaceholders}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="services:LetterPlaceholder">
<Grid ColumnDefinitions="190,*" Margin="0,3">
<TextBlock Grid.Column="0" Text="{Binding Tag}" FontFamily="Monospace" FontSize="12"/>
<TextBlock Grid.Column="1" Text="{Binding Description}" FontSize="12" Opacity="0.7"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</ContentPage>
@@ -263,9 +263,9 @@ public partial class SettingsView : UserControl
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Word-Briefvorlage importieren",
Title = "LehrerApp-Briefvorlage importieren",
AllowMultiple = false,
FileTypeFilter = [new FilePickerFileType("Word-Dokumente") { Patterns = ["*.docx"] }],
FileTypeFilter = [new FilePickerFileType("LehrerApp-Vorlagen") { Patterns = ["*.lavorlage"] }],
});
if (files.Count > 0) vm.ImportLetterTemplate(files[0].Path.LocalPath);
}
@@ -273,8 +273,7 @@ public partial class SettingsView : UserControl
private void OnOpenLetterTemplateClick(object? sender, RoutedEventArgs e)
{
if (sender is not Button { Tag: LetterTemplateListItem item }) return;
var service = App.Services.GetRequiredService<LehrerApp.Core.Services.LetterTemplateService>();
var path = service.GetTemplatePath(item.Model);
var path = item.Model.PackagePath;
if (!File.Exists(path)) return;
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
}
@@ -32,7 +32,7 @@
<TextBlock Text="Briefanrede" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding LetterSalutation}"
PlaceholderText="z.B. Sehr geehrte Frau Mustermann,"/>
<TextBlock Text="Wird unverändert in Word-Vorlagen für Letter.Salutation eingesetzt."
<TextBlock Text="Wird unverändert für den Vorlagen-Platzhalter Anrede eingesetzt."
FontSize="11" Opacity="0.55" TextWrapping="Wrap"/>
</StackPanel>
@@ -3,15 +3,15 @@
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Students"
x:Class="LehrerApp.Desktop.Views.Students.CreateLetterDialog"
x:DataType="vm:CreateLetterDialogViewModel"
Title="Word-Brief erstellen" Width="580" Height="650"
Title="PDF-Brief erstellen" Width="580" Height="760"
MinWidth="500" MinHeight="540" CanResize="True"
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="*,Auto" Margin="24,20">
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="15">
<TextBlock Text="Word-Brief erstellen" Classes="dialogtitle"/>
<TextBlock Text="PDF-Brief erstellen" Classes="dialogtitle"/>
<TextBlock FontSize="12" Opacity="0.65" TextWrapping="Wrap"
Text="Die Vorlage wird vor der Erzeugung erneut geprüft. Das Original bleibt unverändert; gespeichert wird eine frei bearbeitbare DOCX-Kopie."/>
Text="Die portable Vorlage und alle Pflichtfelder werden vor der Erzeugung geprüft. Gespeichert wird ein flaches, durchsuchbares PDF."/>
<StackPanel Spacing="4">
<TextBlock Text="Schüler" FontSize="12" Opacity="0.7"/>
@@ -43,6 +43,15 @@
<CalendarDatePicker SelectedDate="{Binding LetterDate}" HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Brieftext" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding LetterText}" AcceptsReturn="True" TextWrapping="Wrap" MinHeight="110"
PlaceholderText="Inhalt des Briefes"/>
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Lehrkraft / Unterschrift" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding TeacherName}" PlaceholderText="Name der Lehrkraft"/>
</StackPanel>
<Border BorderBrush="#D97706" BorderThickness="1" CornerRadius="6" Padding="10"
IsVisible="{Binding HasIssues}">
@@ -68,7 +77,7 @@
<Grid Grid.Row="1" ColumnDefinitions="*,10,*" Margin="0,18,0,0">
<Button Grid.Column="0" Content="Abbrechen" HorizontalAlignment="Stretch" Click="OnCancel"/>
<Button Grid.Column="2" Content="DOCX speichern …" HorizontalAlignment="Stretch"
<Button Grid.Column="2" Content="PDF speichern …" HorizontalAlignment="Stretch"
IsEnabled="{Binding CanGenerate}" Click="OnGenerate"/>
</Grid>
</Grid>
@@ -14,10 +14,10 @@ public partial class CreateLetterDialog : Window
if (DataContext is not CreateLetterDialogViewModel vm) return;
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
Title = "Word-Brief speichern",
Title = "PDF-Brief speichern",
SuggestedFileName = vm.SuggestedFileName,
DefaultExtension = "docx",
FileTypeChoices = [new FilePickerFileType("Word-Dokumente") { Patterns = ["*.docx"] }],
DefaultExtension = "pdf",
FileTypeChoices = [new FilePickerFileType("PDF-Dateien") { Patterns = ["*.pdf"] }],
});
if (file is null || !vm.Generate(file.Path.LocalPath)) return;
Close(file.Path.LocalPath);
@@ -28,7 +28,7 @@
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Top">
<TextBlock Text="{Binding StudentStatus}" VerticalAlignment="Center" Opacity="0.65"/>
<Button Content="Word-Brief" Command="{Binding CreateLetterCommand}"
<Button Content="PDF-Brief" Command="{Binding CreateLetterCommand}"
IsVisible="{Binding !IsEditing}"/>
<Button Content="Bearbeiten" Command="{Binding StartEditCommand}"
IsVisible="{Binding !IsEditing}"/>
@@ -8,6 +8,7 @@ using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels.Students;
using LehrerApp.Desktop.Views.Shared;
using Microsoft.Extensions.DependencyInjection;
using LehrerApp.Templating;
namespace LehrerApp.Desktop.Views.Students;
@@ -57,7 +58,8 @@ public partial class StudentDetailView : UserControl
if (owner is null || DataContext is not StudentDetailViewModel { Student: { } student }) return;
var vm = new CreateLetterDialogViewModel(student,
App.Services.GetRequiredService<LehrerApp.Core.Services.LetterTemplateService>(),
App.Services.GetRequiredService<TemplateStore>(),
App.Services.GetRequiredService<ITemplateRenderer>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGroupRepository>());
var dialog = new CreateLetterDialog { DataContext = vm };