Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 37 additions & 9 deletions FaultRecordUxBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,60 @@ private static void OnListBoxItemLoaded(object sender, RoutedEventArgs e)
{
Tag = CapabilityPanelMarker,
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(4, 4, 0, 0)
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(4, 2, 7, 0)
};
capabilityPanel.Children.Add(gooseButton);
capabilityPanel.Children.Add(smvButton);
capabilityPanel.Children.Add(fileButton);

Grid.SetRow(capabilityPanel, Grid.GetRow(actionPanel));
Grid.SetColumn(capabilityPanel, Grid.GetColumn(actionPanel));
Grid.SetColumnSpan(capabilityPanel, Math.Max(1, Grid.GetColumnSpan(actionPanel)));
Panel.SetZIndex(capabilityPanel, Math.Max(11, Panel.GetZIndex(actionPanel) + 1));
// Keep protocol capabilities on their own card row. The previous overlay shared
// the lifecycle-action row and could cover the Stop button when File Transfer was enabled.
cardGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
Grid.SetRow(capabilityPanel, cardGrid.RowDefinitions.Count - 1);
Grid.SetColumn(capabilityPanel, 1);
Panel.SetZIndex(capabilityPanel, 10);
cardGrid.Children.Add(capabilityPanel);

var registration = new CardRegistration(
currentDevice,
gooseButton,
smvButton,
fileButton,
(_, _) => RefreshCapabilityState(currentDevice, gooseButton, smvButton, fileButton));
(_, _) => QueueCapabilityRefresh(item, currentDevice, gooseButton, smvButton, fileButton));
Registrations.Add(item, registration);
currentDevice.PropertyChanged += registration.PropertyChangedHandler;
RefreshCapabilityState(currentDevice, gooseButton, smvButton, fileButton);
}));
}

private static void QueueCapabilityRefresh(
ListBoxItem item,
Iec61850MonitorDevice device,
Button gooseButton,
Button smvButton,
Button fileButton)
{
void RefreshOnUiThread()
{
if (!item.IsLoaded || !ReferenceEquals(item.DataContext, device))
return;

RefreshCapabilityState(device, gooseButton, smvButton, fileButton);
}

if (item.Dispatcher.CheckAccess())
{
RefreshOnUiThread();
return;
}

// Discovery and connection state can be published from worker threads. WPF controls
// have thread affinity, so every capability-state update must be marshalled to the card UI.
item.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, new Action(RefreshOnUiThread));
}

private static Button CreateCapabilityButton(string capability, string pathData, RoutedEventHandler click)
{
var icon = new System.Windows.Shapes.Path
Expand Down Expand Up @@ -273,4 +301,4 @@ private sealed record CardRegistration(
Button SmvButton,
Button FileButton,
PropertyChangedEventHandler PropertyChangedHandler);
}
}
42 changes: 21 additions & 21 deletions FaultRecordWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@
<StackPanel>
<TextBlock Text="IEC 61850 Fault Record Transfer" FontSize="18" FontWeight="SemiBold" Foreground="#17243A"/>
<TextBlock Margin="0,3,0,0" FontSize="12.5" Foreground="#607089">
<Run Text="{Binding DeviceName}"/>
<Run Text="{Binding DeviceName, Mode=OneTime}"/>
<Run Text=" • "/>
<Run Text="{Binding EndpointText}"/>
<Run Text="{Binding EndpointText, Mode=OneTime}"/>
</TextBlock>
</StackPanel>
<Border Grid.Column="1" Background="#E8F0FE" CornerRadius="12" Padding="10,5" VerticalAlignment="Center">
Expand All @@ -117,16 +117,16 @@
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Text="Remote directory" VerticalAlignment="Center" FontSize="12.5" Foreground="#42526A"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding RemoteDirectory, UpdateSourceTrigger=PropertyChanged}"
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding RemoteDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource FieldStyle}" ToolTip="Leave empty to start at the relay file-store root"/>
<Button Grid.Row="0" Grid.Column="3" Content="Scan fault records" Click="Scan_Click"
Style="{StaticResource PrimaryButtonStyle}" IsEnabled="{Binding IsNotBusy}"/>
Style="{StaticResource PrimaryButtonStyle}" IsEnabled="{Binding IsNotBusy, Mode=OneWay}"/>

<TextBlock Grid.Row="2" Grid.Column="0" Text="Save to" VerticalAlignment="Center" FontSize="12.5" Foreground="#42526A"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DestinationDirectory, UpdateSourceTrigger=PropertyChanged}"
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DestinationDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource FieldStyle}"/>
<Button Grid.Row="2" Grid.Column="3" Content="Choose folder" Click="ChooseFolder_Click"
Style="{StaticResource SecondaryButtonStyle}" IsEnabled="{Binding IsNotBusy}"/>
Style="{StaticResource SecondaryButtonStyle}" IsEnabled="{Binding IsNotBusy, Mode=OneWay}"/>
</Grid>
</Border>

Expand All @@ -142,9 +142,9 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Discovered COMTRADE records" FontSize="13.5" FontWeight="SemiBold" Foreground="#26364D"/>
<TextBlock Grid.Column="1" Text="{Binding SelectionSummary}" FontSize="11.5" Foreground="#68778D" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Text="{Binding SelectionSummary, Mode=OneWay}" FontSize="11.5" Foreground="#68778D" VerticalAlignment="Center"/>
</Grid>
<DataGrid Grid.Row="1" ItemsSource="{Binding Records}" AutoGenerateColumns="False"
<DataGrid Grid.Row="1" ItemsSource="{Binding Records, Mode=OneWay}" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False"
HeadersVisibility="Column" GridLinesVisibility="All" BorderThickness="0"
SelectionMode="Extended" SelectionUnit="FullRow" Background="White">
Expand All @@ -157,13 +157,13 @@
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Record" Binding="{Binding RecordName}" Width="2*" IsReadOnly="True"/>
<DataGridTextColumn Header="Modified" Binding="{Binding ModifiedText}" Width="145" IsReadOnly="True"/>
<DataGridTextColumn Header="Size" Binding="{Binding SizeText}" Width="95" IsReadOnly="True"/>
<DataGridTextColumn Header="Files" Binding="{Binding FilesText}" Width="125" IsReadOnly="True"/>
<DataGridTextColumn Header="Completeness" Binding="{Binding Completeness}" Width="145" IsReadOnly="True"/>
<DataGridTextColumn Header="Remote directory" Binding="{Binding RemoteDirectory}" Width="2*" IsReadOnly="True"/>
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="155" IsReadOnly="True"/>
<DataGridTextColumn Header="Record" Binding="{Binding RecordName, Mode=OneWay}" Width="2*" IsReadOnly="True"/>
<DataGridTextColumn Header="Modified" Binding="{Binding ModifiedText, Mode=OneWay}" Width="145" IsReadOnly="True"/>
<DataGridTextColumn Header="Size" Binding="{Binding SizeText, Mode=OneWay}" Width="95" IsReadOnly="True"/>
<DataGridTextColumn Header="Files" Binding="{Binding FilesText, Mode=OneWay}" Width="125" IsReadOnly="True"/>
<DataGridTextColumn Header="Completeness" Binding="{Binding Completeness, Mode=OneWay}" Width="145" IsReadOnly="True"/>
<DataGridTextColumn Header="Remote directory" Binding="{Binding RemoteDirectory, Mode=OneWay}" Width="2*" IsReadOnly="True"/>
<DataGridTextColumn Header="Status" Binding="{Binding Status, Mode=OneWay}" Width="155" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Expand All @@ -184,14 +184,14 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Text="{Binding StatusText}" FontSize="12" Foreground="#4B5D75" TextTrimming="CharacterEllipsis"/>
<ProgressBar Grid.Row="2" Grid.Column="0" Height="8" Minimum="0" Maximum="100" Value="{Binding ProgressValue}"
IsIndeterminate="{Binding IsIndeterminate}" Background="#E5EBF3" Foreground="#2563EB"/>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Text="{Binding StatusText, Mode=OneWay}" FontSize="12" Foreground="#4B5D75" TextTrimming="CharacterEllipsis"/>
<ProgressBar Grid.Row="2" Grid.Column="0" Height="8" Minimum="0" Maximum="100" Value="{Binding ProgressValue, Mode=OneWay}"
IsIndeterminate="{Binding IsIndeterminate, Mode=OneWay}" Background="#E5EBF3" Foreground="#2563EB"/>
<Button Grid.Row="2" Grid.Column="2" Content="Cancel" Click="Cancel_Click"
Style="{StaticResource SecondaryButtonStyle}" IsEnabled="{Binding IsBusy}"/>
Style="{StaticResource SecondaryButtonStyle}" IsEnabled="{Binding IsBusy, Mode=OneWay}"/>
<Button Grid.Row="2" Grid.Column="4" Content="Download selected" Click="Download_Click"
Style="{StaticResource PrimaryButtonStyle}" IsEnabled="{Binding CanDownload}"/>
Style="{StaticResource PrimaryButtonStyle}" IsEnabled="{Binding CanDownload, Mode=OneWay}"/>
</Grid>
</Border>
</Grid>
</Window>
</Window>
1 change: 0 additions & 1 deletion FaultRecordWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ private async Task ScanAsync()
var catalog = await _client.DiscoverAsync(
RemoteDirectory,
_operationCancellation.Token);

Records.Clear();
foreach (var record in catalog.Records)
{
Expand Down
46 changes: 46 additions & 0 deletions scripts/verify-fault-record-bindings.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
$ErrorActionPreference = 'Stop'

$root = Split-Path -Parent $PSScriptRoot
$xamlPath = Join-Path $root 'FaultRecordWindow.xaml'
$xaml = Get-Content $xamlPath -Raw

$requiredOneTimeBindings = @('DeviceName', 'EndpointText')
foreach ($property in $requiredOneTimeBindings) {
$pattern = "\{Binding\s+$([regex]::Escape($property))\s*,[^}]*Mode=OneTime[^}]*\}"
if ($xaml -notmatch $pattern) {
throw "FaultRecordWindow immutable binding '$property' must explicitly use Mode=OneTime."
}
}

$requiredOneWayBindings = @(
'IsNotBusy',
'SelectionSummary',
'Records',
'RecordName',
'ModifiedText',
'SizeText',
'FilesText',
'Completeness',
'StatusText',
'ProgressValue',
'IsIndeterminate',
'IsBusy',
'CanDownload'
)

foreach ($property in $requiredOneWayBindings) {
$pattern = "\{Binding\s+$([regex]::Escape($property))\s*,[^}]*Mode=OneWay[^}]*\}"
if ($xaml -notmatch $pattern) {
throw "FaultRecordWindow display binding '$property' must explicitly use Mode=OneWay."
}
}

$requiredTwoWayBindings = @('RemoteDirectory', 'DestinationDirectory', 'IsSelected')
foreach ($property in $requiredTwoWayBindings) {
$pattern = "\{Binding\s+$([regex]::Escape($property))\s*,[^}]*Mode=TwoWay[^}]*\}"
if ($xaml -notmatch $pattern) {
throw "FaultRecordWindow editable binding '$property' must explicitly use Mode=TwoWay."
}
}

Write-Host 'Fault record binding modes are explicit and valid.' -ForegroundColor Green
2 changes: 2 additions & 0 deletions scripts/verify-source-clean.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,6 @@ if ($Problems.Count -gt 0) {
throw "ArIED source tree failed clean-room validation with $($Problems.Count) problem(s)."
}

& (Join-Path $PSScriptRoot "verify-fault-record-bindings.ps1")

Write-Host "All Git-tracked ArIED content passed source, website, external-IP, and current-license checks." -ForegroundColor Green
Loading