From 5691e3fed31cb5dbc304ac54045759540006d362 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 15 Oct 2026 16:04:33 +0700 Subject: [PATCH] feat(browser): full-row expansion and exact selected-DO explicit reads --- MainWindow.IedBrowser.cs | 91 ++++++++++++++-- MainWindow.xaml | 9 ++ Models/IedBrowserPresentation.cs | 3 + Models/IedBrowserReadPlan.cs | 12 ++- Models/IedBrowserStructuredDetails.cs | 1 + .../M214SelectedDataObjectReadTests.cs | 100 ++++++++++++++++++ 6 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 tests/ARSAS.Tests/M214SelectedDataObjectReadTests.cs diff --git a/MainWindow.IedBrowser.cs b/MainWindow.IedBrowser.cs index c049105f0..098f6a178 100644 --- a/MainWindow.IedBrowser.cs +++ b/MainWindow.IedBrowser.cs @@ -22,6 +22,7 @@ public partial class MainWindow private readonly IedBrowserDetailProjection _iedBrowserDetails = new(); private readonly IedBrowserNavigationTrail _iedBrowserNavigation = new(); private bool _iedBrowserSyncingSelection; + private string? _iedBrowserSelectedDoReference; private int _iedBrowserNavigationRenderTicket; private long _iedBrowserLastLiveVersion = -1; private LiveIedCanonicalModel? _iedBrowserLastCfCanonicalModel; @@ -240,7 +241,13 @@ private async void IedBrowserReadValues_Click(object sender, RoutedEventArgs e) } } var model = device.LiveDiscoveryModel ?? device.SclWorkspace?.DesignModel; - var targets = IedBrowserReadPlan.Build(model, _iedBrowserNavigation.Current, readAll); + var currentPage = _iedBrowserNavigation.Current; + var selectedDo = !readAll && + ReferenceEquals(_iedBrowserRenderedPage, currentPage) && + currentPage?.Payload is LiveIedLogicalNodeModel + ? _iedBrowserSelectedDoReference : null; + var targets = IedBrowserReadPlan.Build( + model, currentPage, readAll, selectedDo); if (targets.Count == 0) { IedBrowserSubtitle.Text = "No model-verified readable FC attributes in this selection."; @@ -305,7 +312,8 @@ void DrainReadResults() RefreshIedBrowserVisibleValues(); } device.ResetDiscoveryProgress("Preparing read-only MMS values…", - readAll ? "Reading all IED values" : "Reading Browser values"); + readAll ? "Reading all IED values" : selectedDo is not null + ? "Reading selected DO " + selectedDo : "Reading Browser values"); device.IsBrowserReading = true; // Preserve the ongoing report monitor lifecycle. IedBrowserReadPageButton.IsEnabled = false; IedBrowserReadAllButton.IsEnabled = false; @@ -995,6 +1003,75 @@ private void RefreshIedBrowser() UpdateIedBrowserRcbReportDetails(); // metadata may arrive without a scalar update } + private static bool IedBrowserClickOriginIsButton( + object originalSource, DependencyObject container) + { + var node = originalSource as DependencyObject; + while (node is not null && !ReferenceEquals(node, container)) + { + if (node is System.Windows.Controls.Primitives.ButtonBase) + return true; + node = node is FrameworkContentElement content + ? content.Parent + : System.Windows.Media.VisualTreeHelper.GetParent(node); + } + return false; + } + + private void IedBrowserTreeRow_MouseUp( + object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (e.ClickCount != 1 || + sender is not DataGridRow { DataContext: IedBrowserTreeRow row } || + !row.HasChildren || IedBrowserClickOriginIsButton(e.OriginalSource, + (DependencyObject)sender)) + return; + // A virtual collection reset must not replace the clicked selection + // with a transient null and erase the navigation trail. + _iedBrowserSyncingSelection = true; + try + { + _iedBrowserTree.Toggle(row); + IedBrowserTreeGrid.SelectedItem = row; + } + finally { _iedBrowserSyncingSelection = false; } + _iedBrowserNavigation.SelectFromTree(row); + var owner = SelectedDevice; + var ticket = ++_iedBrowserNavigationRenderTicket; + Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => + { + if (ticket != _iedBrowserNavigationRenderTicket || + MainTabs.SelectedIndex != IedBrowserWorkspaceIndex || + !ReferenceEquals(SelectedDevice, owner) || + !ReferenceEquals(_iedBrowserNavigation.Current, row)) + return; + var model = owner?.LiveDiscoveryModel ?? owner?.SclWorkspace?.DesignModel; + RenderIedBrowserSelection(row, model, owner?.IsConnected == true); + })); + e.Handled = true; + } + + private void IedBrowserDetailRow_MouseUp( + object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (e.ClickCount != 1 || + sender is not ListViewItem { DataContext: IedBrowserDetailRow detail } || + IedBrowserClickOriginIsButton(e.OriginalSource, + (DependencyObject)sender)) + return; + if (!string.IsNullOrEmpty(detail.ReadObjectReference) && + _iedBrowserNavigation.Current?.Payload is LiveIedLogicalNodeModel) + { + _iedBrowserSelectedDoReference = detail.ReadObjectReference; + IedBrowserReadPageButton.ToolTip = + "Read all model-proven safe FC attributes in selected DO " + + detail.ReadObjectReference + "; no writes or automatic polling."; + } + if (!detail.HasChildren) return; + ToggleIedBrowserDetails(detail); + e.Handled = true; + } + private void IedBrowserTreeToggle_Click(object sender, RoutedEventArgs e) { if (sender is not Button { Tag: IedBrowserTreeRow row }) @@ -1056,12 +1133,8 @@ private void IedBrowserDetailItem_DoubleClick(object sender, System.Windows.Inpu return; } - if (!detail.HasChildren) - return; - // Quality/time and ordered dataset member rows drill down lazily in place, - // preserving the selected IEC object and the surrounding report context. - ToggleIedBrowserDetails(detail); - e.Handled = true; + // Full-row single click handles expansion. Double-click retains only + // genuine object navigation; never undo the first toggle here. } private void IedBrowserBack_Click(object sender, RoutedEventArgs e) @@ -1130,6 +1203,8 @@ private void ToggleIedBrowserDetails(IedBrowserDetailRow row) private void RenderIedBrowserSelection( IedBrowserTreeRow? row, LiveIedModelDiscoveryDocument? model, bool connected) { + if (!ReferenceEquals(_iedBrowserRenderedPage, row)) + _iedBrowserSelectedDoReference = null; _iedBrowserRenderedPage = row; _iedBrowserRenderedConnected = connected; _iedBrowserGooseExpected = null; diff --git a/MainWindow.xaml b/MainWindow.xaml index 3ae178e95..c2ee8877a 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -2013,6 +2013,13 @@ VirtualizingPanel.VirtualizationMode="Recycling" ScrollViewer.CanContentScroll="True" SelectionChanged="IedBrowserTree_SelectionChanged"> + + + @@ -2115,6 +2122,8 @@