Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
198 commits
Select commit Hold shift + click to select a range
bdea756
Fix report-backed FAT analog evidence latency
masarray Sep 6, 2026
4007aeb
Remove per-event FAT evidence disk barriers
masarray Sep 6, 2026
318771d
Guard relay-bench FAT immediate evidence behavior
masarray Sep 6, 2026
74289c3
Keep final FAT evidence durability without per-event fsync
masarray Sep 6, 2026
8e2429f
Preserve FAT capture status contract for immediate report evidence
masarray Sep 6, 2026
db81148
Align FAT V2 regressions with immediate report evidence
masarray Sep 6, 2026
ad2c4b1
Fast-path already-live Engineering FAT signal matching
masarray Sep 6, 2026
a2592af
Guard already-live Engineering FAT fast attach
masarray Sep 6, 2026
992de77
P0: project FAT command live values from shared process image
masarray Sep 6, 2026
c39ec8c
P0: keep FAT command rows stable during live updates
masarray Sep 6, 2026
2fd1426
test: complete relay-bench fast-path fixture
masarray Sep 6, 2026
c1063c7
test: lock stable FAT command live projection
masarray Sep 6, 2026
90c62d7
fix(fat): keep Value1/Value2 rolling with latest process change
masarray Sep 6, 2026
cb79103
fix(fat): advance completed evidence pair on every new condition
masarray Sep 6, 2026
6716fce
test(fat): lock rolling Value1/Value2 regression
masarray Sep 6, 2026
0f20e27
fix(fat): remove preparation progress visual-tree hot loop
masarray Sep 6, 2026
f3a122d
test(fat): lock preparation progress off hot visual loop
masarray Sep 6, 2026
c56a945
fix(fat-report): remove optional download gate wording
masarray Sep 6, 2026
478909f
test(fat-report): lock concise COMTRADE evidence layout
masarray Sep 6, 2026
d89c61c
Fix FAT rolling capture operator override regression
masarray Sep 6, 2026
ef168e6
Align FAT progress regression with P0 responsiveness guard
masarray Sep 6, 2026
38cf760
Defer FAT close journal sealing off UI thread
masarray Sep 6, 2026
ff77630
Keep FAT close responsive while sealing journals
masarray Sep 6, 2026
f89a2c5
Guard responsive FAT close journal sealing
masarray Sep 6, 2026
991ef5b
Fix FAT P0 first-frame canonical templates
masarray Sep 6, 2026
3352a7f
Lock P0 first-frame and SCL connect-only regressions
masarray Sep 6, 2026
1340a0b
Fix FAT close lifecycle compile guard
masarray Sep 6, 2026
484c5a5
Fix deferred FAT seal record count typing
masarray Sep 6, 2026
612e1b5
Test Engineering Play static dataset connect-only path
masarray Sep 6, 2026
31b8c1b
P0: coalesce FAT journal flushes during UI resume
masarray Sep 6, 2026
cb0d411
P0: keep FAT resume and stop responsive
masarray Sep 6, 2026
33aa8b5
P0: cache FAT live projection index
masarray Sep 6, 2026
3e281d7
P0: project realtime IED connection health into FAT cards
masarray Sep 6, 2026
7b73e23
Fix P0 runtime action model import
masarray Sep 6, 2026
1c5f2a3
P0: roll composite FAT Value2 on any phase change
masarray Sep 6, 2026
0698f3c
Test composite FAT Value2 rolling
masarray Sep 6, 2026
b1c0bcb
Lock P0 FAT responsiveness and connection-health contracts
masarray Sep 6, 2026
4e86a61
P0: paint Start Continue busy state before FAT work
masarray Sep 6, 2026
4c07a3c
P0: instrument FAT command latency
masarray Sep 6, 2026
a53cc38
Test Start Continue responsive button path
masarray Sep 6, 2026
728404e
P0: align FAT command bridge regression with timing instrumentation
masarray Sep 6, 2026
4d87db7
P0: suppress stale poll flicker after confirmed control feedback
masarray Sep 6, 2026
520dac9
P0: cover command feedback freshness fence
masarray Sep 6, 2026
fd928d8
P0: tighten freshness fence source contract
masarray Sep 6, 2026
d84bcca
P0: keep stale command verification out of SOE
masarray Sep 6, 2026
38d9b1d
P0: cover freshness fence SOE integrity
masarray Sep 6, 2026
7158412
P0: preserve exact report provenance through command freshness fence
masarray Sep 6, 2026
a1dad1e
P0: lock exact SOE origin through freshness fence
masarray Sep 6, 2026
9eb8d1d
P0: behavior-test command freshness and SOE authority
masarray Sep 6, 2026
e35f420
P0: bind freshness fence to active control transaction
masarray Sep 6, 2026
35734a0
P0: behavior-test active command transaction fence
masarray Sep 6, 2026
f91abd0
P0: lock freshness fence to active command scope
masarray Sep 6, 2026
6d6628e
fix(fat): keep coherent completed transitions PASS across resume
masarray Sep 6, 2026
e6937db
fix(fat): keep current Value2 rolling after snapshot and recapture
masarray Sep 6, 2026
a435743
fix(fat): move evidence journal I/O off WPF hot path
masarray Sep 6, 2026
da7ff2e
fix(fat): remove FAT tooltips and compact actions to one row
masarray Sep 6, 2026
26f88c8
perf(fat): remove progress tooltip work from dispatcher ticks
masarray Sep 6, 2026
ef221e3
fix(fat): wrap full IEC references in report preview
masarray Sep 6, 2026
0307c08
feat(fat): append final tested witnessed approved sign-off page
masarray Sep 6, 2026
8f43baf
fix: accept coherent FAT pairs across source sequence resets
masarray Sep 6, 2026
8070110
fix: make rolling FAT capture decision journal-first
masarray Sep 6, 2026
f6307d3
fix(fat): preserve explicit operator evidence while rolling live pairs
masarray Sep 6, 2026
54424b7
fix(fat): review partial transition after reconnect gap
masarray Sep 6, 2026
36c6241
test(fat): align P0 invariants with compact one-row header
masarray Sep 6, 2026
bf0c52c
test(fat): lock one-row compact header layout
masarray Sep 6, 2026
773ceba
test(fat): verify queued journal writer instead of retired flush shim
masarray Sep 6, 2026
34db374
test(fat): lock nonblocking queued evidence journal path
masarray Sep 6, 2026
bfeb767
fix(fat): scope continuity review to explicit resume gaps
masarray Sep 6, 2026
396996e
test(fat): keep resume continuity regression non-destructive
masarray Sep 6, 2026
4eaa63b
test(fat): promote complete rolling pair journal-first
masarray Sep 6, 2026
618daca
fix(fat): advance rolling pair before current-pair assessment
masarray Sep 6, 2026
849be91
test(fat): align stale recapture review with current-pair semantics
masarray Sep 6, 2026
da16eac
fix(fat): reassert shared process image after control
masarray Sep 6, 2026
32aefab
fix(fat): bypass legacy blocking relay-bench hot paths
masarray Sep 6, 2026
1d24bfc
perf(fat): stop amplifying every evidence notification
masarray Sep 6, 2026
a7ea91b
fix(fat): make report preview a usable FAT record
masarray Sep 6, 2026
7037515
fix(fat): issue report as FAT record with sign-off
masarray Sep 6, 2026
9a00f9d
test(fat): lock physical bench non-blocking hot paths
masarray Sep 6, 2026
86fe907
fix(fat): keep current Value1 Value2 rolling after recapture
masarray Sep 6, 2026
267646f
test(fat): keep latest process pair after operator recapture
masarray Sep 6, 2026
b205c7c
fix(fat): coalesce legacy window notification fanout
masarray Sep 6, 2026
a9793b7
perf(fat): consume Engineering process image instead of raw runtime t…
masarray Sep 6, 2026
5f4e64e
fix(fat): keep LIVE VALUE owned by shared process image
masarray Sep 6, 2026
58fadff
perf(fat): route evidence from shared Engineering process image
masarray Sep 6, 2026
6d1286a
test(fat): lock shared Engineering process image authority
masarray Sep 6, 2026
4ebc86d
test(fat): lock freeze and live-value authority regressions
masarray Sep 6, 2026
69e192c
test(fat): complete operator rolling point fixture
masarray Sep 6, 2026
468e370
fix(fat): do not enqueue evidence for sequence-only churn
masarray Sep 6, 2026
18f9904
fix(fat): forward only changed session projection properties
masarray Sep 6, 2026
801073e
fix(fat): keep shared-live path free of legacy preparation call
masarray Sep 6, 2026
bbe929f
fix(fat): keep completed pair stable while polled candidate settles
masarray Sep 6, 2026
82b23dd
fix(fat): prime shared image before evidence session activation
masarray Sep 6, 2026
0fa8ceb
test(fat): prevent post-start full-image evidence burst
masarray Sep 6, 2026
fda7587
polish(fat-report): simplify customer-facing report text
masarray Sep 6, 2026
36b4c8c
test(fat-report): lock customer-facing report polish
masarray Sep 6, 2026
98500b9
feat(fat-report): unify preview typography
masarray Sep 7, 2026
e6ab04c
feat(fat-report): add Inter-first typography policy
masarray Sep 7, 2026
3bfe845
feat(fat-report): embed unified Inter or Segoe PDF font
masarray Sep 7, 2026
034f080
refactor(fat-report): improve evidence readability and hierarchy
masarray Sep 7, 2026
d87daac
polish(fat-report): align supplemental footer typography
masarray Sep 7, 2026
abb640c
feat(fat-ui): add per-IED session close and Lucide report toolbar
masarray Sep 7, 2026
b8ae3f0
feat(fat-report): add phase-aware signal display names
masarray Sep 7, 2026
031ec9c
feat(fat-report): polish signal labels and micro typography
masarray Sep 7, 2026
4c79bf6
fix(fat-ui): make report polish self-contained
masarray Sep 7, 2026
307b5e7
feat(fat-report): apply customer-facing report polish
masarray Sep 7, 2026
9209a66
feat(fat-report): support arbitrary per-IED report scopes
masarray Sep 7, 2026
f13339d
test(fat-report): lock professional PDF and per-IED behavior
masarray Sep 7, 2026
5d62eaf
fix(fat-ui): reuse existing visual-tree helper
masarray Sep 7, 2026
708753d
fix(fat-report): keep scoped IED collection strongly typed
masarray Sep 7, 2026
79ab48a
test(fat-report): lock Inter with Segoe UI fallback
masarray Sep 7, 2026
4ee46ab
test(fat-report): align executive typography contract
masarray Sep 7, 2026
8d5e52e
fix(fat): keep complete good state pairs PASS across sessions
masarray Sep 7, 2026
8850a12
fix(fat): hide removed signals from active FAT grid
masarray Sep 7, 2026
8bde411
fix(fat-report): use Lucide save and refresh icons
masarray Sep 7, 2026
16e864f
fix(comtrade): make re-download selectable and overwrite via staging
masarray Sep 7, 2026
77ae7b4
Phase B: add per-IED progress identity fingerprints
masarray Sep 7, 2026
6bd32b2
Phase B: restore FAT progress per IED identity
masarray Sep 7, 2026
f8ac6ba
Phase B: regress per-IED FAT progress restore isolation
masarray Sep 7, 2026
a51c0d3
test(fat-report): align preview icon regression with lucide save
masarray Sep 7, 2026
047ccfd
test(fat): align current evidence regressions with reconnect provenan…
masarray Sep 7, 2026
e7c99bc
Phase C: freeze per-IED FAT report snapshots
masarray Sep 7, 2026
eb1143b
Phase C: use immutable report scope for preview and PDF
masarray Sep 7, 2026
075679c
Phase C: regress immutable per-IED report isolation
masarray Sep 7, 2026
d836caf
Phase C: fail closed on ambiguous report IED ownership
masarray Sep 7, 2026
10090b4
test: align IED report scope with immutable snapshot
masarray Sep 7, 2026
fddffac
fix: establish Engineering SCL authority before FAT publish
masarray Sep 7, 2026
b68046d
test: lock Engineering-first SCL append authority
masarray Sep 7, 2026
978e4ee
Fix shared command safety defaults for Engineering and FAT
masarray Sep 7, 2026
1cf676e
Publish FAT live process image before evidence capture
masarray Sep 7, 2026
7e8ecc6
Make removed FAT signals leave the active grid immediately
masarray Sep 7, 2026
df94c23
Make FAT signal names IEC phase-aware without changing identity
masarray Sep 7, 2026
acb6b3b
Make downloaded COMTRADE rows reliably selectable for re-download
masarray Sep 7, 2026
fa72bb6
Detect offline IEDs and reconnect automatically from shared Engineeri…
masarray Sep 7, 2026
c39aa5d
Add multi-select RCB export window for generic SCL
masarray Sep 7, 2026
9b741f5
Export multiple selected RCBs with native IED DataSets
masarray Sep 7, 2026
2bf5993
Lock FAT live-before-evidence ordering regression
masarray Sep 7, 2026
d2698be
Add regression coverage for latest field-reported fixes
masarray Sep 7, 2026
611e35f
Fix shared process regression assertions for deferred route owner
masarray Sep 7, 2026
d482bea
Avoid auto-reconnect after an intentional manual disconnect
masarray Sep 7, 2026
4768b2e
Keep multi-RCB export continuations on the WPF dispatcher
masarray Sep 7, 2026
020e688
P0: use semantic IEC signal names in live grids
masarray Sep 7, 2026
1897576
P0: compact FAT live IED subtitle
masarray Sep 7, 2026
210eed6
P0: detect proven IED loss after ICMP transport errors
masarray Sep 7, 2026
d80f6dd
P0: make downloaded COMTRADE selection single-authority
masarray Sep 7, 2026
d2246de
P0: include downloaded records in COMTRADE select-all
masarray Sep 7, 2026
e17951e
P0: route production RCB button directly to multi-select export
masarray Sep 7, 2026
71c734a
fix: remove invalid FAT row presentation hook
masarray Sep 7, 2026
c95174d
fix: preserve field presentation regressions with semantic names
masarray Sep 7, 2026
0ac7168
fix(fat): never auto-toggle operator test selection
masarray Sep 7, 2026
5bac219
fix(comtrade): make row click and checkbox share one selection authority
masarray Sep 7, 2026
879a6e1
fix(rcb): guarantee production button routes to true multi-select export
masarray Sep 7, 2026
4354ede
Fix production RCB export routing initialization
masarray Sep 7, 2026
4134988
Allow FAT IEC reference column to expand
masarray Sep 7, 2026
61c63b9
Make Engineering semantic signal names deterministic
masarray Sep 7, 2026
4d3d867
Gate FAT evidence behind committed LIVE projection
masarray Sep 7, 2026
6cf42ed
Restore IED Explorer header home navigation
masarray Sep 7, 2026
30d0b77
Lock latest field bench UX contracts
masarray Sep 7, 2026
d854e97
Fix FAT column sizing compile and late apply
masarray Sep 7, 2026
fe4be16
Fix FAT live commit index rebuild compile
masarray Sep 7, 2026
5e9b949
Update shared FAT process image regression contract
masarray Sep 7, 2026
3693cbb
Align latest field regressions with production fixes
masarray Sep 7, 2026
a64db5e
Fix FAT signal column semantic phase labels
masarray Sep 7, 2026
9866f9f
Force production RCB multi-select pointer workflow
masarray Sep 7, 2026
9da75f9
Fix COMTRADE redownload checkbox visual selection
masarray Sep 7, 2026
5f58d2b
Make FAT LIVE mirror immediate before evidence
masarray Sep 7, 2026
7205fe0
Add regression guards for latest relay bench fixes
masarray Sep 7, 2026
1f083cd
Apply FAT semantic signal template after V2 column rebuild
masarray Sep 7, 2026
8d83f3e
Align relay bench semantic regression with final template authority
masarray Sep 7, 2026
9ba5cb7
Fix FAT semantic names from full DO/DA phase context
masarray Sep 7, 2026
4f30e3f
Replace actual FAT V2 SignalName text column with semantic template
masarray Sep 7, 2026
43aa075
Rewire production RCB button instance to multi-select exporter
masarray Sep 7, 2026
4734d77
Remove RCB pointer shim and use standard independent checkboxes
masarray Sep 7, 2026
b7dfca8
Make COMTRADE selected state visibly deterministic after pointer input
masarray Sep 7, 2026
f920b82
Commit FAT live value and evidence from the same runtime snapshot
masarray Sep 7, 2026
5aa7705
Test atomic FAT report-frame live and evidence route
masarray Sep 7, 2026
70bca3f
Cover production FAT column, RCB handler, COMTRADE tick and atomic li…
masarray Sep 7, 2026
8c90ef0
Align latest field regressions with atomic FAT process route
masarray Sep 7, 2026
925e815
Add behavior tests for FAT phase context and independent RCB selection
masarray Sep 7, 2026
725b96f
Keep COMTRADE selected tick visible across row virtualization
masarray Sep 7, 2026
0eeeb90
Fix COMTRADE checkbox visual and pointer authority
masarray Sep 7, 2026
6ee423f
Route production RCB action directly to multi-select export
masarray Sep 7, 2026
9eb0de9
Make Interlock and Sync defaults deterministic
masarray Sep 7, 2026
183f86f
Lock relay-bench production routes in regression tests
masarray Sep 7, 2026
96cc945
Update field round-2 tests for real production fixes
masarray Sep 7, 2026
d6a027c
P0: bind downloaded COMTRADE selection to one visual authority
masarray Sep 7, 2026
a90fa37
P0: keep COMTRADE header and native checkbox state unified
masarray Sep 7, 2026
ef91aec
P0: route COMTRADE row clicks through native selection binding
masarray Sep 7, 2026
5110a72
P0: resolve RCB device before consuming production click
masarray Sep 7, 2026
185e18f
P0: resolve FAT command feedback from exact shared process identity
masarray Sep 7, 2026
d86aecf
P0: keep command feedback resolver type-safe on shared point model
masarray Sep 7, 2026
ba05ab7
P0: initialize Sync defaults before FAT command projection
masarray Sep 7, 2026
affb2de
P0: seed FAT LIVE from settled Engineering process image
masarray Sep 7, 2026
6bd6fbc
test: align field bench guards with hardened RCB and COMTRADE routes
masarray Sep 7, 2026
ba1d376
test: follow current production RCB and re-download authorities
masarray Sep 7, 2026
01d6860
Fix RCB row toggle and remove noisy export confirmation
masarray Sep 7, 2026
5bc8860
Export source-backed multi RCB directly from XML
masarray Sep 7, 2026
6f34144
Bind FAT initial live values to Engineering process image
masarray Sep 7, 2026
1e53941
Use alias-aware Engineering image for FAT initial seed
masarray Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions FaultRecordWindow.DownloadedSelectionBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Threading;

namespace ArIED61850Tester;

/// <summary>
/// Single visual/state authority for downloaded COMTRADE re-download selection.
/// The legacy row model intentionally rejects IsSelected for already-downloaded records,
/// so a notifying proxy owns the native WPF CheckBox while the existing staged-overwrite
/// HashSet remains the transfer authority. No synthetic glyph or manual IsChecked paint is used.
/// </summary>
public partial class FaultRecordWindow
{
private readonly Dictionary<string, DownloadedSelectionProxy> _downloadedSelectionProxies =
new(StringComparer.OrdinalIgnoreCase);
private bool _downloadedSelectionBindingInstalled;

[ModuleInitializer]
internal static void RegisterDownloadedSelectionBindingAuthority()
{
EventManager.RegisterClassHandler(
typeof(FaultRecordWindow),
FrameworkElement.LoadedEvent,
new RoutedEventHandler(DownloadedSelectionBinding_WindowLoaded),
handledEventsToo: true);
}

private static void DownloadedSelectionBinding_WindowLoaded(object sender, RoutedEventArgs e)
{
if (sender is not FaultRecordWindow window || window._downloadedSelectionBindingInstalled)
return;

// Install after the normal window Loaded path has completed. This intentionally
// removes the old manual PreviewMouseDown checkbox toggle and lets WPF TwoWay binding
// own the native check mark from this point forward.
window.Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(window.InstallDownloadedSelectionBindingAuthority));
}

private void InstallDownloadedSelectionBindingAuthority()
{
if (_downloadedSelectionBindingInstalled)
return;

_downloadedSelectionBindingInstalled = true;
FaultRecordsGrid.PreviewMouseLeftButtonDown -= RedownloadGrid_PreviewMouseLeftButtonDown;
FaultRecordsGrid.LoadingRow += DownloadedSelectionBinding_LoadingRow;
Closed += DownloadedSelectionBinding_Closed;

RebindVisibleDownloadedSelectionRows();
}

private void DownloadedSelectionBinding_Closed(object? sender, EventArgs e)
{
FaultRecordsGrid.LoadingRow -= DownloadedSelectionBinding_LoadingRow;
Closed -= DownloadedSelectionBinding_Closed;
_downloadedSelectionProxies.Clear();
}

private void DownloadedSelectionBinding_LoadingRow(object? sender, DataGridRowEventArgs e)
{
// RedownloadUx.LoadingRow was registered first and performs the legacy setup at
// DispatcherPriority.Loaded. Re-apply the authoritative binding one turn later.
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(() => BindDownloadedSelectionRow(e.Row)));
}

private void RebindVisibleDownloadedSelectionRows()
{
FaultRecordsGrid.UpdateLayout();
foreach (var row in Records)
{
if (FaultRecordsGrid.ItemContainerGenerator.ContainerFromItem(row) is DataGridRow gridRow)
BindDownloadedSelectionRow(gridRow);
}
}

private void BindDownloadedSelectionRow(DataGridRow gridRow)
{
if (gridRow.DataContext is not FaultRecordRow row ||
row.LocalState != FaultRecordLocalState.Downloaded)
{
return;
}

var checkBox = FindVisualDescendants<CheckBox>(gridRow).FirstOrDefault();
if (checkBox == null)
return;

var proxy = GetDownloadedSelectionProxy(row);

// Remove every legacy manual checkbox writer. The native CheckBox gets one TwoWay
// binding and therefore its visual tick can no longer diverge from transfer state.
checkBox.Click -= DownloadedRecordCheckBox_Click;
BindingOperations.ClearBinding(checkBox, ToggleButton.IsCheckedProperty);
BindingOperations.ClearBinding(checkBox, UIElement.IsEnabledProperty);
BindingOperations.SetBinding(
checkBox,
ToggleButton.IsCheckedProperty,
new Binding(nameof(DownloadedSelectionProxy.IsSelected))
{
Source = proxy,
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
checkBox.IsEnabled = row.Record.Files.Count > 0 && !IsBusy;
checkBox.ToolTip = "Already downloaded. Check to download again and safely overwrite the existing local copy.";
}

private DownloadedSelectionProxy GetDownloadedSelectionProxy(FaultRecordRow row)
{
var recordId = row.Record.RecordId;
if (_downloadedSelectionProxies.TryGetValue(recordId, out var existing))
return existing;

var proxy = new DownloadedSelectionProxy(
_redownloadSelections.Contains(recordId),
selected => DownloadedSelectionProxy_Changed(row, selected));
_downloadedSelectionProxies[recordId] = proxy;
return proxy;
}

private void DownloadedSelectionProxy_Changed(FaultRecordRow row, bool selected)
{
if (selected)
_redownloadSelections.Add(row.Record.RecordId);
else
_redownloadSelections.Remove(row.Record.RecordId);

UpdateSmartSelectionUi();
RefreshFaultRecordHeaderSelection();
}

private bool IsDownloadedTransferSelected(FaultRecordRow row)
=> _downloadedSelectionProxies.TryGetValue(row.Record.RecordId, out var proxy)
? proxy.IsSelected
: _redownloadSelections.Contains(row.Record.RecordId);

private void SetDownloadedTransferSelection(FaultRecordRow row, bool selected)
{
if (row.LocalState != FaultRecordLocalState.Downloaded)
return;

var proxy = GetDownloadedSelectionProxy(row);
proxy.IsSelected = selected;

// If the state was already equal, the proxy intentionally emits no notification;
// still synchronize the backing set because scan/local-state transitions can rebuild it.
if (selected)
_redownloadSelections.Add(row.Record.RecordId);
else
_redownloadSelections.Remove(row.Record.RecordId);

Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(() =>
{
ConfigureRecordRow(row); // preserve legacy enabled/tooltip semantics
if (FaultRecordsGrid.ItemContainerGenerator.ContainerFromItem(row) is DataGridRow gridRow)
BindDownloadedSelectionRow(gridRow); // then restore the one authoritative binding
UpdateSmartSelectionUi();
RefreshFaultRecordHeaderSelection();
}));
}

private sealed class DownloadedSelectionProxy : INotifyPropertyChanged
{
private readonly Action<bool> _changed;
private bool _isSelected;

public DownloadedSelectionProxy(bool isSelected, Action<bool> changed)
{
_isSelected = isSelected;
_changed = changed;
}

public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value)
return;
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
_changed(value);
}
}

public event PropertyChangedEventHandler? PropertyChanged;
}
}
46 changes: 36 additions & 10 deletions FaultRecordWindow.HeaderSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
namespace ArIED61850Tester;

/// <summary>
/// Adds a tri-state select-all checkbox to the fault-record Get column. Selecting all
/// affects only rows that are currently eligible for download; clearing always clears
/// every row so stale disabled selections cannot remain hidden.
/// Adds a tri-state select-all checkbox to the fault-record Get column. A record with relay
/// files is selectable whether it is a first download or an intentional re-download. Native
/// first-download rows use FaultRecordRow.IsSelected; downloaded rows use the notifying
/// re-download selection authority so the native WPF checkbox and transfer state cannot diverge.
/// </summary>
public partial class FaultRecordWindow
{
Expand Down Expand Up @@ -75,7 +76,7 @@ private void EnsureFaultRecordHeaderSelection()
VerticalAlignment = VerticalAlignment.Center,
Cursor = Cursors.Hand,
Focusable = true,
ToolTip = "Check / uncheck all downloadable fault records"
ToolTip = "Check / uncheck all downloadable or re-downloadable fault records"
};
AutomationProperties.SetName(headerCheckBox, "Toggle all downloadable fault records");
headerCheckBox.Click += FaultRecordHeaderSelectionCheckBox_Click;
Expand Down Expand Up @@ -105,10 +106,26 @@ private void FaultRecordHeaderSelectionCheckBox_Click(object sender, RoutedEvent
{
foreach (var row in Records)
{
if (target)
row.IsSelected = row.CanSelectForDownload;
else
if (!HasTransferableFiles(row))
{
row.IsSelected = false;
if (row.LocalState == FaultRecordLocalState.Downloaded)
SetDownloadedTransferSelection(row, false);
else
_redownloadSelections.Remove(row.Record.RecordId);
continue;
}

if (row.LocalState == FaultRecordLocalState.Downloaded)
{
row.IsSelected = false;
SetDownloadedTransferSelection(row, target);
}
else
{
_redownloadSelections.Remove(row.Record.RecordId);
row.IsSelected = target && row.CanSelectForDownload;
}
}
}
finally
Expand All @@ -117,6 +134,7 @@ private void FaultRecordHeaderSelectionCheckBox_Click(object sender, RoutedEvent
}

RaiseSelectionState();
UpdateSmartSelectionUi();
RefreshFaultRecordHeaderSelection();
}

Expand Down Expand Up @@ -161,25 +179,33 @@ private void RefreshFaultRecordHeaderSelection()
if (header == null)
return;

var eligibleCount = Records.Count(row => row.CanSelectForDownload);
var eligibleCount = Records.Count(HasTransferableFiles);
header.IsEnabled = !IsBusy && eligibleCount > 0;
header.IsChecked = GetFaultRecordHeaderSelectionState();
}

private bool? GetFaultRecordHeaderSelectionState()
{
var eligible = Records.Where(row => row.CanSelectForDownload).ToArray();
var eligible = Records.Where(HasTransferableFiles).ToArray();
if (eligible.Length == 0)
return false;

var selected = eligible.Count(row => row.IsSelected);
var selected = eligible.Count(IsSelectedForTransfer);
if (selected == 0)
return false;
if (selected == eligible.Length)
return true;
return null;
}

private static bool HasTransferableFiles(FaultRecordRow row)
=> row.Record.Files.Count > 0;

private bool IsSelectedForTransfer(FaultRecordRow row)
=> row.LocalState == FaultRecordLocalState.Downloaded
? IsDownloadedTransferSelected(row)
: row.IsSelected;

private void FaultRecordHeaderSelectionWindow_Closed(object? sender, EventArgs e)
{
PropertyChanged -= FaultRecordHeaderSelectionWindow_PropertyChanged;
Expand Down
98 changes: 98 additions & 0 deletions FaultRecordWindow.RedownloadSelectionAuthority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ArIED61850Tester;

/// <summary>
/// Row-body selection authority for the fault-record grid. Native WPF CheckBoxes own checkbox
/// clicks. This class handles only clicks on the rest of a transferable row and routes downloaded
/// rows through the same notifying selection proxy used by the checkbox and header selection.
/// </summary>
public partial class FaultRecordWindow
{
[ModuleInitializer]
internal static void RegisterRedownloadSelectionAuthority()
{
EventManager.RegisterClassHandler(
typeof(DataGrid),
UIElement.PreviewMouseLeftButtonDownEvent,
new MouseButtonEventHandler(RedownloadSelectionAuthority_Down),
handledEventsToo: true);
}

private static void RedownloadSelectionAuthority_Down(object sender, MouseButtonEventArgs e)
{
if (sender is not DataGrid grid ||
Window.GetWindow(grid) is not FaultRecordWindow window ||
!ReferenceEquals(grid, window.FaultRecordsGrid) ||
window.IsBusy ||
e.ChangedButton != MouseButton.Left ||
e.OriginalSource is not DependencyObject source)
{
return;
}

// Native checkbox binding owns checkbox clicks. Row-body clicks are a fast-workflow
// convenience and must not create a second toggle for the same pointer action.
if (FindRedownloadSelectionAncestor<CheckBox>(source) != null)
return;

if (!TryResolveTransferRow(source, out var row) || row.Record.Files.Count == 0)
return;

if (row.LocalState == FaultRecordLocalState.Downloaded)
{
window.SetDownloadedTransferSelection(row, !window.IsDownloadedTransferSelected(row));
}
else
{
if (!row.CanSelectForDownload)
return;
row.IsSelected = !row.IsSelected;
}

e.Handled = true;
window.UpdateSmartSelectionUi();
window.RefreshFaultRecordHeaderSelection();
}

private static bool TryResolveTransferRow(DependencyObject? source, out FaultRecordRow row)
{
row = null!;
var dataGridRow = FindRedownloadSelectionAncestor<DataGridRow>(source);
if (dataGridRow?.DataContext is not FaultRecordRow candidate)
return false;

row = candidate;
return true;
}

private static T? FindRedownloadSelectionAncestor<T>(DependencyObject? source)
where T : DependencyObject
{
var current = source;
while (current != null)
{
if (current is T match)
return match;

DependencyObject? parent = null;
try
{
parent = VisualTreeHelper.GetParent(current);
}
catch (InvalidOperationException)
{
}

if (parent == null && current is FrameworkElement element)
parent = element.Parent;
current = parent;
}

return null;
}
}
Loading
Loading