Punkteeingabe: Enter und Pfeiltasten unterstützen beide Korrektur-Workflows

Enter läuft jetzt zeilenweise (Arbeit für Arbeit), Pfeil-Hoch/-Runter spaltenweise
mit Umbruch zur Nachbaraufgabe (Aufgabe für Aufgabe) - beides ohne Moduswechsel,
da sich beide Reihenfolgen ohnehin auf dieselbe Tabelle abbilden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 22:25:53 +02:00
co-authored by Claude Sonnet 5
parent 89a6376fb6
commit dbd8777e64
2 changed files with 76 additions and 10 deletions
@@ -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<ExamResultRow>((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<TextBox>()
.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();
}
+10 -3
View File
@@ -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`).