diff --git a/LehrerApp.Desktop/Views/Groups/ExamGradingDialog.axaml.cs b/LehrerApp.Desktop/Views/Groups/ExamGradingDialog.axaml.cs index e50832a..6cddd54 100644 --- a/LehrerApp.Desktop/Views/Groups/ExamGradingDialog.axaml.cs +++ b/LehrerApp.Desktop/Views/Groups/ExamGradingDialog.axaml.cs @@ -48,7 +48,7 @@ public partial class ExamGradingDialog : Window { Header = string.IsNullOrWhiteSpace(task.Title) ? $"Aufg. {task.Nr}" : $"{task.Nr}. {task.Title}", Width = new DataGridLength(1, DataGridLengthUnitType.Star), - CellTemplate = BuildPointsCellTemplate(i, grid), + CellTemplate = BuildPointsCellTemplate(i, grid, vm.Tasks.Count), }); } @@ -74,7 +74,7 @@ public partial class ExamGradingDialog : Window }); } - private IDataTemplate BuildPointsCellTemplate(int taskIndex, DataGrid grid) + private IDataTemplate BuildPointsCellTemplate(int taskIndex, DataGrid grid, int taskCount) { var cellName = $"Pts_{taskIndex}"; return new FuncDataTemplate((row, _) => @@ -102,7 +102,7 @@ public partial class ExamGradingDialog : Window }; tb.LostFocus += (_, _) => CommitPointsCell(tb, cell); - tb.KeyDown += (_, e) => HandleCellKeyDown(e, grid, row, cellName); + tb.KeyDown += (_, e) => HandlePointsCellKeyDown(e, grid, row, taskIndex, taskCount); return tb; }); @@ -150,14 +150,53 @@ public partial class ExamGradingDialog : Window Padding = new Thickness(6, 4), }; tb.LostFocus += (_, _) => row.Comment = tb.Text ?? ""; - tb.KeyDown += (_, e) => HandleCellKeyDown(e, grid, row, cellName); + tb.KeyDown += (_, e) => HandleCommentCellKeyDown(e, grid, row, cellName); return tb; }); } - /// Enter/Pfeil-Hoch/Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile - /// (Tab funktioniert bereits über die normale Fokus-Reihenfolge). - private void HandleCellKeyDown(KeyEventArgs e, DataGrid grid, object rowItem, string cellName) + /// Zwei Korrektur-Workflows (Nutzer-Feedback): "Arbeit für Arbeit" (ein Schüler komplett, + /// dann der nächste) und "Aufgabe für Aufgabe" (eine Aufgabe für alle Schüler, dann die + /// nächste) — beide bewusst ohne Moduswechsel über zwei verschiedene Tasten, weil sich beide + /// Reihenfolgen ohnehin schon auf die Tabelle abbilden (Zeile vs. Spalte). Enter läuft + /// zeilenweise (am Zeilenende zurück zu Aufgabe 1 der nächsten Zeile), Pfeil-hoch/-runter + /// spaltenweise (am Spaltenende weiter zur Nachbaraufgabe) — beides bricht am Tabellenende + /// einfach ab, kein Rundlauf zurück zum Anfang. + private void HandlePointsCellKeyDown(KeyEventArgs e, DataGrid grid, ExamResultRow row, int taskIndex, int taskCount) + { + if (e.Key is not (Key.Down or Key.Up or Key.Enter)) return; + e.Handled = true; + + if (grid.ItemsSource is not IList items) return; + var rowIdx = items.IndexOf(row); + if (rowIdx < 0) return; + + int targetRowIdx; + int targetTaskIndex; + + if (e.Key == Key.Enter) + { + targetTaskIndex = taskIndex + 1; + targetRowIdx = rowIdx; + if (targetTaskIndex >= taskCount) { targetTaskIndex = 0; targetRowIdx = rowIdx + 1; } + } + else + { + targetTaskIndex = taskIndex; + targetRowIdx = e.Key == Key.Up ? rowIdx - 1 : rowIdx + 1; + if (targetRowIdx < 0) { targetRowIdx = items.Count - 1; targetTaskIndex = taskIndex - 1; } + else if (targetRowIdx >= items.Count) { targetRowIdx = 0; targetTaskIndex = taskIndex + 1; } + } + + if (targetTaskIndex < 0 || targetTaskIndex >= taskCount + || targetRowIdx < 0 || targetRowIdx >= items.Count) return; + + FocusPointsCell(grid, items[targetRowIdx]!, targetTaskIndex); + } + + /// Enter/Pfeil-Hoch/Pfeil-Runter springen im Kommentarfeld zur gleichen Spalte in der + /// Nachbarzeile (Tab funktioniert bereits über die normale Fokus-Reihenfolge). + private void HandleCommentCellKeyDown(KeyEventArgs e, DataGrid grid, object rowItem, string cellName) { if (e.Key is not (Key.Down or Key.Up or Key.Enter)) return; e.Handled = true; @@ -181,5 +220,25 @@ public partial class ExamGradingDialog : Window }, DispatcherPriority.Loaded); } + /// Spalte 0 ist "Schüler", danach folgen die Aufgaben-Spalten in Reihenfolge — daher der + /// Offset von 1 beim Auflösen des Spaltenobjekts für ScrollIntoView (muss auch horizontal + /// zur Nachbaraufgabe scrollen, nicht nur vertikal zur Nachbarzeile). + private void FocusPointsCell(DataGrid grid, object targetItem, int targetTaskIndex) + { + var targetColumn = grid.Columns.ElementAtOrDefault(1 + targetTaskIndex); + if (targetColumn is null) return; + grid.ScrollIntoView(targetItem, targetColumn); + + var cellName = $"Pts_{targetTaskIndex}"; + Dispatcher.UIThread.Post(() => + { + var targetTb = grid.GetVisualDescendants().OfType() + .FirstOrDefault(t => t.DataContext == targetItem && t.Name == cellName); + if (targetTb is null) return; + targetTb.Focus(); + targetTb.SelectAll(); + }, DispatcherPriority.Loaded); + } + private void OnClose(object? sender, RoutedEventArgs e) => Close(); } diff --git a/TODO.md b/TODO.md index 4cf5a13..0860e45 100644 --- a/TODO.md +++ b/TODO.md @@ -73,9 +73,16 @@ pragmatisch über "Abwesend" bei den übrigen Schülern statt über eine eigene Note wird live über `GradingService.CalculateGrade()` berechnet. Umgesetzt als eigener Dialog `ExamGradingDialog`, erreichbar über "Punkte eingeben" im Klausuren-Tab (Button/Kontextmenü bei ausgewählter Klausur). -- [x] **1.4.2** Tastaturnavigation: Tab bewegt sich nativ zur nächsten Zelle, Enter/Pfeil-Hoch/ - Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile, direkte Zifferneingabe - und halbe Punkte (Komma oder Punkt) erlaubt. +- [x] **1.4.2** Tastaturnavigation unterstützt zwei Korrektur-Workflows (Nutzer-Feedback: + "Arbeit für Arbeit" bei kleinen/leichten Klausuren z.B. Jahrgang 5, sonst eher "Aufgabe für + Aufgabe") ohne Moduswechsel — beide Reihenfolgen bilden sich auf dieselbe Tabelle ab. + Tab bewegt sich nativ zeilenweise zur nächsten Zelle. Enter läuft ebenfalls zeilenweise + (nächste Aufgabe desselben Schülers, am Zeilenende zurück zu Aufgabe 1 der nächsten Zeile) + — für "Arbeit für Arbeit". Pfeil-Hoch/Pfeil-Runter laufen spaltenweise (gleiche Aufgabe, + nächster/vorheriger Schüler, am Spaltenende weiter zur Nachbaraufgabe, `ScrollIntoView` + scrollt dabei automatisch auch horizontal mit) — für "Aufgabe für Aufgabe". Beides bricht + am Tabellenende einfach ab, kein Rundlauf zurück zum Anfang. Direkte Zifferneingabe und + halbe Punkte (Komma oder Punkt) weiterhin erlaubt. - [x] **1.4.3** Kennzeichnung "Abwesend" (`ExamResult.Absent`) per Checkbox — Note zeigt "abwesend" statt einer berechneten Note. Ausschluss aus Statistiken über 1.5 umgesetzt. - [x] **1.4.4** Kommentarfeld pro Schüler (`ExamResult.Comment`).