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:
@@ -48,7 +48,7 @@ public partial class ExamGradingDialog : Window
|
|||||||
{
|
{
|
||||||
Header = string.IsNullOrWhiteSpace(task.Title) ? $"Aufg. {task.Nr}" : $"{task.Nr}. {task.Title}",
|
Header = string.IsNullOrWhiteSpace(task.Title) ? $"Aufg. {task.Nr}" : $"{task.Nr}. {task.Title}",
|
||||||
Width = new DataGridLength(1, DataGridLengthUnitType.Star),
|
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}";
|
var cellName = $"Pts_{taskIndex}";
|
||||||
return new FuncDataTemplate<ExamResultRow>((row, _) =>
|
return new FuncDataTemplate<ExamResultRow>((row, _) =>
|
||||||
@@ -102,7 +102,7 @@ public partial class ExamGradingDialog : Window
|
|||||||
};
|
};
|
||||||
|
|
||||||
tb.LostFocus += (_, _) => CommitPointsCell(tb, cell);
|
tb.LostFocus += (_, _) => CommitPointsCell(tb, cell);
|
||||||
tb.KeyDown += (_, e) => HandleCellKeyDown(e, grid, row, cellName);
|
tb.KeyDown += (_, e) => HandlePointsCellKeyDown(e, grid, row, taskIndex, taskCount);
|
||||||
|
|
||||||
return tb;
|
return tb;
|
||||||
});
|
});
|
||||||
@@ -150,14 +150,53 @@ public partial class ExamGradingDialog : Window
|
|||||||
Padding = new Thickness(6, 4),
|
Padding = new Thickness(6, 4),
|
||||||
};
|
};
|
||||||
tb.LostFocus += (_, _) => row.Comment = tb.Text ?? "";
|
tb.LostFocus += (_, _) => row.Comment = tb.Text ?? "";
|
||||||
tb.KeyDown += (_, e) => HandleCellKeyDown(e, grid, row, cellName);
|
tb.KeyDown += (_, e) => HandleCommentCellKeyDown(e, grid, row, cellName);
|
||||||
return tb;
|
return tb;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enter/Pfeil-Hoch/Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile
|
/// Zwei Korrektur-Workflows (Nutzer-Feedback): "Arbeit für Arbeit" (ein Schüler komplett,
|
||||||
/// (Tab funktioniert bereits über die normale Fokus-Reihenfolge).
|
/// dann der nächste) und "Aufgabe für Aufgabe" (eine Aufgabe für alle Schüler, dann die
|
||||||
private void HandleCellKeyDown(KeyEventArgs e, DataGrid grid, object rowItem, string cellName)
|
/// 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;
|
if (e.Key is not (Key.Down or Key.Up or Key.Enter)) return;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@@ -181,5 +220,25 @@ public partial class ExamGradingDialog : Window
|
|||||||
}, DispatcherPriority.Loaded);
|
}, 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();
|
private void OnClose(object? sender, RoutedEventArgs e) => Close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,9 +73,16 @@ pragmatisch über "Abwesend" bei den übrigen Schülern statt über eine eigene
|
|||||||
Note wird live über `GradingService.CalculateGrade()` berechnet.
|
Note wird live über `GradingService.CalculateGrade()` berechnet.
|
||||||
Umgesetzt als eigener Dialog `ExamGradingDialog`, erreichbar über "Punkte eingeben"
|
Umgesetzt als eigener Dialog `ExamGradingDialog`, erreichbar über "Punkte eingeben"
|
||||||
im Klausuren-Tab (Button/Kontextmenü bei ausgewählter Klausur).
|
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/
|
- [x] **1.4.2** Tastaturnavigation unterstützt zwei Korrektur-Workflows (Nutzer-Feedback:
|
||||||
Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile, direkte Zifferneingabe
|
"Arbeit für Arbeit" bei kleinen/leichten Klausuren z.B. Jahrgang 5, sonst eher "Aufgabe für
|
||||||
und halbe Punkte (Komma oder Punkt) erlaubt.
|
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"
|
- [x] **1.4.3** Kennzeichnung "Abwesend" (`ExamResult.Absent`) per Checkbox — Note zeigt "abwesend"
|
||||||
statt einer berechneten Note. Ausschluss aus Statistiken über 1.5 umgesetzt.
|
statt einer berechneten Note. Ausschluss aus Statistiken über 1.5 umgesetzt.
|
||||||
- [x] **1.4.4** Kommentarfeld pro Schüler (`ExamResult.Comment`).
|
- [x] **1.4.4** Kommentarfeld pro Schüler (`ExamResult.Comment`).
|
||||||
|
|||||||
Reference in New Issue
Block a user