|
|
@@ -18,12 +18,7 @@ namespace YZWater.Avalonia.Controls;
|
|
|
/// <summary>
|
|
|
/// 通用泵站 2D 工艺流程图控件 (SCADA 风格)。
|
|
|
/// 设计画布 960×540,等比缩放适配实际尺寸。
|
|
|
-///
|
|
|
-/// <para>支持两种模式:</para>
|
|
|
-/// <list type="bullet">
|
|
|
-/// <item><b>动态模式</b>:设置 <see cref="Profile"/> 后,根据项目档案自动布局渲染</item>
|
|
|
-/// <item><b>兼容模式</b>:使用旧版硬编码属性(Tank1Level 等),保持向后兼容</item>
|
|
|
-/// </list>
|
|
|
+/// 根据项目档案 (Profile) + 模拟量/数字量字典自动布局渲染。
|
|
|
/// </summary>
|
|
|
public class PumpStation2DControl : Control
|
|
|
{
|
|
|
@@ -41,7 +36,7 @@ public class PumpStation2DControl : Control
|
|
|
public static readonly StyledProperty<ICommand?> DeviceClickedCommandProperty =
|
|
|
AvaloniaProperty.Register<PumpStation2DControl, ICommand?>(nameof(DeviceClickedCommand));
|
|
|
|
|
|
- /// <summary>项目档案 — 设置后自动切换到动态模式</summary>
|
|
|
+ /// <summary>项目档案 — 设置后自动布局渲染</summary>
|
|
|
public ProjectProfile? Profile
|
|
|
{
|
|
|
get => GetValue(ProfileProperty);
|
|
|
@@ -69,56 +64,6 @@ public class PumpStation2DControl : Control
|
|
|
set => SetValue(DeviceClickedCommandProperty, value);
|
|
|
}
|
|
|
|
|
|
- // ═══ 旧版硬编码属性(兼容模式) ═══
|
|
|
-
|
|
|
- public static readonly StyledProperty<double> Tank1LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank1Level));
|
|
|
- public static readonly StyledProperty<double> Tank2LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank2Level));
|
|
|
- public static readonly StyledProperty<double> Tank3LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank3Level));
|
|
|
- public static readonly StyledProperty<double> Tank4LevelProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Tank4Level));
|
|
|
- public static readonly StyledProperty<double> InflowRateProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(InflowRate));
|
|
|
- public static readonly StyledProperty<double> OutflowRateProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(OutflowRate));
|
|
|
- public static readonly StyledProperty<bool> Pump1RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump1Running));
|
|
|
- public static readonly StyledProperty<bool> Pump2RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump2Running));
|
|
|
- public static readonly StyledProperty<bool> Pump3RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump3Running));
|
|
|
- public static readonly StyledProperty<bool> Pump4RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump4Running));
|
|
|
- public static readonly StyledProperty<bool> Pump5RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Pump5Running));
|
|
|
- public static readonly StyledProperty<bool> Fan1RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Fan1Running));
|
|
|
- public static readonly StyledProperty<bool> Fan2RunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(Fan2Running));
|
|
|
- public static readonly StyledProperty<ValveStatus> Valve1StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve1Status));
|
|
|
- public static readonly StyledProperty<ValveStatus> Valve2StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve2Status));
|
|
|
- public static readonly StyledProperty<ValveStatus> Valve3StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve3Status));
|
|
|
- public static readonly StyledProperty<ValveStatus> Valve4StatusProperty = AvaloniaProperty.Register<PumpStation2DControl, ValveStatus>(nameof(Valve4Status));
|
|
|
- public static readonly StyledProperty<bool> IsInflowRunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(IsInflowRunning));
|
|
|
- public static readonly StyledProperty<bool> IsOutflowRunningProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(IsOutflowRunning));
|
|
|
- public static readonly StyledProperty<bool> HasAlarmProperty = AvaloniaProperty.Register<PumpStation2DControl, bool>(nameof(HasAlarm));
|
|
|
- public static readonly StyledProperty<int> AlarmCountProperty = AvaloniaProperty.Register<PumpStation2DControl, int>(nameof(AlarmCount));
|
|
|
- public static readonly StyledProperty<int> RunningDeviceCountProperty = AvaloniaProperty.Register<PumpStation2DControl, int>(nameof(RunningDeviceCount));
|
|
|
- public static readonly StyledProperty<double> EfficiencyProperty = AvaloniaProperty.Register<PumpStation2DControl, double>(nameof(Efficiency));
|
|
|
-
|
|
|
- public double Tank1Level { get => GetValue(Tank1LevelProperty); set => SetValue(Tank1LevelProperty, value); }
|
|
|
- public double Tank2Level { get => GetValue(Tank2LevelProperty); set => SetValue(Tank2LevelProperty, value); }
|
|
|
- public double Tank3Level { get => GetValue(Tank3LevelProperty); set => SetValue(Tank3LevelProperty, value); }
|
|
|
- public double Tank4Level { get => GetValue(Tank4LevelProperty); set => SetValue(Tank4LevelProperty, value); }
|
|
|
- public double InflowRate { get => GetValue(InflowRateProperty); set => SetValue(InflowRateProperty, value); }
|
|
|
- public double OutflowRate { get => GetValue(OutflowRateProperty); set => SetValue(OutflowRateProperty, value); }
|
|
|
- public bool Pump1Running { get => GetValue(Pump1RunningProperty); set => SetValue(Pump1RunningProperty, value); }
|
|
|
- public bool Pump2Running { get => GetValue(Pump2RunningProperty); set => SetValue(Pump2RunningProperty, value); }
|
|
|
- public bool Pump3Running { get => GetValue(Pump3RunningProperty); set => SetValue(Pump3RunningProperty, value); }
|
|
|
- public bool Pump4Running { get => GetValue(Pump4RunningProperty); set => SetValue(Pump4RunningProperty, value); }
|
|
|
- public bool Pump5Running { get => GetValue(Pump5RunningProperty); set => SetValue(Pump5RunningProperty, value); }
|
|
|
- public bool Fan1Running { get => GetValue(Fan1RunningProperty); set => SetValue(Fan1RunningProperty, value); }
|
|
|
- public bool Fan2Running { get => GetValue(Fan2RunningProperty); set => SetValue(Fan2RunningProperty, value); }
|
|
|
- public ValveStatus Valve1Status { get => GetValue(Valve1StatusProperty); set => SetValue(Valve1StatusProperty, value); }
|
|
|
- public ValveStatus Valve2Status { get => GetValue(Valve2StatusProperty); set => SetValue(Valve2StatusProperty, value); }
|
|
|
- public ValveStatus Valve3Status { get => GetValue(Valve3StatusProperty); set => SetValue(Valve3StatusProperty, value); }
|
|
|
- public ValveStatus Valve4Status { get => GetValue(Valve4StatusProperty); set => SetValue(Valve4StatusProperty, value); }
|
|
|
- public bool IsInflowRunning { get => GetValue(IsInflowRunningProperty); set => SetValue(IsInflowRunningProperty, value); }
|
|
|
- public bool IsOutflowRunning { get => GetValue(IsOutflowRunningProperty); set => SetValue(IsOutflowRunningProperty, value); }
|
|
|
- public bool HasAlarm { get => GetValue(HasAlarmProperty); set => SetValue(HasAlarmProperty, value); }
|
|
|
- public int AlarmCount { get => GetValue(AlarmCountProperty); set => SetValue(AlarmCountProperty, value); }
|
|
|
- public int RunningDeviceCount { get => GetValue(RunningDeviceCountProperty); set => SetValue(RunningDeviceCountProperty, value); }
|
|
|
- public double Efficiency { get => GetValue(EfficiencyProperty); set => SetValue(EfficiencyProperty, value); }
|
|
|
-
|
|
|
// ═══ 动画状态 ═══
|
|
|
|
|
|
private double _phase;
|
|
|
@@ -150,17 +95,6 @@ public class PumpStation2DControl : Control
|
|
|
|
|
|
static PumpStation2DControl()
|
|
|
{
|
|
|
- // 旧属性变化 → 兼容模式重绘
|
|
|
- AffectsRender<PumpStation2DControl>(
|
|
|
- Tank1LevelProperty, Tank2LevelProperty, Tank3LevelProperty, Tank4LevelProperty,
|
|
|
- InflowRateProperty, OutflowRateProperty,
|
|
|
- Pump1RunningProperty, Pump2RunningProperty, Pump3RunningProperty, Pump4RunningProperty, Pump5RunningProperty,
|
|
|
- Fan1RunningProperty, Fan2RunningProperty,
|
|
|
- Valve1StatusProperty, Valve2StatusProperty, Valve3StatusProperty, Valve4StatusProperty,
|
|
|
- IsInflowRunningProperty, IsOutflowRunningProperty, HasAlarmProperty, AlarmCountProperty,
|
|
|
- RunningDeviceCountProperty, EfficiencyProperty);
|
|
|
-
|
|
|
- // 新属性变化 → 重算布局 + 重绘
|
|
|
AffectsRender<PumpStation2DControl>(ProfileProperty, AnalogValuesProperty, DigitalValuesProperty);
|
|
|
}
|
|
|
|
|
|
@@ -194,29 +128,19 @@ public class PumpStation2DControl : Control
|
|
|
ThemeHelper.ThemeChanged += OnThemeChanged;
|
|
|
_timer = DispatcherTimer.Run(() =>
|
|
|
{
|
|
|
- var needsAnimation = IsDynamicMode ? HasDynamicAnimation : HasLegacyAnimation;
|
|
|
- if (needsAnimation)
|
|
|
+ if (HasDynamicAnimation)
|
|
|
{
|
|
|
_phase = (_phase + 1) % 360;
|
|
|
_faultFlash = (_faultFlash + 0.03) % 1.0;
|
|
|
|
|
|
// 仅在实际有泵/扇运行时才旋转,避免静止设备无意义转动
|
|
|
- if (IsDynamicMode)
|
|
|
- {
|
|
|
- var anyPumpRunning = _cachedLayout?.Devices.Any(d =>
|
|
|
- d.DeviceType == "Pump" && GetDigitalValue($"{d.DeviceId}.Run")) ?? false;
|
|
|
- var anyFanRunning = _cachedLayout?.Devices.Any(d =>
|
|
|
- d.DeviceType == "Fan" && GetDigitalValue($"{d.DeviceId}.Run")) ?? false;
|
|
|
-
|
|
|
- if (anyPumpRunning) _pumpAngle = (_pumpAngle + 4) % 360;
|
|
|
- if (anyFanRunning) _fanAngle = (_fanAngle + 6) % 360;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // 兼容模式:按旧逻辑
|
|
|
- if (AnyPumpRunning) _pumpAngle = (_pumpAngle + 4) % 360;
|
|
|
- if (Fan1Running || Fan2Running) _fanAngle = (_fanAngle + 6) % 360;
|
|
|
- }
|
|
|
+ var anyPumpRunning = _cachedLayout?.Devices.Any(d =>
|
|
|
+ d.DeviceType == "Pump" && GetDigitalValue($"{d.DeviceId}.Run")) ?? false;
|
|
|
+ var anyFanRunning = _cachedLayout?.Devices.Any(d =>
|
|
|
+ d.DeviceType == "Fan" && GetDigitalValue($"{d.DeviceId}.Run")) ?? false;
|
|
|
+
|
|
|
+ if (anyPumpRunning) _pumpAngle = (_pumpAngle + 4) % 360;
|
|
|
+ if (anyFanRunning) _fanAngle = (_fanAngle + 6) % 360;
|
|
|
|
|
|
InvalidateVisual();
|
|
|
}
|
|
|
@@ -234,10 +158,7 @@ public class PumpStation2DControl : Control
|
|
|
|
|
|
private void OnThemeChanged() => InvalidateVisual();
|
|
|
|
|
|
- // ═══ 模式判断 ═══
|
|
|
-
|
|
|
- /// <summary>是否处于动态模式(已设置 Profile)</summary>
|
|
|
- public bool IsDynamicMode => Profile != null;
|
|
|
+ // ═══ 动画判断 ═══
|
|
|
|
|
|
private bool HasDynamicAnimation
|
|
|
{
|
|
|
@@ -252,33 +173,57 @@ public class PumpStation2DControl : Control
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private bool HasLegacyAnimation =>
|
|
|
- AnyPumpRunning || Fan1Running || Fan2Running || IsInflowRunning || IsOutflowRunning;
|
|
|
+ // ═══ 点击事件 ═══
|
|
|
|
|
|
- private bool AnyPumpRunning =>
|
|
|
- Pump1Running || Pump2Running || Pump3Running || Pump4Running || Pump5Running;
|
|
|
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnPointerPressed(e);
|
|
|
|
|
|
- // ═══ 设计坐标常量 (960 × 540) — 仅兼容模式使用 ═══
|
|
|
+ if (_cachedLayout == null) return;
|
|
|
|
|
|
- private const double DW = 960, DH = 540;
|
|
|
+ var pos = e.GetPosition(this);
|
|
|
+ var margin = 8.0;
|
|
|
+ var scaleX = (Bounds.Width - 2 * margin) / DW;
|
|
|
+ var scaleY = (Bounds.Height - 2 * margin) / DH;
|
|
|
+ var scale = Math.Min(scaleX, scaleY);
|
|
|
+ var offsetX = margin + (Bounds.Width - 2 * margin - DW * scale) / 2;
|
|
|
+ var offsetY = margin + (Bounds.Height - 2 * margin - DH * scale) / 2;
|
|
|
|
|
|
- private static readonly Rect DTank1 = new(55, 130, 120, 140);
|
|
|
- private static readonly Rect DTank2 = new(275, 85, 195, 210);
|
|
|
- private static readonly Rect DTank3 = new(275, 400, 155, 105);
|
|
|
- private static readonly Rect DTank4 = new(720, 130, 120, 140);
|
|
|
+ // 将屏幕坐标转换回设计坐标
|
|
|
+ var designX = (pos.X - offsetX) / scale;
|
|
|
+ var designY = (pos.Y - offsetY) / scale;
|
|
|
+ var designPos = new Point(designX, designY);
|
|
|
|
|
|
- private static readonly Point[] DPumps = { new(300, 340), new(338, 340), new(376, 340), new(414, 340), new(452, 340) };
|
|
|
+ var hitDevice = _cachedLayout.Devices.FirstOrDefault(d => d.Bounds.Contains(designPos));
|
|
|
+ if (hitDevice != null && DeviceClickedCommand?.CanExecute(hitDevice.DeviceId) == true)
|
|
|
+ {
|
|
|
+ DeviceClickedCommand.Execute(hitDevice.DeviceId);
|
|
|
+ e.Handled = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- private static readonly Point DFan1 = new(590, 450);
|
|
|
- private static readonly Point DFan2 = new(660, 450);
|
|
|
+ // ═══ 值读取辅助 ═══
|
|
|
+
|
|
|
+ private float GetAnalogValue(string key)
|
|
|
+ {
|
|
|
+ return AnalogValues != null && AnalogValues.TryGetValue(key, out var v) ? v : 0f;
|
|
|
+ }
|
|
|
|
|
|
- private static readonly Point DV1 = new(218, 200);
|
|
|
- private static readonly Point DV2 = new(555, 195);
|
|
|
- private static readonly Point DV3 = new(420, 395);
|
|
|
- private static readonly Point DV4 = new(230, 450);
|
|
|
+ private bool GetDigitalValue(string key)
|
|
|
+ {
|
|
|
+ return DigitalValues != null && DigitalValues.TryGetValue(key, out var v) && v;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ═══ 坐标变换辅助 ═══
|
|
|
+
|
|
|
+ private static double S(double offset, double scale, double designVal) => offset + designVal * scale;
|
|
|
+ private static double W(double scale, double designW) => designW * scale;
|
|
|
+ private static double H(double scale, double designH) => designH * scale;
|
|
|
+ private static double R(double scale, double designR) => designR * scale;
|
|
|
|
|
|
- private static readonly Point DFM1 = new(22, 195);
|
|
|
- private static readonly Point DFM2 = new(870, 195);
|
|
|
+ // ═══ 设计画布常量 ═══
|
|
|
+
|
|
|
+ private const double DW = 960, DH = 540;
|
|
|
|
|
|
// ═══ 主渲染 ═══
|
|
|
|
|
|
@@ -292,14 +237,11 @@ public class PumpStation2DControl : Control
|
|
|
// 背景(工业 SCADA 径向渐变:中心亮→边缘暗)
|
|
|
context.DrawRectangle(s_bgBrush, null, bounds);
|
|
|
|
|
|
- if (IsDynamicMode)
|
|
|
- RenderDynamic(context, bounds);
|
|
|
- else
|
|
|
- RenderLegacy(context, bounds);
|
|
|
+ RenderDynamic(context, bounds);
|
|
|
}
|
|
|
|
|
|
// ════════════════════════════════════════════════════════
|
|
|
- // 动态模式渲染
|
|
|
+ // 渲染
|
|
|
// ════════════════════════════════════════════════════════
|
|
|
|
|
|
private void RenderDynamic(DrawingContext context, Rect bounds)
|
|
|
@@ -405,7 +347,7 @@ public class PumpStation2DControl : Control
|
|
|
DrawLegend(context, offsetX, offsetY, scale);
|
|
|
|
|
|
// ─── 报警边框 ───
|
|
|
- if (GetDigitalValue("Alarm.Active") || HasAlarm)
|
|
|
+ if (GetDigitalValue("Alarm.Active"))
|
|
|
{
|
|
|
var sceneRect = new Rect(margin, margin, bounds.Width - 2 * margin, bounds.Height - 2 * margin);
|
|
|
context.DrawRectangle(null, new Pen(ThemeHelper.Danger, 2), sceneRect, 4);
|
|
|
@@ -867,7 +809,7 @@ public class PumpStation2DControl : Control
|
|
|
{
|
|
|
const double lampR = 7; // 灯半径(设计坐标)
|
|
|
const double spacing = 26; // 灯间距
|
|
|
- var hasAlarm = GetDigitalValue("Alarm.Active") || HasAlarm;
|
|
|
+ var hasAlarm = GetDigitalValue("Alarm.Active");
|
|
|
|
|
|
var lampY = offsetY + 16; // 与标题同行
|
|
|
var startX = offsetX + DW * scale - 30 - profile.Pumps.Count * spacing * scale;
|
|
|
@@ -1020,273 +962,7 @@ public class PumpStation2DControl : Control
|
|
|
ThemeHelper.TextSecondary, stemPenThickness: Math.Max(1, 1.5 * scale));
|
|
|
}
|
|
|
|
|
|
- // ════════════════════════════════════════════════════════
|
|
|
- // 兼容模式渲染(旧版硬编码布局)
|
|
|
- // ════════════════════════════════════════════════════════
|
|
|
-
|
|
|
- private void RenderLegacy(DrawingContext context, Rect bounds)
|
|
|
- {
|
|
|
- var margin = 8.0;
|
|
|
- var scaleX = (bounds.Width - 2 * margin) / DW;
|
|
|
- var scaleY = (bounds.Height - 2 * margin) / DH;
|
|
|
- var scale = Math.Min(scaleX, scaleY);
|
|
|
- var offsetX = margin + (bounds.Width - 2 * margin - DW * scale) / 2;
|
|
|
- var offsetY = margin + (bounds.Height - 2 * margin - DH * scale) / 2;
|
|
|
-
|
|
|
- // 地面线
|
|
|
- DrawLine(context, offsetX, offsetY, scale, 0, 520, 960, 520, ThemeHelper.Border, 1);
|
|
|
-
|
|
|
- // ═══ 管道 ═══
|
|
|
-
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 0, 200, 55, IsInflowRunning, 10);
|
|
|
- DrawFlowMeter(context, offsetX, offsetY, scale, DFM1, "进水", InflowRate, IsInflowRunning);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 48, 200, 55, IsInflowRunning, 10);
|
|
|
-
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 175, 200, 195, IsInflowRunning, 8);
|
|
|
- DrawValve(context, offsetX, offsetY, scale, DV1, "V1", Valve1Status);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 240, 200, 275, IsInflowRunning, 8);
|
|
|
-
|
|
|
- DrawPipeV(context, offsetX, offsetY, scale, 372, 295, 325, AnyPumpRunning, 8);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 285, 365, 470, AnyPumpRunning, 10);
|
|
|
-
|
|
|
- DrawPipeV(context, offsetX, offsetY, scale, 500, 365, 195, IsOutflowRunning, 10);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 500, 195, 530, IsOutflowRunning, 10);
|
|
|
- DrawValve(context, offsetX, offsetY, scale, DV2, "V2", Valve2Status);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 580, 195, 720, IsOutflowRunning, 10);
|
|
|
-
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 840, 200, 860, IsOutflowRunning, 10);
|
|
|
- DrawFlowMeter(context, offsetX, offsetY, scale, DFM2, "出水", OutflowRate, IsOutflowRunning);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 896, 200, 960, IsOutflowRunning, 10);
|
|
|
-
|
|
|
- DrawPipeV(context, offsetX, offsetY, scale, 420, 365, 378, false, 8);
|
|
|
- DrawValve(context, offsetX, offsetY, scale, DV3, "V3", Valve3Status);
|
|
|
- DrawPipeV(context, offsetX, offsetY, scale, 420, 412, 400, false, 8);
|
|
|
-
|
|
|
- DrawPipeV(context, offsetX, offsetY, scale, 230, 295, 430, false, 8);
|
|
|
- DrawPipeH(context, offsetX, offsetY, scale, 230, 450, 275, false, 8);
|
|
|
- DrawValve(context, offsetX, offsetY, scale, DV4, "V4", Valve4Status);
|
|
|
-
|
|
|
- // ═══ 水箱 ═══
|
|
|
-
|
|
|
- DrawTank(context, offsetX, offsetY, scale, DTank1, Tank1Level, "进水池", "IN",
|
|
|
- Color.FromRgb(0, 137, 123));
|
|
|
- DrawTank(context, offsetX, offsetY, scale, DTank2, Tank2Level, "集水井", "SUMP",
|
|
|
- Color.FromRgb(33, 150, 243));
|
|
|
- DrawTank(context, offsetX, offsetY, scale, DTank3, Tank3Level, "调蓄池", "BUF",
|
|
|
- Color.FromRgb(126, 87, 194));
|
|
|
- DrawTank(context, offsetX, offsetY, scale, DTank4, Tank4Level, "出水池", "OUT",
|
|
|
- Color.FromRgb(0, 150, 136));
|
|
|
-
|
|
|
- // ═══ 泵组 ═══
|
|
|
-
|
|
|
- bool[] pumpRunning = { Pump1Running, Pump2Running, Pump3Running, Pump4Running, Pump5Running };
|
|
|
- string[] pumpLabels = { "P1", "P2", "P3", "P4", "P5" };
|
|
|
- for (int i = 0; i < 5; i++)
|
|
|
- DrawPump(context, offsetX, offsetY, scale, DPumps[i], pumpLabels[i], pumpRunning[i]);
|
|
|
-
|
|
|
- // ═══ 风机 ═══
|
|
|
-
|
|
|
- DrawFan(context, offsetX, offsetY, scale, DFan1, "F1", Fan1Running);
|
|
|
- DrawFan(context, offsetX, offsetY, scale, DFan2, "F2", Fan2Running);
|
|
|
-
|
|
|
- var fanAreaX = S(offsetX, scale, 555);
|
|
|
- var fanAreaY = S(offsetY, scale, 418);
|
|
|
- var fanAreaW = W(scale, 180);
|
|
|
- var fanAreaH = H(scale, 75);
|
|
|
- context.DrawRectangle(null, new Pen(ThemeHelper.Border, 1),
|
|
|
- new Rect(fanAreaX, fanAreaY, fanAreaW, fanAreaH), 3, 3);
|
|
|
- DrawLabel(context, offsetX, offsetY, scale, 560, 422, "风机区", 10, ThemeHelper.TextTertiary);
|
|
|
-
|
|
|
- // ═══ 报警边框 ═══
|
|
|
-
|
|
|
- if (HasAlarm)
|
|
|
- {
|
|
|
- var sceneRect = new Rect(margin, margin, bounds.Width - 2 * margin, bounds.Height - 2 * margin);
|
|
|
- context.DrawRectangle(null, new Pen(ThemeHelper.Danger, 2), sceneRect, 4);
|
|
|
- }
|
|
|
-
|
|
|
- // ═══ HUD ═══
|
|
|
-
|
|
|
- DrawHud(context, bounds, scale);
|
|
|
- }
|
|
|
-
|
|
|
- // ═══ 值读取辅助 ═══
|
|
|
-
|
|
|
- private float GetAnalogValue(string key)
|
|
|
- {
|
|
|
- return AnalogValues != null && AnalogValues.TryGetValue(key, out var v) ? v : 0f;
|
|
|
- }
|
|
|
-
|
|
|
- private bool GetDigitalValue(string key)
|
|
|
- {
|
|
|
- return DigitalValues != null && DigitalValues.TryGetValue(key, out var v) && v;
|
|
|
- }
|
|
|
-
|
|
|
- // ═══ 点击事件 ═══
|
|
|
-
|
|
|
- protected override void OnPointerPressed(PointerPressedEventArgs e)
|
|
|
- {
|
|
|
- base.OnPointerPressed(e);
|
|
|
-
|
|
|
- if (!IsDynamicMode || _cachedLayout == null) return;
|
|
|
-
|
|
|
- var pos = e.GetPosition(this);
|
|
|
- var margin = 8.0;
|
|
|
- var scaleX = (Bounds.Width - 2 * margin) / DW;
|
|
|
- var scaleY = (Bounds.Height - 2 * margin) / DH;
|
|
|
- var scale = Math.Min(scaleX, scaleY);
|
|
|
- var offsetX = margin + (Bounds.Width - 2 * margin - DW * scale) / 2;
|
|
|
- var offsetY = margin + (Bounds.Height - 2 * margin - DH * scale) / 2;
|
|
|
-
|
|
|
- // 将屏幕坐标转换回设计坐标
|
|
|
- var designX = (pos.X - offsetX) / scale;
|
|
|
- var designY = (pos.Y - offsetY) / scale;
|
|
|
- var designPos = new Point(designX, designY);
|
|
|
-
|
|
|
- var hitDevice = _cachedLayout.Devices.FirstOrDefault(d => d.Bounds.Contains(designPos));
|
|
|
- if (hitDevice != null && DeviceClickedCommand?.CanExecute(hitDevice.DeviceId) == true)
|
|
|
- {
|
|
|
- DeviceClickedCommand.Execute(hitDevice.DeviceId);
|
|
|
- e.Handled = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // ═══ 兼容模式绘制辅助 ═══
|
|
|
-
|
|
|
- private static double S(double offset, double scale, double designVal) => offset + designVal * scale;
|
|
|
- private static double W(double scale, double designW) => designW * scale;
|
|
|
- private static double H(double scale, double designH) => designH * scale;
|
|
|
- private static double R(double scale, double designR) => designR * scale;
|
|
|
-
|
|
|
- private void DrawTank(DrawingContext context, double ox, double oy, double sc,
|
|
|
- Rect designRect, double level, string name, string code, Color waterColor)
|
|
|
- {
|
|
|
- var x = S(ox, sc, designRect.X);
|
|
|
- var y = S(oy, sc, designRect.Y);
|
|
|
- var w = W(sc, designRect.Width);
|
|
|
- var h = H(sc, designRect.Height);
|
|
|
- var rect = new Rect(x, y, w, h);
|
|
|
-
|
|
|
- TankRenderer.Draw(context, rect, level, waterColor, name, code,
|
|
|
- ThemeHelper.SurfaceBg, ThemeHelper.Border,
|
|
|
- ThemeHelper.TextPrimary, ThemeHelper.TextTertiary,
|
|
|
- ThemeHelper.Warning, ThemeHelper.Danger);
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawPipeH(DrawingContext context, double ox, double oy, double sc,
|
|
|
- double x1Design, double yDesign, double x2Design, bool isFlowing, double thicknessDesign)
|
|
|
- {
|
|
|
- var x1 = S(ox, sc, x1Design);
|
|
|
- var y = S(oy, sc, yDesign);
|
|
|
- var x2 = S(ox, sc, x2Design);
|
|
|
- var thickness = R(sc, thicknessDesign);
|
|
|
-
|
|
|
- PipeRenderer.DrawHorizontal(context, x1, y, x2, thickness, isFlowing, _phase);
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawPipeV(DrawingContext context, double ox, double oy, double sc,
|
|
|
- double xDesign, double y1Design, double y2Design, bool isFlowing, double thicknessDesign)
|
|
|
- {
|
|
|
- var x = S(ox, sc, xDesign);
|
|
|
- var y1 = S(oy, sc, y1Design);
|
|
|
- var y2 = S(oy, sc, y2Design);
|
|
|
- var thickness = R(sc, thicknessDesign);
|
|
|
-
|
|
|
- PipeRenderer.DrawVertical(context, x, y1, y2, thickness, isFlowing, _phase);
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawPump(DrawingContext context, double ox, double oy, double sc,
|
|
|
- Point designPos, string label, bool running)
|
|
|
- {
|
|
|
- var cx = S(ox, sc, designPos.X);
|
|
|
- var cy = S(oy, sc, designPos.Y);
|
|
|
- var r = R(sc, 18);
|
|
|
- var statusColor = running ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.TextDisabled);
|
|
|
-
|
|
|
- PumpRenderer.Draw(context, new Point(cx, cy), r, running, statusColor,
|
|
|
- _pumpAngle, label, ThemeHelper.SurfaceBg, ThemeHelper.Border, ThemeHelper.TextPrimary,
|
|
|
- bladeCount: 4, bladePenThickness: Math.Max(1.5, 2.5 * sc));
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawFan(DrawingContext context, double ox, double oy, double sc,
|
|
|
- Point designPos, string label, bool running)
|
|
|
- {
|
|
|
- var cx = S(ox, sc, designPos.X);
|
|
|
- var cy = S(oy, sc, designPos.Y);
|
|
|
- var r = R(sc, 20);
|
|
|
- var statusColor = running ? GetColor(ThemeHelper.Info) : GetColor(ThemeHelper.TextDisabled);
|
|
|
-
|
|
|
- FanRenderer.Draw(context, new Point(cx, cy), r, running, _fanAngle, label,
|
|
|
- statusColor, ThemeHelper.SurfaceBg, ThemeHelper.TextPrimary,
|
|
|
- bladeCount: 5, bladePenThickness: Math.Max(2, 3 * sc));
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawValve(DrawingContext context, double ox, double oy, double sc,
|
|
|
- Point designPos, string label, ValveStatus status)
|
|
|
- {
|
|
|
- var cx = S(ox, sc, designPos.X);
|
|
|
- var cy = S(oy, sc, designPos.Y);
|
|
|
- var s = R(sc, 12);
|
|
|
-
|
|
|
- var color = status switch
|
|
|
- {
|
|
|
- ValveStatus.Open => GetColor(ThemeHelper.Success),
|
|
|
- ValveStatus.Middle => GetColor(ThemeHelper.Warning),
|
|
|
- ValveStatus.Closed => GetColor(ThemeHelper.Danger),
|
|
|
- _ => GetColor(ThemeHelper.TextDisabled)
|
|
|
- };
|
|
|
-
|
|
|
- ValveRenderer.Draw(context, new Point(cx, cy), s, color, label,
|
|
|
- ThemeHelper.TextSecondary, stemPenThickness: Math.Max(1, 1.5 * sc));
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawFlowMeter(DrawingContext context, double ox, double oy, double sc,
|
|
|
- Point designPos, string label, double value, bool active)
|
|
|
- {
|
|
|
- var cx = S(ox, sc, designPos.X);
|
|
|
- var cy = S(oy, sc, designPos.Y);
|
|
|
- var w = W(sc, 56);
|
|
|
- var h = H(sc, 32);
|
|
|
-
|
|
|
- FlowMeterRenderer.Draw(context, new Point(cx, cy), w, h, label, value, active,
|
|
|
- GetColor(ThemeHelper.Success), GetColor(ThemeHelper.TextDisabled),
|
|
|
- ThemeHelper.SurfaceBg, ThemeHelper.TextTertiary);
|
|
|
- }
|
|
|
-
|
|
|
- private void DrawHud(DrawingContext context, Rect bounds, double scale)
|
|
|
- {
|
|
|
- var panelW = Math.Max(120, 185 * Math.Min(scale, 1.5));
|
|
|
- var panelH = Math.Max(45, 58 * Math.Min(scale, 1.5));
|
|
|
- var panelX = bounds.Right - panelW - 10;
|
|
|
- var panelY = bounds.Top + 8;
|
|
|
- var panelRect = new Rect(panelX, panelY, panelW, panelH);
|
|
|
-
|
|
|
- context.DrawRectangle(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.92),
|
|
|
- new Pen(ThemeHelper.Border, 1), panelRect, 3, 3);
|
|
|
-
|
|
|
- var fontSize = Math.Max(8, 10 * Math.Min(scale, 1.5));
|
|
|
- var smallFont = Math.Max(7, 9 * Math.Min(scale, 1.5));
|
|
|
-
|
|
|
- DrawLabelAt(context, panelX + 8, panelY + 6, "泵站总览", fontSize * 1.15, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
- DrawLabelAt(context, panelX + 8, panelY + 6 + fontSize * 1.5,
|
|
|
- $"运行 {RunningDeviceCount} 效率 {Efficiency:F0}%", smallFont, ThemeHelper.TextSecondary);
|
|
|
-
|
|
|
- var alarmColor = HasAlarm ? ThemeHelper.Danger : ThemeHelper.Success;
|
|
|
- DrawLabelAt(context, panelX + 8, panelY + 6 + fontSize * 1.5 + smallFont * 1.5,
|
|
|
- $"报警 {AlarmCount}", smallFont, alarmColor, FontWeight.Bold);
|
|
|
- }
|
|
|
-
|
|
|
- // ═══ 文字/线条绘制辅助 ═══
|
|
|
-
|
|
|
- private void DrawLabel(DrawingContext context, double ox, double oy, double sc,
|
|
|
- double designX, double designY, string text, double designFontSize,
|
|
|
- IBrush brush, FontWeight weight = FontWeight.Normal)
|
|
|
- {
|
|
|
- var x = S(ox, sc, designX);
|
|
|
- var y = S(oy, sc, designY);
|
|
|
- var size = Math.Max(8, designFontSize * sc);
|
|
|
- DrawLabelAt(context, x, y, text, size, brush, weight);
|
|
|
- }
|
|
|
+ // ═══ 图例绘制辅助 ═══
|
|
|
|
|
|
private static void DrawLabelAt(DrawingContext context, double x, double y, string text,
|
|
|
double size, IBrush brush, FontWeight weight = FontWeight.Normal)
|