feat: redesign dashboard with Fluent KPI tiles, connection strip, and devices column
- Replace LCD-style readings with a 3×2 KPI tile grid (Fluent card surfaces, 52pt values) - Add persistent top connection strip with horizontal chips + pump name badge - Add elapsed test timer (DispatcherTimer, mm:ss) to Test Summary card - Restyle Test Summary and Active Alarms with Fluent brushes/iconography - Add Devices column (CAN / K-Line / Bench tiles) between KPI grid and test/alarms - Enumerates attached PCAN USB channels via PCAN_ATTACHED_CHANNELS API - Enumerates FTDI K-Line adapters via existing FtdiInterface helpers - Click to connect/disconnect; confirmation dialog when session active or test running - Hover tint: blue = will connect, red = will disconnect; Bench row is read-only stub - Extend ICanService with SelectedChannel + EnumerateAttachedChannels() - Expose IKwpService.ConnectedPort for active session device tracking - Add DeviceRow button style with MultiDataTrigger hover colour logic - Add 30+ new localization keys (ES + EN) for KPI labels, devices, confirmations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
8
App.xaml
8
App.xaml
@@ -1,13 +1,21 @@
|
|||||||
<Application x:Class="HC_APTBS.App"
|
<Application x:Class="HC_APTBS.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
xmlns:local="clr-namespace:HC_APTBS">
|
xmlns:local="clr-namespace:HC_APTBS">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<!-- WPF-UI: must come first so app styles can override -->
|
||||||
|
<ui:ThemesDictionary Theme="Light"/>
|
||||||
|
<ui:ControlsDictionary/>
|
||||||
|
|
||||||
|
<!-- App resources -->
|
||||||
<ResourceDictionary Source="Resources/Strings.es.xaml"/>
|
<ResourceDictionary Source="Resources/Strings.es.xaml"/>
|
||||||
<ResourceDictionary Source="Resources/Styles.xaml"/>
|
<ResourceDictionary Source="Resources/Styles.xaml"/>
|
||||||
<ResourceDictionary Source="Resources/NavStyles.xaml"/>
|
<ResourceDictionary Source="Resources/NavStyles.xaml"/>
|
||||||
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
using HC_APTBS.Infrastructure.Logging;
|
using HC_APTBS.Infrastructure.Logging;
|
||||||
|
using Wpf.Ui.Appearance;
|
||||||
using HC_APTBS.Infrastructure.Pcan;
|
using HC_APTBS.Infrastructure.Pcan;
|
||||||
using HC_APTBS.Models;
|
using HC_APTBS.Models;
|
||||||
using HC_APTBS.Services;
|
using HC_APTBS.Services;
|
||||||
@@ -27,6 +29,10 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
// Apply WPF-UI theme and accent colour before any UI is constructed.
|
||||||
|
ApplicationThemeManager.Apply(ApplicationTheme.Light);
|
||||||
|
ApplicationAccentColorManager.Apply(Color.FromRgb(0x21, 0x96, 0xF3));
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
ConfigureServices(services);
|
ConfigureServices(services);
|
||||||
_serviceProvider = services.BuildServiceProvider();
|
_serviceProvider = services.BuildServiceProvider();
|
||||||
|
|||||||
@@ -40,7 +40,8 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
|
||||||
<PackageReference Include="System.IO.Ports" Version="10.0.5" />
|
<PackageReference Include="System.IO.Ports" Version="10.0.5" />
|
||||||
<PackageReference Include="ToggleSwitch" Version="1.2.0" />
|
<!-- WPF-UI: FluentWindow chrome + unified control theming -->
|
||||||
|
<PackageReference Include="WPF-UI" Version="3.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Embedded images — default report logo fallback -->
|
<!-- Embedded images — default report logo fallback -->
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace HC_APTBS.Infrastructure.Pcan
|
|||||||
|
|
||||||
// ── State ────────────────────────────────────────────────────────────────
|
// ── State ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private readonly TPCANHandle _channel;
|
private TPCANHandle _channel;
|
||||||
private TPCANBaudrate _baudrate;
|
private TPCANBaudrate _baudrate;
|
||||||
private readonly IAppLogger _log;
|
private readonly IAppLogger _log;
|
||||||
|
|
||||||
@@ -75,6 +75,19 @@ namespace HC_APTBS.Infrastructure.Pcan
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsConnected => !_stopRead;
|
public bool IsConnected => !_stopRead;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TPCANHandle SelectedChannel
|
||||||
|
{
|
||||||
|
get => _channel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (IsConnected)
|
||||||
|
throw new System.InvalidOperationException(
|
||||||
|
"Cannot change the CAN channel while connected. Call Disconnect() first.");
|
||||||
|
_channel = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Construction ─────────────────────────────────────────────────────────
|
// ── Construction ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -91,6 +104,45 @@ namespace HC_APTBS.Infrastructure.Pcan
|
|||||||
_log = logger;
|
_log = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── ICanService: discovery ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public System.Collections.Generic.IReadOnlyList<AttachedPcanChannel> EnumerateAttachedChannels()
|
||||||
|
{
|
||||||
|
var result = new System.Collections.Generic.List<AttachedPcanChannel>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var countStatus = PCANBasic.GetValue(
|
||||||
|
PCANBasic.PCAN_NONEBUS,
|
||||||
|
TPCANParameter.PCAN_ATTACHED_CHANNELS_COUNT,
|
||||||
|
out uint count,
|
||||||
|
sizeof(uint));
|
||||||
|
|
||||||
|
if (countStatus != TPCANStatus.PCAN_ERROR_OK || count == 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var buffer = new TPCANChannelInformation[count];
|
||||||
|
var infoStatus = PCANBasic.GetValue(
|
||||||
|
PCANBasic.PCAN_NONEBUS,
|
||||||
|
TPCANParameter.PCAN_ATTACHED_CHANNELS,
|
||||||
|
buffer);
|
||||||
|
|
||||||
|
if (infoStatus != TPCANStatus.PCAN_ERROR_OK)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
foreach (var ch in buffer)
|
||||||
|
{
|
||||||
|
if (ch.device_type == TPCANDevice.PCAN_USB)
|
||||||
|
result.Add(new AttachedPcanChannel(ch.channel_handle, ch.device_name ?? $"PCAN-USB ({ch.channel_handle:X})"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Warning(LogId, $"EnumerateAttachedChannels failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// ── ICanService: lifecycle ────────────────────────────────────────────────
|
// ── ICanService: lifecycle ────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Window x:Class="HC_APTBS.MainWindow"
|
<ui:FluentWindow x:Class="HC_APTBS.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
|
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
|
||||||
@@ -13,13 +14,18 @@
|
|||||||
WindowState="Maximized"
|
WindowState="Maximized"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
FontFamily="Ebrima"
|
FontFamily="Ebrima"
|
||||||
Background="#FFEDEDED"
|
|
||||||
Closing="OnWindowClosing">
|
Closing="OnWindowClosing">
|
||||||
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
|
|
||||||
|
<!-- ── WPF-UI custom title bar (replaces OS chrome) ──────────────────── -->
|
||||||
|
<ui:TitleBar DockPanel.Dock="Top"
|
||||||
|
Title="{DynamicResource App.Title}"
|
||||||
|
ShowMaximize="True"
|
||||||
|
ShowMinimize="True"/>
|
||||||
|
|
||||||
<!-- ── Persistent app header: pump identification + connection state ──── -->
|
<!-- ── Persistent app header: pump identification + connection state ──── -->
|
||||||
<Border DockPanel.Dock="Top" Background="#FFEDEDED"
|
<Border DockPanel.Dock="Top" Background="Transparent"
|
||||||
BorderBrush="#999" BorderThickness="0,0,0,1" Visibility="Collapsed">
|
BorderBrush="#999" BorderThickness="0,0,0,1" Visibility="Collapsed">
|
||||||
<Grid Margin="4,2">
|
<Grid Margin="4,2">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -204,4 +210,4 @@
|
|||||||
</TabControl>
|
</TabControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Window>
|
</ui:FluentWindow>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using HC_APTBS.ViewModels;
|
using HC_APTBS.ViewModels;
|
||||||
|
using Wpf.Ui.Controls;
|
||||||
|
|
||||||
namespace HC_APTBS;
|
namespace HC_APTBS;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ namespace HC_APTBS;
|
|||||||
/// Code-behind for MainWindow — minimal: sets DataContext and forwards the
|
/// Code-behind for MainWindow — minimal: sets DataContext and forwards the
|
||||||
/// Closing event to a ViewModel command if needed.
|
/// Closing event to a ViewModel command if needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : FluentWindow
|
||||||
{
|
{
|
||||||
public MainWindow(MainViewModel viewModel)
|
public MainWindow(MainViewModel viewModel)
|
||||||
{
|
{
|
||||||
|
|||||||
13
Models/AttachedPcanChannel.cs
Normal file
13
Models/AttachedPcanChannel.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Peak.Can.Basic;
|
||||||
|
using TPCANHandle = System.UInt16;
|
||||||
|
|
||||||
|
namespace HC_APTBS.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a PCAN USB channel that is physically attached to the system,
|
||||||
|
/// as reported by the PCAN-Basic <c>PCAN_ATTACHED_CHANNELS</c> API call.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Handle">PCAN channel handle (e.g. <c>PCANBasic.PCAN_USBBUS1</c>).</param>
|
||||||
|
/// <param name="DisplayName">Human-readable name returned by the PCAN driver (e.g. "PCAN-USB (1)").</param>
|
||||||
|
public sealed record AttachedPcanChannel(TPCANHandle Handle, string DisplayName);
|
||||||
|
}
|
||||||
@@ -42,6 +42,40 @@
|
|||||||
<sys:String x:Key="Dashboard.Action.Stop">Stop</sys:String>
|
<sys:String x:Key="Dashboard.Action.Stop">Stop</sys:String>
|
||||||
<sys:String x:Key="Dashboard.Action.EmergencyStop">EMERGENCY STOP</sys:String>
|
<sys:String x:Key="Dashboard.Action.EmergencyStop">EMERGENCY STOP</sys:String>
|
||||||
|
|
||||||
|
<!-- ── Dashboard KPI tile labels ──────────────────────────────────────── -->
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Rpm">Bench RPM</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Qdelivery">Q delivery</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.P1">Pressure P1</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.P2">Pressure P2</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Tin">Oil in T</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Tout">Oil out T</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.Rpm">rpm</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.CcS">cc/s</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.Bar">bar</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.Celsius">°C</sys:String>
|
||||||
|
<!-- Dashboard test summary extras -->
|
||||||
|
<sys:String x:Key="Dashboard.TestSummary.Elapsed">Elapsed:</sys:String>
|
||||||
|
<!-- Dashboard connection chip extras -->
|
||||||
|
<sys:String x:Key="Dashboard.Conn.Pump.Label">Pump:</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Conn.NoPump">No pump</sys:String>
|
||||||
|
|
||||||
|
<!-- ── Dashboard Devices column ─────────────────────────────────────── -->
|
||||||
|
<sys:String x:Key="Dashboard.Devices">Devices</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Can">CAN</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Kline">K-Line</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Bench">Bench</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Refresh">Refresh</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Idle">Available</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Connected">Connected</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Active">Session active</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Failed">Error</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.None">No devices found</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.BenchRow">Bench controller</sys:String>
|
||||||
|
<!-- Confirmation dialogs -->
|
||||||
|
<sys:String x:Key="Devices.Confirm.Title">Confirm device change</sys:String>
|
||||||
|
<sys:String x:Key="Devices.Confirm.Body.Active">The {0} session is active. Disconnect?</sys:String>
|
||||||
|
<sys:String x:Key="Devices.Confirm.Body.TestRunning">A test is running. Changing device state may abort it. Continue?</sys:String>
|
||||||
|
|
||||||
<!-- ── Status bar / connection indicators ───────────────────────────── -->
|
<!-- ── Status bar / connection indicators ───────────────────────────── -->
|
||||||
<sys:String x:Key="Status.Label">Status:</sys:String>
|
<sys:String x:Key="Status.Label">Status:</sys:String>
|
||||||
<sys:String x:Key="Status.Can">CAN</sys:String>
|
<sys:String x:Key="Status.Can">CAN</sys:String>
|
||||||
|
|||||||
@@ -42,6 +42,40 @@
|
|||||||
<sys:String x:Key="Dashboard.Action.Stop">Detener</sys:String>
|
<sys:String x:Key="Dashboard.Action.Stop">Detener</sys:String>
|
||||||
<sys:String x:Key="Dashboard.Action.EmergencyStop">PARADA DE EMERGENCIA</sys:String>
|
<sys:String x:Key="Dashboard.Action.EmergencyStop">PARADA DE EMERGENCIA</sys:String>
|
||||||
|
|
||||||
|
<!-- ── Dashboard KPI tile labels ──────────────────────────────────────── -->
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Rpm">RPM del banco</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Qdelivery">Q. caudal</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.P1">Presión P1</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.P2">Presión P2</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Tin">T. entrada</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Tout">T. salida</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.Rpm">rpm</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.CcS">cc/s</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.Bar">bar</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Kpi.Unit.Celsius">°C</sys:String>
|
||||||
|
<!-- Dashboard test summary extras -->
|
||||||
|
<sys:String x:Key="Dashboard.TestSummary.Elapsed">Duración:</sys:String>
|
||||||
|
<!-- Dashboard connection chip extras -->
|
||||||
|
<sys:String x:Key="Dashboard.Conn.Pump.Label">Bomba:</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Conn.NoPump">Sin bomba</sys:String>
|
||||||
|
|
||||||
|
<!-- ── Dashboard Devices column ─────────────────────────────────────── -->
|
||||||
|
<sys:String x:Key="Dashboard.Devices">Dispositivos</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Can">CAN</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Kline">K-Line</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Bench">Banco</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.Refresh">Actualizar</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Idle">Disponible</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Connected">Conectado</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Active">Sesión activa</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.State.Failed">Error</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.None">Sin dispositivos</sys:String>
|
||||||
|
<sys:String x:Key="Dashboard.Devices.BenchRow">Controlador del banco</sys:String>
|
||||||
|
<!-- Confirmation dialogs -->
|
||||||
|
<sys:String x:Key="Devices.Confirm.Title">Confirmar cambio de dispositivo</sys:String>
|
||||||
|
<sys:String x:Key="Devices.Confirm.Body.Active">La sesión {0} está activa. ¿Desea desconectar?</sys:String>
|
||||||
|
<sys:String x:Key="Devices.Confirm.Body.TestRunning">Hay una prueba en ejecución. Cambiar el estado del dispositivo puede interrumpirla. ¿Continuar?</sys:String>
|
||||||
|
|
||||||
<!-- ── Status bar / connection indicators ───────────────────────────── -->
|
<!-- ── Status bar / connection indicators ───────────────────────────── -->
|
||||||
<sys:String x:Key="Status.Label">Estado:</sys:String>
|
<sys:String x:Key="Status.Label">Estado:</sys:String>
|
||||||
<sys:String x:Key="Status.Can">CAN</sys:String>
|
<sys:String x:Key="Status.Can">CAN</sys:String>
|
||||||
|
|||||||
@@ -42,11 +42,169 @@
|
|||||||
<Setter Property="Margin" Value="2,4"/>
|
<Setter Property="Margin" Value="2,4"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Relay toggle button style -->
|
<!-- Relay toggle button style — inherits WPF-UI Button appearance -->
|
||||||
<Style x:Key="RelayButton" TargetType="Button">
|
<Style x:Key="RelayButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||||
<Setter Property="Padding" Value="6,3"/>
|
<Setter Property="Padding" Value="6,3"/>
|
||||||
<Setter Property="Margin" Value="3,2"/>
|
<Setter Property="Margin" Value="3,2"/>
|
||||||
<Setter Property="FontSize" Value="11"/>
|
<Setter Property="FontSize" Value="11"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Base style for state-indicating toggle buttons (on/off with colour feedback).
|
||||||
|
Uses a custom ControlTemplate so that TemplateBinding Background is honoured
|
||||||
|
in ALL states — including IsChecked=True. Derived styles add IsChecked triggers
|
||||||
|
with a custom Background colour (green, amber, blue, etc.) and those colours are
|
||||||
|
guaranteed to propagate, unlike WPF-UI's own ToggleButton template which has an
|
||||||
|
internal IsChecked trigger that would override a simple BasedOn + Background setter.
|
||||||
|
Hover/pressed feedback is applied via a transparent overlay so it works on any
|
||||||
|
background colour without conflicting with the checked state.
|
||||||
|
-->
|
||||||
|
<Style x:Key="FluentStateToggle" TargetType="ToggleButton">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
|
||||||
|
<Setter Property="Padding" Value="8,4"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ToggleButton">
|
||||||
|
<Grid>
|
||||||
|
<Border x:Name="Root"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="4"
|
||||||
|
SnapsToDevicePixels="True"/>
|
||||||
|
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
||||||
|
<!-- Transparent overlay — darkens on hover/pressed over any background colour -->
|
||||||
|
<Border x:Name="HoverOverlay" CornerRadius="4" Background="Transparent"
|
||||||
|
IsHitTestVisible="False"/>
|
||||||
|
</Grid>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="HoverOverlay" Property="Background" Value="#18000000"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="HoverOverlay" Property="Background" Value="#30000000"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.4"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ── Dashboard KPI tile styles ─────────────────────────────────────── -->
|
||||||
|
<Style x:Key="KpiTile" TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource CardBackgroundFillColorDefaultBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Padding" Value="16"/>
|
||||||
|
<Setter Property="Margin" Value="6"/>
|
||||||
|
<Setter Property="MinHeight" Value="140"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="KpiHeaderText" TargetType="TextBlock">
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,0,4"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="KpiValueText" TargetType="TextBlock">
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
|
||||||
|
<Setter Property="FontSize" Value="52"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||||
|
<Setter Property="TextAlignment" Value="Left"/>
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="KpiUnitText" TargetType="TextBlock">
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||||
|
<Setter Property="Margin" Value="6,0,0,6"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Connection strip chip -->
|
||||||
|
<Style x:Key="ConnChip" TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="CornerRadius" Value="14"/>
|
||||||
|
<Setter Property="Padding" Value="14,6"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Status dot (10 px ellipse; Fill overridden by DataTrigger for state colour) -->
|
||||||
|
<Style x:Key="StatusDot" TargetType="Ellipse">
|
||||||
|
<Setter Property="Width" Value="10"/>
|
||||||
|
<Setter Property="Height" Value="10"/>
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorNeutralBrush}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Margin" Value="6,0,4,0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ── Device row button — hover tint indicates intent (connect=blue, disconnect=red) -->
|
||||||
|
<Style x:Key="DeviceRow" TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Padding" Value="10,6"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,0,4"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="Bd"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
CornerRadius="6">
|
||||||
|
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<!-- Hover + disconnected → accent blue (will connect) -->
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsMouseOver}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding IsConnected}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentFillColorSecondaryBrush}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
<!-- Hover + connected → critical red (will disconnect) -->
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsMouseOver}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding IsConnected}" Value="True"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SystemFillColorCriticalBackgroundBrush}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
<!-- Disabled → muted (bench placeholder) -->
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="Bd" Property="Opacity" Value="0.5"/>
|
||||||
|
<Setter Property="Cursor" Value="Arrow"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -41,6 +41,22 @@ namespace HC_APTBS.Services
|
|||||||
/// <summary>True when the CAN read thread is running and the channel is open.</summary>
|
/// <summary>True when the CAN read thread is running and the channel is open.</summary>
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The PCAN channel handle that will be used on the next <see cref="Connect"/> call.
|
||||||
|
/// Defaults to the channel supplied at construction.
|
||||||
|
/// Throws <see cref="System.InvalidOperationException"/> when set while <see cref="IsConnected"/> is true.
|
||||||
|
/// </summary>
|
||||||
|
TPCANHandle SelectedChannel { get; set; }
|
||||||
|
|
||||||
|
// ── Discovery ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates PCAN USB channels that are physically attached to the system.
|
||||||
|
/// Returns an empty list if no adapters are connected or if the PCAN-Basic DLL
|
||||||
|
/// is unavailable. Never throws.
|
||||||
|
/// </summary>
|
||||||
|
System.Collections.Generic.IReadOnlyList<AttachedPcanChannel> EnumerateAttachedChannels();
|
||||||
|
|
||||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ namespace HC_APTBS.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string? DetectKLinePort();
|
string? DetectKLinePort();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The FTDI serial number of the device that is currently holding an open
|
||||||
|
/// K-Line session, or <see langword="null"/> when no session is active.
|
||||||
|
/// </summary>
|
||||||
|
string? ConnectedPort { get; }
|
||||||
|
|
||||||
// ── Mid-read notifications ────────────────────────────────────────────
|
// ── Mid-read notifications ────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ namespace HC_APTBS.Services.Impl
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public KLineConnectionState KLineState => _kLineState;
|
public KLineConnectionState KLineState => _kLineState;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string? ConnectedPort => _connectedPort;
|
||||||
|
|
||||||
// ── Constructor ───────────────────────────────────────────────────────────
|
// ── Constructor ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <param name="logger">Application logger.</param>
|
/// <param name="logger">Application logger.</param>
|
||||||
@@ -289,7 +292,7 @@ namespace HC_APTBS.Services.Impl
|
|||||||
Report(85, "Reading fault codes...");
|
Report(85, "Reading fault codes...");
|
||||||
kwp.KeepAlive();
|
kwp.KeepAlive();
|
||||||
var faultCodes = kwp.ReadFaultCodes();
|
var faultCodes = kwp.ReadFaultCodes();
|
||||||
result[KlineKeys.Errors] = faultCodes.Count > 0
|
result[KlineKeys.Errors] = faultCodes?.Count > 0
|
||||||
? string.Join(Environment.NewLine, faultCodes)
|
? string.Join(Environment.NewLine, faultCodes)
|
||||||
: KlineKeys.NoErrors;
|
: KlineKeys.NoErrors;
|
||||||
|
|
||||||
@@ -418,7 +421,7 @@ namespace HC_APTBS.Services.Impl
|
|||||||
Report(85, "Reading fault codes...");
|
Report(85, "Reading fault codes...");
|
||||||
kwp.KeepAlive();
|
kwp.KeepAlive();
|
||||||
var faultCodes = kwp.ReadFaultCodes();
|
var faultCodes = kwp.ReadFaultCodes();
|
||||||
result[KlineKeys.Errors] = faultCodes.Count > 0
|
result[KlineKeys.Errors] = faultCodes?.Count > 0
|
||||||
? string.Join(Environment.NewLine, faultCodes)
|
? string.Join(Environment.NewLine, faultCodes)
|
||||||
: KlineKeys.NoErrors;
|
: KlineKeys.NoErrors;
|
||||||
|
|
||||||
@@ -621,14 +624,15 @@ namespace HC_APTBS.Services.Impl
|
|||||||
|
|
||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
_busLock.Wait();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_log.Info(LogId, "TryFastUnlock: sending unlock command over K-Line");
|
_log.Info(LogId, "TryFastUnlock: sending unlock command over K-Line");
|
||||||
var packets = _sessionKwp.SendCustom(
|
var packets = _sessionKwp!.SendCustom(
|
||||||
new List<byte> { 0x02, 0x88, 0x02, 0x03, 0xA8, 0x01, 0x00 });
|
new List<byte> { 0x02, 0x88, 0x02, 0x03, 0xA8, 0x01, 0x00 });
|
||||||
|
|
||||||
bool nak = packets.Count == 1
|
bool nak = packets.Count == 1
|
||||||
&& packets[0] is HC_APTBS.Infrastructure.Kwp.Packets.NakPacket;
|
&& packets[0] is NakPacket;
|
||||||
|
|
||||||
_log.Info(LogId, $"TryFastUnlock: {(nak ? "NAK — pump rejected" : "ACK — pump unlocked")}");
|
_log.Info(LogId, $"TryFastUnlock: {(nak ? "NAK — pump rejected" : "ACK — pump unlocked")}");
|
||||||
return !nak;
|
return !nak;
|
||||||
@@ -638,6 +642,10 @@ namespace HC_APTBS.Services.Impl
|
|||||||
_log.Warning(LogId, $"TryFastUnlock failed: {ex.Message}");
|
_log.Warning(LogId, $"TryFastUnlock failed: {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_busLock.Release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,33 +694,33 @@ namespace HC_APTBS.Services.Impl
|
|||||||
{
|
{
|
||||||
while (!ct.IsCancellationRequested)
|
while (!ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
// Non-blocking try-acquire: if an operation holds the lock
|
||||||
|
// we skip this cycle — the operation itself keeps the bus alive.
|
||||||
|
if (await _busLock.WaitAsync(0, ct))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_sessionKwp!.KeepAlive();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { return; }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error(LogId, $"Keep-alive failed: {ex.Message}");
|
||||||
|
CleanupSession();
|
||||||
|
SetState(KLineConnectionState.Failed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_busLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(KeepAliveIntervalMs, ct);
|
await Task.Delay(KeepAliveIntervalMs, ct);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { return; }
|
catch (OperationCanceledException) { return; }
|
||||||
|
|
||||||
// Non-blocking try-acquire: if an operation holds the lock
|
|
||||||
// we skip this cycle — the operation itself keeps the bus alive.
|
|
||||||
if (!await _busLock.WaitAsync(0, ct))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_sessionKwp!.KeepAlive();
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) { return; }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(LogId, $"Keep-alive failed: {ex.Message}");
|
|
||||||
CleanupSession();
|
|
||||||
SetState(KLineConnectionState.Failed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_busLock.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,7 +754,7 @@ namespace HC_APTBS.Services.Impl
|
|||||||
var codes = _sessionKwp.ReadFaultCodes();
|
var codes = _sessionKwp.ReadFaultCodes();
|
||||||
_sessionKwp.KeepAlive();
|
_sessionKwp.KeepAlive();
|
||||||
Report(100, "Done.");
|
Report(100, "Done.");
|
||||||
return codes.Count > 0
|
return codes?.Count > 0
|
||||||
? string.Join(Environment.NewLine, codes)
|
? string.Join(Environment.NewLine, codes)
|
||||||
: KlineKeys.NoErrors;
|
: KlineKeys.NoErrors;
|
||||||
}
|
}
|
||||||
@@ -773,7 +781,7 @@ namespace HC_APTBS.Services.Impl
|
|||||||
var codes = _sessionKwp.ReadFaultCodes();
|
var codes = _sessionKwp.ReadFaultCodes();
|
||||||
_sessionKwp.KeepAlive();
|
_sessionKwp.KeepAlive();
|
||||||
Report(100, "Done.");
|
Report(100, "Done.");
|
||||||
return codes.Count > 0
|
return codes?.Count > 0
|
||||||
? string.Join(Environment.NewLine, codes)
|
? string.Join(Environment.NewLine, codes)
|
||||||
: KlineKeys.NoErrors;
|
: KlineKeys.NoErrors;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using HC_APTBS.Models;
|
using HC_APTBS.Models;
|
||||||
@@ -65,6 +66,14 @@ namespace HC_APTBS.ViewModels
|
|||||||
|
|
||||||
private CancellationTokenSource? _testCts;
|
private CancellationTokenSource? _testCts;
|
||||||
|
|
||||||
|
// ── Test elapsed timer ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Ticks every second while a test is running to update <see cref="TestElapsed"/>.</summary>
|
||||||
|
private DispatcherTimer? _testTimer;
|
||||||
|
|
||||||
|
/// <summary>UTC start time of the current test; used by the timer to compute elapsed duration.</summary>
|
||||||
|
private DateTime _testStartedUtc;
|
||||||
|
|
||||||
// ── Unlock tracking ──────────────────────────────────────────────────────
|
// ── Unlock tracking ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>CTS for the currently running immobilizer unlock, if any.</summary>
|
/// <summary>CTS for the currently running immobilizer unlock, if any.</summary>
|
||||||
@@ -139,9 +148,6 @@ namespace HC_APTBS.ViewModels
|
|||||||
/// <summary>Diagnostic Trouble Code list for the Pump page §3.b sub-section.</summary>
|
/// <summary>Diagnostic Trouble Code list for the Pump page §3.b sub-section.</summary>
|
||||||
public DtcListViewModel DtcList { get; }
|
public DtcListViewModel DtcList { get; }
|
||||||
|
|
||||||
/// <summary>Auth gate for the Pump page §3.d Adaptation sub-section.</summary>
|
|
||||||
public AuthGateViewModel AdaptationAuth { get; }
|
|
||||||
|
|
||||||
// ── Page ViewModels (thin façades over the child VMs above) ───────────────
|
// ── Page ViewModels (thin façades over the child VMs above) ───────────────
|
||||||
|
|
||||||
/// <summary>Dashboard navigation page VM.</summary>
|
/// <summary>Dashboard navigation page VM.</summary>
|
||||||
@@ -203,14 +209,12 @@ namespace HC_APTBS.ViewModels
|
|||||||
AngleDisplay = new AngleDisplayViewModel(configService);
|
AngleDisplay = new AngleDisplayViewModel(configService);
|
||||||
DashboardAlarms = new DashboardAlarmsViewModel(configService.Settings.Alarms);
|
DashboardAlarms = new DashboardAlarmsViewModel(configService.Settings.Alarms);
|
||||||
DtcList = new DtcListViewModel(kwpService, localizationService, logger);
|
DtcList = new DtcListViewModel(kwpService, localizationService, logger);
|
||||||
AdaptationAuth = new AuthGateViewModel(configService, localizationService);
|
|
||||||
|
|
||||||
// Page ViewModels are thin façades over the child VMs above; they hold a
|
// Page ViewModels are thin façades over the child VMs above; they hold a
|
||||||
// reference back to this coordinator so page XAML can bind MainViewModel-owned
|
// reference back to this coordinator so page XAML can bind MainViewModel-owned
|
||||||
// values via {Binding Root.X}.
|
// values via {Binding Root.X}.
|
||||||
DashboardPage = new DashboardPageViewModel(this);
|
DashboardPage = new DashboardPageViewModel(this, canService, kwpService);
|
||||||
BenchPage = new BenchPageViewModel(this, benchService, configService);
|
BenchPage = new BenchPageViewModel(this, benchService, configService);
|
||||||
PumpPage = new PumpPageViewModel(this, DtcList, AdaptationAuth);
|
PumpPage = new PumpPageViewModel(this, DtcList);
|
||||||
TestsPage = new TestsPageViewModel(this, configService, localizationService);
|
TestsPage = new TestsPageViewModel(this, configService, localizationService);
|
||||||
SettingsPage = new SettingsPageViewModel(configService, localizationService);
|
SettingsPage = new SettingsPageViewModel(configService, localizationService);
|
||||||
SettingsPage.SettingsSaved += OnSettingsSaved;
|
SettingsPage.SettingsSaved += OnSettingsSaved;
|
||||||
@@ -548,6 +552,9 @@ namespace HC_APTBS.ViewModels
|
|||||||
/// <summary>True when the current test results have been saved to a report.</summary>
|
/// <summary>True when the current test results have been saved to a report.</summary>
|
||||||
[ObservableProperty] private bool _isTestSaved = true;
|
[ObservableProperty] private bool _isTestSaved = true;
|
||||||
|
|
||||||
|
/// <summary>Elapsed time since the current test started. Updated every second; retains last value when idle.</summary>
|
||||||
|
[ObservableProperty] private TimeSpan _testElapsed;
|
||||||
|
|
||||||
// ── Commands: test ────────────────────────────────────────────────────────
|
// ── Commands: test ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>Starts the test sequence for the current pump.</summary>
|
/// <summary>Starts the test sequence for the current pump.</summary>
|
||||||
@@ -806,6 +813,14 @@ namespace HC_APTBS.ViewModels
|
|||||||
{
|
{
|
||||||
IsTestRunning = true;
|
IsTestRunning = true;
|
||||||
VerboseStatus = _loc.GetString("Test.Started");
|
VerboseStatus = _loc.GetString("Test.Started");
|
||||||
|
|
||||||
|
_testStartedUtc = DateTime.UtcNow;
|
||||||
|
TestElapsed = TimeSpan.Zero;
|
||||||
|
_testTimer = new DispatcherTimer(
|
||||||
|
TimeSpan.FromSeconds(1),
|
||||||
|
DispatcherPriority.Normal,
|
||||||
|
(_, _) => TestElapsed = DateTime.UtcNow - _testStartedUtc,
|
||||||
|
App.Current.Dispatcher);
|
||||||
TestPanel.IsRunning = true;
|
TestPanel.IsRunning = true;
|
||||||
TestPanel.ResetResults();
|
TestPanel.ResetResults();
|
||||||
ResultDisplay.Clear();
|
ResultDisplay.Clear();
|
||||||
@@ -817,6 +832,9 @@ namespace HC_APTBS.ViewModels
|
|||||||
private void OnTestFinished(bool interrupted, bool success)
|
private void OnTestFinished(bool interrupted, bool success)
|
||||||
=> App.Current.Dispatcher.Invoke(() =>
|
=> App.Current.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
|
_testTimer?.Stop();
|
||||||
|
_testTimer = null;
|
||||||
|
|
||||||
IsTestRunning = false;
|
IsTestRunning = false;
|
||||||
LastTestSuccess = !interrupted && success;
|
LastTestSuccess = !interrupted && success;
|
||||||
VerboseStatus = interrupted ? _loc.GetString("Test.Stopped") : (success ? _loc.GetString("Common.Pass") : _loc.GetString("Common.Fail"));
|
VerboseStatus = interrupted ? _loc.GetString("Test.Stopped") : (success ? _loc.GetString("Common.Pass") : _loc.GetString("Common.Fail"));
|
||||||
|
|||||||
287
ViewModels/Pages/DashboardDevicesViewModel.cs
Normal file
287
ViewModels/Pages/DashboardDevicesViewModel.cs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using HC_APTBS.Infrastructure.Kwp;
|
||||||
|
using HC_APTBS.Models;
|
||||||
|
using HC_APTBS.Services;
|
||||||
|
using HC_APTBS.ViewModels.Dialogs;
|
||||||
|
using HC_APTBS.Views.Dialogs;
|
||||||
|
|
||||||
|
namespace HC_APTBS.ViewModels.Pages
|
||||||
|
{
|
||||||
|
/// <summary>Kind of physical device represented by a <see cref="DeviceItem"/>.</summary>
|
||||||
|
public enum DeviceKind { Can, KLine, Bench }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single detected device row in the Devices column.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class DeviceItem : ObservableObject
|
||||||
|
{
|
||||||
|
/// <summary>What kind of adapter this row represents.</summary>
|
||||||
|
public DeviceKind Kind { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Device identity string: PCAN handle as hex for CAN, FTDI serial for K-Line,
|
||||||
|
/// empty for the single Bench placeholder row.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>PCAN channel handle (only meaningful when <see cref="Kind"/> is <see cref="DeviceKind.Can"/>).</summary>
|
||||||
|
public ushort CanHandle { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Display name shown in the tile row.</summary>
|
||||||
|
[ObservableProperty] private string _name = "";
|
||||||
|
|
||||||
|
/// <summary>Short state label (right-aligned in the row).</summary>
|
||||||
|
[ObservableProperty] private string _stateLabel = "";
|
||||||
|
|
||||||
|
/// <summary>True when this device is the currently active connection.</summary>
|
||||||
|
[ObservableProperty] private bool _isConnected;
|
||||||
|
|
||||||
|
/// <summary>False for the Bench placeholder, which cannot be clicked.</summary>
|
||||||
|
public bool IsEnabled { get; init; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel for the Devices column on the Dashboard.
|
||||||
|
///
|
||||||
|
/// <para>Owns three observable collections of <see cref="DeviceItem"/> (one per kind)
|
||||||
|
/// and exposes toggle/refresh commands. Communicates with CAN and K-Line services
|
||||||
|
/// through <see cref="ICanService"/> and <see cref="IKwpService"/> and reads
|
||||||
|
/// cross-cutting state (test running, connection flags) via <see cref="MainViewModel"/>.</para>
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class DashboardDevicesViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly MainViewModel _root;
|
||||||
|
private readonly ICanService _can;
|
||||||
|
private readonly IKwpService _kwp;
|
||||||
|
|
||||||
|
/// <summary>Detected PCAN USB channels.</summary>
|
||||||
|
public ObservableCollection<DeviceItem> CanDevices { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>Detected FTDI K-Line adapters.</summary>
|
||||||
|
public ObservableCollection<DeviceItem> KLineDevices { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>Single bench-controller placeholder row.</summary>
|
||||||
|
public ObservableCollection<DeviceItem> BenchDevices { get; } = new();
|
||||||
|
|
||||||
|
public DashboardDevicesViewModel(MainViewModel root, ICanService can, IKwpService kwp)
|
||||||
|
{
|
||||||
|
_root = root;
|
||||||
|
_can = can;
|
||||||
|
_kwp = kwp;
|
||||||
|
|
||||||
|
root.PropertyChanged += OnRootPropertyChanged;
|
||||||
|
|
||||||
|
RefreshCanDevices();
|
||||||
|
RefreshKLineDevices();
|
||||||
|
RefreshBenchDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Refresh commands ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Re-enumerates attached PCAN USB channels.</summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void RefreshCanDevices()
|
||||||
|
{
|
||||||
|
CanDevices.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var channels = _can.EnumerateAttachedChannels();
|
||||||
|
foreach (var ch in channels)
|
||||||
|
{
|
||||||
|
CanDevices.Add(new DeviceItem
|
||||||
|
{
|
||||||
|
Kind = DeviceKind.Can,
|
||||||
|
Id = ch.Handle.ToString("X"),
|
||||||
|
CanHandle = ch.Handle,
|
||||||
|
Name = ch.DisplayName,
|
||||||
|
IsConnected = _root.IsCanConnected && _can.SelectedChannel == ch.Handle,
|
||||||
|
StateLabel = GetCanStateLabel(_root.IsCanConnected && _can.SelectedChannel == ch.Handle),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* PCAN DLL missing — leave list empty */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Re-enumerates connected FTDI K-Line adapters.</summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void RefreshKLineDevices()
|
||||||
|
{
|
||||||
|
KLineDevices.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint count = FtdiInterface.GetDevicesCount();
|
||||||
|
if (count == 0) return;
|
||||||
|
|
||||||
|
var list = new FT_DEVICE_INFO_NODE[count];
|
||||||
|
FtdiInterface.GetDeviceList(list);
|
||||||
|
|
||||||
|
foreach (var dev in list)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dev.SerialNumber)) continue;
|
||||||
|
bool connected = _kwp.KLineState == KLineConnectionState.Connected
|
||||||
|
&& _kwp.ConnectedPort == dev.SerialNumber;
|
||||||
|
bool failed = _kwp.KLineState == KLineConnectionState.Failed
|
||||||
|
&& _kwp.ConnectedPort == dev.SerialNumber;
|
||||||
|
KLineDevices.Add(new DeviceItem
|
||||||
|
{
|
||||||
|
Kind = DeviceKind.KLine,
|
||||||
|
Id = dev.SerialNumber,
|
||||||
|
Name = string.IsNullOrEmpty(dev.Description)
|
||||||
|
? dev.SerialNumber
|
||||||
|
: $"{dev.Description} ({dev.SerialNumber})",
|
||||||
|
IsConnected = connected,
|
||||||
|
StateLabel = GetKLineStateLabel(connected, failed),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* FTDI DLL not loaded — leave list empty */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshBenchDevices()
|
||||||
|
{
|
||||||
|
BenchDevices.Clear();
|
||||||
|
BenchDevices.Add(new DeviceItem
|
||||||
|
{
|
||||||
|
Kind = DeviceKind.Bench,
|
||||||
|
Id = "bench",
|
||||||
|
Name = Str("Dashboard.Devices.BenchRow"),
|
||||||
|
IsConnected = _root.IsBenchConnected,
|
||||||
|
StateLabel = GetBenchStateLabel(_root.IsBenchConnected),
|
||||||
|
IsEnabled = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toggle command ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connects or disconnects the device represented by <paramref name="item"/>.
|
||||||
|
/// Shows a confirmation dialog when a session is active or a test is running.
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ToggleDevice(DeviceItem? item)
|
||||||
|
{
|
||||||
|
if (item is null || !item.IsEnabled) return;
|
||||||
|
|
||||||
|
bool testRunning = _root.IsTestRunning;
|
||||||
|
bool sessionActive = item.IsConnected;
|
||||||
|
|
||||||
|
if (testRunning)
|
||||||
|
{
|
||||||
|
if (!Confirm(Str("Devices.Confirm.Title"), Str("Devices.Confirm.Body.TestRunning")))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (sessionActive)
|
||||||
|
{
|
||||||
|
string body = string.Format(Str("Devices.Confirm.Body.Active"),
|
||||||
|
item.Kind == DeviceKind.Can ? "CAN" : "K-Line");
|
||||||
|
if (!Confirm(Str("Devices.Confirm.Title"), body))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (item.Kind)
|
||||||
|
{
|
||||||
|
case DeviceKind.Can:
|
||||||
|
await ToggleCanAsync(item);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeviceKind.KLine:
|
||||||
|
await ToggleKLineAsync(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ToggleCanAsync(DeviceItem item)
|
||||||
|
{
|
||||||
|
if (item.IsConnected)
|
||||||
|
{
|
||||||
|
_root.DisconnectCanCommand.Execute(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try { _can.SelectedChannel = item.CanHandle; }
|
||||||
|
catch { return; }
|
||||||
|
_root.ConnectCanCommand.Execute(null);
|
||||||
|
}
|
||||||
|
await Task.Delay(600); // allow liveness event propagation
|
||||||
|
RefreshCanDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ToggleKLineAsync(DeviceItem item)
|
||||||
|
{
|
||||||
|
if (item.IsConnected)
|
||||||
|
{
|
||||||
|
_kwp.Disconnect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try { await _kwp.ConnectAsync(item.Id, CancellationToken.None); }
|
||||||
|
catch { /* ConnectAsync throws on init failure — leave state as-is */ }
|
||||||
|
}
|
||||||
|
RefreshKLineDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── State change wiring ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void OnRootPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case nameof(MainViewModel.IsCanConnected):
|
||||||
|
App.Current.Dispatcher.Invoke(RefreshCanDevices);
|
||||||
|
break;
|
||||||
|
case nameof(MainViewModel.KLineState):
|
||||||
|
App.Current.Dispatcher.Invoke(RefreshKLineDevices);
|
||||||
|
break;
|
||||||
|
case nameof(MainViewModel.IsBenchConnected):
|
||||||
|
App.Current.Dispatcher.Invoke(SyncBenchState);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncBenchState()
|
||||||
|
{
|
||||||
|
if (BenchDevices.Count == 0) return;
|
||||||
|
var row = BenchDevices[0];
|
||||||
|
row.IsConnected = _root.IsBenchConnected;
|
||||||
|
row.StateLabel = GetBenchStateLabel(_root.IsBenchConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static bool Confirm(string title, string message)
|
||||||
|
{
|
||||||
|
var vm = new ConfirmDialogViewModel
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Message = message,
|
||||||
|
ConfirmText = Str("Common.Yes"),
|
||||||
|
CancelText = Str("Common.Cancel"),
|
||||||
|
};
|
||||||
|
var dlg = new ConfirmDialog(vm) { Owner = Application.Current.MainWindow };
|
||||||
|
dlg.ShowDialog();
|
||||||
|
return vm.Accepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Str(string key)
|
||||||
|
=> Application.Current.TryFindResource(key) as string ?? key;
|
||||||
|
|
||||||
|
private static string GetCanStateLabel(bool connected)
|
||||||
|
=> connected ? Str("Dashboard.Devices.State.Connected") : Str("Dashboard.Devices.State.Idle");
|
||||||
|
|
||||||
|
private static string GetKLineStateLabel(bool connected, bool failed)
|
||||||
|
{
|
||||||
|
if (connected) return Str("Dashboard.Devices.State.Active");
|
||||||
|
if (failed) return Str("Dashboard.Devices.State.Failed");
|
||||||
|
return Str("Dashboard.Devices.State.Idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetBenchStateLabel(bool connected)
|
||||||
|
=> connected ? Str("Dashboard.Devices.State.Connected") : Str("Dashboard.Devices.State.Idle");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using HC_APTBS.Services;
|
||||||
|
|
||||||
namespace HC_APTBS.ViewModels.Pages
|
namespace HC_APTBS.ViewModels.Pages
|
||||||
{
|
{
|
||||||
@@ -17,9 +18,13 @@ namespace HC_APTBS.ViewModels.Pages
|
|||||||
/// <summary>Active alarm aggregator bound to the Dashboard alarm list.</summary>
|
/// <summary>Active alarm aggregator bound to the Dashboard alarm list.</summary>
|
||||||
public DashboardAlarmsViewModel Alarms => Root.DashboardAlarms;
|
public DashboardAlarmsViewModel Alarms => Root.DashboardAlarms;
|
||||||
|
|
||||||
public DashboardPageViewModel(MainViewModel root)
|
/// <summary>Devices column ViewModel — CAN, K-Line, and Bench device tiles.</summary>
|
||||||
|
public DashboardDevicesViewModel Devices { get; }
|
||||||
|
|
||||||
|
public DashboardPageViewModel(MainViewModel root, ICanService can, IKwpService kwp)
|
||||||
{
|
{
|
||||||
Root = root;
|
Root = root;
|
||||||
|
Devices = new DashboardDevicesViewModel(root, can, kwp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ namespace HC_APTBS.ViewModels.Pages
|
|||||||
/// <summary>Diagnostic Trouble Code list (§3.b).</summary>
|
/// <summary>Diagnostic Trouble Code list (§3.b).</summary>
|
||||||
public DtcListViewModel DtcList { get; }
|
public DtcListViewModel DtcList { get; }
|
||||||
|
|
||||||
/// <summary>Adaptation sub-section auth gate (§3.d).</summary>
|
|
||||||
public AuthGateViewModel AdaptationAuth { get; }
|
|
||||||
|
|
||||||
/// <summary>DFI management (§3.d).</summary>
|
/// <summary>DFI management (§3.d).</summary>
|
||||||
public DfiManageViewModel DfiViewModel => Root.DfiViewModel;
|
public DfiManageViewModel DfiViewModel => Root.DfiViewModel;
|
||||||
|
|
||||||
@@ -82,12 +79,10 @@ namespace HC_APTBS.ViewModels.Pages
|
|||||||
/// <summary>Constructs the page VM and subscribes to relevant Root state changes.</summary>
|
/// <summary>Constructs the page VM and subscribes to relevant Root state changes.</summary>
|
||||||
public PumpPageViewModel(
|
public PumpPageViewModel(
|
||||||
MainViewModel root,
|
MainViewModel root,
|
||||||
DtcListViewModel dtcList,
|
DtcListViewModel dtcList)
|
||||||
AuthGateViewModel adaptationAuth)
|
|
||||||
{
|
{
|
||||||
Root = root;
|
Root = root;
|
||||||
DtcList = dtcList;
|
DtcList = dtcList;
|
||||||
AdaptationAuth = adaptationAuth;
|
|
||||||
|
|
||||||
// Initialise derived flags from the current Root state.
|
// Initialise derived flags from the current Root state.
|
||||||
RefreshDerivedFlags();
|
RefreshDerivedFlags();
|
||||||
@@ -120,11 +115,6 @@ namespace HC_APTBS.ViewModels.Pages
|
|||||||
IsUnlockApplicable = Root.CurrentPump != null && Root.CurrentPump.UnlockType != 0;
|
IsUnlockApplicable = Root.CurrentPump != null && Root.CurrentPump.UnlockType != 0;
|
||||||
OnPropertyChanged(nameof(UnlockVm));
|
OnPropertyChanged(nameof(UnlockVm));
|
||||||
|
|
||||||
// When the pump changes, re-lock the adaptation gate — a new operator
|
|
||||||
// may be handling a different pump.
|
|
||||||
if (AdaptationAuth.IsAuthenticated)
|
|
||||||
AdaptationAuth.LockCommand.Execute(null);
|
|
||||||
|
|
||||||
// Drop any stale DTCs from the previous pump.
|
// Drop any stale DTCs from the previous pump.
|
||||||
DtcList.Reset();
|
DtcList.Reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,240 +1,345 @@
|
|||||||
<UserControl x:Class="HC_APTBS.Views.Pages.DashboardPage"
|
<UserControl x:Class="HC_APTBS.Views.Pages.DashboardPage"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
|
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="700" d:DesignWidth="980">
|
d:DesignHeight="860" d:DesignWidth="1740">
|
||||||
<!--
|
<!--
|
||||||
Dashboard — operator "at a glance" landing page.
|
Dashboard — operator "at a glance" landing page.
|
||||||
DataContext: DashboardPageViewModel. Read-first. Only Start / Stop / E-Stop are interactive.
|
DataContext: DashboardPageViewModel.
|
||||||
|
Layout: connection strip (top) | KPI tiles (left) + devices column (centre) + test/alarms column (right) | action bar (bottom)
|
||||||
|
Connect/Disconnect for CAN and K-Line is performed via the Devices column tiles.
|
||||||
-->
|
-->
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
|
||||||
<!-- Section card style -->
|
|
||||||
<Style x:Key="DashCard" TargetType="Border">
|
|
||||||
<Setter Property="Background" Value="#FAFAFA"/>
|
|
||||||
<Setter Property="BorderBrush" Value="#DDD"/>
|
|
||||||
<Setter Property="BorderThickness" Value="1"/>
|
|
||||||
<Setter Property="CornerRadius" Value="4"/>
|
|
||||||
<Setter Property="Padding" Value="10"/>
|
|
||||||
<Setter Property="Margin" Value="4"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="DashHeader" TargetType="TextBlock">
|
|
||||||
<Setter Property="FontSize" Value="13"/>
|
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
|
||||||
<Setter Property="Foreground" Value="#333"/>
|
|
||||||
<Setter Property="Margin" Value="0,0,0,6"/>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid Margin="6">
|
<Grid Margin="12">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="Auto"/> <!-- connection strip -->
|
||||||
<RowDefinition Height="Auto"/> <!-- footer: quick actions -->
|
<RowDefinition Height="*"/> <!-- main content -->
|
||||||
|
<RowDefinition Height="Auto"/> <!-- action bar -->
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="340"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- ── Left column: readings ───────────────────────────────────────── -->
|
<!-- ── Row 0: Connection strip ─────────────────────────────────────── -->
|
||||||
<Border Grid.Row="0" Grid.Column="0" Style="{StaticResource DashCard}">
|
<uc:DashboardConnectionView Grid.Row="0"/>
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.Readings}" Style="{StaticResource DashHeader}"/>
|
|
||||||
<uc:DashboardReadingsView/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- ── Right column: connections + test summary + alarms ───────────── -->
|
<!-- ── Row 1: KPI grid (left) + devices column (centre) + test/alarms (right) -->
|
||||||
<Grid Grid.Row="0" Grid.Column="1">
|
<Grid Grid.Row="1" Margin="0,0,0,8">
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="2.2*"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="0.9*" MinWidth="260"/>
|
||||||
|
<ColumnDefinition Width="1*" MinWidth="380"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Connections -->
|
<!-- KPI readings grid -->
|
||||||
<Border Grid.Row="0" Grid.Column="0" Style="{StaticResource DashCard}">
|
<uc:DashboardReadingsView Grid.Column="0" Margin="0,0,4,0"/>
|
||||||
<uc:DashboardConnectionView/>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Test summary -->
|
<!-- Devices column: CAN / K-Line / Bench device tiles -->
|
||||||
<Border Grid.Row="0" Grid.Column="1" Style="{StaticResource DashCard}">
|
<uc:DashboardDevicesView Grid.Column="1" Margin="4,0,4,0"/>
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.TestSummary}" Style="{StaticResource DashHeader}"/>
|
|
||||||
|
|
||||||
<!-- Active test view (when running) -->
|
<!-- Right column: test summary + alarms -->
|
||||||
<StackPanel Visibility="{Binding Root.IsTestRunning, Converter={StaticResource BoolToVis}}">
|
<Grid Grid.Column="2" Margin="4,0,0,0">
|
||||||
<Grid Margin="0,2">
|
<Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<RowDefinition Height="Auto"/>
|
||||||
<ColumnDefinition Width="80"/>
|
<RowDefinition Height="*"/>
|
||||||
<ColumnDefinition Width="*"/>
|
</Grid.RowDefinitions>
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.TestActive}" Foreground="#555" FontSize="12"/>
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Root.TestPanel.TestName}"
|
|
||||||
FontWeight="SemiBold" FontSize="13" TextTrimming="CharacterEllipsis"/>
|
|
||||||
</Grid>
|
|
||||||
<Grid Margin="0,2">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="80"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.TestPhase}" Foreground="#555" FontSize="12"/>
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Root.CurrentPhaseName}"
|
|
||||||
FontFamily="Consolas" FontSize="13" TextTrimming="CharacterEllipsis"/>
|
|
||||||
</Grid>
|
|
||||||
<TextBlock Text="{Binding Root.VerboseStatus}" FontStyle="Italic"
|
|
||||||
Foreground="#666" FontSize="11" Margin="0,6,0,0"
|
|
||||||
TextWrapping="Wrap"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- Idle view (last result or "no test run") -->
|
<!-- ── Test summary card ────────────────────────────────────── -->
|
||||||
|
<Border Grid.Row="0"
|
||||||
|
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1" CornerRadius="8" Padding="16" Margin="0,0,0,8">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel.Style>
|
<!-- Card header -->
|
||||||
<Style TargetType="StackPanel">
|
<DockPanel Margin="0,0,0,12">
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="Timer24" FontSize="16"
|
||||||
<Style.Triggers>
|
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
|
||||||
<DataTrigger Binding="{Binding Root.IsTestRunning}" Value="True">
|
Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
<TextBlock Text="{DynamicResource Dashboard.TestSummary}"
|
||||||
</DataTrigger>
|
FontSize="14" FontWeight="SemiBold"
|
||||||
</Style.Triggers>
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
</Style>
|
VerticalAlignment="Center"/>
|
||||||
</StackPanel.Style>
|
</DockPanel>
|
||||||
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.NoTestRunning}"
|
<!-- Running view -->
|
||||||
Foreground="#888" FontStyle="Italic" FontSize="12"/>
|
<Grid Visibility="{Binding Root.IsTestRunning, Converter={StaticResource BoolToVis}}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Border Margin="0,8,0,0" Padding="8,4" CornerRadius="3"
|
<!-- Test name + spinner -->
|
||||||
HorizontalAlignment="Left">
|
<DockPanel Grid.Row="0" Margin="0,0,0,8">
|
||||||
<Border.Style>
|
<ui:ProgressRing DockPanel.Dock="Right" Width="22" Height="22"
|
||||||
<Style TargetType="Border">
|
IsIndeterminate="True"
|
||||||
<Setter Property="Background" Value="#D62828"/>
|
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
<TextBlock Text="{Binding Root.TestPanel.TestName}"
|
||||||
|
FontSize="14" FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- Phase name -->
|
||||||
|
<Grid Grid.Row="1" Margin="0,0,0,6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.TestPhase}"
|
||||||
|
FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Root.CurrentPhaseName}"
|
||||||
|
FontSize="12" FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Elapsed timer -->
|
||||||
|
<Grid Grid.Row="2" Margin="0,0,0,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.TestSummary.Elapsed}"
|
||||||
|
FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Text="{Binding Root.TestElapsed, StringFormat={}{0:mm\\:ss}}"
|
||||||
|
FontSize="13" FontWeight="SemiBold" FontFamily="Consolas"
|
||||||
|
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Verbose status -->
|
||||||
|
<TextBlock Grid.Row="3" Text="{Binding Root.VerboseStatus}"
|
||||||
|
FontSize="11" FontStyle="Italic"
|
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
|
||||||
|
TextWrapping="Wrap" MaxHeight="32"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Idle view -->
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel.Style>
|
||||||
|
<Style TargetType="StackPanel">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<MultiDataTrigger>
|
<DataTrigger Binding="{Binding Root.IsTestRunning}" Value="True">
|
||||||
<MultiDataTrigger.Conditions>
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
<Condition Binding="{Binding Root.IsTestSaved}" Value="False"/>
|
|
||||||
</MultiDataTrigger.Conditions>
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
</MultiDataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
|
|
||||||
<Setter Property="Background" Value="#26C200"/>
|
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</Border.Style>
|
</StackPanel.Style>
|
||||||
<TextBlock Foreground="White" FontWeight="Bold" FontSize="12">
|
|
||||||
<TextBlock.Style>
|
<TextBlock Text="{DynamicResource Dashboard.NoTestRunning}"
|
||||||
<Style TargetType="TextBlock">
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.LastTestFail}"/>
|
FontStyle="Italic" FontSize="12" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<!-- Last test result pill — only visible while result is unsaved -->
|
||||||
|
<Border Padding="10,6" CornerRadius="6" HorizontalAlignment="Left">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemFillColorCriticalBackgroundBrush}"/>
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding Root.IsTestSaved}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
|
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.LastTestPass}"/>
|
<Setter Property="Background" Value="{DynamicResource SystemFillColorSuccessBackgroundBrush}"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</TextBlock.Style>
|
</Border.Style>
|
||||||
</TextBlock>
|
<StackPanel Orientation="Horizontal">
|
||||||
</Border>
|
<ui:SymbolIcon FontSize="14" Margin="0,0,6,0" VerticalAlignment="Center">
|
||||||
|
<ui:SymbolIcon.Style>
|
||||||
|
<Style TargetType="ui:SymbolIcon">
|
||||||
|
<Setter Property="Symbol" Value="DismissCircle24"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
|
||||||
|
<Setter Property="Symbol" Value="CheckmarkCircle24"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ui:SymbolIcon.Style>
|
||||||
|
</ui:SymbolIcon>
|
||||||
|
<TextBlock FontWeight="SemiBold" FontSize="12" VerticalAlignment="Center">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.LastTestFail}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.LastTestPass}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Border>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Alarms (spans both columns of the right grid) -->
|
<!-- ── Active alarms card ───────────────────────────────────── -->
|
||||||
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource DashCard}">
|
<Border Grid.Row="1"
|
||||||
<DockPanel>
|
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
<TextBlock DockPanel.Dock="Top" Text="{DynamicResource Dashboard.Alarms}"
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
Style="{StaticResource DashHeader}"/>
|
BorderThickness="1" CornerRadius="8" Padding="16">
|
||||||
|
<DockPanel>
|
||||||
|
<!-- Card header -->
|
||||||
|
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,12">
|
||||||
|
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="AlertUrgent24" FontSize="16"
|
||||||
|
Foreground="{DynamicResource SystemFillColorCautionBrush}"
|
||||||
|
Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Alarms}"
|
||||||
|
FontSize="14" FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
<!-- "System OK" banner when no alarms -->
|
<!-- System OK banner (no active alarms) -->
|
||||||
<Border Background="#26C200" CornerRadius="3" Padding="10,6"
|
<Border DockPanel.Dock="Top"
|
||||||
HorizontalAlignment="Left" VerticalAlignment="Top"
|
Background="{DynamicResource SystemFillColorSuccessBackgroundBrush}"
|
||||||
Visibility="{Binding Alarms.IsClear, Converter={StaticResource BoolToVis}}">
|
BorderBrush="{DynamicResource SystemFillColorSuccessBrush}"
|
||||||
<TextBlock Text="{DynamicResource Dashboard.AlarmsNone}"
|
BorderThickness="1" CornerRadius="6" Padding="12,8" Margin="0,0,0,4"
|
||||||
Foreground="White" FontWeight="Bold" FontSize="12"/>
|
Visibility="{Binding Alarms.IsClear, Converter={StaticResource BoolToVis}}">
|
||||||
</Border>
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ui:SymbolIcon Symbol="CheckmarkCircle24" FontSize="16"
|
||||||
|
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
|
||||||
|
Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.AlarmsNone}"
|
||||||
|
FontWeight="SemiBold" FontSize="12"
|
||||||
|
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- Active alarm list -->
|
<!-- Active alarm list -->
|
||||||
<ItemsControl ItemsSource="{Binding Alarms.ActiveAlarms}">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<ItemsControl.Style>
|
<ScrollViewer.Style>
|
||||||
<Style TargetType="ItemsControl">
|
<Style TargetType="ScrollViewer">
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding Alarms.IsClear}" Value="True">
|
<DataTrigger Binding="{Binding Alarms.IsClear}" Value="True">
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</ItemsControl.Style>
|
</ScrollViewer.Style>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl ItemsSource="{Binding Alarms.ActiveAlarms}">
|
||||||
<DataTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<Border Margin="0,2" Padding="8,4" CornerRadius="3">
|
<DataTemplate>
|
||||||
<Border.Style>
|
<Border Margin="0,3" Padding="10,8" CornerRadius="6">
|
||||||
<Style TargetType="Border">
|
<Border.Style>
|
||||||
<Setter Property="Background" Value="#FFB020"/> <!-- warning -->
|
<Style TargetType="Border">
|
||||||
<Style.Triggers>
|
<Setter Property="Background" Value="{DynamicResource SystemFillColorCautionBackgroundBrush}"/>
|
||||||
<DataTrigger Binding="{Binding IsCritical}" Value="True">
|
<Style.Triggers>
|
||||||
<Setter Property="Background" Value="#D62828"/> <!-- critical -->
|
<DataTrigger Binding="{Binding IsCritical}" Value="True">
|
||||||
</DataTrigger>
|
<Setter Property="Background" Value="{DynamicResource SystemFillColorCriticalBackgroundBrush}"/>
|
||||||
</Style.Triggers>
|
</DataTrigger>
|
||||||
</Style>
|
</Style.Triggers>
|
||||||
</Border.Style>
|
</Style>
|
||||||
<Grid>
|
</Border.Style>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<TextBlock Text="●" Foreground="White" FontSize="14" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Description}"
|
<ui:SymbolIcon VerticalAlignment="Center" Margin="0,0,8,0">
|
||||||
Foreground="White" FontWeight="SemiBold" FontSize="12"
|
<ui:SymbolIcon.Style>
|
||||||
VerticalAlignment="Center" TextWrapping="Wrap"/>
|
<Style TargetType="ui:SymbolIcon">
|
||||||
<TextBlock Grid.Column="2" Foreground="#FFF" FontSize="11" FontFamily="Consolas"
|
<Setter Property="Symbol" Value="AlertOn24"/>
|
||||||
VerticalAlignment="Center" Margin="6,0,0,0">
|
<Setter Property="FontSize" Value="16"/>
|
||||||
<Run Text="bit "/><Run Text="{Binding Bit, Mode=OneWay}"/>
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCautionBrush}"/>
|
||||||
</TextBlock>
|
<Style.Triggers>
|
||||||
</Grid>
|
<DataTrigger Binding="{Binding IsCritical}" Value="True">
|
||||||
</Border>
|
<Setter Property="Symbol" Value="ErrorCircle24"/>
|
||||||
</DataTemplate>
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}"/>
|
||||||
</ItemsControl.ItemTemplate>
|
</DataTrigger>
|
||||||
</ItemsControl>
|
</Style.Triggers>
|
||||||
</DockPanel>
|
</Style>
|
||||||
</Border>
|
</ui:SymbolIcon.Style>
|
||||||
|
</ui:SymbolIcon>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Description}"
|
||||||
|
FontWeight="SemiBold" FontSize="12"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
VerticalAlignment="Center" TextWrapping="Wrap"/>
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
FontSize="11" FontFamily="Consolas"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
VerticalAlignment="Center" Margin="10,0,0,0">
|
||||||
|
<Run Text="bit "/><Run Text="{Binding Bit, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- ── Footer: quick actions ───────────────────────────────────────── -->
|
<!-- ── Row 2: Action bar ────────────────────────────────────────────── -->
|
||||||
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
|
<Border Grid.Row="2"
|
||||||
Background="#F0F0F0" BorderBrush="#CCC" BorderThickness="0,1,0,0"
|
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||||
Padding="8" Margin="4,4,4,4">
|
BorderThickness="0,1,0,0" Padding="4,10">
|
||||||
<DockPanel LastChildFill="False">
|
<DockPanel LastChildFill="False">
|
||||||
<Button DockPanel.Dock="Left"
|
|
||||||
Content="{DynamicResource Dashboard.Action.StartTest}"
|
|
||||||
Command="{Binding Root.StartTestCommand}"
|
|
||||||
Height="44" MinWidth="140" FontSize="13" FontWeight="Bold"
|
|
||||||
Foreground="DarkGreen" Margin="0,0,6,0"
|
|
||||||
ToolTipService.ShowOnDisabled="True"
|
|
||||||
ToolTip="{DynamicResource Dashboard.Action.StartTest.Tip}"/>
|
|
||||||
|
|
||||||
<Button DockPanel.Dock="Left"
|
<!-- E-Stop — pinned right, visually dominant -->
|
||||||
Content="{DynamicResource Dashboard.Action.Stop}"
|
<ui:Button DockPanel.Dock="Right"
|
||||||
Command="{Binding Root.StopTestCommand}"
|
Appearance="Danger"
|
||||||
Height="44" MinWidth="120" FontSize="13" FontWeight="Bold"
|
Content="{DynamicResource Dashboard.Action.EmergencyStop}"
|
||||||
Foreground="DarkRed" Margin="0,0,6,0"/>
|
Command="{Binding Root.EmergencyStopCommand}"
|
||||||
|
FontSize="15" FontWeight="Bold"
|
||||||
|
MinWidth="240" Height="50">
|
||||||
|
<ui:Button.Icon>
|
||||||
|
<ui:SymbolIcon Symbol="DismissCircle24"/>
|
||||||
|
</ui:Button.Icon>
|
||||||
|
</ui:Button>
|
||||||
|
|
||||||
|
<!-- Start Test -->
|
||||||
|
<ui:Button DockPanel.Dock="Left"
|
||||||
|
Appearance="Primary"
|
||||||
|
Content="{DynamicResource Dashboard.Action.StartTest}"
|
||||||
|
Command="{Binding Root.StartTestCommand}"
|
||||||
|
MinWidth="160" Height="46"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
ToolTipService.ShowOnDisabled="True"
|
||||||
|
ToolTip="{DynamicResource Dashboard.Action.StartTest.Tip}">
|
||||||
|
<ui:Button.Icon>
|
||||||
|
<ui:SymbolIcon Symbol="PlayCircle24"/>
|
||||||
|
</ui:Button.Icon>
|
||||||
|
</ui:Button>
|
||||||
|
|
||||||
|
<!-- Stop -->
|
||||||
|
<ui:Button DockPanel.Dock="Left"
|
||||||
|
Appearance="Secondary"
|
||||||
|
Content="{DynamicResource Dashboard.Action.Stop}"
|
||||||
|
Command="{Binding Root.StopTestCommand}"
|
||||||
|
MinWidth="120" Height="46">
|
||||||
|
<ui:Button.Icon>
|
||||||
|
<ui:SymbolIcon Symbol="RecordStop24"/>
|
||||||
|
</ui:Button.Icon>
|
||||||
|
</ui:Button>
|
||||||
|
|
||||||
<!-- E-Stop pinned right, red, always visible -->
|
|
||||||
<Button DockPanel.Dock="Right"
|
|
||||||
Content="{DynamicResource Dashboard.Action.EmergencyStop}"
|
|
||||||
Command="{Binding Root.EmergencyStopCommand}"
|
|
||||||
Height="44" MinWidth="200" FontSize="14" FontWeight="Bold"
|
|
||||||
Foreground="White" Background="#D62828" BorderBrush="#8B0000"
|
|
||||||
BorderThickness="2"/>
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -151,25 +151,8 @@
|
|||||||
</ListBoxItem>
|
</ListBoxItem>
|
||||||
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Adaptation}"
|
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Adaptation}"
|
||||||
IsEnabled="{Binding IsPumpSelected}">
|
IsEnabled="{Binding IsPumpSelected}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
|
||||||
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
|
Style="{StaticResource SubNavText}"/>
|
||||||
Style="{StaticResource SubNavText}"/>
|
|
||||||
<TextBlock Text=" 🔒" FontSize="11"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding DataContext.AdaptationAuth.IsAuthenticated,
|
|
||||||
RelativeSource={RelativeSource AncestorType=UserControl}}"
|
|
||||||
Value="True">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</ListBoxItem>
|
</ListBoxItem>
|
||||||
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Unlock}">
|
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Unlock}">
|
||||||
<ListBoxItem.Style>
|
<ListBoxItem.Style>
|
||||||
@@ -216,27 +199,21 @@
|
|||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<!-- 3d Adaptation (auth-gated) -->
|
<!-- 3d Adaptation -->
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<uc:AuthGateView DataContext="{Binding AdaptationAuth}">
|
<Border Background="#FAFAFA" BorderBrush="#DDD"
|
||||||
<uc:AuthGateView.GatedContent>
|
BorderThickness="1" CornerRadius="4"
|
||||||
<Border Background="#FAFAFA" BorderBrush="#DDD"
|
Padding="12" Margin="6">
|
||||||
BorderThickness="1" CornerRadius="4"
|
<StackPanel>
|
||||||
Padding="12" Margin="6">
|
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
|
||||||
<StackPanel>
|
FontSize="15" FontWeight="SemiBold"
|
||||||
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
|
Foreground="#333" Margin="0,0,0,8"/>
|
||||||
FontSize="15" FontWeight="SemiBold"
|
<uc:DfiManageView DataContext="{Binding DfiViewModel}"/>
|
||||||
Foreground="#333" Margin="0,0,0,8"/>
|
<Separator Margin="0,10"/>
|
||||||
<uc:DfiManageView DataContext="{Binding DataContext.DfiViewModel,
|
<uc:PumpControlView DataContext="{Binding PumpControl}"/>
|
||||||
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
</StackPanel>
|
||||||
<Separator Margin="0,10"/>
|
</Border>
|
||||||
<uc:PumpControlView DataContext="{Binding DataContext.PumpControl,
|
|
||||||
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</uc:AuthGateView.GatedContent>
|
|
||||||
</uc:AuthGateView>
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<ToggleButton IsChecked="{Binding BenchControl.IsDirectionRight}"
|
<ToggleButton IsChecked="{Binding BenchControl.IsDirectionRight}"
|
||||||
Height="32" FontSize="12" FontWeight="SemiBold">
|
Height="32" FontSize="12" FontWeight="SemiBold">
|
||||||
<ToggleButton.Style>
|
<ToggleButton.Style>
|
||||||
<Style TargetType="ToggleButton">
|
<Style TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.Left}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.Left}"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
@@ -72,13 +72,13 @@
|
|||||||
<ToggleButton IsChecked="{Binding BenchControl.IsOilPumpOn}"
|
<ToggleButton IsChecked="{Binding BenchControl.IsOilPumpOn}"
|
||||||
Height="32" FontSize="12" FontWeight="SemiBold">
|
Height="32" FontSize="12" FontWeight="SemiBold">
|
||||||
<ToggleButton.Style>
|
<ToggleButton.Style>
|
||||||
<Style TargetType="ToggleButton">
|
<Style TargetType="ToggleButton" BasedOn="{StaticResource FluentStateToggle}">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.OilOff}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.OilOff}"/>
|
||||||
<Setter Property="Background" Value="LightGray"/>
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.OilOn}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.OilOn}"/>
|
||||||
<Setter Property="Background" Value="#80FF80"/>
|
<Setter Property="Background" Value="#26C200"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
</Trigger>
|
</Trigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
|
|||||||
@@ -1,158 +1,232 @@
|
|||||||
<UserControl x:Class="HC_APTBS.Views.UserControls.DashboardConnectionView"
|
<UserControl x:Class="HC_APTBS.Views.UserControls.DashboardConnectionView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
|
xmlns:models="clr-namespace:HC_APTBS.Models"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:models="clr-namespace:HC_APTBS.Models"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="160" d:DesignWidth="220">
|
d:DesignHeight="72" d:DesignWidth="1100">
|
||||||
<!--
|
<!--
|
||||||
Connection status block for the Dashboard.
|
Connection status strip for the Dashboard — four horizontal chips + pump name badge.
|
||||||
DataContext is DashboardPageViewModel; pills read from Root.X.
|
DataContext is DashboardPageViewModel; reads Root.IsCanConnected, Root.IsBenchConnected,
|
||||||
Pill uses the shared ConnIndicator style. Gray = offline, green = live, red = K-Line failed.
|
Root.IsPumpConnected, Root.KLineState via DataTriggers.
|
||||||
-->
|
-->
|
||||||
<StackPanel>
|
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
<TextBlock Text="{DynamicResource Dashboard.Connections}"
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
FontSize="13" FontWeight="SemiBold" Foreground="#333"
|
BorderThickness="1" CornerRadius="8"
|
||||||
Margin="0,0,0,6"/>
|
Padding="14,10" Margin="0,0,0,8">
|
||||||
|
<DockPanel LastChildFill="False">
|
||||||
|
|
||||||
<!-- CAN bus -->
|
<!-- ── Pump name badge (right-docked) ──────────────────────────── -->
|
||||||
<Grid Margin="0,2">
|
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="16,0,0,0">
|
||||||
<Grid.ColumnDefinitions>
|
<TextBlock Text="{DynamicResource Dashboard.Conn.Pump.Label}"
|
||||||
<ColumnDefinition Width="*"/>
|
FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
<ColumnDefinition Width="80"/>
|
VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||||
</Grid.ColumnDefinitions>
|
<!-- Model name — shown when pump is selected -->
|
||||||
<TextBlock Text="{DynamicResource Dashboard.Conn.Can}" VerticalAlignment="Center" FontSize="12"/>
|
<TextBlock Text="{Binding Root.PumpIdentification.CurrentPump.Model}"
|
||||||
<Border Grid.Column="1" MinWidth="72" Height="22">
|
FontSize="13" FontWeight="SemiBold"
|
||||||
<Border.Style>
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
|
VerticalAlignment="Center">
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Root.IsCanConnected}" Value="True">
|
|
||||||
<Setter Property="Background" Value="#26C200"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Border.Style>
|
|
||||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
||||||
FontSize="10" FontWeight="SemiBold" Foreground="White"
|
|
||||||
Text="{DynamicResource Dashboard.StateOnline}">
|
|
||||||
<TextBlock.Style>
|
<TextBlock.Style>
|
||||||
<Style TargetType="TextBlock">
|
<Style TargetType="TextBlock">
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding Root.IsCanConnected}" Value="True">
|
<DataTrigger Binding="{Binding Root.PumpIdentification.CurrentPump}" Value="{x:Null}">
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</TextBlock.Style>
|
</TextBlock.Style>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Border>
|
<!-- Placeholder — shown when no pump is selected -->
|
||||||
</Grid>
|
<TextBlock Text="{DynamicResource Dashboard.Conn.NoPump}"
|
||||||
|
FontSize="12" FontStyle="Italic"
|
||||||
<!-- Bench liveness -->
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
|
||||||
<Grid Margin="0,2">
|
VerticalAlignment="Center">
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
<ColumnDefinition Width="80"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.Conn.Bench}" VerticalAlignment="Center" FontSize="12"/>
|
|
||||||
<Border Grid.Column="1" MinWidth="72" Height="22">
|
|
||||||
<Border.Style>
|
|
||||||
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Root.IsBenchConnected}" Value="True">
|
|
||||||
<Setter Property="Background" Value="#26C200"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Border.Style>
|
|
||||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
||||||
FontSize="10" FontWeight="SemiBold" Foreground="White">
|
|
||||||
<TextBlock.Style>
|
<TextBlock.Style>
|
||||||
<Style TargetType="TextBlock">
|
<Style TargetType="TextBlock">
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding Root.IsBenchConnected}" Value="True">
|
<DataTrigger Binding="{Binding Root.PumpIdentification.CurrentPump}" Value="{x:Null}">
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</TextBlock.Style>
|
</TextBlock.Style>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Border>
|
</StackPanel>
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Pump liveness -->
|
<!-- ── Connection chips ─────────────────────────────────────────── -->
|
||||||
<Grid Margin="0,2">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*"/>
|
<!-- CAN bus chip -->
|
||||||
<ColumnDefinition Width="80"/>
|
<Border Style="{StaticResource ConnChip}">
|
||||||
</Grid.ColumnDefinitions>
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<TextBlock Text="{DynamicResource Dashboard.Conn.Pump}" VerticalAlignment="Center" FontSize="12"/>
|
<ui:SymbolIcon Symbol="PlugConnected24" FontSize="15"
|
||||||
<Border Grid.Column="1" MinWidth="72" Height="22">
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"/>
|
||||||
<Border.Style>
|
<TextBlock Text="{DynamicResource Dashboard.Conn.Can}" FontSize="12"
|
||||||
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" VerticalAlignment="Center"
|
||||||
<Style.Triggers>
|
Margin="0,0,10,0"/>
|
||||||
<DataTrigger Binding="{Binding Root.IsPumpConnected}" Value="True">
|
<Ellipse>
|
||||||
<Setter Property="Background" Value="#26C200"/>
|
<Ellipse.Style>
|
||||||
</DataTrigger>
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
</Style.Triggers>
|
<Style.Triggers>
|
||||||
</Style>
|
<DataTrigger Binding="{Binding Root.IsCanConnected}" Value="True">
|
||||||
</Border.Style>
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
</DataTrigger>
|
||||||
FontSize="10" FontWeight="SemiBold" Foreground="White">
|
</Style.Triggers>
|
||||||
<TextBlock.Style>
|
</Style>
|
||||||
<Style TargetType="TextBlock">
|
</Ellipse.Style>
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
|
</Ellipse>
|
||||||
|
<TextBlock FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.IsCanConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Bench controller chip -->
|
||||||
|
<Border Style="{StaticResource ConnChip}">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<ui:SymbolIcon Symbol="DesktopPulse24" FontSize="15"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Conn.Bench}" FontSize="12"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" VerticalAlignment="Center"
|
||||||
|
Margin="0,0,10,0"/>
|
||||||
|
<Ellipse>
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.IsBenchConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.IsBenchConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Pump ECU chip -->
|
||||||
|
<Border Style="{StaticResource ConnChip}">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<ui:SymbolIcon Symbol="Server24" FontSize="15"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Conn.Pump}" FontSize="12"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" VerticalAlignment="Center"
|
||||||
|
Margin="0,0,10,0"/>
|
||||||
|
<Ellipse>
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.IsPumpConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.IsPumpConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- K-Line session chip -->
|
||||||
|
<Border>
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border" BasedOn="{StaticResource ConnChip}">
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding Root.IsPumpConnected}" Value="True">
|
<DataTrigger Binding="{Binding Root.KLineState}"
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
|
Value="{x:Static models:KLineConnectionState.Failed}">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SystemFillColorCautionBackgroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemFillColorCautionBrush}"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</TextBlock.Style>
|
</Border.Style>
|
||||||
</TextBlock>
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
</Border>
|
<ui:SymbolIcon Symbol="UsbPlug24" FontSize="15"
|
||||||
</Grid>
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Conn.KLine}" FontSize="12"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" VerticalAlignment="Center"
|
||||||
|
Margin="0,0,10,0"/>
|
||||||
|
<Ellipse>
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.KLineState}"
|
||||||
|
Value="{x:Static models:KLineConnectionState.Connected}">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Root.KLineState}"
|
||||||
|
Value="{x:Static models:KLineConnectionState.Failed}">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorCautionBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateClosed}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Root.KLineState}"
|
||||||
|
Value="{x:Static models:KLineConnectionState.Connected}">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOpen}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Root.KLineState}"
|
||||||
|
Value="{x:Static models:KLineConnectionState.Failed}">
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.StateFailed}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCautionBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- K-Line session -->
|
</StackPanel>
|
||||||
<Grid Margin="0,2">
|
</DockPanel>
|
||||||
<Grid.ColumnDefinitions>
|
</Border>
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
<ColumnDefinition Width="80"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="{DynamicResource Dashboard.Conn.KLine}" VerticalAlignment="Center" FontSize="12"/>
|
|
||||||
<Border Grid.Column="1" MinWidth="72" Height="22">
|
|
||||||
<Border.Style>
|
|
||||||
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Root.KLineState}" Value="{x:Static models:KLineConnectionState.Connected}">
|
|
||||||
<Setter Property="Background" Value="#26C200"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding Root.KLineState}" Value="{x:Static models:KLineConnectionState.Failed}">
|
|
||||||
<Setter Property="Background" Value="#FF3333"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Border.Style>
|
|
||||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
||||||
FontSize="10" FontWeight="SemiBold" Foreground="White">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateClosed}"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Root.KLineState}" Value="{x:Static models:KLineConnectionState.Connected}">
|
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOpen}"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding Root.KLineState}" Value="{x:Static models:KLineConnectionState.Failed}">
|
|
||||||
<Setter Property="Text" Value="{DynamicResource Dashboard.StateFailed}"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
272
Views/UserControls/DashboardDevicesView.xaml
Normal file
272
Views/UserControls/DashboardDevicesView.xaml
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
<UserControl x:Class="HC_APTBS.Views.UserControls.DashboardDevicesView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
|
xmlns:vm="clr-namespace:HC_APTBS.ViewModels.Pages"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="600" d:DesignWidth="280">
|
||||||
|
<!--
|
||||||
|
Devices column — three equal-height tiles (CAN / K-Line / Bench).
|
||||||
|
DataContext is DashboardPageViewModel; all commands/collections are under Devices.
|
||||||
|
-->
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- ── CAN tile ─────────────────────────────────────────────────────── -->
|
||||||
|
<Border Grid.Row="0" Style="{StaticResource KpiTile}" Margin="0,0,0,4">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Tile header -->
|
||||||
|
<DockPanel Grid.Row="0" Margin="0,0,0,8">
|
||||||
|
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="PlugConnected24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
Margin="0,0,6,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Devices.Can}"
|
||||||
|
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
|
||||||
|
<!-- Refresh button -->
|
||||||
|
<Button DockPanel.Dock="Right"
|
||||||
|
Command="{Binding Devices.RefreshCanDevicesCommand}"
|
||||||
|
Background="Transparent" BorderThickness="0"
|
||||||
|
Padding="4" Cursor="Hand"
|
||||||
|
ToolTip="{DynamicResource Dashboard.Devices.Refresh}">
|
||||||
|
<ui:SymbolIcon Symbol="ArrowClockwise24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
|
||||||
|
</Button>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- Device list or empty placeholder -->
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<Grid>
|
||||||
|
<!-- Placeholder when no devices are found -->
|
||||||
|
<TextBlock>
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.Devices.None}"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="FontStyle" Value="Italic"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Devices.CanDevices.Count}" Value="0">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding Devices.CanDevices}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:DeviceItem}">
|
||||||
|
<Button Style="{StaticResource DeviceRow}"
|
||||||
|
Command="{Binding DataContext.Devices.ToggleDeviceCommand,
|
||||||
|
RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
IsEnabled="{Binding IsEnabled}">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="{Binding StateLabel}"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
FontSize="11" FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
VerticalAlignment="Center" Margin="6,0,0,0"/>
|
||||||
|
<Ellipse DockPanel.Dock="Left">
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock Text="{Binding Name}"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ── K-Line tile ──────────────────────────────────────────────────── -->
|
||||||
|
<Border Grid.Row="1" Style="{StaticResource KpiTile}" Margin="0,4,0,4">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Tile header -->
|
||||||
|
<DockPanel Grid.Row="0" Margin="0,0,0,8">
|
||||||
|
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="UsbPlug24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
Margin="0,0,6,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Devices.Kline}"
|
||||||
|
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
|
||||||
|
<Button DockPanel.Dock="Right"
|
||||||
|
Command="{Binding Devices.RefreshKLineDevicesCommand}"
|
||||||
|
Background="Transparent" BorderThickness="0"
|
||||||
|
Padding="4" Cursor="Hand"
|
||||||
|
ToolTip="{DynamicResource Dashboard.Devices.Refresh}">
|
||||||
|
<ui:SymbolIcon Symbol="ArrowClockwise24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
|
||||||
|
</Button>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<Grid>
|
||||||
|
<TextBlock>
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Setter Property="Text" Value="{DynamicResource Dashboard.Devices.None}"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="FontStyle" Value="Italic"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Devices.KLineDevices.Count}" Value="0">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding Devices.KLineDevices}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:DeviceItem}">
|
||||||
|
<Button Style="{StaticResource DeviceRow}"
|
||||||
|
Command="{Binding DataContext.Devices.ToggleDeviceCommand,
|
||||||
|
RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
IsEnabled="{Binding IsEnabled}">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="{Binding StateLabel}"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
FontSize="11" FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center" Margin="6,0,0,0">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<Ellipse DockPanel.Dock="Left">
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock Text="{Binding Name}"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ── Bench tile ───────────────────────────────────────────────────── -->
|
||||||
|
<Border Grid.Row="2" Style="{StaticResource KpiTile}" Margin="0,4,0,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Tile header (no refresh button — nothing to enumerate) -->
|
||||||
|
<DockPanel Grid.Row="0" Margin="0,0,0,8">
|
||||||
|
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="DesktopPulse24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
Margin="0,0,6,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Devices.Bench}"
|
||||||
|
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<ItemsControl ItemsSource="{Binding Devices.BenchDevices}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:DeviceItem}">
|
||||||
|
<!-- IsEnabled=false disables hover; Bench rows are not clickable -->
|
||||||
|
<Button Style="{StaticResource DeviceRow}"
|
||||||
|
IsEnabled="{Binding IsEnabled}">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="{Binding StateLabel}"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
FontSize="11" FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center" Margin="6,0,0,0">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<Ellipse DockPanel.Dock="Left">
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock Text="{Binding Name}"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
16
Views/UserControls/DashboardDevicesView.xaml.cs
Normal file
16
Views/UserControls/DashboardDevicesView.xaml.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace HC_APTBS.Views.UserControls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Devices column for the Dashboard page — CAN, K-Line, and Bench device tiles.
|
||||||
|
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.Pages.DashboardPageViewModel"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class DashboardDevicesView : UserControl
|
||||||
|
{
|
||||||
|
public DashboardDevicesView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,103 +1,142 @@
|
|||||||
<UserControl x:Class="HC_APTBS.Views.UserControls.DashboardReadingsView"
|
<UserControl x:Class="HC_APTBS.Views.UserControls.DashboardReadingsView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="300" d:DesignWidth="320">
|
d:DesignHeight="340" d:DesignWidth="900">
|
||||||
<!--
|
<!--
|
||||||
Dashboard-only compact LCD panel.
|
Dashboard KPI readings grid — 6 Fluent tiles in a 2-row × 3-column uniform grid.
|
||||||
DataContext is DashboardPageViewModel; binds via Root.X to MainViewModel live readings.
|
DataContext is DashboardPageViewModel; binds via Root.X to MainViewModel live readings.
|
||||||
Read-first: no controls, no popups.
|
|
||||||
-->
|
-->
|
||||||
<StackPanel>
|
<UniformGrid Rows="2" Columns="3">
|
||||||
|
|
||||||
<!-- RPM: oversized single-line read -->
|
<!-- Tile 1: RPM -->
|
||||||
<Border Style="{StaticResource LcdBlue}" Margin="0,0,0,4" Padding="10,6">
|
<Border Style="{StaticResource KpiTile}">
|
||||||
<Grid Height="76">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="{Binding Root.BenchRpm, StringFormat=F0}"
|
|
||||||
FontSize="60" FontWeight="UltraBold" Foreground="#EBEBFF"
|
|
||||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
|
||||||
FontFamily="Consolas"/>
|
|
||||||
<TextBlock Grid.Column="1" Text="{DynamicResource Bench.Rpm}"
|
|
||||||
FontSize="18" Foreground="#FFFFEB6E"
|
|
||||||
VerticalAlignment="Center" Margin="6,0,0,0"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Temperatures + Pressures side by side -->
|
|
||||||
<Grid Margin="0,0,0,4">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- Pressures -->
|
|
||||||
<Border Grid.Column="0" Style="{StaticResource LcdBlue}" Margin="0,0,2,0" Padding="8,4">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="60"/>
|
|
||||||
<ColumnDefinition/>
|
|
||||||
<ColumnDefinition Width="30"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Text="{DynamicResource Bench.P1}" Grid.Row="0" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
|
|
||||||
<TextBlock Text="{DynamicResource Bench.P2}" Grid.Row="1" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
|
|
||||||
<TextBlock Text="{Binding Root.Pressure, StringFormat=F1}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="20" FontFamily="Consolas"/>
|
|
||||||
<TextBlock Text="{Binding Root.Pressure2, StringFormat=F1}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="20" FontFamily="Consolas"/>
|
|
||||||
<TextBlock Text="bar" Grid.Row="0" Grid.Column="2" Foreground="#EBEBFF" FontSize="13" VerticalAlignment="Center" Margin="4,0"/>
|
|
||||||
<TextBlock Text="bar" Grid.Row="1" Grid.Column="2" Foreground="#EBEBFF" FontSize="13" VerticalAlignment="Center" Margin="4,0"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Temperatures -->
|
|
||||||
<Border Grid.Column="1" Style="{StaticResource LcdBlue}" Margin="2,0,0,0" Padding="8,4">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="60"/>
|
|
||||||
<ColumnDefinition/>
|
|
||||||
<ColumnDefinition Width="30"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Text="{DynamicResource Bench.TempIn}" Grid.Row="0" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
|
|
||||||
<TextBlock Text="{DynamicResource Bench.TempOut}" Grid.Row="1" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
|
|
||||||
<TextBlock Text="{DynamicResource Bench.TempTank}" Grid.Row="2" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
|
|
||||||
<TextBlock Text="{Binding Root.TempIn, StringFormat=F1}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Foreground="#EBEBFF" FontSize="20" FontFamily="Consolas"/>
|
|
||||||
<TextBlock Text="{Binding Root.TempOut, StringFormat=F1}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Foreground="#EBEBFF" FontSize="20" FontFamily="Consolas"/>
|
|
||||||
<TextBlock Text="{Binding Root.BenchTemp, StringFormat=F1}" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Foreground="#EBEBFF" FontSize="20" FontFamily="Consolas"/>
|
|
||||||
<TextBlock Text="°C" Grid.Row="0" Grid.Column="2" Foreground="#EBEBFF" FontSize="13" VerticalAlignment="Center" Margin="4,0"/>
|
|
||||||
<TextBlock Text="°C" Grid.Row="1" Grid.Column="2" Foreground="#EBEBFF" FontSize="13" VerticalAlignment="Center" Margin="4,0"/>
|
|
||||||
<TextBlock Text="°C" Grid.Row="2" Grid.Column="2" Foreground="#EBEBFF" FontSize="13" VerticalAlignment="Center" Margin="4,0"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Flow: Q-Delivery -->
|
|
||||||
<Border Style="{StaticResource LcdBlue}" Padding="8,4">
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<ColumnDefinition Width="90"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<ColumnDefinition/>
|
<RowDefinition Height="*"/>
|
||||||
<ColumnDefinition Width="46"/>
|
</Grid.RowDefinitions>
|
||||||
</Grid.ColumnDefinitions>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock Text="{DynamicResource Bench.QDelivery}" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
|
<ui:SymbolIcon Symbol="Gauge24" FontSize="14"
|
||||||
<TextBlock Text="{Binding Root.QDelivery, StringFormat=F1}"
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"
|
||||||
Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center"
|
VerticalAlignment="Center"/>
|
||||||
Foreground="#EBEBFF" FontSize="22" FontFamily="Consolas"/>
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Rpm}" Style="{StaticResource KpiHeaderText}"
|
||||||
<TextBlock Text="cc/s" Grid.Column="2" Foreground="#EBEBFF" FontSize="13" VerticalAlignment="Center" Margin="4,0"/>
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="{Binding Root.BenchRpm, StringFormat=F0}" Style="{StaticResource KpiValueText}"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Unit.Rpm}" Style="{StaticResource KpiUnitText}"/>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
</StackPanel>
|
<!-- Tile 2: Q-Delivery -->
|
||||||
|
<Border Style="{StaticResource KpiTile}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ui:SymbolIcon Symbol="Drop24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Qdelivery}" Style="{StaticResource KpiHeaderText}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="{Binding Root.QDelivery, StringFormat=F1}" Style="{StaticResource KpiValueText}"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Unit.CcS}" Style="{StaticResource KpiUnitText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Tile 3: Pressure P1 -->
|
||||||
|
<Border Style="{StaticResource KpiTile}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ui:SymbolIcon Symbol="ArrowTrendingLines24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.P1}" Style="{StaticResource KpiHeaderText}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="{Binding Root.Pressure, StringFormat=F1}" Style="{StaticResource KpiValueText}"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Unit.Bar}" Style="{StaticResource KpiUnitText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Tile 4: Pressure P2 -->
|
||||||
|
<Border Style="{StaticResource KpiTile}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ui:SymbolIcon Symbol="ArrowTrendingLines24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.P2}" Style="{StaticResource KpiHeaderText}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="{Binding Root.Pressure2, StringFormat=F1}" Style="{StaticResource KpiValueText}"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Unit.Bar}" Style="{StaticResource KpiUnitText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Tile 5: Oil inlet temperature -->
|
||||||
|
<Border Style="{StaticResource KpiTile}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ui:SymbolIcon Symbol="Temperature24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Tin}" Style="{StaticResource KpiHeaderText}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="{Binding Root.TempIn, StringFormat=F1}" Style="{StaticResource KpiValueText}"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Unit.Celsius}" Style="{StaticResource KpiUnitText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Tile 6: Oil outlet temperature -->
|
||||||
|
<Border Style="{StaticResource KpiTile}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ui:SymbolIcon Symbol="ArrowUpload24" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" Margin="0,0,6,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Tout}" Style="{StaticResource KpiHeaderText}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<TextBlock Text="{Binding Root.TempOut, StringFormat=F1}" Style="{StaticResource KpiValueText}"/>
|
||||||
|
<TextBlock Text="{DynamicResource Dashboard.Kpi.Unit.Celsius}" Style="{StaticResource KpiUnitText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</UniformGrid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
DataContext = RelayBankViewModel.
|
DataContext = RelayBankViewModel.
|
||||||
-->
|
-->
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<Style x:Key="RelayToggle" TargetType="ToggleButton">
|
<Style x:Key="RelayToggle" TargetType="ToggleButton"
|
||||||
|
BasedOn="{StaticResource FluentStateToggle}">
|
||||||
<Setter Property="Height" Value="28"/>
|
<Setter Property="Height" Value="28"/>
|
||||||
<Setter Property="FontSize" Value="11"/>
|
<Setter Property="FontSize" Value="11"/>
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
<Setter Property="Margin" Value="0,2,0,0"/>
|
<Setter Property="Margin" Value="0,2,0,0"/>
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
||||||
<Setter Property="Background" Value="LightGray"/>
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
||||||
|
|||||||
@@ -50,9 +50,8 @@
|
|||||||
<TextBlock Text="{DynamicResource Bench.DepositHeater}" FontSize="10" Foreground="DimGray" Margin="0,10,0,2"/>
|
<TextBlock Text="{DynamicResource Bench.DepositHeater}" FontSize="10" Foreground="DimGray" Margin="0,10,0,2"/>
|
||||||
<ToggleButton IsChecked="{Binding IsHeaterOn}" Height="28" FontSize="11" FontWeight="SemiBold">
|
<ToggleButton IsChecked="{Binding IsHeaterOn}" Height="28" FontSize="11" FontWeight="SemiBold">
|
||||||
<ToggleButton.Style>
|
<ToggleButton.Style>
|
||||||
<Style TargetType="ToggleButton">
|
<Style TargetType="ToggleButton" BasedOn="{StaticResource FluentStateToggle}">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
||||||
<Setter Property="Background" Value="LightGray"/>
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
||||||
@@ -67,9 +66,8 @@
|
|||||||
<TextBlock Text="{DynamicResource Bench.DepositCooler}" FontSize="10" Foreground="DimGray" Margin="0,6,0,2"/>
|
<TextBlock Text="{DynamicResource Bench.DepositCooler}" FontSize="10" Foreground="DimGray" Margin="0,6,0,2"/>
|
||||||
<ToggleButton IsChecked="{Binding IsDepositCoolerOn}" Height="28" FontSize="11" FontWeight="SemiBold">
|
<ToggleButton IsChecked="{Binding IsDepositCoolerOn}" Height="28" FontSize="11" FontWeight="SemiBold">
|
||||||
<ToggleButton.Style>
|
<ToggleButton.Style>
|
||||||
<Style TargetType="ToggleButton">
|
<Style TargetType="ToggleButton" BasedOn="{StaticResource FluentStateToggle}">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
||||||
<Setter Property="Background" Value="LightGray"/>
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
||||||
@@ -84,9 +82,8 @@
|
|||||||
<TextBlock Text="{DynamicResource Bench.TinCooler}" FontSize="10" Foreground="DimGray" Margin="0,6,0,2"/>
|
<TextBlock Text="{DynamicResource Bench.TinCooler}" FontSize="10" Foreground="DimGray" Margin="0,6,0,2"/>
|
||||||
<ToggleButton IsChecked="{Binding IsTinCoolerOn}" Height="28" FontSize="11" FontWeight="SemiBold">
|
<ToggleButton IsChecked="{Binding IsTinCoolerOn}" Height="28" FontSize="11" FontWeight="SemiBold">
|
||||||
<ToggleButton.Style>
|
<ToggleButton.Style>
|
||||||
<Style TargetType="ToggleButton">
|
<Style TargetType="ToggleButton" BasedOn="{StaticResource FluentStateToggle}">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOff}"/>
|
||||||
<Setter Property="Background" Value="LightGray"/>
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
<Setter Property="Content" Value="{DynamicResource Bench.RelayOn}"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user