Klausur-Deckblatt: Fortschrittsbalken und Erzeugung im Hintergrund
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Ein Klassensatz dauert spürbar und fror den Dialog ein. GenerateAsync setzt jetzt per Task.Run, RenderBatchToPdf meldet jeden gesetzten Datensatz über ein optionales IProgress<int>. Das abschließende Document.Merge meldet nichts, dort läuft der Balken unbestimmt. Währenddessen sind die Schaltflächen gesperrt und der Dialog bleibt offen. Ein einzelnes Deckblatt wird nicht mehr doppelt gesetzt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
24a30881f3
commit
e4a81e8974
9 files changed
+93
-23
No files matched your search
@@ -31,12 +31,20 @@ public partial class ExamCoverSheetDialogViewModel : ObservableObject
|
||||
/// <summary>Vorbelegt mit dem internen Klausurtitel, hier nur für diesen Druck änderbar — der
|
||||
/// Titel der Klausur bleibt unberührt. Leer gelassen gilt wieder der interne Titel.</summary>
|
||||
[ObservableProperty] private string _coverTitle;
|
||||
[ObservableProperty, NotifyPropertyChangedFor(nameof(CanGenerate))] private bool _isGenerating;
|
||||
[ObservableProperty] private int _progressValue;
|
||||
[ObservableProperty] private int _progressMaximum = 1;
|
||||
/// <summary>Alle Deckblätter sind gesetzt, das Sammel-PDF entsteht — dieser Schritt meldet
|
||||
/// keinen Zwischenstand, der Balken läuft deshalb unbestimmt.</summary>
|
||||
[ObservableProperty] private bool _isMerging;
|
||||
[ObservableProperty] private string _progressText = "";
|
||||
|
||||
public string ExamTitle => _exam.Title;
|
||||
public string ExamLabel => $"{_exam.Title} · {_group.Name} · {_exam.Date:dd.MM.yyyy}";
|
||||
public ObservableCollection<LetterTemplateChoice> Templates { get; } = [];
|
||||
public ObservableCollection<ExamCoverStudentChoice> Students { get; } = [];
|
||||
public bool HasNoTemplates => Templates.Count == 0;
|
||||
public bool CanGenerate => !HasNoTemplates && !IsGenerating;
|
||||
public string SuggestedFileName => SanitizeFileName($"Deckblätter_{_group.Name}_{_exam.Title}.pdf");
|
||||
|
||||
public ExamCoverSheetDialogViewModel(Exam exam, LearningGroup group, string subjectName,
|
||||
@@ -68,11 +76,12 @@ public partial class ExamCoverSheetDialogViewModel : ObservableObject
|
||||
FillResults = Students.Any(s => s.IsCorrected);
|
||||
}
|
||||
|
||||
/// <summary>Schreibt das Sammel-PDF und meldet den Erfolg.</summary>
|
||||
public bool Generate(string outputPath)
|
||||
/// <summary>Schreibt das Sammel-PDF und meldet den Erfolg. Gesetzt wird im Hintergrund, damit
|
||||
/// der Dialog bei einem Klassensatz bedienbar bleibt und den Fortschritt zeigen kann.</summary>
|
||||
public async Task<bool> GenerateAsync(string outputPath)
|
||||
{
|
||||
StatusMessage = "";
|
||||
if (SelectedTemplate is null) return false;
|
||||
if (SelectedTemplate is null || IsGenerating) return false;
|
||||
var included = Students.Where(s => s.IsIncluded).ToList();
|
||||
if (included.Count == 0) { StatusMessage = "Kein Schüler ausgewählt."; return false; }
|
||||
try
|
||||
@@ -82,8 +91,15 @@ public partial class ExamCoverSheetDialogViewModel : ObservableObject
|
||||
var data = included.Select(s => (ITemplateDataProvider)new LetterDataProvider(
|
||||
ExamCoverPlaceholderBuilder.Build(_exam, _group, _subjectName, s.Model, s.Result, FillResults,
|
||||
_grading, loaded, today, s.Niveau, TeacherAbbreviation, CoverTitle))).ToList();
|
||||
var result = _renderer.RenderBatchToPdf(loaded, data);
|
||||
File.WriteAllBytes(outputPath, result.Pdf);
|
||||
ReportProgress(0, included.Count);
|
||||
IsGenerating = true;
|
||||
var progress = new Progress<int>(done => ReportProgress(done, included.Count));
|
||||
var result = await Task.Run(() =>
|
||||
{
|
||||
var rendered = _renderer.RenderBatchToPdf(loaded, data, progress);
|
||||
File.WriteAllBytes(outputPath, rendered.Pdf);
|
||||
return rendered;
|
||||
});
|
||||
|
||||
var message = $"{included.Count} Deckblätter erzeugt.";
|
||||
var blank = FillResults ? included.Count(s => !s.IsCorrected) : 0;
|
||||
@@ -96,6 +112,17 @@ public partial class ExamCoverSheetDialogViewModel : ObservableObject
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or InvalidDataException or UnauthorizedAccessException or TemplateValidationException)
|
||||
{ StatusMessage = $"Deckblätter konnten nicht erzeugt werden: {ex.Message}"; return false; }
|
||||
finally { IsGenerating = false; }
|
||||
}
|
||||
|
||||
private void ReportProgress(int done, int total)
|
||||
{
|
||||
ProgressMaximum = total;
|
||||
ProgressValue = done;
|
||||
IsMerging = done >= total && total > 1;
|
||||
ProgressText = IsMerging
|
||||
? "Sammel-PDF wird zusammengefügt …"
|
||||
: $"{done} von {total} Deckblättern gesetzt …";
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
|
||||
@@ -49,13 +49,21 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<TextBlock Grid.Row="3" Text="{Binding StatusMessage}" FontSize="12" Margin="0,10,0,0" TextWrapping="Wrap"
|
||||
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<StackPanel Grid.Row="3">
|
||||
<StackPanel Spacing="6" Margin="0,10,0,0" IsVisible="{Binding IsGenerating}">
|
||||
<ProgressBar Minimum="0" Maximum="{Binding ProgressMaximum}" Value="{Binding ProgressValue}"
|
||||
IsIndeterminate="{Binding IsMerging}" Height="6"/>
|
||||
<TextBlock Text="{Binding ProgressText}" FontSize="12" Opacity="0.65"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding StatusMessage}" FontSize="12" Margin="0,10,0,0" TextWrapping="Wrap"
|
||||
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="4" ColumnDefinitions="*,8,*" Margin="0,16,0,0">
|
||||
<Button Grid.Column="0" Content="Schließen" HorizontalAlignment="Stretch" Click="OnClose"/>
|
||||
<Button Grid.Column="0" Content="Schließen" HorizontalAlignment="Stretch" Click="OnClose"
|
||||
IsEnabled="{Binding !IsGenerating}"/>
|
||||
<Button Grid.Column="2" Content="PDF erzeugen…" HorizontalAlignment="Stretch" Click="OnGenerate"
|
||||
IsEnabled="{Binding !HasNoTemplates}"/>
|
||||
IsEnabled="{Binding CanGenerate}"/>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -7,7 +7,13 @@ namespace LehrerApp.Desktop.Views.Groups;
|
||||
|
||||
public partial class ExamCoverSheetDialog : Window
|
||||
{
|
||||
public ExamCoverSheetDialog() => InitializeComponent();
|
||||
public ExamCoverSheetDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
// Solange das PDF entsteht, bleibt der Dialog offen — sonst liefe die Erzeugung ohne
|
||||
// sichtbare Rückmeldung weiter.
|
||||
Closing += (_, e) => e.Cancel = DataContext is ExamCoverSheetDialogViewModel { IsGenerating: true };
|
||||
}
|
||||
|
||||
private async void OnGenerate(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -21,7 +27,7 @@ public partial class ExamCoverSheetDialog : Window
|
||||
});
|
||||
if (file is null) return;
|
||||
var path = file.Path.LocalPath;
|
||||
if (vm.Generate(path))
|
||||
if (await vm.GenerateAsync(path))
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user