已合并 PR 34: 合并AxisControl运动控制控件
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Common.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 虚拟摇杆核心计算逻辑(纯函数,无副作用)| Virtual joystick core calculation logic (pure functions, no side effects)
|
||||
/// </summary>
|
||||
public static class JoystickCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// 将位移限制在圆形区域内 | Clamp displacement within circular radius
|
||||
/// </summary>
|
||||
/// <param name="dx">X 轴偏移量 | X axis displacement</param>
|
||||
/// <param name="dy">Y 轴偏移量 | Y axis displacement</param>
|
||||
/// <param name="radius">最大半径(必须大于 0)| Maximum radius (must be greater than 0)</param>
|
||||
/// <returns>限制后的 (clampedDx, clampedDy) 元组 | Clamped (clampedDx, clampedDy) tuple</returns>
|
||||
public static (double clampedDx, double clampedDy) ClampToRadius(double dx, double dy, double radius)
|
||||
{
|
||||
if (radius <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(radius), "半径必须大于 0 | Radius must be greater than 0");
|
||||
|
||||
var distance = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 如果距离在半径内,直接返回原值 | If within radius, return original values
|
||||
if (distance <= radius)
|
||||
return (dx, dy);
|
||||
|
||||
// 归一化到半径上 | Normalize to radius boundary
|
||||
var scale = radius / distance;
|
||||
return (dx * scale, dy * scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算归一化输出,包含死区映射 | Calculate normalized output with dead zone mapping
|
||||
/// </summary>
|
||||
/// <param name="dx">X 轴偏移量 | X axis displacement</param>
|
||||
/// <param name="dy">Y 轴偏移量 | Y axis displacement</param>
|
||||
/// <param name="radius">最大半径(必须大于 0)| Maximum radius (must be greater than 0)</param>
|
||||
/// <param name="deadZone">死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)</param>
|
||||
/// <param name="mode">摇杆模式 | Joystick mode</param>
|
||||
/// <returns>归一化输出 (outputX, outputY) 元组,范围 [-1.0, 1.0] | Normalized output tuple, range [-1.0, 1.0]</returns>
|
||||
public static (double outputX, double outputY) CalculateOutput(
|
||||
double dx, double dy, double radius, double deadZone, JoystickMode mode)
|
||||
{
|
||||
if (radius <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(radius), "半径必须大于 0 | Radius must be greater than 0");
|
||||
|
||||
if (deadZone < 0.0 || deadZone >= 1.0)
|
||||
throw new ArgumentOutOfRangeException(nameof(deadZone), "死区比例必须在 [0.0, 1.0) 范围内 | Dead zone must be in range [0.0, 1.0)");
|
||||
|
||||
// 步骤 1-2:将位移限制在半径内 | Step 1-2: Clamp displacement within radius
|
||||
var (clampedDx, clampedDy) = ClampToRadius(dx, dy, radius);
|
||||
|
||||
// 步骤 3:计算比例值 | Step 3: Calculate ratio
|
||||
var ratioX = clampedDx / radius;
|
||||
var ratioY = clampedDy / radius;
|
||||
|
||||
// 步骤 4:对每个轴分别应用死区 | Step 4: Apply dead zone to each axis independently
|
||||
var outputX = ApplyDeadZone(ratioX, deadZone);
|
||||
var outputY = ApplyDeadZone(ratioY, deadZone);
|
||||
|
||||
// 步骤 5:SingleAxisY 模式下强制 OutputX = 0 | Step 5: Force OutputX = 0 in SingleAxisY mode
|
||||
if (mode == JoystickMode.SingleAxisY)
|
||||
outputX = 0.0;
|
||||
|
||||
return (outputX, outputY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对单轴比例值应用死区映射 | Apply dead zone mapping to a single axis ratio
|
||||
/// </summary>
|
||||
/// <param name="ratio">轴比例值,范围 [-1.0, 1.0] | Axis ratio, range [-1.0, 1.0]</param>
|
||||
/// <param name="deadZone">死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)</param>
|
||||
/// <returns>死区映射后的输出值 | Output value after dead zone mapping</returns>
|
||||
internal static double ApplyDeadZone(double ratio, double deadZone)
|
||||
{
|
||||
var absRatio = Math.Abs(ratio);
|
||||
|
||||
// 在死区内,输出为 0 | Within dead zone, output is 0
|
||||
if (absRatio < deadZone)
|
||||
return 0.0;
|
||||
|
||||
// 死区外线性映射:sign(ratio) × (absRatio - D) / (1 - D) | Linear mapping outside dead zone
|
||||
return Math.Sign(ratio) * (absRatio - deadZone) / (1.0 - deadZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace XP.Common.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 虚拟摇杆轴模式枚举 | Virtual joystick axis mode enumeration
|
||||
/// </summary>
|
||||
public enum JoystickMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 双轴模式:X + Y 自由移动 | Dual axis mode: free movement in X and Y directions
|
||||
/// </summary>
|
||||
DualAxis,
|
||||
|
||||
/// <summary>
|
||||
/// 单轴 Y 模式:仅上下移动 | Single axis Y mode: vertical movement only
|
||||
/// </summary>
|
||||
SingleAxisY
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace XP.Common.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 鼠标按键类型枚举 | Mouse button type enumeration
|
||||
/// </summary>
|
||||
public enum MouseButtonType
|
||||
{
|
||||
/// <summary>
|
||||
/// 未按下 | No button pressed
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标左键 | Left mouse button
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标右键 | Right mouse button
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace XP.Common.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 虚拟摇杆 UserControl,提供圆形区域内的鼠标拖拽操控能力 | Virtual joystick UserControl providing mouse drag interaction within a circular area
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 支持双轴/单轴 Y 模式、死区配置、归一化比例输出(-1.0 ~ 1.0)、鼠标左右键区分及四向功能图标切换。
|
||||
/// Supports dual-axis/single-axis Y mode, dead zone configuration, normalized proportional output (-1.0 ~ 1.0),
|
||||
/// left/right mouse button differentiation, and four-directional icon switching.
|
||||
/// </remarks>
|
||||
public partial class VirtualJoystick : UserControl
|
||||
{
|
||||
#region 私有字段 | Private Fields
|
||||
|
||||
/// <summary>是否正在拖拽 | Whether dragging is in progress</summary>
|
||||
private bool _isDragging;
|
||||
|
||||
/// <summary>控件中心点坐标 | Control center point coordinates</summary>
|
||||
private Point _centerPoint;
|
||||
|
||||
/// <summary>操控点的平移变换 | Translate transform for thumb element</summary>
|
||||
private readonly TranslateTransform _thumbTransform = new TranslateTransform();
|
||||
|
||||
/// <summary>操控点元素引用 | Thumb element reference</summary>
|
||||
private Ellipse? _thumbElement;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数 | Constructor
|
||||
|
||||
public VirtualJoystick()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 控件加载完成后绑定操控点的 TranslateTransform | Bind thumb TranslateTransform after control loaded
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
_thumbElement = FindName("PART_Thumb") as Ellipse;
|
||||
if (_thumbElement != null)
|
||||
_thumbElement.RenderTransform = _thumbTransform;
|
||||
|
||||
// 初始化时更新背景和图标可见性 | Update background and icon visibility on init
|
||||
UpdateIconVisibility();
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JoystickMode 依赖属性 | JoystickMode Dependency Property
|
||||
|
||||
public static readonly DependencyProperty JoystickModeProperty =
|
||||
DependencyProperty.Register(nameof(JoystickMode), typeof(JoystickMode), typeof(VirtualJoystick),
|
||||
new PropertyMetadata(JoystickMode.DualAxis, OnJoystickModeChanged));
|
||||
|
||||
/// <summary>获取或设置摇杆轴模式 | Gets or sets the joystick axis mode</summary>
|
||||
public JoystickMode JoystickMode
|
||||
{
|
||||
get => (JoystickMode)GetValue(JoystickModeProperty);
|
||||
set => SetValue(JoystickModeProperty, value);
|
||||
}
|
||||
|
||||
private static void OnJoystickModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is VirtualJoystick joystick)
|
||||
joystick.UpdateIconVisibility();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OutputX/OutputY 只读依赖属性 | OutputX/OutputY Read-Only Dependency Properties
|
||||
|
||||
private static readonly DependencyPropertyKey OutputXPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(nameof(OutputX), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.0));
|
||||
public static readonly DependencyProperty OutputXProperty = OutputXPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>X 轴归一化输出值 [-1.0, 1.0] | X-axis normalized output</summary>
|
||||
public double OutputX
|
||||
{
|
||||
get => (double)GetValue(OutputXProperty);
|
||||
internal set => SetValue(OutputXPropertyKey, value);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey OutputYPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(nameof(OutputY), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.0));
|
||||
public static readonly DependencyProperty OutputYProperty = OutputYPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>Y 轴归一化输出值 [-1.0, 1.0] | Y-axis normalized output</summary>
|
||||
public double OutputY
|
||||
{
|
||||
get => (double)GetValue(OutputYProperty);
|
||||
internal set => SetValue(OutputYPropertyKey, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeadZone 依赖属性 | DeadZone Dependency Property
|
||||
|
||||
public static readonly DependencyProperty DeadZoneProperty =
|
||||
DependencyProperty.Register(nameof(DeadZone), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.05));
|
||||
|
||||
/// <summary>死区比例 [0.0, 1.0],默认 0.05 | Dead zone ratio, default 0.05</summary>
|
||||
public double DeadZone
|
||||
{
|
||||
get => (double)GetValue(DeadZoneProperty);
|
||||
set => SetValue(DeadZoneProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ActiveMouseButton 只读依赖属性 | ActiveMouseButton Read-Only Dependency Property
|
||||
|
||||
private static readonly DependencyPropertyKey ActiveMouseButtonPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(nameof(ActiveMouseButton), typeof(MouseButtonType), typeof(VirtualJoystick),
|
||||
new PropertyMetadata(MouseButtonType.None, OnActiveMouseButtonChanged));
|
||||
public static readonly DependencyProperty ActiveMouseButtonProperty = ActiveMouseButtonPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>当前激活的鼠标按键 | Currently active mouse button</summary>
|
||||
public MouseButtonType ActiveMouseButton
|
||||
{
|
||||
get => (MouseButtonType)GetValue(ActiveMouseButtonProperty);
|
||||
internal set => SetValue(ActiveMouseButtonPropertyKey, value);
|
||||
}
|
||||
|
||||
private static void OnActiveMouseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is VirtualJoystick joystick)
|
||||
joystick.UpdateIconVisibility();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 四向图标依赖属性 | Directional Icon Dependency Properties
|
||||
|
||||
// 左键图标 | Left button icons
|
||||
public static readonly DependencyProperty LeftButtonTopIconProperty = DependencyProperty.Register(nameof(LeftButtonTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonTopIcon { get => GetValue(LeftButtonTopIconProperty); set => SetValue(LeftButtonTopIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty LeftButtonBottomIconProperty = DependencyProperty.Register(nameof(LeftButtonBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonBottomIcon { get => GetValue(LeftButtonBottomIconProperty); set => SetValue(LeftButtonBottomIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty LeftButtonLeftIconProperty = DependencyProperty.Register(nameof(LeftButtonLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonLeftIcon { get => GetValue(LeftButtonLeftIconProperty); set => SetValue(LeftButtonLeftIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty LeftButtonRightIconProperty = DependencyProperty.Register(nameof(LeftButtonRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonRightIcon { get => GetValue(LeftButtonRightIconProperty); set => SetValue(LeftButtonRightIconProperty, value); }
|
||||
|
||||
// 右键图标 | Right button icons
|
||||
public static readonly DependencyProperty RightButtonTopIconProperty = DependencyProperty.Register(nameof(RightButtonTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonTopIcon { get => GetValue(RightButtonTopIconProperty); set => SetValue(RightButtonTopIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RightButtonBottomIconProperty = DependencyProperty.Register(nameof(RightButtonBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonBottomIcon { get => GetValue(RightButtonBottomIconProperty); set => SetValue(RightButtonBottomIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RightButtonLeftIconProperty = DependencyProperty.Register(nameof(RightButtonLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonLeftIcon { get => GetValue(RightButtonLeftIconProperty); set => SetValue(RightButtonLeftIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RightButtonRightIconProperty = DependencyProperty.Register(nameof(RightButtonRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonRightIcon { get => GetValue(RightButtonRightIconProperty); set => SetValue(RightButtonRightIconProperty, value); }
|
||||
|
||||
// 默认图标 | Default icons
|
||||
public static readonly DependencyProperty DefaultTopIconProperty = DependencyProperty.Register(nameof(DefaultTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultTopIcon { get => GetValue(DefaultTopIconProperty); set => SetValue(DefaultTopIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DefaultBottomIconProperty = DependencyProperty.Register(nameof(DefaultBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultBottomIcon { get => GetValue(DefaultBottomIconProperty); set => SetValue(DefaultBottomIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DefaultLeftIconProperty = DependencyProperty.Register(nameof(DefaultLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultLeftIcon { get => GetValue(DefaultLeftIconProperty); set => SetValue(DefaultLeftIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DefaultRightIconProperty = DependencyProperty.Register(nameof(DefaultRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultRightIcon { get => GetValue(DefaultRightIconProperty); set => SetValue(DefaultRightIconProperty, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 鼠标交互事件处理 | Mouse Interaction Event Handlers
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
if (_isDragging || !IsEnabled) return;
|
||||
StartDrag(MouseButtonType.Left);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseRightButtonDown(e);
|
||||
if (_isDragging || !IsEnabled) return;
|
||||
StartDrag(MouseButtonType.Right);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
if (!_isDragging) return;
|
||||
|
||||
try
|
||||
{
|
||||
var mousePosition = e.GetPosition(this);
|
||||
var dx = mousePosition.X - _centerPoint.X;
|
||||
var dy = mousePosition.Y - _centerPoint.Y;
|
||||
|
||||
// SingleAxisY 模式下强制水平位移为零,操控点只能上下移动 | Force dx=0 in SingleAxisY mode, thumb moves vertically only
|
||||
if (JoystickMode == JoystickMode.SingleAxisY)
|
||||
dx = 0;
|
||||
|
||||
var radius = GetRadius();
|
||||
if (radius <= 0) return;
|
||||
|
||||
var (clampedDx, clampedDy) = JoystickCalculator.ClampToRadius(dx, dy, radius);
|
||||
var (outputX, outputY) = JoystickCalculator.CalculateOutput(dx, dy, radius, DeadZone, JoystickMode);
|
||||
|
||||
OutputX = outputX;
|
||||
OutputY = outputY;
|
||||
_thumbTransform.X = clampedDx;
|
||||
_thumbTransform.Y = clampedDy;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetToCenter();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
if (!_isDragging || ActiveMouseButton != MouseButtonType.Left) return;
|
||||
EndDrag();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseRightButtonUp(e);
|
||||
if (!_isDragging || ActiveMouseButton != MouseButtonType.Right) return;
|
||||
EndDrag();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 拖拽辅助方法 | Drag Helper Methods
|
||||
|
||||
private void StartDrag(MouseButtonType buttonType)
|
||||
{
|
||||
CaptureMouse();
|
||||
ActiveMouseButton = buttonType;
|
||||
_centerPoint = new Point(ActualWidth / 2.0, ActualHeight / 2.0);
|
||||
_isDragging = true;
|
||||
}
|
||||
|
||||
private void EndDrag()
|
||||
{
|
||||
ResetToCenter();
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
/// <summary>重置操控点到中心位置 | Reset thumb to center</summary>
|
||||
internal void ResetToCenter()
|
||||
{
|
||||
OutputX = 0.0;
|
||||
OutputY = 0.0;
|
||||
ActiveMouseButton = MouseButtonType.None;
|
||||
_isDragging = false;
|
||||
_thumbTransform.X = 0.0;
|
||||
_thumbTransform.Y = 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可用半径(限制为控件尺寸的 80%)| Get usable radius (limited to 80% of control size)
|
||||
/// 双轴取宽高最小值的一半,单轴取高度的一半 | DualAxis uses min(W,H)/2, SingleAxisY uses H/2
|
||||
/// </summary>
|
||||
private double GetRadius()
|
||||
{
|
||||
var raw = JoystickMode == JoystickMode.SingleAxisY
|
||||
? ActualHeight / 2.0
|
||||
: Math.Min(ActualWidth, ActualHeight) / 2.0;
|
||||
return raw * 0.8;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 图标可见性切换 | Icon Visibility Switching
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ActiveMouseButton 和 JoystickMode 更新图标可见性 | Update icon visibility based on ActiveMouseButton and JoystickMode
|
||||
/// </summary>
|
||||
private void UpdateIconVisibility()
|
||||
{
|
||||
// 查找命名元素 | Find named elements
|
||||
var defaultTop = FindName("DefaultTopPresenter") as UIElement;
|
||||
var defaultBottom = FindName("DefaultBottomPresenter") as UIElement;
|
||||
var defaultLeft = FindName("DefaultLeftPresenter") as UIElement;
|
||||
var defaultRight = FindName("DefaultRightPresenter") as UIElement;
|
||||
var leftTop = FindName("LeftTopPresenter") as UIElement;
|
||||
var leftBottom = FindName("LeftBottomPresenter") as UIElement;
|
||||
var leftLeft = FindName("LeftLeftPresenter") as UIElement;
|
||||
var leftRight = FindName("LeftRightPresenter") as UIElement;
|
||||
var rightTop = FindName("RightTopPresenter") as UIElement;
|
||||
var rightBottom = FindName("RightBottomPresenter") as UIElement;
|
||||
var rightLeft = FindName("RightLeftPresenter") as UIElement;
|
||||
var rightRight = FindName("RightRightPresenter") as UIElement;
|
||||
var dualBg = FindName("DualAxisBackground") as UIElement;
|
||||
var singleBg = FindName("SingleAxisBackground") as UIElement;
|
||||
var hLine = FindName("HorizontalLine") as UIElement;
|
||||
|
||||
if (defaultTop == null) return; // 控件尚未加载 | Control not yet loaded
|
||||
|
||||
// 切换背景形状:双轴=圆形,单轴=腰圆 | Switch background: DualAxis=circle, SingleAxisY=capsule
|
||||
var isSingleAxis = JoystickMode == JoystickMode.SingleAxisY;
|
||||
if (dualBg != null) dualBg.Visibility = isSingleAxis ? Visibility.Collapsed : Visibility.Visible;
|
||||
if (singleBg != null) singleBg.Visibility = isSingleAxis ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (hLine != null) hLine.Visibility = isSingleAxis ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
// 先全部隐藏 | Hide all first
|
||||
SetVisibility(Visibility.Collapsed, leftTop, leftBottom, leftLeft, leftRight, rightTop, rightBottom, rightLeft, rightRight);
|
||||
SetVisibility(Visibility.Visible, defaultTop, defaultBottom, defaultLeft, defaultRight);
|
||||
|
||||
// 根据 ActiveMouseButton 切换 | Switch based on ActiveMouseButton
|
||||
switch (ActiveMouseButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
SetVisibility(Visibility.Collapsed, defaultTop, defaultBottom, defaultLeft, defaultRight);
|
||||
SetVisibility(Visibility.Visible, leftTop, leftBottom, leftLeft, leftRight);
|
||||
if (_thumbElement != null) _thumbElement.Fill = new SolidColorBrush(Color.FromRgb(0x3A, 0x7B, 0xC8));
|
||||
break;
|
||||
case MouseButtonType.Right:
|
||||
SetVisibility(Visibility.Collapsed, defaultTop, defaultBottom, defaultLeft, defaultRight);
|
||||
SetVisibility(Visibility.Visible, rightTop, rightBottom, rightLeft, rightRight);
|
||||
if (_thumbElement != null) _thumbElement.Fill = new SolidColorBrush(Color.FromRgb(0x5B, 0xA8, 0x5B));
|
||||
break;
|
||||
default:
|
||||
if (_thumbElement != null) _thumbElement.Fill = new SolidColorBrush(Color.FromRgb(0x4A, 0x90, 0xD9));
|
||||
break;
|
||||
}
|
||||
|
||||
// SingleAxisY 模式下隐藏左右图标 | Hide left/right icons in SingleAxisY mode
|
||||
if (isSingleAxis)
|
||||
{
|
||||
SetVisibility(Visibility.Collapsed, defaultLeft, defaultRight, leftLeft, leftRight, rightLeft, rightRight);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetVisibility(Visibility visibility, params UIElement?[] elements)
|
||||
{
|
||||
foreach (var element in elements)
|
||||
if (element != null) element.Visibility = visibility;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<UserControl x:Class="XP.Common.Controls.VirtualJoystick"
|
||||
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"
|
||||
xmlns:local="clr-namespace:XP.Common.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="200" d:DesignHeight="200"
|
||||
Cursor="Hand">
|
||||
|
||||
<Grid x:Name="PART_Root">
|
||||
|
||||
<!-- ========== 双轴模式背景:圆形 | DualAxis background: Circle ========== -->
|
||||
<Ellipse x:Name="DualAxisBackground"
|
||||
Fill="#FFF5F5F5" Stroke="#FFCCCCCC" StrokeThickness="1.5" />
|
||||
|
||||
<!-- ========== 单轴模式背景:腰圆 | SingleAxisY background: Capsule ========== -->
|
||||
<Border x:Name="SingleAxisBackground"
|
||||
Background="#FFF5F5F5" BorderBrush="#FFCCCCCC" BorderThickness="1.5"
|
||||
CornerRadius="20" Visibility="Collapsed" />
|
||||
|
||||
<!-- 十字形背景虚线(双轴显示水平+垂直,单轴只显示垂直)| Cross-shaped dashed lines -->
|
||||
<Line x:Name="HorizontalLine" X1="0" Y1="0" X2="1" Y2="0" Stretch="Fill"
|
||||
Stroke="#FFD0D0D0" StrokeThickness="1" StrokeDashArray="4 2"
|
||||
VerticalAlignment="Center" IsHitTestVisible="False" />
|
||||
<Line X1="0" Y1="0" X2="0" Y2="1" Stretch="Fill"
|
||||
Stroke="#FFD0D0D0" StrokeThickness="1" StrokeDashArray="4 2"
|
||||
HorizontalAlignment="Center" IsHitTestVisible="False" />
|
||||
|
||||
<!-- 四向图标区域 | Four-directional icon area -->
|
||||
<Grid IsHitTestVisible="False" Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 上方图标 | Top icons -->
|
||||
<ContentPresenter x:Name="DefaultTopPresenter" Grid.Row="0" Grid.Column="1"
|
||||
Content="{Binding DefaultTopIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top" />
|
||||
<ContentPresenter x:Name="LeftTopPresenter" Grid.Row="0" Grid.Column="1"
|
||||
Content="{Binding LeftButtonTopIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightTopPresenter" Grid.Row="0" Grid.Column="1"
|
||||
Content="{Binding RightButtonTopIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top" Visibility="Collapsed" />
|
||||
|
||||
<!-- 下方图标 | Bottom icons -->
|
||||
<ContentPresenter x:Name="DefaultBottomPresenter" Grid.Row="2" Grid.Column="1"
|
||||
Content="{Binding DefaultBottomIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" />
|
||||
<ContentPresenter x:Name="LeftBottomPresenter" Grid.Row="2" Grid.Column="1"
|
||||
Content="{Binding LeftButtonBottomIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightBottomPresenter" Grid.Row="2" Grid.Column="1"
|
||||
Content="{Binding RightButtonBottomIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Collapsed" />
|
||||
|
||||
<!-- 左方图标 | Left icons -->
|
||||
<ContentPresenter x:Name="DefaultLeftPresenter" Grid.Row="1" Grid.Column="0"
|
||||
Content="{Binding DefaultLeftIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<ContentPresenter x:Name="LeftLeftPresenter" Grid.Row="1" Grid.Column="0"
|
||||
Content="{Binding LeftButtonLeftIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightLeftPresenter" Grid.Row="1" Grid.Column="0"
|
||||
Content="{Binding RightButtonLeftIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
|
||||
<!-- 右方图标 | Right icons -->
|
||||
<ContentPresenter x:Name="DefaultRightPresenter" Grid.Row="1" Grid.Column="2"
|
||||
Content="{Binding DefaultRightIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||
<ContentPresenter x:Name="LeftRightPresenter" Grid.Row="1" Grid.Column="2"
|
||||
Content="{Binding LeftButtonRightIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightRightPresenter" Grid.Row="1" Grid.Column="2"
|
||||
Content="{Binding RightButtonRightIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
</Grid>
|
||||
|
||||
<!-- 中心操控点 | Center thumb -->
|
||||
<Ellipse x:Name="PART_Thumb" Width="28" Height="28"
|
||||
Fill="#FF4A90D9" Stroke="#FF2A6AB0" StrokeThickness="2"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Cursor="SizeAll" IsHitTestVisible="False">
|
||||
<Ellipse.Effect>
|
||||
<DropShadowEffect Color="#40000000" BlurRadius="4" ShadowDepth="1" Opacity="0.5" />
|
||||
</Ellipse.Effect>
|
||||
</Ellipse>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -31,8 +31,7 @@
|
||||
<PackageReference Include="Telerik.UI.for.Wpf.NetCore.Xaml" Version="2024.1.408" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\ViewModels\" />
|
||||
<Folder Include="Controls\Views\" />
|
||||
|
||||
<Folder Include="Extensions\" />
|
||||
<Folder Include="Serialization\" />
|
||||
<Folder Include="Converters\" />
|
||||
|
||||
Binary file not shown.
@@ -45,6 +45,11 @@ namespace XP.Hardware.MotionControl.Abstractions
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult Stop();
|
||||
|
||||
/// <summary>设置 Jog 速度 | Set jog speed</summary>
|
||||
/// <param name="speedPercent">速度百分比(0~100)| Speed percentage (0~100)</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult SetJogSpeed(double speedPercent);
|
||||
|
||||
/// <summary>从 PLC 更新状态 | Update status from PLC</summary>
|
||||
void UpdateStatus();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace XP.Hardware.MotionControl.Abstractions
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult Stop();
|
||||
|
||||
/// <summary>设置 Jog 速度 | Set jog speed</summary>
|
||||
/// <param name="speedPercent">速度百分比(0~100)| Speed percentage (0~100)</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult SetJogSpeed(double speedPercent);
|
||||
|
||||
/// <summary>从 PLC 更新状态 | Update status from PLC</summary>
|
||||
void UpdateStatus();
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ namespace XP.Hardware.MotionControl.Abstractions
|
||||
/// <inheritdoc/>
|
||||
public abstract MotionResult Stop();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract MotionResult SetJogSpeed(double speedPercent);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void UpdateStatus();
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace XP.Hardware.MotionControl.Abstractions
|
||||
/// <inheritdoc/>
|
||||
public abstract MotionResult Stop();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract MotionResult SetJogSpeed(double speedPercent);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void UpdateStatus();
|
||||
}
|
||||
|
||||
@@ -95,6 +95,13 @@ namespace XP.Hardware.MotionControl.Implementations
|
||||
return MotionResult.Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override MotionResult SetJogSpeed(double speedPercent)
|
||||
{
|
||||
_signalService.EnqueueWrite(_speedSignal, (float)speedPercent);
|
||||
return MotionResult.Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateStatus()
|
||||
{
|
||||
|
||||
@@ -91,6 +91,14 @@ namespace XP.Hardware.MotionControl.Implementations
|
||||
return MotionResult.Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override MotionResult SetJogSpeed(double speedPercent)
|
||||
{
|
||||
if (!_enabled) return MotionResult.Fail($"旋转轴 {_axisId} 已禁用,拒绝设置 Jog 速度命令");
|
||||
_signalService.EnqueueWrite(_speedSignal, (float)speedPercent);
|
||||
return MotionResult.Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateStatus()
|
||||
{
|
||||
|
||||
@@ -258,4 +258,47 @@ SourceZ → {3:F2}mm
|
||||
DetectorZ → {4:F2}mm
|
||||
Confirm to filll move matrix?</value>
|
||||
</data>
|
||||
<!-- AxisControlView localization resources -->
|
||||
<data name="AC_AxisPositions" xml:space="preserve">
|
||||
<value>Axis Positions</value>
|
||||
</data>
|
||||
<data name="AC_StageX" xml:space="preserve">
|
||||
<value>Stage X</value>
|
||||
</data>
|
||||
<data name="AC_StageY" xml:space="preserve">
|
||||
<value>Stage Y</value>
|
||||
</data>
|
||||
<data name="AC_SourceZ" xml:space="preserve">
|
||||
<value>Source Z</value>
|
||||
</data>
|
||||
<data name="AC_DetectorZ" xml:space="preserve">
|
||||
<value>Detector Z</value>
|
||||
</data>
|
||||
<data name="AC_DetectorSwing" xml:space="preserve">
|
||||
<value>Detector Swing</value>
|
||||
</data>
|
||||
<data name="AC_StageRotation" xml:space="preserve">
|
||||
<value>Stage Rotation</value>
|
||||
</data>
|
||||
<data name="AC_FixtureRotation" xml:space="preserve">
|
||||
<value>Fixture Rotation</value>
|
||||
</data>
|
||||
<data name="AC_SafetyParams" xml:space="preserve">
|
||||
<value>Safety Parameters</value>
|
||||
</data>
|
||||
<data name="AC_SafetyHeight" xml:space="preserve">
|
||||
<value>Safety Height</value>
|
||||
</data>
|
||||
<data name="AC_CalibrationValue" xml:space="preserve">
|
||||
<value>Calibration Value</value>
|
||||
</data>
|
||||
<data name="AC_Enable" xml:space="preserve">
|
||||
<value>Physical Joystick Enable</value>
|
||||
</data>
|
||||
<data name="AC_Save" xml:space="preserve">
|
||||
<value>Save Position</value>
|
||||
</data>
|
||||
<data name="AC_Restore" xml:space="preserve">
|
||||
<value>Restore Position</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -301,4 +301,61 @@ DetectorZ → {4:F2}mm
|
||||
确认填入信息? | Confirm to filll move matrix?</value>
|
||||
<comment>几何反算确认提示 | Geometry inverse confirmation message</comment>
|
||||
</data>
|
||||
<!-- AxisControlView 多语言资源 | AxisControlView localization resources -->
|
||||
<data name="AC_AxisPositions" xml:space="preserve">
|
||||
<value>轴位置</value>
|
||||
<comment>轴位置分组标题 | Axis positions group title</comment>
|
||||
</data>
|
||||
<data name="AC_StageX" xml:space="preserve">
|
||||
<value>载物台 X</value>
|
||||
<comment>载物台 X 轴标签 | Stage X axis label</comment>
|
||||
</data>
|
||||
<data name="AC_StageY" xml:space="preserve">
|
||||
<value>载物台 Y</value>
|
||||
<comment>载物台 Y 轴标签 | Stage Y axis label</comment>
|
||||
</data>
|
||||
<data name="AC_SourceZ" xml:space="preserve">
|
||||
<value>射线源 Z</value>
|
||||
<comment>射线源 Z 轴标签 | Source Z axis label</comment>
|
||||
</data>
|
||||
<data name="AC_DetectorZ" xml:space="preserve">
|
||||
<value>探测器 Z</value>
|
||||
<comment>探测器 Z 轴标签 | Detector Z axis label</comment>
|
||||
</data>
|
||||
<data name="AC_DetectorSwing" xml:space="preserve">
|
||||
<value>探测器摆动</value>
|
||||
<comment>探测器摆动旋转轴标签 | Detector swing rotary axis label</comment>
|
||||
</data>
|
||||
<data name="AC_StageRotation" xml:space="preserve">
|
||||
<value>载物台旋转</value>
|
||||
<comment>载物台旋转轴标签 | Stage rotation axis label</comment>
|
||||
</data>
|
||||
<data name="AC_FixtureRotation" xml:space="preserve">
|
||||
<value>夹具旋转</value>
|
||||
<comment>夹具旋转轴标签 | Fixture rotation axis label</comment>
|
||||
</data>
|
||||
<data name="AC_SafetyParams" xml:space="preserve">
|
||||
<value>安全参数</value>
|
||||
<comment>安全参数分组标题 | Safety parameters group title</comment>
|
||||
</data>
|
||||
<data name="AC_SafetyHeight" xml:space="preserve">
|
||||
<value>安全高度</value>
|
||||
<comment>探测器安全高度限定值标签 | Safety height label</comment>
|
||||
</data>
|
||||
<data name="AC_CalibrationValue" xml:space="preserve">
|
||||
<value>校准计算值</value>
|
||||
<comment>校准自动计算值标签 | Calibration value label</comment>
|
||||
</data>
|
||||
<data name="AC_Enable" xml:space="preserve">
|
||||
<value>实体操作摇杆使能</value>
|
||||
<comment>使能开关标签 | Enable toggle label</comment>
|
||||
</data>
|
||||
<data name="AC_Save" xml:space="preserve">
|
||||
<value>保存当前位置</value>
|
||||
<comment>保存按钮文本 | Save button text</comment>
|
||||
</data>
|
||||
<data name="AC_Restore" xml:space="preserve">
|
||||
<value>恢复前一位置</value>
|
||||
<comment>恢复按钮文本 | Restore button text</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -258,4 +258,47 @@ SourceZ → {3:F2}mm
|
||||
DetectorZ → {4:F2}mm
|
||||
确认填入目标值信息?</value>
|
||||
</data>
|
||||
<!-- AxisControlView 多语言资源 | AxisControlView localization resources -->
|
||||
<data name="AC_AxisPositions" xml:space="preserve">
|
||||
<value>轴位置</value>
|
||||
</data>
|
||||
<data name="AC_StageX" xml:space="preserve">
|
||||
<value>载物台 X</value>
|
||||
</data>
|
||||
<data name="AC_StageY" xml:space="preserve">
|
||||
<value>载物台 Y</value>
|
||||
</data>
|
||||
<data name="AC_SourceZ" xml:space="preserve">
|
||||
<value>射线源 Z</value>
|
||||
</data>
|
||||
<data name="AC_DetectorZ" xml:space="preserve">
|
||||
<value>探测器 Z</value>
|
||||
</data>
|
||||
<data name="AC_DetectorSwing" xml:space="preserve">
|
||||
<value>探测器摆动</value>
|
||||
</data>
|
||||
<data name="AC_StageRotation" xml:space="preserve">
|
||||
<value>载物台旋转</value>
|
||||
</data>
|
||||
<data name="AC_FixtureRotation" xml:space="preserve">
|
||||
<value>夹具旋转</value>
|
||||
</data>
|
||||
<data name="AC_SafetyParams" xml:space="preserve">
|
||||
<value>安全参数</value>
|
||||
</data>
|
||||
<data name="AC_SafetyHeight" xml:space="preserve">
|
||||
<value>安全高度</value>
|
||||
</data>
|
||||
<data name="AC_CalibrationValue" xml:space="preserve">
|
||||
<value>校准计算值</value>
|
||||
</data>
|
||||
<data name="AC_Enable" xml:space="preserve">
|
||||
<value>实体操作摇杆使能</value>
|
||||
</data>
|
||||
<data name="AC_Save" xml:space="preserve">
|
||||
<value>保存当前位置</value>
|
||||
</data>
|
||||
<data name="AC_Restore" xml:space="preserve">
|
||||
<value>恢复前一位置</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -258,4 +258,47 @@ SourceZ → {3:F2}mm
|
||||
DetectorZ → {4:F2}mm
|
||||
確填入目標值資料?</value>
|
||||
</data>
|
||||
<!-- AxisControlView 多語言資源 | AxisControlView localization resources -->
|
||||
<data name="AC_AxisPositions" xml:space="preserve">
|
||||
<value>軸位置</value>
|
||||
</data>
|
||||
<data name="AC_StageX" xml:space="preserve">
|
||||
<value>載物台 X</value>
|
||||
</data>
|
||||
<data name="AC_StageY" xml:space="preserve">
|
||||
<value>載物台 Y</value>
|
||||
</data>
|
||||
<data name="AC_SourceZ" xml:space="preserve">
|
||||
<value>射線源 Z</value>
|
||||
</data>
|
||||
<data name="AC_DetectorZ" xml:space="preserve">
|
||||
<value>探測器 Z</value>
|
||||
</data>
|
||||
<data name="AC_DetectorSwing" xml:space="preserve">
|
||||
<value>探測器擺動</value>
|
||||
</data>
|
||||
<data name="AC_StageRotation" xml:space="preserve">
|
||||
<value>載物台旋轉</value>
|
||||
</data>
|
||||
<data name="AC_FixtureRotation" xml:space="preserve">
|
||||
<value>夾具旋轉</value>
|
||||
</data>
|
||||
<data name="AC_SafetyParams" xml:space="preserve">
|
||||
<value>安全參數</value>
|
||||
</data>
|
||||
<data name="AC_SafetyHeight" xml:space="preserve">
|
||||
<value>安全高度</value>
|
||||
</data>
|
||||
<data name="AC_CalibrationValue" xml:space="preserve">
|
||||
<value>校準計算值</value>
|
||||
</data>
|
||||
<data name="AC_Enable" xml:space="preserve">
|
||||
<value>實體操作搖桿使能</value>
|
||||
</data>
|
||||
<data name="AC_Save" xml:space="preserve">
|
||||
<value>保存當前位置</value>
|
||||
</data>
|
||||
<data name="AC_Restore" xml:space="preserve">
|
||||
<value>恢復前一位置</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -106,6 +106,24 @@ namespace XP.Hardware.MotionControl.Services
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult JogRotaryStop(RotaryAxisId axisId);
|
||||
|
||||
/// <summary>
|
||||
/// 设置直线轴 Jog 速度 | Set linear axis jog speed
|
||||
/// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC
|
||||
/// </summary>
|
||||
/// <param name="axisId">直线轴标识 | Linear axis identifier</param>
|
||||
/// <param name="speedPercent">速度百分比(0~100)| Speed percentage (0~100)</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult SetJogSpeed(AxisId axisId, double speedPercent);
|
||||
|
||||
/// <summary>
|
||||
/// 设置旋转轴 Jog 速度 | Set rotary axis jog speed
|
||||
/// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC
|
||||
/// </summary>
|
||||
/// <param name="axisId">旋转轴标识 | Rotary axis identifier</param>
|
||||
/// <param name="speedPercent">速度百分比(0~100)| Speed percentage (0~100)</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
MotionResult SetJogRotarySpeed(RotaryAxisId axisId, double speedPercent);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 安全门控制 | Safety Door Control
|
||||
|
||||
@@ -488,6 +488,60 @@ namespace XP.Hardware.MotionControl.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置直线轴 Jog 速度 | Set linear axis jog speed
|
||||
/// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC
|
||||
/// </summary>
|
||||
public MotionResult SetJogSpeed(AxisId axisId, double speedPercent)
|
||||
{
|
||||
var axis = _motionSystem.GetLinearAxis(axisId);
|
||||
var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0;
|
||||
var result = axis.SetJogSpeed(actualSpeed);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Debug("直线轴 {AxisId} Jog 速度已设置,百分比={SpeedPercent}%,实际速度={ActualSpeed} | Linear axis {AxisId} jog speed set, percent={SpeedPercent}%, actual={ActualSpeed}",
|
||||
axisId, speedPercent, actualSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("直线轴 {AxisId} 设置 Jog 速度被拒绝:{Reason} | Linear axis {AxisId} set jog speed rejected: {Reason}", axisId, result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置旋转轴 Jog 速度 | Set rotary axis jog speed
|
||||
/// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC
|
||||
/// </summary>
|
||||
public MotionResult SetJogRotarySpeed(RotaryAxisId axisId, double speedPercent)
|
||||
{
|
||||
var axis = _motionSystem.GetRotaryAxis(axisId);
|
||||
|
||||
// 禁用轴检查 | Disabled axis check
|
||||
if (!axis.Enabled)
|
||||
{
|
||||
_logger.Warn("旋转轴 {AxisId} 已禁用,拒绝设置 Jog 速度命令 | Rotary axis {AxisId} is disabled, set jog speed rejected", axisId);
|
||||
return MotionResult.Fail($"旋转轴 {axisId} 已禁用 | Rotary axis {axisId} is disabled");
|
||||
}
|
||||
|
||||
var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0;
|
||||
var result = axis.SetJogSpeed(actualSpeed);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Debug("旋转轴 {AxisId} Jog 速度已设置,百分比={SpeedPercent}%,实际速度={ActualSpeed} | Rotary axis {AxisId} jog speed set, percent={SpeedPercent}%, actual={ActualSpeed}",
|
||||
axisId, speedPercent, actualSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("旋转轴 {AxisId} 设置 Jog 速度被拒绝:{Reason} | Rotary axis {AxisId} set jog speed rejected: {Reason}", axisId, result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 安全门控制 | Safety Door Control
|
||||
|
||||
@@ -0,0 +1,896 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using XP.Common.Controls;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.MotionControl.Abstractions;
|
||||
using XP.Hardware.MotionControl.Abstractions.Enums;
|
||||
using XP.Hardware.MotionControl.Abstractions.Events;
|
||||
using XP.Hardware.MotionControl.Config;
|
||||
using XP.Hardware.MotionControl.Services;
|
||||
using XP.Hardware.Plc.Abstractions;
|
||||
using XP.Common.Localization;
|
||||
|
||||
namespace XP.Hardware.MotionControl.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 轴控制面板 ViewModel | Axis Control Panel ViewModel
|
||||
/// 集成摇杆、轴位置输入框、安全参数和使能控制 | Integrates joystick, axis position inputs, safety parameters and enable control
|
||||
/// </summary>
|
||||
public class AxisControlViewModel : BindableBase
|
||||
{
|
||||
private readonly IMotionControlService _motionControlService;
|
||||
private readonly IMotionSystem _motionSystem;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly MotionControlConfig _config;
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
#region 内部状态跟踪 | Internal State Tracking
|
||||
|
||||
/// <summary>直线轴 Jog 活跃状态 | Linear axis jog active states</summary>
|
||||
private readonly Dictionary<AxisId, bool> _linearJogActive = new();
|
||||
|
||||
/// <summary>旋转轴 Jog 活跃状态 | Rotary axis jog active states</summary>
|
||||
private readonly Dictionary<RotaryAxisId, bool> _rotaryJogActive = new();
|
||||
|
||||
/// <summary>输入框编辑冻结标志 | Input box editing freeze flags</summary>
|
||||
private readonly Dictionary<string, bool> _editingFlags = new();
|
||||
|
||||
/// <summary>保存的轴位置数据 | Saved axis position data</summary>
|
||||
private SavedPositions _savedPositions;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数 | Constructor
|
||||
|
||||
public AxisControlViewModel(
|
||||
IMotionControlService motionControlService,
|
||||
IMotionSystem motionSystem,
|
||||
IEventAggregator eventAggregator,
|
||||
MotionControlConfig config,
|
||||
IPlcService plcService,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_motionControlService = motionControlService ?? throw new ArgumentNullException(nameof(motionControlService));
|
||||
_motionSystem = motionSystem ?? throw new ArgumentNullException(nameof(motionSystem));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<AxisControlViewModel>();
|
||||
|
||||
// 监听 PLC 连接状态变化 | Listen for PLC connection status changes
|
||||
_plcService.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(IPlcService.IsConnected))
|
||||
OnPlcConnectionChanged();
|
||||
};
|
||||
|
||||
// 初始化旋转轴输入框可见性 | Initialize rotary axis input box visibility
|
||||
DetectorSwingVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.DetectorSwing)
|
||||
&& _config.RotaryAxes[RotaryAxisId.DetectorSwing].Enabled
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
StageRotationVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation)
|
||||
&& _config.RotaryAxes[RotaryAxisId.StageRotation].Enabled
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
FixtureRotationVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation)
|
||||
&& _config.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// 初始化命令 | Initialize commands
|
||||
ToggleEnableCommand = new DelegateCommand(ExecuteToggleEnable, () => IsPlcConnected);
|
||||
SavePositionsCommand = new DelegateCommand(ExecuteSavePositions);
|
||||
RestorePositionsCommand = new DelegateCommand(ExecuteRestorePositions, () => _savedPositions != null && IsPlcConnected);
|
||||
|
||||
// 初始化 Jog 状态跟踪字典 | Initialize jog state tracking dictionaries
|
||||
foreach (AxisId axisId in Enum.GetValues(typeof(AxisId)))
|
||||
_linearJogActive[axisId] = false;
|
||||
foreach (RotaryAxisId axisId in Enum.GetValues(typeof(RotaryAxisId)))
|
||||
_rotaryJogActive[axisId] = false;
|
||||
|
||||
// 初始化 PLC 连接状态 | Initialize PLC connection status
|
||||
_isPlcConnected = _plcService.IsConnected;
|
||||
|
||||
// 订阅事件 | Subscribe to events
|
||||
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Subscribe(OnGeometryUpdated, ThreadOption.UIThread);
|
||||
_eventAggregator.GetEvent<AxisStatusChangedEvent>().Subscribe(OnAxisStatusChanged, ThreadOption.UIThread);
|
||||
|
||||
// 初始化时主动刷新一次轴位置 | Refresh axis positions on initialization
|
||||
try
|
||||
{
|
||||
StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
|
||||
StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
|
||||
SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition;
|
||||
DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition;
|
||||
DetectorSwingAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
|
||||
StageRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
|
||||
FixtureRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
|
||||
}
|
||||
catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 摇杆输出属性 | Joystick Output Properties
|
||||
|
||||
private double _dualJoystickOutputX;
|
||||
/// <summary>双轴摇杆 X 轴输出 | Dual-axis joystick X output</summary>
|
||||
public double DualJoystickOutputX
|
||||
{
|
||||
get => _dualJoystickOutputX;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _dualJoystickOutputX, value))
|
||||
HandleDualJoystickOutput();
|
||||
}
|
||||
}
|
||||
|
||||
private double _dualJoystickOutputY;
|
||||
/// <summary>双轴摇杆 Y 轴输出 | Dual-axis joystick Y output</summary>
|
||||
public double DualJoystickOutputY
|
||||
{
|
||||
get => _dualJoystickOutputY;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _dualJoystickOutputY, value))
|
||||
HandleDualJoystickOutput();
|
||||
}
|
||||
}
|
||||
|
||||
private MouseButtonType _dualJoystickActiveButton;
|
||||
/// <summary>双轴摇杆当前激活按键 | Dual-axis joystick active mouse button</summary>
|
||||
public MouseButtonType DualJoystickActiveButton
|
||||
{
|
||||
get => _dualJoystickActiveButton;
|
||||
set
|
||||
{
|
||||
var oldValue = _dualJoystickActiveButton;
|
||||
if (SetProperty(ref _dualJoystickActiveButton, value))
|
||||
{
|
||||
// 按键释放时停止所有双轴摇杆关联轴 | Stop all dual joystick axes when button released
|
||||
if (value == MouseButtonType.None && oldValue != MouseButtonType.None)
|
||||
StopDualJoystickAxes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _singleJoystickOutputY;
|
||||
/// <summary>单轴摇杆 Y 轴输出 | Single-axis joystick Y output</summary>
|
||||
public double SingleJoystickOutputY
|
||||
{
|
||||
get => _singleJoystickOutputY;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _singleJoystickOutputY, value))
|
||||
HandleSingleJoystickOutput();
|
||||
}
|
||||
}
|
||||
|
||||
private MouseButtonType _singleJoystickActiveButton;
|
||||
/// <summary>单轴摇杆当前激活按键 | Single-axis joystick active mouse button</summary>
|
||||
public MouseButtonType SingleJoystickActiveButton
|
||||
{
|
||||
get => _singleJoystickActiveButton;
|
||||
set
|
||||
{
|
||||
var oldValue = _singleJoystickActiveButton;
|
||||
if (SetProperty(ref _singleJoystickActiveButton, value))
|
||||
{
|
||||
// 按键释放时停止所有单轴摇杆关联轴 | Stop all single joystick axes when button released
|
||||
if (value == MouseButtonType.None && oldValue != MouseButtonType.None)
|
||||
StopSingleJoystickAxes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 轴位置属性 | Axis Position Properties
|
||||
|
||||
private double _stageXPosition;
|
||||
/// <summary>载物台 X 轴位置 | Stage X axis position</summary>
|
||||
public double StageXPosition { get => _stageXPosition; set => SetProperty(ref _stageXPosition, value); }
|
||||
|
||||
private double _stageYPosition;
|
||||
/// <summary>载物台 Y 轴位置 | Stage Y axis position</summary>
|
||||
public double StageYPosition { get => _stageYPosition; set => SetProperty(ref _stageYPosition, value); }
|
||||
|
||||
private double _sourceZPosition;
|
||||
/// <summary>射线源 Z 轴位置 | Source Z axis position</summary>
|
||||
public double SourceZPosition { get => _sourceZPosition; set => SetProperty(ref _sourceZPosition, value); }
|
||||
|
||||
private double _detectorZPosition;
|
||||
/// <summary>探测器 Z 轴位置 | Detector Z axis position</summary>
|
||||
public double DetectorZPosition { get => _detectorZPosition; set => SetProperty(ref _detectorZPosition, value); }
|
||||
|
||||
private double _detectorSwingAngle;
|
||||
/// <summary>探测器摆动角度 | Detector swing angle</summary>
|
||||
public double DetectorSwingAngle { get => _detectorSwingAngle; set => SetProperty(ref _detectorSwingAngle, value); }
|
||||
|
||||
private double _stageRotationAngle;
|
||||
/// <summary>载物台旋转角度 | Stage rotation angle</summary>
|
||||
public double StageRotationAngle { get => _stageRotationAngle; set => SetProperty(ref _stageRotationAngle, value); }
|
||||
|
||||
private double _fixtureRotationAngle;
|
||||
/// <summary>夹具旋转角度 | Fixture rotation angle</summary>
|
||||
public double FixtureRotationAngle { get => _fixtureRotationAngle; set => SetProperty(ref _fixtureRotationAngle, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 旋转轴可见性 | Rotary Axis Visibility
|
||||
|
||||
private Visibility _detectorSwingVisibility;
|
||||
/// <summary>探测器摆动输入框可见性 | Detector swing input box visibility</summary>
|
||||
public Visibility DetectorSwingVisibility { get => _detectorSwingVisibility; private set => SetProperty(ref _detectorSwingVisibility, value); }
|
||||
|
||||
private Visibility _stageRotationVisibility;
|
||||
/// <summary>载物台旋转输入框可见性 | Stage rotation input box visibility</summary>
|
||||
public Visibility StageRotationVisibility { get => _stageRotationVisibility; private set => SetProperty(ref _stageRotationVisibility, value); }
|
||||
|
||||
private Visibility _fixtureRotationVisibility;
|
||||
/// <summary>夹具旋转输入框可见性 | Fixture rotation input box visibility</summary>
|
||||
public Visibility FixtureRotationVisibility { get => _fixtureRotationVisibility; private set => SetProperty(ref _fixtureRotationVisibility, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 安全参数属性 | Safety Parameter Properties
|
||||
|
||||
private double _safetyHeight;
|
||||
/// <summary>探测器安全高度限定值 | Detector safety height limit</summary>
|
||||
public double SafetyHeight { get => _safetyHeight; set => SetProperty(ref _safetyHeight, value); }
|
||||
|
||||
private double _calibrationValue;
|
||||
/// <summary>校准自动计算值 | Calibration auto-calculated value</summary>
|
||||
public double CalibrationValue { get => _calibrationValue; set => SetProperty(ref _calibrationValue, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 使能与状态属性 | Enable and Status Properties
|
||||
|
||||
private bool _isJoystickEnabled = true;
|
||||
/// <summary>摇杆使能状态 | Joystick enable state</summary>
|
||||
public bool IsJoystickEnabled { get => _isJoystickEnabled; set => SetProperty(ref _isJoystickEnabled, value); }
|
||||
|
||||
private bool _isPlcConnected;
|
||||
/// <summary>PLC 连接状态 | PLC connection status</summary>
|
||||
public bool IsPlcConnected { get => _isPlcConnected; set => SetProperty(ref _isPlcConnected, value); }
|
||||
|
||||
private string _errorMessage;
|
||||
/// <summary>错误提示信息 | Error message</summary>
|
||||
public string ErrorMessage { get => _errorMessage; set => SetProperty(ref _errorMessage, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令 | Commands
|
||||
|
||||
/// <summary>切换使能开关命令 | Toggle enable switch command</summary>
|
||||
public DelegateCommand ToggleEnableCommand { get; }
|
||||
|
||||
/// <summary>保存当前轴位置命令 | Save current axis positions command</summary>
|
||||
public DelegateCommand SavePositionsCommand { get; }
|
||||
|
||||
/// <summary>恢复保存的轴位置命令 | Restore saved axis positions command</summary>
|
||||
public DelegateCommand RestorePositionsCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件回调 | Event Callbacks
|
||||
|
||||
/// <summary>
|
||||
/// 几何参数更新回调,刷新轴实际位置 | Geometry updated callback, refresh axis actual positions
|
||||
/// </summary>
|
||||
private void OnGeometryUpdated(GeometryData data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsEditing(nameof(StageXPosition)))
|
||||
StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
|
||||
if (!IsEditing(nameof(StageYPosition)))
|
||||
StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
|
||||
if (!IsEditing(nameof(SourceZPosition)))
|
||||
SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition;
|
||||
if (!IsEditing(nameof(DetectorZPosition)))
|
||||
DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition;
|
||||
if (!IsEditing(nameof(DetectorSwingAngle)))
|
||||
DetectorSwingAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
|
||||
if (!IsEditing(nameof(StageRotationAngle)))
|
||||
StageRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
|
||||
if (!IsEditing(nameof(FixtureRotationAngle)))
|
||||
FixtureRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
|
||||
}
|
||||
catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轴状态变化回调,更新对应轴位置 | Axis status changed callback, update corresponding axis position
|
||||
/// </summary>
|
||||
private void OnAxisStatusChanged(AxisStatusChangedData data)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (data.AxisId)
|
||||
{
|
||||
case AxisId.StageX:
|
||||
if (!IsEditing(nameof(StageXPosition)))
|
||||
StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
|
||||
break;
|
||||
case AxisId.StageY:
|
||||
if (!IsEditing(nameof(StageYPosition)))
|
||||
StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
|
||||
break;
|
||||
case AxisId.SourceZ:
|
||||
if (!IsEditing(nameof(SourceZPosition)))
|
||||
SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition;
|
||||
break;
|
||||
case AxisId.DetectorZ:
|
||||
if (!IsEditing(nameof(DetectorZPosition)))
|
||||
DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑状态辅助 | Editing State Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定输入框是否正在编辑 | Check if specified input box is being edited
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称 | Property name</param>
|
||||
/// <returns>是否正在编辑 | Whether editing</returns>
|
||||
private bool IsEditing(string propertyName)
|
||||
{
|
||||
return _editingFlags.TryGetValue(propertyName, out var editing) && editing;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 异常保护 | Exception Protection
|
||||
|
||||
/// <summary>
|
||||
/// 安全执行:捕获异常避免 UI 崩溃 | Safe execution: catches exceptions to prevent UI crash
|
||||
/// </summary>
|
||||
private void SafeRun(Action action)
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "运动控制操作异常:{Message} | Motion control operation error: {Message}", ex.Message);
|
||||
ErrorMessage = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令状态刷新 | Command State Refresh
|
||||
|
||||
/// <summary>
|
||||
/// 刷新所有命令的 CanExecute 状态 | Refresh CanExecute state of all commands
|
||||
/// </summary>
|
||||
private void RaiseCommandCanExecuteChanged()
|
||||
{
|
||||
ToggleEnableCommand.RaiseCanExecuteChanged();
|
||||
SavePositionsCommand.RaiseCanExecuteChanged();
|
||||
RestorePositionsCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PLC 连接状态变化处理:断开时停止所有 Jog 并禁用操作,重连时清除错误
|
||||
/// PLC connection state change handler: stop all jog and disable operations on disconnect, clear error on reconnect
|
||||
/// </summary>
|
||||
private void OnPlcConnectionChanged()
|
||||
{
|
||||
IsPlcConnected = _plcService.IsConnected;
|
||||
RaiseCommandCanExecuteChanged();
|
||||
|
||||
if (!_plcService.IsConnected)
|
||||
{
|
||||
// PLC 断开:停止所有活跃的 Jog 操作 | PLC disconnected: stop all active jog operations
|
||||
foreach (var axisId in _linearJogActive.Keys)
|
||||
{
|
||||
if (_linearJogActive[axisId])
|
||||
{
|
||||
_linearJogActive[axisId] = false;
|
||||
_logger.Debug("PLC 断开,直线轴 Jog 已标记停止:{AxisId} | PLC disconnected, linear axis jog marked stopped: {AxisId}", axisId);
|
||||
}
|
||||
}
|
||||
foreach (var axisId in _rotaryJogActive.Keys)
|
||||
{
|
||||
if (_rotaryJogActive[axisId])
|
||||
{
|
||||
_rotaryJogActive[axisId] = false;
|
||||
_logger.Debug("PLC 断开,旋转轴 Jog 已标记停止:{AxisId} | PLC disconnected, rotary axis jog marked stopped: {AxisId}", axisId);
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用摇杆 | Disable joystick
|
||||
IsJoystickEnabled = false;
|
||||
|
||||
// 显示连接断开提示 | Show disconnection message
|
||||
ErrorMessage = LocalizationHelper.Get("MC_PlcNotConnected");
|
||||
|
||||
_logger.Warn("PLC 连接断开,已停止所有 Jog 并禁用运动控制 | PLC disconnected, all jog stopped and motion control disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
// PLC 重连:清除错误信息(不自动启用摇杆,需用户手动开启)
|
||||
// PLC reconnected: clear error message (don't auto-enable joystick, user must manually enable)
|
||||
ErrorMessage = null;
|
||||
|
||||
_logger.Info("PLC 连接已恢复 | PLC connection restored");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 使能开关与保存/恢复命令 | Enable Toggle and Save/Restore Commands
|
||||
|
||||
/// <summary>
|
||||
/// 切换摇杆使能状态,并发送使能状态到 PLC | Toggle joystick enable state and send to PLC
|
||||
/// </summary>
|
||||
private void ExecuteToggleEnable()
|
||||
{
|
||||
IsJoystickEnabled = !IsJoystickEnabled;
|
||||
_logger.Info("摇杆使能状态切换:{Enabled} | Joystick enable toggled: {Enabled}", IsJoystickEnabled);
|
||||
// TODO: 发送使能状态到 PLC(根据实际 PLC 信号定义)| Send enable state to PLC (based on actual PLC signal definition)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前 6 个轴位置到内部变量 | Save current 6 axis positions to internal variable
|
||||
/// </summary>
|
||||
private void ExecuteSavePositions()
|
||||
{
|
||||
_savedPositions = new SavedPositions
|
||||
{
|
||||
StageX = StageXPosition,
|
||||
StageY = StageYPosition,
|
||||
SourceZ = SourceZPosition,
|
||||
DetectorZ = DetectorZPosition,
|
||||
DetectorSwing = DetectorSwingAngle,
|
||||
StageRotation = StageRotationAngle,
|
||||
FixtureRotation = FixtureRotationAngle
|
||||
};
|
||||
RestorePositionsCommand.RaiseCanExecuteChanged();
|
||||
_logger.Info("轴位置已保存 | Axis positions saved");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从保存的数据恢复到输入框,并发送移动命令 | Restore saved data to input boxes and send move commands
|
||||
/// </summary>
|
||||
private void ExecuteRestorePositions()
|
||||
{
|
||||
if (_savedPositions == null) return;
|
||||
|
||||
// 恢复到输入框 | Restore to input boxes
|
||||
StageXPosition = _savedPositions.StageX;
|
||||
StageYPosition = _savedPositions.StageY;
|
||||
SourceZPosition = _savedPositions.SourceZ;
|
||||
DetectorZPosition = _savedPositions.DetectorZ;
|
||||
DetectorSwingAngle = _savedPositions.DetectorSwing;
|
||||
StageRotationAngle = _savedPositions.StageRotation;
|
||||
FixtureRotationAngle = _savedPositions.FixtureRotation;
|
||||
|
||||
// 发送移动命令 | Send move commands
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.MoveToTarget(AxisId.StageX, _savedPositions.StageX);
|
||||
_motionControlService.MoveToTarget(AxisId.StageY, _savedPositions.StageY);
|
||||
_motionControlService.MoveToTarget(AxisId.SourceZ, _savedPositions.SourceZ);
|
||||
_motionControlService.MoveToTarget(AxisId.DetectorZ, _savedPositions.DetectorZ);
|
||||
_motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, _savedPositions.DetectorSwing);
|
||||
_motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, _savedPositions.StageRotation);
|
||||
_motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, _savedPositions.FixtureRotation);
|
||||
});
|
||||
_logger.Info("轴位置已恢复并发送移动命令 | Axis positions restored and move commands sent");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 摇杆 Jog 映射逻辑 | Joystick Jog Mapping Logic
|
||||
|
||||
/// <summary>
|
||||
/// 处理双轴摇杆输出变化,根据当前激活按键映射到对应轴的 Jog 操作
|
||||
/// Handle dual joystick output changes, map to corresponding axis jog based on active button
|
||||
/// </summary>
|
||||
private void HandleDualJoystickOutput()
|
||||
{
|
||||
switch (DualJoystickActiveButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
// 左键:X→StageX Jog,Y→StageY Jog | Left button: X→StageX Jog, Y→StageY Jog
|
||||
UpdateLinearJog(AxisId.StageX, DualJoystickOutputX);
|
||||
UpdateLinearJog(AxisId.StageY, DualJoystickOutputY);
|
||||
break;
|
||||
|
||||
case MouseButtonType.Right:
|
||||
// 右键:X→DetectorSwing Jog,Y→StageRotation 或 FixtureRotation Jog
|
||||
// Right button: X→DetectorSwing Jog, Y→StageRotation or FixtureRotation Jog
|
||||
UpdateRotaryJog(RotaryAxisId.DetectorSwing, DualJoystickOutputX);
|
||||
var rotationAxisId = GetEnabledRotationAxisId();
|
||||
if (rotationAxisId.HasValue)
|
||||
UpdateRotaryJog(rotationAxisId.Value, DualJoystickOutputY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理单轴摇杆输出变化,根据当前激活按键映射到对应轴的 Jog 操作
|
||||
/// Handle single joystick output changes, map to corresponding axis jog based on active button
|
||||
/// </summary>
|
||||
private void HandleSingleJoystickOutput()
|
||||
{
|
||||
switch (SingleJoystickActiveButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
// 左键:Y→SourceZ Jog | Left button: Y→SourceZ Jog
|
||||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||||
break;
|
||||
|
||||
case MouseButtonType.Right:
|
||||
// 右键:Y→DetectorZ Jog | Right button: Y→DetectorZ Jog
|
||||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新直线轴 Jog 状态:启动、更新速度或停止
|
||||
/// Update linear axis jog state: start, update speed, or stop
|
||||
/// </summary>
|
||||
/// <param name="axisId">直线轴标识 | Linear axis identifier</param>
|
||||
/// <param name="output">摇杆输出值(-1.0 ~ 1.0)| Joystick output value (-1.0 ~ 1.0)</param>
|
||||
private void UpdateLinearJog(AxisId axisId, double output)
|
||||
{
|
||||
if (output != 0)
|
||||
{
|
||||
var speedPercent = Math.Abs(output) * 100;
|
||||
var positive = output > 0;
|
||||
|
||||
if (!_linearJogActive[axisId])
|
||||
{
|
||||
// 从零变为非零:先设速度再启动 Jog | Zero to non-zero: set speed then start jog
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.SetJogSpeed(axisId, speedPercent);
|
||||
_motionControlService.JogStart(axisId, positive);
|
||||
_linearJogActive[axisId] = true;
|
||||
_logger.Debug("直线轴 Jog 启动:{AxisId},方向={Direction},速度={Speed}% | Linear axis jog started: {AxisId}, direction={Direction}, speed={Speed}%",
|
||||
axisId, positive ? "正向" : "反向", speedPercent);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已在 Jog 中:仅更新速度 | Already jogging: update speed only
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.SetJogSpeed(axisId, speedPercent);
|
||||
_logger.Debug("直线轴 Jog 速度更新:{AxisId},速度={Speed}% | Linear axis jog speed updated: {AxisId}, speed={Speed}%",
|
||||
axisId, speedPercent);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从非零变为零:停止 Jog | Non-zero to zero: stop jog
|
||||
if (_linearJogActive[axisId])
|
||||
{
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.JogStop(axisId);
|
||||
_linearJogActive[axisId] = false;
|
||||
_logger.Debug("直线轴 Jog 停止:{AxisId} | Linear axis jog stopped: {AxisId}", axisId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新旋转轴 Jog 状态:启动、更新速度或停止
|
||||
/// Update rotary axis jog state: start, update speed, or stop
|
||||
/// </summary>
|
||||
/// <param name="axisId">旋转轴标识 | Rotary axis identifier</param>
|
||||
/// <param name="output">摇杆输出值(-1.0 ~ 1.0)| Joystick output value (-1.0 ~ 1.0)</param>
|
||||
private void UpdateRotaryJog(RotaryAxisId axisId, double output)
|
||||
{
|
||||
if (output != 0)
|
||||
{
|
||||
var speedPercent = Math.Abs(output) * 100;
|
||||
var positive = output > 0;
|
||||
|
||||
if (!_rotaryJogActive[axisId])
|
||||
{
|
||||
// 从零变为非零:先设速度再启动 Jog | Zero to non-zero: set speed then start jog
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.SetJogRotarySpeed(axisId, speedPercent);
|
||||
_motionControlService.JogRotaryStart(axisId, positive);
|
||||
_rotaryJogActive[axisId] = true;
|
||||
_logger.Debug("旋转轴 Jog 启动:{AxisId},方向={Direction},速度={Speed}% | Rotary axis jog started: {AxisId}, direction={Direction}, speed={Speed}%",
|
||||
axisId, positive ? "正向" : "反向", speedPercent);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已在 Jog 中:仅更新速度 | Already jogging: update speed only
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.SetJogRotarySpeed(axisId, speedPercent);
|
||||
_logger.Debug("旋转轴 Jog 速度更新:{AxisId},速度={Speed}% | Rotary axis jog speed updated: {AxisId}, speed={Speed}%",
|
||||
axisId, speedPercent);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从非零变为零:停止 Jog | Non-zero to zero: stop jog
|
||||
if (_rotaryJogActive[axisId])
|
||||
{
|
||||
SafeRun(() =>
|
||||
{
|
||||
_motionControlService.JogRotaryStop(axisId);
|
||||
_rotaryJogActive[axisId] = false;
|
||||
_logger.Debug("旋转轴 Jog 停止:{AxisId} | Rotary axis jog stopped: {AxisId}", axisId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止双轴摇杆控制的所有轴 Jog | Stop all axes controlled by dual joystick
|
||||
/// </summary>
|
||||
private void StopDualJoystickAxes()
|
||||
{
|
||||
// 左键关联轴:StageX、StageY | Left button axes: StageX, StageY
|
||||
UpdateLinearJog(AxisId.StageX, 0);
|
||||
UpdateLinearJog(AxisId.StageY, 0);
|
||||
|
||||
// 右键关联轴:DetectorSwing、StageRotation/FixtureRotation | Right button axes: DetectorSwing, StageRotation/FixtureRotation
|
||||
UpdateRotaryJog(RotaryAxisId.DetectorSwing, 0);
|
||||
var rotationAxisId = GetEnabledRotationAxisId();
|
||||
if (rotationAxisId.HasValue)
|
||||
UpdateRotaryJog(rotationAxisId.Value, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止单轴摇杆控制的所有轴 Jog | Stop all axes controlled by single joystick
|
||||
/// </summary>
|
||||
private void StopSingleJoystickAxes()
|
||||
{
|
||||
// 左键关联轴:SourceZ | Left button axis: SourceZ
|
||||
UpdateLinearJog(AxisId.SourceZ, 0);
|
||||
|
||||
// 右键关联轴:DetectorZ | Right button axis: DetectorZ
|
||||
UpdateLinearJog(AxisId.DetectorZ, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前启用的旋转轴标识(StageRotation 或 FixtureRotation 二选一)
|
||||
/// Get the currently enabled rotation axis ID (StageRotation or FixtureRotation, one of two)
|
||||
/// </summary>
|
||||
/// <returns>启用的旋转轴标识,若均未启用则返回 null | Enabled rotary axis ID, or null if none enabled</returns>
|
||||
private RotaryAxisId? GetEnabledRotationAxisId()
|
||||
{
|
||||
if (_config.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation)
|
||||
&& _config.RotaryAxes[RotaryAxisId.StageRotation].Enabled)
|
||||
return RotaryAxisId.StageRotation;
|
||||
|
||||
if (_config.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation)
|
||||
&& _config.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled)
|
||||
return RotaryAxisId.FixtureRotation;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入框编辑与目标位置 | Input Box Editing and Target Position
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定输入框的编辑状态 | Set editing state for specified input box
|
||||
/// GotFocus 时设为 true 冻结实时更新,LostFocus 时设为 false 恢复更新
|
||||
/// Set to true on GotFocus to freeze live updates, false on LostFocus to resume
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称 | Property name</param>
|
||||
/// <param name="isEditing">是否正在编辑 | Whether editing</param>
|
||||
public void SetEditing(string propertyName, bool isEditing)
|
||||
{
|
||||
_editingFlags[propertyName] = isEditing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认输入框编辑,发送目标位置移动命令 | Confirm input box edit, send target position move command
|
||||
/// Enter 键触发,调用 MoveToTarget/MoveRotaryToTarget 后恢复实时更新
|
||||
/// Triggered by Enter key, calls MoveToTarget/MoveRotaryToTarget then resumes live updates
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称 | Property name</param>
|
||||
public void ConfirmPosition(string propertyName)
|
||||
{
|
||||
var value = GetPropertyValue(propertyName);
|
||||
SafeRun(() =>
|
||||
{
|
||||
var result = SendMoveCommand(propertyName, value);
|
||||
if (result.Success)
|
||||
_logger.Info("目标位置已发送:{Property}={Value} | Target position sent: {Property}={Value}", propertyName, value);
|
||||
else
|
||||
_logger.Warn("目标位置发送失败:{Property}={Value},原因={Reason} | Target position send failed: {Property}={Value}, reason={Reason}", propertyName, value, result.ErrorMessage);
|
||||
});
|
||||
_editingFlags[propertyName] = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消输入框编辑,恢复实时更新并显示当前实际值 | Cancel input box edit, resume live updates and show actual value
|
||||
/// Escape 键或 LostFocus 触发 | Triggered by Escape key or LostFocus
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称 | Property name</param>
|
||||
public void CancelEditing(string propertyName)
|
||||
{
|
||||
_editingFlags[propertyName] = false;
|
||||
RestoreActualValue(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 步进移动:上下箭头改变数值并直接发送移动命令,不进入编辑冻结
|
||||
/// Step move: arrow keys change value and send move command directly, without entering editing freeze
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称 | Property name</param>
|
||||
/// <param name="delta">步进增量(默认 ±0.1)| Step delta (default ±0.1)</param>
|
||||
public void StepPosition(string propertyName, double delta)
|
||||
{
|
||||
var currentValue = GetPropertyValue(propertyName);
|
||||
var newValue = currentValue + delta;
|
||||
SetPropertyValue(propertyName, newValue);
|
||||
SafeRun(() =>
|
||||
{
|
||||
var result = SendMoveCommand(propertyName, newValue);
|
||||
if (!result.Success)
|
||||
_logger.Warn("步进移动失败:{Property},原因={Reason} | Step move failed: {Property}, reason={Reason}", propertyName, result.ErrorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据属性名称发送对应轴的移动命令 | Send move command for corresponding axis based on property name
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称 | Property name</param>
|
||||
/// <param name="value">目标值 | Target value</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
private MotionResult SendMoveCommand(string propertyName, double value)
|
||||
{
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(StageXPosition):
|
||||
return _motionControlService.MoveToTarget(AxisId.StageX, value);
|
||||
case nameof(StageYPosition):
|
||||
return _motionControlService.MoveToTarget(AxisId.StageY, value);
|
||||
case nameof(SourceZPosition):
|
||||
return _motionControlService.MoveToTarget(AxisId.SourceZ, value);
|
||||
case nameof(DetectorZPosition):
|
||||
return _motionControlService.MoveToTarget(AxisId.DetectorZ, value);
|
||||
case nameof(DetectorSwingAngle):
|
||||
return _motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, value);
|
||||
case nameof(StageRotationAngle):
|
||||
return _motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, value);
|
||||
case nameof(FixtureRotationAngle):
|
||||
return _motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, value);
|
||||
default:
|
||||
return MotionResult.Fail($"未知的属性名称:{propertyName} | Unknown property name: {propertyName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据属性名称获取当前绑定值 | Get current bound value by property name
|
||||
/// </summary>
|
||||
private double GetPropertyValue(string propertyName)
|
||||
{
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(StageXPosition): return StageXPosition;
|
||||
case nameof(StageYPosition): return StageYPosition;
|
||||
case nameof(SourceZPosition): return SourceZPosition;
|
||||
case nameof(DetectorZPosition): return DetectorZPosition;
|
||||
case nameof(DetectorSwingAngle): return DetectorSwingAngle;
|
||||
case nameof(StageRotationAngle): return StageRotationAngle;
|
||||
case nameof(FixtureRotationAngle): return FixtureRotationAngle;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据属性名称设置绑定值 | Set bound value by property name
|
||||
/// </summary>
|
||||
private void SetPropertyValue(string propertyName, double value)
|
||||
{
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(StageXPosition): StageXPosition = value; break;
|
||||
case nameof(StageYPosition): StageYPosition = value; break;
|
||||
case nameof(SourceZPosition): SourceZPosition = value; break;
|
||||
case nameof(DetectorZPosition): DetectorZPosition = value; break;
|
||||
case nameof(DetectorSwingAngle): DetectorSwingAngle = value; break;
|
||||
case nameof(StageRotationAngle): StageRotationAngle = value; break;
|
||||
case nameof(FixtureRotationAngle): FixtureRotationAngle = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复输入框为轴的当前实际值 | Restore input box to axis actual value
|
||||
/// </summary>
|
||||
private void RestoreActualValue(string propertyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(StageXPosition):
|
||||
StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
|
||||
break;
|
||||
case nameof(StageYPosition):
|
||||
StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
|
||||
break;
|
||||
case nameof(SourceZPosition):
|
||||
SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition;
|
||||
break;
|
||||
case nameof(DetectorZPosition):
|
||||
DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition;
|
||||
break;
|
||||
case nameof(DetectorSwingAngle):
|
||||
DetectorSwingAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
|
||||
break;
|
||||
case nameof(StageRotationAngle):
|
||||
StageRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
|
||||
break;
|
||||
case nameof(FixtureRotationAngle):
|
||||
FixtureRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 安全参数逻辑 | Safety Parameter Logic
|
||||
|
||||
/// <summary>
|
||||
/// 确认探测器安全高度限定值 | Confirm detector safety height limit value
|
||||
/// Enter 键触发,保存当前值 | Triggered by Enter key, saves current value
|
||||
/// </summary>
|
||||
public void ConfirmSafetyHeight()
|
||||
{
|
||||
_logger.Info("探测器安全高度限定值已保存:{Value} | Detector safety height limit saved: {Value}", SafetyHeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认校准自动计算值 | Confirm calibration auto-calculated value
|
||||
/// Enter 键触发,保存当前值 | Triggered by Enter key, saves current value
|
||||
/// </summary>
|
||||
public void ConfirmCalibrationValue()
|
||||
{
|
||||
_logger.Info("校准自动计算值已保存:{Value} | Calibration auto-calculated value saved: {Value}", CalibrationValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部数据类 | Internal Data Classes
|
||||
|
||||
/// <summary>
|
||||
/// 保存的轴位置数据 | Saved axis position data
|
||||
/// </summary>
|
||||
private class SavedPositions
|
||||
{
|
||||
public double StageX { get; set; }
|
||||
public double StageY { get; set; }
|
||||
public double SourceZ { get; set; }
|
||||
public double DetectorZ { get; set; }
|
||||
public double DetectorSwing { get; set; }
|
||||
public double StageRotation { get; set; }
|
||||
public double FixtureRotation { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<UserControl x:Class="XP.Hardware.MotionControl.Views.AxisControlView"
|
||||
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"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
|
||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:controls="clr-namespace:XP.Common.Controls;assembly=XP.Common"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
MinWidth="350"
|
||||
HorizontalAlignment="Stretch" Background="White">
|
||||
|
||||
<Grid Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ========== 上部:左(轴位置)+ 右(摇杆)========== -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ===== 左侧:轴位置输入框 ===== -->
|
||||
<Grid Grid.Column="0" Margin="0,0,4,0" VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="60"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="{loc:Localization AC_StageX}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="0" Grid.Column="1" x:Name="NumStageX" Value="{Binding StageXPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumStageX_GotFocus" LostFocus="NumStageX_LostFocus" KeyDown="NumStageX_KeyDown" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{loc:Localization AC_StageY}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="1" Grid.Column="1" x:Name="NumStageY" Value="{Binding StageYPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumStageY_GotFocus" LostFocus="NumStageY_LostFocus" KeyDown="NumStageY_KeyDown" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{loc:Localization AC_SourceZ}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="2" Grid.Column="1" x:Name="NumSourceZ" Value="{Binding SourceZPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumSourceZ_GotFocus" LostFocus="NumSourceZ_LostFocus" KeyDown="NumSourceZ_KeyDown" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="{loc:Localization AC_DetectorZ}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="3" Grid.Column="1" x:Name="NumDetectorZ" Value="{Binding DetectorZPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumDetectorZ_GotFocus" LostFocus="NumDetectorZ_LostFocus" KeyDown="NumDetectorZ_KeyDown" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{loc:Localization AC_DetectorSwing}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1" Visibility="{Binding DetectorSwingVisibility}"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="4" Grid.Column="1" x:Name="NumDetSwing" Value="{Binding DetectorSwingAngle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumDetSwing_GotFocus" LostFocus="NumDetSwing_LostFocus" KeyDown="NumDetSwing_KeyDown" Visibility="{Binding DetectorSwingVisibility}" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="{loc:Localization AC_StageRotation}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1" Visibility="{Binding StageRotationVisibility}"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="5" Grid.Column="1" x:Name="NumStageRot" Value="{Binding StageRotationAngle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumStageRot_GotFocus" LostFocus="NumStageRot_LostFocus" KeyDown="NumStageRot_KeyDown" Visibility="{Binding StageRotationVisibility}" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="{loc:Localization AC_FixtureRotation}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1" Visibility="{Binding FixtureRotationVisibility}"/>
|
||||
<telerik:RadNumericUpDown Grid.Row="6" Grid.Column="1" x:Name="NumFixtureRot" Value="{Binding FixtureRotationAngle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ValueFormat="Numeric" NumberDecimalDigits="1" SmallChange="0.1" IsEditable="True" GotFocus="NumFixtureRot_GotFocus" LostFocus="NumFixtureRot_LostFocus" KeyDown="NumFixtureRot_KeyDown" Visibility="{Binding FixtureRotationVisibility}" Margin="0,1" Height="24" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
</Grid>
|
||||
|
||||
<!-- ===== 右侧:摇杆区域 ===== -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Margin="4,12,4,12">
|
||||
<!-- 单轴摇杆(腰圆)-->
|
||||
<controls:VirtualJoystick x:Name="SingleJoystick"
|
||||
JoystickMode="SingleAxisY"
|
||||
Width="40" Height="160"
|
||||
IsEnabled="{Binding IsJoystickEnabled}"
|
||||
DefaultTopIcon="↑" DefaultBottomIcon="↓"
|
||||
LeftButtonTopIcon="Src↑" LeftButtonBottomIcon="Src↓"
|
||||
RightButtonTopIcon="Det↑" RightButtonBottomIcon="Det↓"
|
||||
Margin="2,0,8,0"/>
|
||||
|
||||
<!-- 双轴摇杆(圆形)-->
|
||||
<controls:VirtualJoystick x:Name="DualJoystick"
|
||||
JoystickMode="DualAxis"
|
||||
Width="160" Height="160"
|
||||
IsEnabled="{Binding IsJoystickEnabled}"
|
||||
DefaultTopIcon="↑" DefaultBottomIcon="↓" DefaultLeftIcon="←" DefaultRightIcon="→"
|
||||
LeftButtonTopIcon="Y+" LeftButtonBottomIcon="Y-" LeftButtonLeftIcon="X-" LeftButtonRightIcon="X+"
|
||||
RightButtonTopIcon="Rot+" RightButtonBottomIcon="Rot-" RightButtonLeftIcon="Swing-" RightButtonRightIcon="Swing+"
|
||||
Margin="0,0,2,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- ========== 下部:安全参数(一行均布)+ 操作按钮(一行均布)========== -->
|
||||
<StackPanel Grid.Row="1" Margin="0,8,0,0">
|
||||
|
||||
<!-- 安全参数一行(均布)-->
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="{loc:Localization AC_SafetyHeight}"
|
||||
FontSize="11" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<telerik:RadNumericUpDown Grid.Column="1" x:Name="NumSafetyHeight"
|
||||
Value="{Binding SafetyHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
|
||||
KeyDown="NumSafetyHeight_KeyDown"
|
||||
Height="24" MinWidth="70" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<TextBlock Grid.Column="3" Text="{loc:Localization AC_CalibrationValue}"
|
||||
FontSize="11" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<telerik:RadNumericUpDown Grid.Column="4" x:Name="NumCalibration"
|
||||
Value="{Binding CalibrationValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
|
||||
KeyDown="NumCalibration_KeyDown"
|
||||
Height="24" MinWidth="70" FontSize="11" telerik:StyleManager.Theme="Crystal"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 操作按钮一行(均布)-->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 使能开关 -->
|
||||
<TextBlock Grid.Column="0" Text="{loc:Localization AC_Enable}" FontSize="11" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<telerik:RadToggleSwitchButton Grid.Column="1" IsChecked="{Binding IsJoystickEnabled, Mode=TwoWay}"
|
||||
Command="{Binding ToggleEnableCommand}"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<!-- 保存当前位置 -->
|
||||
<telerik:RadButton Grid.Column="3" Content="{loc:Localization AC_Save}"
|
||||
Command="{Binding SavePositionsCommand}"
|
||||
Height="24" MinWidth="80" Padding="8,0" FontSize="11"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
|
||||
<!-- 恢复前一位置 -->
|
||||
<telerik:RadButton Grid.Column="5" Content="{loc:Localization AC_Restore}"
|
||||
Command="{Binding RestorePositionsCommand}"
|
||||
Height="24" MinWidth="80" Padding="8,0" FontSize="11"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<TextBlock Text="{Binding ErrorMessage}" FontSize="10" Foreground="#FFE53935"
|
||||
TextWrapping="Wrap" Margin="0,4,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ErrorMessage}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ErrorMessage}" Value="">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,234 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using XP.Common.Controls;
|
||||
using XP.Hardware.MotionControl.ViewModels;
|
||||
|
||||
namespace XP.Hardware.MotionControl.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 轴控制面板视图 | Axis Control Panel View
|
||||
/// 集成摇杆、轴位置输入框、安全参数和使能控制的 UserControl
|
||||
/// UserControl integrating joysticks, axis position inputs, safety parameters and enable control
|
||||
/// </summary>
|
||||
public partial class AxisControlView : UserControl
|
||||
{
|
||||
public AxisControlView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 监听摇杆只读依赖属性变化,推送到 ViewModel | Listen to joystick read-only DP changes and push to ViewModel
|
||||
var dualOutputXDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputXProperty, typeof(VirtualJoystick));
|
||||
var dualOutputYDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputYProperty, typeof(VirtualJoystick));
|
||||
var dualButtonDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.ActiveMouseButtonProperty, typeof(VirtualJoystick));
|
||||
var singleOutputYDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputYProperty, typeof(VirtualJoystick));
|
||||
var singleButtonDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.ActiveMouseButtonProperty, typeof(VirtualJoystick));
|
||||
|
||||
dualOutputXDesc?.AddValueChanged(DualJoystick, (s, e) =>
|
||||
{
|
||||
if (ViewModel != null) ViewModel.DualJoystickOutputX = DualJoystick.OutputX;
|
||||
});
|
||||
dualOutputYDesc?.AddValueChanged(DualJoystick, (s, e) =>
|
||||
{
|
||||
if (ViewModel != null) ViewModel.DualJoystickOutputY = DualJoystick.OutputY;
|
||||
});
|
||||
dualButtonDesc?.AddValueChanged(DualJoystick, (s, e) =>
|
||||
{
|
||||
if (ViewModel != null) ViewModel.DualJoystickActiveButton = DualJoystick.ActiveMouseButton;
|
||||
});
|
||||
singleOutputYDesc?.AddValueChanged(SingleJoystick, (s, e) =>
|
||||
{
|
||||
if (ViewModel != null) ViewModel.SingleJoystickOutputY = SingleJoystick.OutputY;
|
||||
});
|
||||
singleButtonDesc?.AddValueChanged(SingleJoystick, (s, e) =>
|
||||
{
|
||||
if (ViewModel != null) ViewModel.SingleJoystickActiveButton = SingleJoystick.ActiveMouseButton;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前 ViewModel 实例 | Get current ViewModel instance
|
||||
/// </summary>
|
||||
private AxisControlViewModel ViewModel => DataContext as AxisControlViewModel;
|
||||
|
||||
#region Stage X 事件处理 | Stage X Event Handlers
|
||||
|
||||
private void NumStageX_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.StageXPosition), true);
|
||||
}
|
||||
|
||||
private void NumStageX_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.StageXPosition));
|
||||
}
|
||||
|
||||
private void NumStageX_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.StageXPosition), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stage Y 事件处理 | Stage Y Event Handlers
|
||||
|
||||
private void NumStageY_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.StageYPosition), true);
|
||||
}
|
||||
|
||||
private void NumStageY_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.StageYPosition));
|
||||
}
|
||||
|
||||
private void NumStageY_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.StageYPosition), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Source Z 事件处理 | Source Z Event Handlers
|
||||
|
||||
private void NumSourceZ_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.SourceZPosition), true);
|
||||
}
|
||||
|
||||
private void NumSourceZ_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.SourceZPosition));
|
||||
}
|
||||
|
||||
private void NumSourceZ_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.SourceZPosition), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Detector Z 事件处理 | Detector Z Event Handlers
|
||||
|
||||
private void NumDetectorZ_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.DetectorZPosition), true);
|
||||
}
|
||||
|
||||
private void NumDetectorZ_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.DetectorZPosition));
|
||||
}
|
||||
|
||||
private void NumDetectorZ_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.DetectorZPosition), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Detector Swing 事件处理 | Detector Swing Event Handlers
|
||||
|
||||
private void NumDetSwing_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.DetectorSwingAngle), true);
|
||||
}
|
||||
|
||||
private void NumDetSwing_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.DetectorSwingAngle));
|
||||
}
|
||||
|
||||
private void NumDetSwing_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.DetectorSwingAngle), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stage Rotation 事件处理 | Stage Rotation Event Handlers
|
||||
|
||||
private void NumStageRot_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.StageRotationAngle), true);
|
||||
}
|
||||
|
||||
private void NumStageRot_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.StageRotationAngle));
|
||||
}
|
||||
|
||||
private void NumStageRot_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.StageRotationAngle), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fixture Rotation 事件处理 | Fixture Rotation Event Handlers
|
||||
|
||||
private void NumFixtureRot_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.SetEditing(nameof(AxisControlViewModel.FixtureRotationAngle), true);
|
||||
}
|
||||
|
||||
private void NumFixtureRot_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.CancelEditing(nameof(AxisControlViewModel.FixtureRotationAngle));
|
||||
}
|
||||
|
||||
private void NumFixtureRot_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
HandleAxisKeyDown(nameof(AxisControlViewModel.FixtureRotationAngle), e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 安全参数键盘事件 | Safety Parameter Key Events
|
||||
|
||||
private void NumSafetyHeight_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
ViewModel?.ConfirmSafetyHeight();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void NumCalibration_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
ViewModel?.ConfirmCalibrationValue();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通用键盘处理 | Common Key Handler
|
||||
|
||||
/// <summary>
|
||||
/// 轴输入框通用键盘处理:Enter 确认,Escape 取消 | Common axis input key handler: Enter to confirm, Escape to cancel
|
||||
/// </summary>
|
||||
private void HandleAxisKeyDown(string propertyName, KeyEventArgs e)
|
||||
{
|
||||
if (ViewModel == null) return;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
ViewModel.ConfirmPosition(propertyName);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Escape:
|
||||
ViewModel.CancelEditing(propertyName);
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -508,7 +508,7 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<views1:RaySourceOperateView Grid.Row="0" Grid.ColumnSpan="2" />
|
||||
<mcViews:MotionControlView Grid.Row="1" Grid.ColumnSpan="2"/>
|
||||
<mcViews:AxisControlView Grid.Row="1" Grid.ColumnSpan="2"/>
|
||||
<views:NavigationPropertyPanelView Grid.Row="2" Grid.ColumnSpan="2" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user