-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathIoListTestingWindow.EmbeddedEngineeringHost.cs
More file actions
288 lines (251 loc) · 11.6 KB
/
Copy pathIoListTestingWindow.EmbeddedEngineeringHost.cs
File metadata and controls
288 lines (251 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using ArIED61850Tester.Models;
namespace ArIED61850Tester;
/// <summary>
/// Hosts the proven IoListTestingWindow production workspace inside MainWindow's FAT tab.
/// The production Window remains loaded-but-hidden so its existing session controller,
/// auto-capture lifecycle, persistence and report-preview code stay the single FAT authority.
/// Only its central workspace + FAT status footer are re-parented into Engineering.
/// </summary>
public partial class IoListTestingWindow
{
private bool _engineeringEmbeddedMountQueued;
private bool _engineeringEmbeddedMounted;
private FrameworkElement? _engineeringEmbeddedSurface;
[ModuleInitializer]
internal static void RegisterEmbeddedEngineeringFatHost()
{
EventManager.RegisterClassHandler(
typeof(IoListTestingWindow),
FrameworkElement.LoadedEvent,
new RoutedEventHandler(EmbeddedEngineeringFatHost_Loaded),
handledEventsToo: true);
}
internal void PrepareForEmbeddedEngineeringHost()
{
// WPF refuses Window.Show() when ShowActivated=false while WindowState=Maximized.
// The production FAT XAML historically starts maximized, therefore normalize the
// invisible donor BEFORE Show(). Its exact center is re-parented into MainWindow on
// Loaded; the donor itself never needs a maximized native HWND.
WindowState = WindowState.Normal;
ShowActivated = false;
ShowInTaskbar = false;
Opacity = 0d;
WindowStartupLocation = WindowStartupLocation.Manual;
Left = -32000d;
Top = -32000d;
}
private static void EmbeddedEngineeringFatHost_Loaded(object sender, RoutedEventArgs e)
{
if (sender is not IoListTestingWindow window ||
!ReferenceEquals(e.OriginalSource, window) ||
window._engineeringEmbeddedMounted ||
window._engineeringEmbeddedMountQueued ||
window.Owner is not MainWindow owner ||
!owner.ProductionFatTabReady)
{
return;
}
window._engineeringEmbeddedMountQueued = true;
// MainWindow's legacy launcher still calls Show() on this Window. Make that bootstrap
// surface invisible immediately; the actual production visual is moved into Engineering
// on the next Loaded-priority dispatcher turn, before ContextIdle command-panel work.
window.PrepareForEmbeddedEngineeringHost();
window.Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() => window.TryMountIntoEngineering(owner)));
}
private void TryMountIntoEngineering(MainWindow owner)
{
_engineeringEmbeddedMountQueued = false;
if (_engineeringEmbeddedMounted || !IsLoaded || !ReferenceEquals(Owner, owner) || !owner.ProductionFatTabReady)
return;
try
{
EnsureProductionFatPresentationForEmbeddedHost();
DisableLegacyEmbeddedCommandPanel();
var surface = DetachProductionFatCentralWorkspace()
?? throw new InvalidOperationException("Production FAT center could not be detached from its donor window.");
_engineeringEmbeddedSurface = surface;
_engineeringEmbeddedMounted = owner.MountProductionFatWorkspace(this, surface);
if (!_engineeringEmbeddedMounted)
throw new InvalidOperationException("Engineering FAT host rejected the production workspace surface.");
// The central FAT view now belongs to MainWindow. Keep this Window loaded and
// hidden because existing controller/session/event code is intentionally reused.
Hide();
}
catch (Exception ex)
{
// Never leave an invisible off-screen donor as a silent blank FAT failure.
// Restore the historical standalone presentation so the operator has a usable
// production FAT surface and a visible diagnostic if embedding itself fails.
WindowState = WindowState.Maximized;
Opacity = 1d;
ShowInTaskbar = true;
ShowActivated = true;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
Left = double.NaN;
Top = double.NaN;
MessageBox.Show(
owner,
$"ARSAS could not embed the production FAT workspace. The standalone FAT window will remain available.\n\n{ex.Message}",
"FAT workspace host",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
private void EnsureProductionFatPresentationForEmbeddedHost()
{
// P1 normally installs this during Loaded. Calling it explicitly is safe/idempotent
// and guarantees the first embedded frame is the same V2 FAT grid as the old Window.
InstallFatV2WorkspaceUx();
// PrintPreview historically installs from OnContentRendered. The bootstrap Window is
// intentionally transparent, so install the exact same production preview explicitly
// before the central workspace is detached. This preserves full-center mode switching.
if (!_printPreviewInstalled)
{
InstallPerIedPrintPreview();
PropertyChanged += PrintPreviewWindow_PropertyChanged;
Session.PropertyChanged += PrintPreviewSession_PropertyChanged;
Closed += PrintPreviewWindow_Closed;
_printPreviewInstalled = true;
}
// The embedded center already declares WorkspacePreviewToggle in XAML. Make that
// visible button the production toggle authority instead of the hidden Window header.
if (WorkspacePreviewToggle != null)
_printPreviewToggle = WorkspacePreviewToggle;
}
private void DisableLegacyEmbeddedCommandPanel()
{
// Engineering already owns one shared Command Dock. Prevent the old FAT window's
// duplicate command panel from being created/refreshed while embedded. A non-null
// sentinel makes the queued legacy installer return immediately; null row/summary
// references make its queued refresh a no-op. No command runtime semantics change.
DetachFatCommandDevice();
if (_fatCommandPanelShell?.Parent is Grid existingHost)
{
var row = Grid.GetRow(_fatCommandPanelShell);
existingHost.Children.Remove(_fatCommandPanelShell);
if (row >= 0 && row < existingHost.RowDefinitions.Count)
existingHost.RowDefinitions[row].Height = new GridLength(0);
if (row - 1 >= 0 && row - 1 < existingHost.RowDefinitions.Count)
existingHost.RowDefinitions[row - 1].Height = new GridLength(0);
}
_fatCommandPanelShell ??= new Border { Visibility = Visibility.Collapsed };
_fatCommandRows = null;
_fatCommandSummary = null;
_fatCommandEmptyState = null;
}
private FrameworkElement? DetachProductionFatCentralWorkspace()
{
if (Content is not Grid root)
return null;
var middle = root.Children
.OfType<Grid>()
.FirstOrDefault(child => Grid.GetRow(child) == 2);
var workspaceBorder = middle?.Children
.OfType<Border>()
.FirstOrDefault(child => Grid.GetColumn(child) == 2);
if (middle == null || workspaceBorder == null)
return null;
var footer = root.Children
.OfType<Border>()
.FirstOrDefault(child => Grid.GetRow(child) == 4);
middle.Children.Remove(workspaceBorder);
if (footer != null)
root.Children.Remove(footer);
var host = new Grid
{
DataContext = this,
Margin = new Thickness(0)
};
host.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
if (footer != null)
{
host.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
host.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
}
workspaceBorder.Margin = new Thickness(0);
Grid.SetRow(workspaceBorder, 0);
Grid.SetColumn(workspaceBorder, 0);
host.Children.Add(workspaceBorder);
if (footer != null)
{
footer.Margin = new Thickness(0);
Grid.SetRow(footer, 2);
Grid.SetColumn(footer, 0);
host.Children.Add(footer);
}
return host;
}
internal void SelectEngineeringDeviceForEmbeddedFat(Iec61850MonitorDevice? device)
{
if (!_engineeringEmbeddedMounted)
return;
// M3 viewed-device contract: the persistent Engineering IED Explorer owns
// what the FAT grid displays. SelectedIed/Session.SelectContext changes only that
// projection; an active capture remains latched inside its per-IED controller.
// Clearing the Explorer selection or selecting an IED outside this FAT projection
// must clear the FAT view instead of silently leaving the previous IED on screen.
if (device == null)
{
if (CanSelectIed)
SelectedIed = null;
return;
}
// Resolve by strongest Engineering identity first. Do not use one OR predicate:
// a weak fallback on an earlier project row must never beat an exact live DeviceId.
var match = Project.Ieds.FirstOrDefault(_ => false);
if (!string.IsNullOrWhiteSpace(device.DeviceId))
{
match = Project.Ieds.FirstOrDefault(ied =>
!string.IsNullOrWhiteSpace(ied.LiveDeviceId) &&
ied.LiveDeviceId.Equals(device.DeviceId, StringComparison.OrdinalIgnoreCase));
}
if (match == null && !string.IsNullOrWhiteSpace(device.SclIedName))
{
match = Project.Ieds.FirstOrDefault(ied =>
ied.IedName.Equals(device.SclIedName, StringComparison.OrdinalIgnoreCase));
}
if (match == null && !string.IsNullOrWhiteSpace(device.Name))
{
match = Project.Ieds.FirstOrDefault(ied =>
ied.IedName.Equals(device.Name, StringComparison.OrdinalIgnoreCase));
}
if (match == null && !string.IsNullOrWhiteSpace(device.IpAddress))
{
match = Project.Ieds.FirstOrDefault(ied =>
!string.IsNullOrWhiteSpace(ied.IpAddress) &&
ied.IpAddress.Equals(device.IpAddress, StringComparison.OrdinalIgnoreCase));
}
if (ReferenceEquals(SelectedIed, match) || !CanSelectIed)
return;
SelectedIed = match;
}
internal void NotifyEmbeddedHostActivated()
{
if (!_engineeringEmbeddedMounted)
return;
RefreshFatV2WorkspaceUx(refreshRows: true);
if (_printPreviewActive)
RefreshPrintPreview();
}
private void EmbeddedEngineeringFatHost_Closed(object? sender, EventArgs e)
{
if (Owner is MainWindow owner)
owner.UnmountProductionFatWorkspace(this);
Closed -= EmbeddedEngineeringFatHost_Closed;
_engineeringEmbeddedMounted = false;
_engineeringEmbeddedSurface = null;
}
// Field initializer cannot attach an instance event. Hook cleanup once the embedded
// surface has actually been mounted; this helper is called from the production owner.
internal void RegisterEmbeddedHostCloseCleanup()
{
Closed -= EmbeddedEngineeringFatHost_Closed;
Closed += EmbeddedEngineeringFatHost_Closed;
}
}