feat: add AngleDisplay MVVM UserControl for advance monitoring
Refactors the old AngleDisplay code-behind into a clean MVVM implementation with AngleDisplayViewModel (PSG/INJ/Manual encoder angles, zero references, lock angle calculation with tolerance color coding) and a pure XAML view. Wires LockAngleFaseReady and PsgModeFaseReady test phase events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,7 @@
|
|||||||
<RowDefinition Height="Auto"/> <!-- Row 1: CAN buttons -->
|
<RowDefinition Height="Auto"/> <!-- Row 1: CAN buttons -->
|
||||||
<RowDefinition Height="Auto"/> <!-- Row 2: two-column info + controls -->
|
<RowDefinition Height="Auto"/> <!-- Row 2: two-column info + controls -->
|
||||||
<RowDefinition Height="Auto"/> <!-- Row 3: flowmeter charts -->
|
<RowDefinition Height="Auto"/> <!-- Row 3: flowmeter charts -->
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Row 4: angle display -->
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- ── Row 0: Connection status indicators ─────────────── -->
|
<!-- ── Row 0: Connection status indicators ─────────────── -->
|
||||||
@@ -430,6 +431,10 @@
|
|||||||
<uc:FlowmeterChartView DataContext="{Binding FlowmeterChart.Over}" Margin="0,4,0,0"/>
|
<uc:FlowmeterChartView DataContext="{Binding FlowmeterChart.Over}" Margin="0,4,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ── Row 4: Advance monitoring (encoder angles) ─────── -->
|
||||||
|
<uc:AngleDisplayView Grid.Row="4" Margin="0,4"
|
||||||
|
DataContext="{Binding AngleDisplay}"/>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Expander>
|
</Expander>
|
||||||
|
|||||||
271
ViewModels/AngleDisplayViewModel.cs
Normal file
271
ViewModels/AngleDisplayViewModel.cs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using HC_APTBS.Services;
|
||||||
|
|
||||||
|
namespace HC_APTBS.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel for the advance monitoring display showing PSG, Injector, and Manual
|
||||||
|
/// encoder angles with zero-reference tracking and lock angle calculation.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AngleDisplayViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private const string NaN = "- -";
|
||||||
|
private const double LockAngleTolerance = 0.1;
|
||||||
|
|
||||||
|
private readonly int _encoderResolution;
|
||||||
|
|
||||||
|
// ── Internal state ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private double _psgRaw;
|
||||||
|
private bool _psgWorking;
|
||||||
|
private double _injRaw;
|
||||||
|
private bool _injWorking;
|
||||||
|
private double _manualRaw;
|
||||||
|
private double _currentRpm;
|
||||||
|
private bool _isDirectionRight;
|
||||||
|
|
||||||
|
private double _zeroPsgEncoder;
|
||||||
|
private double _zeroInjDegrees;
|
||||||
|
private double _injEncoderDegrees;
|
||||||
|
private double _currentManualDegrees;
|
||||||
|
private double _lockAngleValue;
|
||||||
|
private bool _isLockSet;
|
||||||
|
private bool _isManualVisible;
|
||||||
|
|
||||||
|
// ── Observable properties (bound by View) ─────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Formatted relative PSG angle or "- -" when sensor offline.</summary>
|
||||||
|
[ObservableProperty] private string _psgRelativeAngle = NaN;
|
||||||
|
|
||||||
|
/// <summary>Formatted raw PSG encoder degrees or "- -" when sensor offline.</summary>
|
||||||
|
[ObservableProperty] private string _psgEncoderAngle = NaN;
|
||||||
|
|
||||||
|
/// <summary>Foreground brush for PSG angle text (White = OK, Red = offline).</summary>
|
||||||
|
[ObservableProperty] private Brush _psgAngleForeground = Brushes.White;
|
||||||
|
|
||||||
|
/// <summary>Formatted relative INJ angle or "- -" when sensor offline.</summary>
|
||||||
|
[ObservableProperty] private string _injRelativeAngle = NaN;
|
||||||
|
|
||||||
|
/// <summary>Formatted raw INJ encoder degrees or "- -" when sensor offline.</summary>
|
||||||
|
[ObservableProperty] private string _injEncoderAngle = NaN;
|
||||||
|
|
||||||
|
/// <summary>Foreground brush for INJ angle text (White = OK, Red = offline).</summary>
|
||||||
|
[ObservableProperty] private Brush _injAngleForeground = Brushes.White;
|
||||||
|
|
||||||
|
/// <summary>Manual encoder angle in degrees, or "-" when RPM >= 30.</summary>
|
||||||
|
[ObservableProperty] private string _manualAngleText = "-";
|
||||||
|
|
||||||
|
/// <summary>Delta offset input for lock angle calculation (two-way bound to TextBox).</summary>
|
||||||
|
[ObservableProperty] private string _lockAngleDeltaInput = "0";
|
||||||
|
|
||||||
|
/// <summary>Computed lock angle result display.</summary>
|
||||||
|
[ObservableProperty] private string _lockAngleDisplay = "00.0";
|
||||||
|
|
||||||
|
/// <summary>Foreground brush for the lock angle display (Green/Red/White).</summary>
|
||||||
|
[ObservableProperty] private Brush _lockAngleForeground = Brushes.White;
|
||||||
|
|
||||||
|
// ── Constructor ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the angle display ViewModel, reading encoder resolution from configuration.
|
||||||
|
/// </summary>
|
||||||
|
public AngleDisplayViewModel(IConfigurationService configService)
|
||||||
|
{
|
||||||
|
_encoderResolution = configService.Settings.EncoderResolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public update (called from MainViewModel.OnRefreshTick) ───────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Feeds all encoder channel values and recalculates display properties.
|
||||||
|
/// Must be called on the UI thread.
|
||||||
|
/// </summary>
|
||||||
|
public void Update(
|
||||||
|
double psgRaw, bool psgWorking,
|
||||||
|
double injRaw, bool injWorking,
|
||||||
|
double manualRaw, double rpm,
|
||||||
|
bool isDirectionRight)
|
||||||
|
{
|
||||||
|
_psgRaw = psgRaw;
|
||||||
|
_psgWorking = psgWorking;
|
||||||
|
_injRaw = injRaw;
|
||||||
|
_injWorking = injWorking;
|
||||||
|
_manualRaw = manualRaw;
|
||||||
|
_currentRpm = rpm;
|
||||||
|
_isDirectionRight = isDirectionRight;
|
||||||
|
|
||||||
|
RecalculatePsg();
|
||||||
|
RecalculateInj();
|
||||||
|
RecalculateManual();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Commands ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Sets the current PSG encoder value as the zero reference.</summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void SetPsgZero()
|
||||||
|
{
|
||||||
|
_zeroPsgEncoder = _psgRaw;
|
||||||
|
RecalculatePsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sets the current INJ encoder degrees as the zero reference.</summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void SetInjZero()
|
||||||
|
{
|
||||||
|
_zeroInjDegrees = _injEncoderDegrees;
|
||||||
|
RecalculateInj();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public methods for test phase integration ─────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the lock angle delta from the pump definition and returns the computed
|
||||||
|
/// lock angle result. Called when the LockAngleFaseReady event fires.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pumpLockAngle">Angle offset from <see cref="Models.PumpDefinition.LockAngle"/>.</param>
|
||||||
|
/// <returns>Computed lock angle in degrees (0–360).</returns>
|
||||||
|
public double SetLockAngle(double pumpLockAngle)
|
||||||
|
{
|
||||||
|
LockAngleDeltaInput = pumpLockAngle.ToString(CultureInfo.InvariantCulture);
|
||||||
|
return _lockAngleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeros the PSG encoder from a test phase (PsgModeFaseReady event).
|
||||||
|
/// </summary>
|
||||||
|
public void SetPsgZeroFromTest()
|
||||||
|
{
|
||||||
|
SetPsgZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lock angle delta change handler ───────────────────────────────────────
|
||||||
|
|
||||||
|
partial void OnLockAngleDeltaInputChanged(string value)
|
||||||
|
{
|
||||||
|
if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out double delta))
|
||||||
|
{
|
||||||
|
_lockAngleValue = CalculateLockAngle(delta);
|
||||||
|
_isLockSet = true;
|
||||||
|
LockAngleDisplay = FormatAngle(_lockAngleValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecalculateLockColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Private recalculation methods ─────────────────────────────────────────
|
||||||
|
|
||||||
|
private void RecalculatePsg()
|
||||||
|
{
|
||||||
|
if (!_psgWorking)
|
||||||
|
{
|
||||||
|
PsgRelativeAngle = NaN;
|
||||||
|
PsgEncoderAngle = NaN;
|
||||||
|
PsgAngleForeground = Brushes.Red;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsgAngleForeground = Brushes.White;
|
||||||
|
|
||||||
|
double sign = DirectionSign;
|
||||||
|
|
||||||
|
// Raw encoder → degrees, normalized to ±180.
|
||||||
|
double rawDeg = _psgRaw * (360.0 / _encoderResolution);
|
||||||
|
if (rawDeg > 180) rawDeg = 360 - rawDeg;
|
||||||
|
rawDeg *= sign;
|
||||||
|
|
||||||
|
// Relative angle from zero reference.
|
||||||
|
double relDeg = (_psgRaw - _zeroPsgEncoder) * (360.0 / _encoderResolution);
|
||||||
|
if (relDeg > 180) relDeg = 360 - relDeg;
|
||||||
|
relDeg *= sign;
|
||||||
|
|
||||||
|
PsgEncoderAngle = FormatAngle(rawDeg);
|
||||||
|
PsgRelativeAngle = FormatAngle(relDeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecalculateInj()
|
||||||
|
{
|
||||||
|
if (!_injWorking)
|
||||||
|
{
|
||||||
|
InjRelativeAngle = NaN;
|
||||||
|
InjEncoderAngle = NaN;
|
||||||
|
InjAngleForeground = Brushes.Red;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InjAngleForeground = Brushes.White;
|
||||||
|
|
||||||
|
double sign = DirectionSign;
|
||||||
|
|
||||||
|
// INJ encoder → degrees (full 0–360, NO 180 normalization).
|
||||||
|
_injEncoderDegrees = _injRaw * (360.0 / _encoderResolution);
|
||||||
|
|
||||||
|
// Relative angle from zero reference.
|
||||||
|
double relDeg = (_injEncoderDegrees - _zeroInjDegrees) * sign;
|
||||||
|
|
||||||
|
InjEncoderAngle = FormatAngle(_injEncoderDegrees);
|
||||||
|
InjRelativeAngle = FormatAngle(relDeg);
|
||||||
|
|
||||||
|
// Recompute lock angle if already set (INJ encoder changed).
|
||||||
|
if (_isLockSet &&
|
||||||
|
double.TryParse(LockAngleDeltaInput, NumberStyles.Float,
|
||||||
|
CultureInfo.InvariantCulture, out double delta))
|
||||||
|
{
|
||||||
|
_lockAngleValue = CalculateLockAngle(delta);
|
||||||
|
LockAngleDisplay = FormatAngle(_lockAngleValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecalculateManual()
|
||||||
|
{
|
||||||
|
_currentManualDegrees = _manualRaw * (360.0 / _encoderResolution);
|
||||||
|
|
||||||
|
if (_currentRpm < 30)
|
||||||
|
{
|
||||||
|
ManualAngleText = FormatAngle(_currentManualDegrees);
|
||||||
|
_isManualVisible = true;
|
||||||
|
}
|
||||||
|
else if (_isManualVisible)
|
||||||
|
{
|
||||||
|
ManualAngleText = "-";
|
||||||
|
_isManualVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecalculateLockColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecalculateLockColor()
|
||||||
|
{
|
||||||
|
if (!_isManualVisible)
|
||||||
|
{
|
||||||
|
LockAngleForeground = Brushes.White;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isLockSet && Math.Abs(_lockAngleValue - _currentManualDegrees) <= LockAngleTolerance)
|
||||||
|
LockAngleForeground = Brushes.Green;
|
||||||
|
else if (_isLockSet)
|
||||||
|
LockAngleForeground = Brushes.Red;
|
||||||
|
else
|
||||||
|
LockAngleForeground = Brushes.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double CalculateLockAngle(double angleToAdd)
|
||||||
|
{
|
||||||
|
double result = (_injEncoderDegrees + angleToAdd) % 360;
|
||||||
|
if (result < 0) result += 360;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private double DirectionSign => _isDirectionRight ? -1.0 : 1.0;
|
||||||
|
|
||||||
|
private static string FormatAngle(double degrees)
|
||||||
|
=> degrees.ToString("F1", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,9 @@ namespace HC_APTBS.ViewModels
|
|||||||
/// <summary>ViewModel for the two flowmeter real-time charts (Q-Delivery, Q-Over).</summary>
|
/// <summary>ViewModel for the two flowmeter real-time charts (Q-Delivery, Q-Over).</summary>
|
||||||
public FlowmeterChartViewModel FlowmeterChart { get; } = new();
|
public FlowmeterChartViewModel FlowmeterChart { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>ViewModel for the encoder angle monitoring display (PSG, INJ, Manual, Lock Angle).</summary>
|
||||||
|
public AngleDisplayViewModel AngleDisplay { get; }
|
||||||
|
|
||||||
/// <summary>ViewModel for the first pump status display (Status word).</summary>
|
/// <summary>ViewModel for the first pump status display (Status word).</summary>
|
||||||
public StatusDisplayViewModel StatusDisplay1 { get; } = new();
|
public StatusDisplayViewModel StatusDisplay1 { get; } = new();
|
||||||
|
|
||||||
@@ -98,6 +101,7 @@ namespace HC_APTBS.ViewModels
|
|||||||
DfiViewModel = new DfiManageViewModel(kwpService, configService);
|
DfiViewModel = new DfiManageViewModel(kwpService, configService);
|
||||||
PumpControl = new PumpControlViewModel(benchService);
|
PumpControl = new PumpControlViewModel(benchService);
|
||||||
BenchControl = new BenchControlViewModel(benchService, configService);
|
BenchControl = new BenchControlViewModel(benchService, configService);
|
||||||
|
AngleDisplay = new AngleDisplayViewModel(configService);
|
||||||
|
|
||||||
// React to pump changes from the identification child VM.
|
// React to pump changes from the identification child VM.
|
||||||
PumpIdentification.PumpChanged += OnPumpChanged;
|
PumpIdentification.PumpChanged += OnPumpChanged;
|
||||||
@@ -144,6 +148,15 @@ namespace HC_APTBS.ViewModels
|
|||||||
FlowmeterChart.SetTolerance(paramName, value, tolerance);
|
FlowmeterChart.SetTolerance(paramName, value, tolerance);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Angle display: lock angle and PSG zero from test phases
|
||||||
|
_bench.LockAngleFaseReady += () => App.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (CurrentPump != null)
|
||||||
|
CurrentPump.LockAngleResult = AngleDisplay.SetLockAngle(CurrentPump.LockAngle);
|
||||||
|
});
|
||||||
|
_bench.PsgModeFaseReady += () => App.Current.Dispatcher.Invoke(
|
||||||
|
() => AngleDisplay.SetPsgZeroFromTest());
|
||||||
|
|
||||||
// Unlock service status → verbose display
|
// Unlock service status → verbose display
|
||||||
_unlock.StatusChanged += msg => App.Current.Dispatcher.Invoke(
|
_unlock.StatusChanged += msg => App.Current.Dispatcher.Invoke(
|
||||||
() => VerboseStatus = msg);
|
() => VerboseStatus = msg);
|
||||||
@@ -481,6 +494,16 @@ namespace HC_APTBS.ViewModels
|
|||||||
Pressure = _bench.ReadBenchParameter(BenchParameterNames.Pressure);
|
Pressure = _bench.ReadBenchParameter(BenchParameterNames.Pressure);
|
||||||
PsgEncoderValue = _bench.ReadBenchParameter(BenchParameterNames.PsgEncoderValue);
|
PsgEncoderValue = _bench.ReadBenchParameter(BenchParameterNames.PsgEncoderValue);
|
||||||
|
|
||||||
|
// Feed the angle display with all three encoder channels + status.
|
||||||
|
AngleDisplay.Update(
|
||||||
|
PsgEncoderValue,
|
||||||
|
_bench.ReadBenchParameter(BenchParameterNames.PsgEncoderWorking) == 1,
|
||||||
|
_bench.ReadBenchParameter(BenchParameterNames.InjEncoderValue),
|
||||||
|
_bench.ReadBenchParameter(BenchParameterNames.InjEncoderWorking) == 1,
|
||||||
|
_bench.ReadBenchParameter(BenchParameterNames.ManualEncoderValue),
|
||||||
|
BenchRpm,
|
||||||
|
BenchControl.IsDirectionRight);
|
||||||
|
|
||||||
// Feed flowmeter charts and refresh bench controls.
|
// Feed flowmeter charts and refresh bench controls.
|
||||||
FlowmeterChart.AddSamples(QDelivery, QOver);
|
FlowmeterChart.AddSamples(QDelivery, QOver);
|
||||||
BenchControl.RefreshFromTick();
|
BenchControl.RefreshFromTick();
|
||||||
|
|||||||
134
Views/UserControls/AngleDisplayView.xaml
Normal file
134
Views/UserControls/AngleDisplayView.xaml
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<UserControl x:Class="HC_APTBS.Views.UserControls.AngleDisplayView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="200" d:DesignWidth="500">
|
||||||
|
|
||||||
|
<Border Margin="0,4">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="100"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<TextBlock Grid.ColumnSpan="3" Text="ADVANCE MONITORING"
|
||||||
|
HorizontalAlignment="Center" FontSize="20"
|
||||||
|
FontWeight="Bold" FontStyle="Italic"
|
||||||
|
Foreground="DimGray" Margin="0,0,0,4"/>
|
||||||
|
|
||||||
|
<!-- ── Column 0: PSG + INJ encoder readouts ── -->
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||||
|
|
||||||
|
<!-- PSG row -->
|
||||||
|
<StackPanel Orientation="Horizontal" Height="56" Margin="0,2">
|
||||||
|
<Button Content="0" Width="28" Height="28" FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center" Margin="0,0,4,0"
|
||||||
|
Command="{Binding SetPsgZeroCommand}"
|
||||||
|
ToolTip="Set PSG zero reference"/>
|
||||||
|
<Border Style="{DynamicResource LcdBlue}" Padding="4,0" Width="170">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="PSG:" DockPanel.Dock="Left"
|
||||||
|
VerticalAlignment="Bottom" Margin="2,0,0,8"
|
||||||
|
FontSize="10" FontWeight="Bold"
|
||||||
|
Foreground="{Binding PsgAngleForeground}"/>
|
||||||
|
<TextBlock Text="{Binding PsgEncoderAngle}" DockPanel.Dock="Right"
|
||||||
|
VerticalAlignment="Top" HorizontalAlignment="Right"
|
||||||
|
Margin="0,6,4,0" FontSize="12"
|
||||||
|
Foreground="LightGray" FontFamily="Consolas"/>
|
||||||
|
<TextBlock Text="{Binding PsgRelativeAngle}"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
FontSize="30" FontWeight="Bold" FontFamily="Consolas"
|
||||||
|
Foreground="{Binding PsgAngleForeground}"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- INJ row -->
|
||||||
|
<StackPanel Orientation="Horizontal" Height="56" Margin="0,2">
|
||||||
|
<Button Content="0" Width="28" Height="28" FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center" Margin="0,0,4,0"
|
||||||
|
Command="{Binding SetInjZeroCommand}"
|
||||||
|
ToolTip="Set INJ zero reference"/>
|
||||||
|
<Border Style="{DynamicResource LcdBlue}" Padding="4,0" Width="170">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="INJ:" DockPanel.Dock="Left"
|
||||||
|
VerticalAlignment="Bottom" Margin="2,0,0,8"
|
||||||
|
FontSize="10" FontWeight="Bold"
|
||||||
|
Foreground="{Binding InjAngleForeground}"/>
|
||||||
|
<TextBlock Text="{Binding InjEncoderAngle}" DockPanel.Dock="Right"
|
||||||
|
VerticalAlignment="Top" HorizontalAlignment="Right"
|
||||||
|
Margin="0,6,4,0" FontSize="12"
|
||||||
|
Foreground="LightGray" FontFamily="Consolas"/>
|
||||||
|
<TextBlock Text="{Binding InjRelativeAngle}"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
FontSize="30" FontWeight="Bold" FontFamily="Consolas"
|
||||||
|
Foreground="{Binding InjAngleForeground}"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ── Column 1: Delta angle input ── -->
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Bottom"
|
||||||
|
Margin="8,0" Height="60">
|
||||||
|
<TextBlock Text="Δº" FontSize="16" FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center" Margin="0,0,0,2"/>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="+" FontSize="14" VerticalAlignment="Center"
|
||||||
|
Margin="0,0,4,0"/>
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Text="{Binding LockAngleDeltaInput, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
FontSize="14" FontFamily="Consolas" Height="28"
|
||||||
|
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||||
|
Padding="4,2"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="=" FontSize="14"
|
||||||
|
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ── Column 2: Manual (ABS) + Lock angle displays ── -->
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="2" Margin="4,0,0,0">
|
||||||
|
|
||||||
|
<!-- ABS (manual encoder) -->
|
||||||
|
<Border Style="{DynamicResource LcdBlue}" Height="56" Margin="0,2" Padding="4,0">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="ABS º:" DockPanel.Dock="Left"
|
||||||
|
VerticalAlignment="Bottom" Margin="4,0,0,8"
|
||||||
|
FontSize="10" FontWeight="Bold" Foreground="#FFEBEBFF"/>
|
||||||
|
<TextBlock Text="{Binding ManualAngleText}"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
FontSize="32" FontWeight="Bold" FontFamily="Consolas"
|
||||||
|
Foreground="#FFEBEBFF"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- LOCK angle result -->
|
||||||
|
<Border Style="{DynamicResource LcdBlue}" Height="56" Margin="0,2" Padding="4,0">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="LOCK º:" DockPanel.Dock="Left"
|
||||||
|
VerticalAlignment="Bottom" Margin="4,0,0,8"
|
||||||
|
FontSize="10" FontWeight="Bold" Foreground="#FFEBEBFF"/>
|
||||||
|
<TextBlock Text="{Binding LockAngleDisplay}"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
FontSize="32" FontWeight="Bold" FontFamily="Consolas"
|
||||||
|
Foreground="{Binding LockAngleForeground}"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
16
Views/UserControls/AngleDisplayView.xaml.cs
Normal file
16
Views/UserControls/AngleDisplayView.xaml.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace HC_APTBS.Views.UserControls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Advance monitoring display for PSG, Injector, and Manual encoder angles.
|
||||||
|
/// All logic resides in <see cref="ViewModels.AngleDisplayViewModel"/>.
|
||||||
|
/// </summary>
|
||||||
|
public partial class AngleDisplayView : UserControl
|
||||||
|
{
|
||||||
|
public AngleDisplayView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user