优化虚拟摇杆运动控制界面显示问题,将generic修改为usercontrol,单轴摇杆修改为腰圆形状,将功能增加至主框架程序。

This commit is contained in:
QI Mingxuan
2026-04-22 22:49:46 +08:00
parent be03bdf34e
commit 461294986e
10 changed files with 400 additions and 857 deletions
-219
View File
@@ -1,219 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:XP.Common.Controls">
<!--
VirtualJoystick 默认样式和控件模板 | VirtualJoystick default style and control template
视觉结构:圆形裁剪区域 + 十字形背景线 + 四向功能图标 + 中心操控点
Visual structure: Circular clip + cross background + four-directional icons + center thumb
-->
<Style TargetType="{x:Type controls:VirtualJoystick}">
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="200" />
<Setter Property="Background" Value="#FFF5F5F5" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:VirtualJoystick}">
<Grid x:Name="PART_Root">
<!-- 圆形背景和边框 | Circular background and border -->
<Ellipse Fill="{TemplateBinding Background}"
Stroke="#FFCCCCCC"
StrokeThickness="1.5" />
<!-- 十字形背景虚线 | Cross-shaped dashed background lines -->
<!-- 水平线 | Horizontal line -->
<Line X1="0"
Y1="0"
X2="1"
Y2="0"
Stretch="Fill"
Stroke="#FFD0D0D0"
StrokeThickness="1"
StrokeDashArray="4 2"
VerticalAlignment="Center"
IsHitTestVisible="False" />
<!-- 垂直线 | Vertical line -->
<Line X1="0"
Y1="0"
X2="0"
Y2="1"
Stretch="Fill"
Stroke="#FFD0D0D0"
StrokeThickness="1"
StrokeDashArray="4 2"
HorizontalAlignment="Center"
IsHitTestVisible="False" />
<!-- 四向图标区域(3x3 Grid 布局)| Four-directional icon area (3x3 Grid) -->
<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 (3 switchable sets) ===== -->
<ContentPresenter x:Name="DefaultTopPresenter"
Grid.Row="0" Grid.Column="1"
Content="{TemplateBinding DefaultTopIcon}"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
<ContentPresenter x:Name="LeftTopPresenter"
Grid.Row="0" Grid.Column="1"
Content="{TemplateBinding LeftButtonTopIcon}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Visibility="Collapsed" />
<ContentPresenter x:Name="RightTopPresenter"
Grid.Row="0" Grid.Column="1"
Content="{TemplateBinding RightButtonTopIcon}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Visibility="Collapsed" />
<!-- ===== 下方图标 | Bottom icons ===== -->
<ContentPresenter x:Name="DefaultBottomPresenter"
Grid.Row="2" Grid.Column="1"
Content="{TemplateBinding DefaultBottomIcon}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom" />
<ContentPresenter x:Name="LeftBottomPresenter"
Grid.Row="2" Grid.Column="1"
Content="{TemplateBinding LeftButtonBottomIcon}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Visibility="Collapsed" />
<ContentPresenter x:Name="RightBottomPresenter"
Grid.Row="2" Grid.Column="1"
Content="{TemplateBinding RightButtonBottomIcon}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Visibility="Collapsed" />
<!-- ===== 左方图标 | Left icons ===== -->
<ContentPresenter x:Name="DefaultLeftPresenter"
Grid.Row="1" Grid.Column="0"
Content="{TemplateBinding DefaultLeftIcon}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<ContentPresenter x:Name="LeftLeftPresenter"
Grid.Row="1" Grid.Column="0"
Content="{TemplateBinding LeftButtonLeftIcon}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Visibility="Collapsed" />
<ContentPresenter x:Name="RightLeftPresenter"
Grid.Row="1" Grid.Column="0"
Content="{TemplateBinding RightButtonLeftIcon}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Visibility="Collapsed" />
<!-- ===== 右方图标 | Right icons ===== -->
<ContentPresenter x:Name="DefaultRightPresenter"
Grid.Row="1" Grid.Column="2"
Content="{TemplateBinding DefaultRightIcon}"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
<ContentPresenter x:Name="LeftRightPresenter"
Grid.Row="1" Grid.Column="2"
Content="{TemplateBinding LeftButtonRightIcon}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Visibility="Collapsed" />
<ContentPresenter x:Name="RightRightPresenter"
Grid.Row="1" Grid.Column="2"
Content="{TemplateBinding RightButtonRightIcon}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Visibility="Collapsed" />
</Grid>
<!-- 中心操控点 PART_Thumb(通过 TranslateTransform 跟随拖拽)-->
<!-- Center thumb (follows drag via TranslateTransform) -->
<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>
<ControlTemplate.Triggers>
<!-- ===== ActiveMouseButton = Left 时切换图标 | Switch icons when Left button active ===== -->
<DataTrigger Binding="{Binding ActiveMouseButton, RelativeSource={RelativeSource Self}}"
Value="{x:Static controls:MouseButtonType.Left}">
<!-- 隐藏默认图标 | Hide default icons -->
<Setter TargetName="DefaultTopPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="DefaultBottomPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="DefaultLeftPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="DefaultRightPresenter" Property="Visibility" Value="Collapsed" />
<!-- 显示左键图标 | Show left button icons -->
<Setter TargetName="LeftTopPresenter" Property="Visibility" Value="Visible" />
<Setter TargetName="LeftBottomPresenter" Property="Visibility" Value="Visible" />
<Setter TargetName="LeftLeftPresenter" Property="Visibility" Value="Visible" />
<Setter TargetName="LeftRightPresenter" Property="Visibility" Value="Visible" />
<!-- 操控点拖拽中高亮 | Thumb highlight during drag -->
<Setter TargetName="PART_Thumb" Property="Fill" Value="#FF3A7BC8" />
</DataTrigger>
<!-- ===== ActiveMouseButton = Right 时切换图标 | Switch icons when Right button active ===== -->
<DataTrigger Binding="{Binding ActiveMouseButton, RelativeSource={RelativeSource Self}}"
Value="{x:Static controls:MouseButtonType.Right}">
<!-- 隐藏默认图标 | Hide default icons -->
<Setter TargetName="DefaultTopPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="DefaultBottomPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="DefaultLeftPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="DefaultRightPresenter" Property="Visibility" Value="Collapsed" />
<!-- 显示右键图标 | Show right button icons -->
<Setter TargetName="RightTopPresenter" Property="Visibility" Value="Visible" />
<Setter TargetName="RightBottomPresenter" Property="Visibility" Value="Visible" />
<Setter TargetName="RightLeftPresenter" Property="Visibility" Value="Visible" />
<Setter TargetName="RightRightPresenter" Property="Visibility" Value="Visible" />
<!-- 操控点拖拽中高亮(右键使用不同颜色)| Thumb highlight (different color for right button) -->
<Setter TargetName="PART_Thumb" Property="Fill" Value="#FF5BA85B" />
</DataTrigger>
<!-- ===== SingleAxisY 模式下隐藏左右图标 | Hide left/right icons in SingleAxisY mode ===== -->
<DataTrigger Binding="{Binding JoystickMode, RelativeSource={RelativeSource Self}}"
Value="{x:Static controls:JoystickMode.SingleAxisY}">
<!-- 隐藏所有左方图标 | Hide all left icons -->
<Setter TargetName="DefaultLeftPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="LeftLeftPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="RightLeftPresenter" Property="Visibility" Value="Collapsed" />
<!-- 隐藏所有右方图标 | Hide all right icons -->
<Setter TargetName="DefaultRightPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="LeftRightPresenter" Property="Visibility" Value="Collapsed" />
<Setter TargetName="RightRightPresenter" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<!-- 控件禁用时降低透明度 | Reduce opacity when control is disabled -->
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.4" />
<Setter Property="Cursor" Value="Arrow" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
+161 -429
View File
@@ -3,125 +3,95 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes;
namespace XP.Common.Controls namespace XP.Common.Controls
{ {
/// <summary> /// <summary>
/// 虚拟摇杆自定义控件,提供圆形区域内的鼠标拖拽操控能力 | Virtual joystick custom control providing mouse drag interaction within a circular area /// 虚拟摇杆 UserControl,提供圆形区域内的鼠标拖拽操控能力 | Virtual joystick UserControl providing mouse drag interaction within a circular area
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// 支持双轴/单轴 Y 模式、死区配置、归一化比例输出(-1.0 ~ 1.0)、鼠标左右键区分及四向功能图标切换。 /// 支持双轴/单轴 Y 模式、死区配置、归一化比例输出(-1.0 ~ 1.0)、鼠标左右键区分及四向功能图标切换。
/// Supports dual-axis/single-axis Y mode, dead zone configuration, normalized proportional output (-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. /// left/right mouse button differentiation, and four-directional icon switching.
/// </remarks> /// </remarks>
[TemplatePart(Name = PART_Thumb, Type = typeof(UIElement))] public partial class VirtualJoystick : UserControl
public class VirtualJoystick : Control
{ {
/// <summary>
/// 操控点模板部件名称 | Thumb template part name
/// </summary>
private const string PART_Thumb = "PART_Thumb";
#region | Private Fields #region | Private Fields
/// <summary> /// <summary>是否正在拖拽 | Whether dragging is in progress</summary>
/// 是否正在拖拽 | Whether dragging is in progress
/// </summary>
private bool _isDragging; private bool _isDragging;
/// <summary> /// <summary>控件中心点坐标 | Control center point coordinates</summary>
/// 控件中心点坐标 | Control center point coordinates
/// </summary>
private Point _centerPoint; private Point _centerPoint;
/// <summary> /// <summary>操控点的平移变换 | Translate transform for thumb element</summary>
/// 操控点的平移变换,用于跟随鼠标移动 | Translate transform for thumb element to follow mouse movement private readonly TranslateTransform _thumbTransform = new TranslateTransform();
/// </summary>
private TranslateTransform _thumbTransform = new TranslateTransform(); /// <summary>操控点元素引用 | Thumb element reference</summary>
private Ellipse? _thumbElement;
#endregion #endregion
#region | Static Constructor
static VirtualJoystick() #region | Constructor
public VirtualJoystick()
{ {
DefaultStyleKeyProperty.OverrideMetadata( InitializeComponent();
typeof(VirtualJoystick),
new FrameworkPropertyMetadata(typeof(VirtualJoystick))); // 控件加载完成后绑定操控点的 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 #endregion
#region JoystickMode | JoystickMode Dependency Property #region JoystickMode | JoystickMode Dependency Property
/// <summary>
/// 摇杆轴模式依赖属性 | Joystick axis mode dependency property
/// </summary>
public static readonly DependencyProperty JoystickModeProperty = public static readonly DependencyProperty JoystickModeProperty =
DependencyProperty.Register( DependencyProperty.Register(nameof(JoystickMode), typeof(JoystickMode), typeof(VirtualJoystick),
nameof(JoystickMode), new PropertyMetadata(JoystickMode.DualAxis, OnJoystickModeChanged));
typeof(JoystickMode),
typeof(VirtualJoystick),
new PropertyMetadata(JoystickMode.DualAxis));
/// <summary> /// <summary>获取或设置摇杆轴模式 | Gets or sets the joystick axis mode</summary>
/// 获取或设置摇杆轴模式(双轴或单轴 Y)| Gets or sets the joystick axis mode (DualAxis or SingleAxisY)
/// </summary>
public JoystickMode JoystickMode public JoystickMode JoystickMode
{ {
get => (JoystickMode)GetValue(JoystickModeProperty); get => (JoystickMode)GetValue(JoystickModeProperty);
set => SetValue(JoystickModeProperty, value); set => SetValue(JoystickModeProperty, value);
} }
private static void OnJoystickModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is VirtualJoystick joystick)
joystick.UpdateIconVisibility();
}
#endregion #endregion
#region OutputX | OutputX Read-Only Dependency Property #region OutputX/OutputY | OutputX/OutputY Read-Only Dependency Properties
/// <summary>
/// OutputX 只读依赖属性键 | OutputX read-only dependency property key
/// </summary>
private static readonly DependencyPropertyKey OutputXPropertyKey = private static readonly DependencyPropertyKey OutputXPropertyKey =
DependencyProperty.RegisterReadOnly( DependencyProperty.RegisterReadOnly(nameof(OutputX), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.0));
nameof(OutputX),
typeof(double),
typeof(VirtualJoystick),
new PropertyMetadata(0.0));
/// <summary>
/// X 轴归一化输出依赖属性 | X-axis normalized output dependency property
/// </summary>
public static readonly DependencyProperty OutputXProperty = OutputXPropertyKey.DependencyProperty; public static readonly DependencyProperty OutputXProperty = OutputXPropertyKey.DependencyProperty;
/// <summary> /// <summary>X 轴归一化输出值 [-1.0, 1.0] | X-axis normalized output</summary>
/// 获取 X 轴归一化输出值,范围 [-1.0, 1.0] | Gets the X-axis normalized output value, range [-1.0, 1.0]
/// </summary>
public double OutputX public double OutputX
{ {
get => (double)GetValue(OutputXProperty); get => (double)GetValue(OutputXProperty);
internal set => SetValue(OutputXPropertyKey, value); internal set => SetValue(OutputXPropertyKey, value);
} }
#endregion
#region OutputY | OutputY Read-Only Dependency Property
/// <summary>
/// OutputY 只读依赖属性键 | OutputY read-only dependency property key
/// </summary>
private static readonly DependencyPropertyKey OutputYPropertyKey = private static readonly DependencyPropertyKey OutputYPropertyKey =
DependencyProperty.RegisterReadOnly( DependencyProperty.RegisterReadOnly(nameof(OutputY), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.0));
nameof(OutputY),
typeof(double),
typeof(VirtualJoystick),
new PropertyMetadata(0.0));
/// <summary>
/// Y 轴归一化输出依赖属性 | Y-axis normalized output dependency property
/// </summary>
public static readonly DependencyProperty OutputYProperty = OutputYPropertyKey.DependencyProperty; public static readonly DependencyProperty OutputYProperty = OutputYPropertyKey.DependencyProperty;
/// <summary> /// <summary>Y 轴归一化输出值 [-1.0, 1.0] | Y-axis normalized output</summary>
/// 获取 Y 轴归一化输出值,范围 [-1.0, 1.0] | Gets the Y-axis normalized output value, range [-1.0, 1.0]
/// </summary>
public double OutputY public double OutputY
{ {
get => (double)GetValue(OutputYProperty); get => (double)GetValue(OutputYProperty);
@@ -132,19 +102,10 @@ namespace XP.Common.Controls
#region DeadZone | DeadZone Dependency Property #region DeadZone | DeadZone Dependency Property
/// <summary>
/// 死区比例依赖属性 | Dead zone ratio dependency property
/// </summary>
public static readonly DependencyProperty DeadZoneProperty = public static readonly DependencyProperty DeadZoneProperty =
DependencyProperty.Register( DependencyProperty.Register(nameof(DeadZone), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.05));
nameof(DeadZone),
typeof(double),
typeof(VirtualJoystick),
new PropertyMetadata(0.05));
/// <summary> /// <summary>死区比例 [0.0, 1.0],默认 0.05 | Dead zone ratio, default 0.05</summary>
/// 获取或设置死区比例,范围 [0.0, 1.0],默认值 0.05 | Gets or sets the dead zone ratio, range [0.0, 1.0], default 0.05
/// </summary>
public double DeadZone public double DeadZone
{ {
get => (double)GetValue(DeadZoneProperty); get => (double)GetValue(DeadZoneProperty);
@@ -155,407 +116,132 @@ namespace XP.Common.Controls
#region ActiveMouseButton | ActiveMouseButton Read-Only Dependency Property #region ActiveMouseButton | ActiveMouseButton Read-Only Dependency Property
/// <summary>
/// ActiveMouseButton 只读依赖属性键 | ActiveMouseButton read-only dependency property key
/// </summary>
private static readonly DependencyPropertyKey ActiveMouseButtonPropertyKey = private static readonly DependencyPropertyKey ActiveMouseButtonPropertyKey =
DependencyProperty.RegisterReadOnly( DependencyProperty.RegisterReadOnly(nameof(ActiveMouseButton), typeof(MouseButtonType), typeof(VirtualJoystick),
nameof(ActiveMouseButton), new PropertyMetadata(MouseButtonType.None, OnActiveMouseButtonChanged));
typeof(MouseButtonType),
typeof(VirtualJoystick),
new PropertyMetadata(MouseButtonType.None));
/// <summary>
/// 当前激活的鼠标按键依赖属性 | Active mouse button dependency property
/// </summary>
public static readonly DependencyProperty ActiveMouseButtonProperty = ActiveMouseButtonPropertyKey.DependencyProperty; public static readonly DependencyProperty ActiveMouseButtonProperty = ActiveMouseButtonPropertyKey.DependencyProperty;
/// <summary> /// <summary>当前激活的鼠标按键 | Currently active mouse button</summary>
/// 获取当前激活的鼠标按键(None、Left、Right| Gets the currently active mouse button (None, Left, Right)
/// </summary>
public MouseButtonType ActiveMouseButton public MouseButtonType ActiveMouseButton
{ {
get => (MouseButtonType)GetValue(ActiveMouseButtonProperty); get => (MouseButtonType)GetValue(ActiveMouseButtonProperty);
internal set => SetValue(ActiveMouseButtonPropertyKey, value); internal set => SetValue(ActiveMouseButtonPropertyKey, value);
} }
#endregion private static void OnActiveMouseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
#region | Left Button Directional Icon Dependency Properties
/// <summary>
/// 左键拖动时上方图标内容依赖属性 | Left button top icon content dependency property
/// </summary>
public static readonly DependencyProperty LeftButtonTopIconProperty =
DependencyProperty.Register(
nameof(LeftButtonTopIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置左键拖动时上方图标内容 | Gets or sets the top icon content when left button is dragging
/// </summary>
public object? LeftButtonTopIcon
{ {
get => GetValue(LeftButtonTopIconProperty); if (d is VirtualJoystick joystick)
set => SetValue(LeftButtonTopIconProperty, value); joystick.UpdateIconVisibility();
}
/// <summary>
/// 左键拖动时下方图标内容依赖属性 | Left button bottom icon content dependency property
/// </summary>
public static readonly DependencyProperty LeftButtonBottomIconProperty =
DependencyProperty.Register(
nameof(LeftButtonBottomIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置左键拖动时下方图标内容 | Gets or sets the bottom icon content when left button is dragging
/// </summary>
public object? LeftButtonBottomIcon
{
get => GetValue(LeftButtonBottomIconProperty);
set => SetValue(LeftButtonBottomIconProperty, value);
}
/// <summary>
/// 左键拖动时左方图标内容依赖属性 | Left button left icon content dependency property
/// </summary>
public static readonly DependencyProperty LeftButtonLeftIconProperty =
DependencyProperty.Register(
nameof(LeftButtonLeftIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置左键拖动时左方图标内容 | Gets or sets the left icon content when left button is dragging
/// </summary>
public object? LeftButtonLeftIcon
{
get => GetValue(LeftButtonLeftIconProperty);
set => SetValue(LeftButtonLeftIconProperty, value);
}
/// <summary>
/// 左键拖动时右方图标内容依赖属性 | Left button right icon content dependency property
/// </summary>
public static readonly DependencyProperty LeftButtonRightIconProperty =
DependencyProperty.Register(
nameof(LeftButtonRightIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置左键拖动时右方图标内容 | Gets or sets the right icon content when left button is dragging
/// </summary>
public object? LeftButtonRightIcon
{
get => GetValue(LeftButtonRightIconProperty);
set => SetValue(LeftButtonRightIconProperty, value);
} }
#endregion #endregion
#region | Right Button Directional Icon Dependency Properties #region | Directional Icon Dependency Properties
/// <summary> // 左键图标 | Left button icons
/// 右键拖动时上方图标内容依赖属性 | Right button top icon content dependency property public static readonly DependencyProperty LeftButtonTopIconProperty = DependencyProperty.Register(nameof(LeftButtonTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// </summary> public object? LeftButtonTopIcon { get => GetValue(LeftButtonTopIconProperty); set => SetValue(LeftButtonTopIconProperty, value); }
public static readonly DependencyProperty RightButtonTopIconProperty =
DependencyProperty.Register(
nameof(RightButtonTopIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary> public static readonly DependencyProperty LeftButtonBottomIconProperty = DependencyProperty.Register(nameof(LeftButtonBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 获取或设置右键拖动时上方图标内容 | Gets or sets the top icon content when right button is dragging public object? LeftButtonBottomIcon { get => GetValue(LeftButtonBottomIconProperty); set => SetValue(LeftButtonBottomIconProperty, value); }
/// </summary>
public object? RightButtonTopIcon
{
get => GetValue(RightButtonTopIconProperty);
set => SetValue(RightButtonTopIconProperty, value);
}
/// <summary> public static readonly DependencyProperty LeftButtonLeftIconProperty = DependencyProperty.Register(nameof(LeftButtonLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 右键拖动时下方图标内容依赖属性 | Right button bottom icon content dependency property public object? LeftButtonLeftIcon { get => GetValue(LeftButtonLeftIconProperty); set => SetValue(LeftButtonLeftIconProperty, value); }
/// </summary>
public static readonly DependencyProperty RightButtonBottomIconProperty =
DependencyProperty.Register(
nameof(RightButtonBottomIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary> public static readonly DependencyProperty LeftButtonRightIconProperty = DependencyProperty.Register(nameof(LeftButtonRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 获取或设置右键拖动时下方图标内容 | Gets or sets the bottom icon content when right button is dragging public object? LeftButtonRightIcon { get => GetValue(LeftButtonRightIconProperty); set => SetValue(LeftButtonRightIconProperty, value); }
/// </summary>
public object? RightButtonBottomIcon
{
get => GetValue(RightButtonBottomIconProperty);
set => SetValue(RightButtonBottomIconProperty, value);
}
/// <summary> // 右键图标 | Right button icons
/// 右键拖动时左方图标内容依赖属性 | Right button left icon content dependency property public static readonly DependencyProperty RightButtonTopIconProperty = DependencyProperty.Register(nameof(RightButtonTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// </summary> public object? RightButtonTopIcon { get => GetValue(RightButtonTopIconProperty); set => SetValue(RightButtonTopIconProperty, value); }
public static readonly DependencyProperty RightButtonLeftIconProperty =
DependencyProperty.Register(
nameof(RightButtonLeftIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary> public static readonly DependencyProperty RightButtonBottomIconProperty = DependencyProperty.Register(nameof(RightButtonBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 获取或设置右键拖动时左方图标内容 | Gets or sets the left icon content when right button is dragging public object? RightButtonBottomIcon { get => GetValue(RightButtonBottomIconProperty); set => SetValue(RightButtonBottomIconProperty, value); }
/// </summary>
public object? RightButtonLeftIcon
{
get => GetValue(RightButtonLeftIconProperty);
set => SetValue(RightButtonLeftIconProperty, value);
}
/// <summary> public static readonly DependencyProperty RightButtonLeftIconProperty = DependencyProperty.Register(nameof(RightButtonLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 右键拖动时右方图标内容依赖属性 | Right button right icon content dependency property public object? RightButtonLeftIcon { get => GetValue(RightButtonLeftIconProperty); set => SetValue(RightButtonLeftIconProperty, value); }
/// </summary>
public static readonly DependencyProperty RightButtonRightIconProperty =
DependencyProperty.Register(
nameof(RightButtonRightIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary> public static readonly DependencyProperty RightButtonRightIconProperty = DependencyProperty.Register(nameof(RightButtonRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 获取或设置右键拖动时右方图标内容 | Gets or sets the right icon content when right button is dragging public object? RightButtonRightIcon { get => GetValue(RightButtonRightIconProperty); set => SetValue(RightButtonRightIconProperty, value); }
/// </summary>
public object? RightButtonRightIcon
{
get => GetValue(RightButtonRightIconProperty);
set => SetValue(RightButtonRightIconProperty, value);
}
#endregion // 默认图标 | 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); }
#region ThumbElement | ThumbElement Internal Property 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); }
/// <summary> public static readonly DependencyProperty DefaultLeftIconProperty = DependencyProperty.Register(nameof(DefaultLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
/// 操控点 UI 元素引用(从模板获取)| Thumb UI element reference (obtained from template) public object? DefaultLeftIcon { get => GetValue(DefaultLeftIconProperty); set => SetValue(DefaultLeftIconProperty, value); }
/// </summary>
internal UIElement? ThumbElement { get; private set; }
#endregion 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); }
#region | Default Directional Icon Dependency Properties
/// <summary>
/// 默认状态上方图标内容依赖属性 | Default top icon content dependency property
/// </summary>
public static readonly DependencyProperty DefaultTopIconProperty =
DependencyProperty.Register(
nameof(DefaultTopIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置默认状态上方图标内容 | Gets or sets the top icon content in default state
/// </summary>
public object? DefaultTopIcon
{
get => GetValue(DefaultTopIconProperty);
set => SetValue(DefaultTopIconProperty, value);
}
/// <summary>
/// 默认状态下方图标内容依赖属性 | Default bottom icon content dependency property
/// </summary>
public static readonly DependencyProperty DefaultBottomIconProperty =
DependencyProperty.Register(
nameof(DefaultBottomIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置默认状态下方图标内容 | Gets or sets the bottom icon content in default state
/// </summary>
public object? DefaultBottomIcon
{
get => GetValue(DefaultBottomIconProperty);
set => SetValue(DefaultBottomIconProperty, value);
}
/// <summary>
/// 默认状态左方图标内容依赖属性 | Default left icon content dependency property
/// </summary>
public static readonly DependencyProperty DefaultLeftIconProperty =
DependencyProperty.Register(
nameof(DefaultLeftIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置默认状态左方图标内容 | Gets or sets the left icon content in default state
/// </summary>
public object? DefaultLeftIcon
{
get => GetValue(DefaultLeftIconProperty);
set => SetValue(DefaultLeftIconProperty, value);
}
/// <summary>
/// 默认状态右方图标内容依赖属性 | Default right icon content dependency property
/// </summary>
public static readonly DependencyProperty DefaultRightIconProperty =
DependencyProperty.Register(
nameof(DefaultRightIcon),
typeof(object),
typeof(VirtualJoystick),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置默认状态右方图标内容 | Gets or sets the right icon content in default state
/// </summary>
public object? DefaultRightIcon
{
get => GetValue(DefaultRightIconProperty);
set => SetValue(DefaultRightIconProperty, value);
}
#endregion
#region | Template Application
/// <summary>
/// 应用控件模板时查找操控点元素并设置平移变换 | Find thumb element and set up translate transform when template is applied
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// 查找模板中的操控点元素 | Find the thumb element in the template
ThumbElement = GetTemplateChild(PART_Thumb) as UIElement;
if (ThumbElement != null)
{
// 设置平移变换用于拖拽跟随 | Set translate transform for drag following
ThumbElement.RenderTransform = _thumbTransform;
}
}
#endregion #endregion
#region | Mouse Interaction Event Handlers #region | Mouse Interaction Event Handlers
/// <summary>
/// 鼠标左键按下时开始拖拽 | Start dragging when left mouse button is pressed
/// </summary>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{ {
base.OnMouseLeftButtonDown(e); base.OnMouseLeftButtonDown(e);
if (_isDragging || !IsEnabled) return;
// 如果已在拖拽中或控件未启用,忽略 | Ignore if already dragging or control is disabled
if (_isDragging || !IsEnabled)
return;
StartDrag(MouseButtonType.Left); StartDrag(MouseButtonType.Left);
e.Handled = true; e.Handled = true;
} }
/// <summary>
/// 鼠标右键按下时开始拖拽 | Start dragging when right mouse button is pressed
/// </summary>
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
{ {
base.OnMouseRightButtonDown(e); base.OnMouseRightButtonDown(e);
if (_isDragging || !IsEnabled) return;
// 如果已在拖拽中或控件未启用,忽略 | Ignore if already dragging or control is disabled
if (_isDragging || !IsEnabled)
return;
StartDrag(MouseButtonType.Right); StartDrag(MouseButtonType.Right);
e.Handled = true; e.Handled = true;
} }
/// <summary>
/// 鼠标移动时更新操控点位置和输出值 | Update thumb position and output values when mouse moves
/// </summary>
protected override void OnMouseMove(MouseEventArgs e) protected override void OnMouseMove(MouseEventArgs e)
{ {
base.OnMouseMove(e); base.OnMouseMove(e);
if (!_isDragging) return;
if (!_isDragging)
return;
try try
{ {
// 获取鼠标相对于控件的位置 | Get mouse position relative to the control
var mousePosition = e.GetPosition(this); var mousePosition = e.GetPosition(this);
// 计算相对于中心的偏移量 | Calculate displacement from center
var dx = mousePosition.X - _centerPoint.X; var dx = mousePosition.X - _centerPoint.X;
var dy = mousePosition.Y - _centerPoint.Y; var dy = mousePosition.Y - _centerPoint.Y;
// 计算可用半径(取宽高较小值的一半)| Calculate available radius (half of the smaller dimension) // SingleAxisY 模式下强制水平位移为零,操控点只能上下移动 | Force dx=0 in SingleAxisY mode, thumb moves vertically only
if (JoystickMode == JoystickMode.SingleAxisY)
dx = 0;
var radius = GetRadius(); var radius = GetRadius();
if (radius <= 0) if (radius <= 0) return;
return;
// 将位移限制在圆形区域内 | Clamp displacement within circular area
var (clampedDx, clampedDy) = JoystickCalculator.ClampToRadius(dx, dy, radius); var (clampedDx, clampedDy) = JoystickCalculator.ClampToRadius(dx, dy, radius);
// 计算归一化输出(含死区映射)| Calculate normalized output (with dead zone mapping)
var (outputX, outputY) = JoystickCalculator.CalculateOutput(dx, dy, radius, DeadZone, JoystickMode); var (outputX, outputY) = JoystickCalculator.CalculateOutput(dx, dy, radius, DeadZone, JoystickMode);
// 更新输出值 | Update output values
OutputX = outputX; OutputX = outputX;
OutputY = outputY; OutputY = outputY;
// 更新操控点的平移变换位置(使用限制后的位移)| Update thumb translate transform (using clamped displacement)
_thumbTransform.X = clampedDx; _thumbTransform.X = clampedDx;
_thumbTransform.Y = clampedDy; _thumbTransform.Y = clampedDy;
} }
catch catch
{ {
// 异常时重置到中心位置并输出归零 | Reset to center and zero output on exception
ResetToCenter(); ResetToCenter();
} }
e.Handled = true; e.Handled = true;
} }
/// <summary>
/// 鼠标左键释放时结束拖拽 | End dragging when left mouse button is released
/// </summary>
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{ {
base.OnMouseLeftButtonUp(e); base.OnMouseLeftButtonUp(e);
if (!_isDragging || ActiveMouseButton != MouseButtonType.Left) return;
// 仅当正在拖拽且激活按键为左键时处理 | Only handle when dragging with left button active
if (!_isDragging || ActiveMouseButton != MouseButtonType.Left)
return;
EndDrag(); EndDrag();
e.Handled = true; e.Handled = true;
} }
/// <summary>
/// 鼠标右键释放时结束拖拽 | End dragging when right mouse button is released
/// </summary>
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e) protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{ {
base.OnMouseRightButtonUp(e); base.OnMouseRightButtonUp(e);
if (!_isDragging || ActiveMouseButton != MouseButtonType.Right) return;
// 仅当正在拖拽且激活按键为右键时处理 | Only handle when dragging with right button active
if (!_isDragging || ActiveMouseButton != MouseButtonType.Right)
return;
EndDrag(); EndDrag();
e.Handled = true; e.Handled = true;
} }
@@ -564,64 +250,110 @@ namespace XP.Common.Controls
#region | Drag Helper Methods #region | Drag Helper Methods
/// <summary>
/// 开始拖拽操作 | Start drag operation
/// </summary>
/// <param name="buttonType">触发拖拽的鼠标按键类型 | Mouse button type that initiated the drag</param>
private void StartDrag(MouseButtonType buttonType) private void StartDrag(MouseButtonType buttonType)
{ {
// 捕获鼠标以接收控件外的鼠标事件 | Capture mouse to receive events outside the control
CaptureMouse(); CaptureMouse();
// 设置激活的鼠标按键 | Set the active mouse button
ActiveMouseButton = buttonType; ActiveMouseButton = buttonType;
// 计算控件中心点 | Calculate control center point
_centerPoint = new Point(ActualWidth / 2.0, ActualHeight / 2.0); _centerPoint = new Point(ActualWidth / 2.0, ActualHeight / 2.0);
// 标记拖拽状态 | Mark dragging state
_isDragging = true; _isDragging = true;
} }
/// <summary>
/// 结束拖拽操作 | End drag operation
/// </summary>
private void EndDrag() private void EndDrag()
{ {
// 重置到中心位置并归零输出 | Reset to center and zero output
ResetToCenter(); ResetToCenter();
// 释放鼠标捕获 | Release mouse capture
ReleaseMouseCapture(); ReleaseMouseCapture();
} }
/// <summary> /// <summary>重置操控点到中心位置 | Reset thumb to center</summary>
/// 重置操控点到中心位置,输出归零,清除拖拽状态 | Reset thumb to center, zero output, clear drag state
/// </summary>
internal void ResetToCenter() internal void ResetToCenter()
{ {
// 输出值归零 | Zero output values
OutputX = 0.0; OutputX = 0.0;
OutputY = 0.0; OutputY = 0.0;
// 清除激活的鼠标按键 | Clear active mouse button
ActiveMouseButton = MouseButtonType.None; ActiveMouseButton = MouseButtonType.None;
// 清除拖拽状态 | Clear dragging state
_isDragging = false; _isDragging = false;
// 重置操控点平移变换到中心 | Reset thumb translate transform to center
_thumbTransform.X = 0.0; _thumbTransform.X = 0.0;
_thumbTransform.Y = 0.0; _thumbTransform.Y = 0.0;
} }
/// <summary> /// <summary>
/// 获取可用半径(取宽高较小值的一半| Get available radius (half of the smaller dimension) /// 获取可用半径(限制为控件尺寸的 80%| Get usable radius (limited to 80% of control size)
/// 双轴取宽高最小值的一半,单轴取高度的一半 | DualAxis uses min(W,H)/2, SingleAxisY uses H/2
/// </summary> /// </summary>
/// <returns>可用半径 | Available radius</returns>
private double GetRadius() private double GetRadius()
{ {
return Math.Min(ActualWidth, ActualHeight) / 2.0; 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 #endregion
+98
View File
@@ -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>
+1 -2
View File
@@ -31,8 +31,7 @@
<PackageReference Include="Telerik.UI.for.Wpf.NetCore.Xaml" Version="2024.1.408" /> <PackageReference Include="Telerik.UI.for.Wpf.NetCore.Xaml" Version="2024.1.408" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Controls\ViewModels\" />
<Folder Include="Controls\Views\" />
<Folder Include="Extensions\" /> <Folder Include="Extensions\" />
<Folder Include="Serialization\" /> <Folder Include="Serialization\" />
<Folder Include="Converters\" /> <Folder Include="Converters\" />
@@ -293,12 +293,12 @@ Confirm to filll move matrix?</value>
<value>Calibration Value</value> <value>Calibration Value</value>
</data> </data>
<data name="AC_Enable" xml:space="preserve"> <data name="AC_Enable" xml:space="preserve">
<value>Enable</value> <value>Physical Joystick Enable</value>
</data> </data>
<data name="AC_Save" xml:space="preserve"> <data name="AC_Save" xml:space="preserve">
<value>Save</value> <value>Save Position</value>
</data> </data>
<data name="AC_Restore" xml:space="preserve"> <data name="AC_Restore" xml:space="preserve">
<value>Restore</value> <value>Restore Position</value>
</data> </data>
</root> </root>
@@ -347,15 +347,15 @@ DetectorZ → {4:F2}mm
<comment>校准自动计算值标签 | Calibration value label</comment> <comment>校准自动计算值标签 | Calibration value label</comment>
</data> </data>
<data name="AC_Enable" xml:space="preserve"> <data name="AC_Enable" xml:space="preserve">
<value>使能</value> <value>实体操作摇杆使能</value>
<comment>使能开关标签 | Enable toggle label</comment> <comment>使能开关标签 | Enable toggle label</comment>
</data> </data>
<data name="AC_Save" xml:space="preserve"> <data name="AC_Save" xml:space="preserve">
<value>保存</value> <value>保存当前位置</value>
<comment>保存按钮文本 | Save button text</comment> <comment>保存按钮文本 | Save button text</comment>
</data> </data>
<data name="AC_Restore" xml:space="preserve"> <data name="AC_Restore" xml:space="preserve">
<value>恢复</value> <value>恢复前一位置</value>
<comment>恢复按钮文本 | Restore button text</comment> <comment>恢复按钮文本 | Restore button text</comment>
</data> </data>
</root> </root>
@@ -293,12 +293,12 @@ DetectorZ → {4:F2}mm
<value>校准计算值</value> <value>校准计算值</value>
</data> </data>
<data name="AC_Enable" xml:space="preserve"> <data name="AC_Enable" xml:space="preserve">
<value>使能</value> <value>实体操作摇杆使能</value>
</data> </data>
<data name="AC_Save" xml:space="preserve"> <data name="AC_Save" xml:space="preserve">
<value>保存</value> <value>保存当前位置</value>
</data> </data>
<data name="AC_Restore" xml:space="preserve"> <data name="AC_Restore" xml:space="preserve">
<value>恢复</value> <value>恢复前一位置</value>
</data> </data>
</root> </root>
@@ -293,12 +293,12 @@ DetectorZ → {4:F2}mm
<value>校準計算值</value> <value>校準計算值</value>
</data> </data>
<data name="AC_Enable" xml:space="preserve"> <data name="AC_Enable" xml:space="preserve">
<value>使能</value> <value>實體操作搖桿使能</value>
</data> </data>
<data name="AC_Save" xml:space="preserve"> <data name="AC_Save" xml:space="preserve">
<value>保存</value> <value>保存當前位置</value>
</data> </data>
<data name="AC_Restore" xml:space="preserve"> <data name="AC_Restore" xml:space="preserve">
<value>恢復</value> <value>恢復前一位置</value>
</data> </data>
</root> </root>
@@ -9,146 +9,148 @@
xmlns:controls="clr-namespace:XP.Common.Controls;assembly=XP.Common" xmlns:controls="clr-namespace:XP.Common.Controls;assembly=XP.Common"
prism:ViewModelLocator.AutoWireViewModel="True" prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d" mc:Ignorable="d"
MinWidth="350"
HorizontalAlignment="Stretch" Background="White"> HorizontalAlignment="Stretch" Background="White">
<Grid Margin="8"> <Grid Margin="6">
<Grid.ColumnDefinitions> <Grid.RowDefinitions>
<ColumnDefinition Width="Auto" MinWidth="200"/> <RowDefinition Height="*"/>
<ColumnDefinition Width="*"/> <RowDefinition Height="Auto"/>
</Grid.ColumnDefinitions> </Grid.RowDefinitions>
<!-- ========== 左侧区域:轴位置 + 安全参数 | Left: Axis Positions + Safety Params ========== --> <!-- ========== 上部:左(轴位置+ 右(摇杆)========== -->
<StackPanel Grid.Column="0" Margin="0,0,8,0"> <Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 轴位置输入框组 | Axis Position Inputs --> <!-- ===== 左侧:轴位置输入框 ===== -->
<GroupBox Margin="0,0,0,6"> <Grid Grid.Column="0" Margin="0,0,4,0" VerticalAlignment="Center">
<GroupBox.Header> <Grid.ColumnDefinitions>
<TextBlock Text="{loc:Localization AC_AxisPositions}" FontWeight="Bold" FontSize="12"/> <ColumnDefinition Width="Auto"/>
</GroupBox.Header> <ColumnDefinition Width="60"/>
<Grid Margin="5"> </Grid.ColumnDefinitions>
<Grid.ColumnDefinitions> <Grid.RowDefinitions>
<ColumnDefinition Width="Auto" MinWidth="80"/> <RowDefinition Height="Auto"/>
<ColumnDefinition Width="*"/> <RowDefinition Height="Auto"/>
</Grid.ColumnDefinitions> <RowDefinition Height="Auto"/>
<Grid.RowDefinitions> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> </Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Stage X --> <TextBlock Grid.Row="0" Grid.Column="0" Text="{loc:Localization AC_StageX}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{loc:Localization AC_StageX}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2"/>
<telerik:RadNumericUpDown Grid.Row="0" Grid.Column="1" x:Name="NumStageX"
Value="{Binding StageXPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumStageX_GotFocus" LostFocus="NumStageX_LostFocus" KeyDown="NumStageX_KeyDown"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
<!-- Stage Y --> <TextBlock Grid.Row="1" Grid.Column="0" Text="{loc:Localization AC_StageY}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{loc:Localization AC_StageY}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2"/>
<telerik:RadNumericUpDown Grid.Row="1" Grid.Column="1" x:Name="NumStageY"
Value="{Binding StageYPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumStageY_GotFocus" LostFocus="NumStageY_LostFocus" KeyDown="NumStageY_KeyDown"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
<!-- Source Z --> <TextBlock Grid.Row="2" Grid.Column="0" Text="{loc:Localization AC_SourceZ}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{loc:Localization AC_SourceZ}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2"/>
<telerik:RadNumericUpDown Grid.Row="2" Grid.Column="1" x:Name="NumSourceZ"
Value="{Binding SourceZPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumSourceZ_GotFocus" LostFocus="NumSourceZ_LostFocus" KeyDown="NumSourceZ_KeyDown"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
<!-- Detector Z --> <TextBlock Grid.Row="3" Grid.Column="0" Text="{loc:Localization AC_DetectorZ}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="{loc:Localization AC_DetectorZ}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2"/>
<telerik:RadNumericUpDown Grid.Row="3" Grid.Column="1" x:Name="NumDetectorZ"
Value="{Binding DetectorZPosition, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumDetectorZ_GotFocus" LostFocus="NumDetectorZ_LostFocus" KeyDown="NumDetectorZ_KeyDown"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
<!-- Detector Swing(动态显示/隐藏)| Dynamic visibility --> <TextBlock Grid.Row="4" Grid.Column="0" Text="{loc:Localization AC_DetectorSwing}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1" Visibility="{Binding DetectorSwingVisibility}"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="{loc:Localization AC_DetectorSwing}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2"
Visibility="{Binding DetectorSwingVisibility}"/>
<telerik:RadNumericUpDown Grid.Row="4" Grid.Column="1" x:Name="NumDetSwing"
Value="{Binding DetectorSwingAngle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumDetSwing_GotFocus" LostFocus="NumDetSwing_LostFocus" KeyDown="NumDetSwing_KeyDown"
Visibility="{Binding DetectorSwingVisibility}"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
<!-- Stage Rotation(动态显示/隐藏)| Dynamic visibility --> <TextBlock Grid.Row="5" Grid.Column="0" Text="{loc:Localization AC_StageRotation}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1" Visibility="{Binding StageRotationVisibility}"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="{loc:Localization AC_StageRotation}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2"
Visibility="{Binding StageRotationVisibility}"/>
<telerik:RadNumericUpDown Grid.Row="5" Grid.Column="1" x:Name="NumStageRot"
Value="{Binding StageRotationAngle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumStageRot_GotFocus" LostFocus="NumStageRot_LostFocus" KeyDown="NumStageRot_KeyDown"
Visibility="{Binding StageRotationVisibility}"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
<!-- Fixture Rotation(动态显示/隐藏)| Dynamic visibility --> <TextBlock Grid.Row="6" Grid.Column="0" Text="{loc:Localization AC_FixtureRotation}" FontSize="11" VerticalAlignment="Center" Margin="0,1,4,1" Visibility="{Binding FixtureRotationVisibility}"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="{loc:Localization AC_FixtureRotation}" <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"/>
FontSize="12" VerticalAlignment="Center" Margin="0,2" </Grid>
Visibility="{Binding FixtureRotationVisibility}"/>
<telerik:RadNumericUpDown Grid.Row="6" Grid.Column="1" x:Name="NumFixtureRot"
Value="{Binding FixtureRotationAngle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
GotFocus="NumFixtureRot_GotFocus" LostFocus="NumFixtureRot_LostFocus" KeyDown="NumFixtureRot_KeyDown"
Visibility="{Binding FixtureRotationVisibility}"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
</Grid>
</GroupBox>
<!-- 安全参数输入框组 | Safety Parameter Inputs --> <!-- ===== 右侧:摇杆区域 ===== -->
<GroupBox Margin="0,0,0,6"> <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"
<GroupBox.Header> Margin="4,12,4,12">
<TextBlock Text="{loc:Localization AC_SafetyParams}" FontWeight="Bold" FontSize="12"/> <!-- 单轴摇杆(腰圆)-->
</GroupBox.Header> <controls:VirtualJoystick x:Name="SingleJoystick"
<Grid Margin="5"> JoystickMode="SingleAxisY"
<Grid.ColumnDefinitions> Width="40" Height="160"
<ColumnDefinition Width="Auto" MinWidth="80"/> IsEnabled="{Binding IsJoystickEnabled}"
<ColumnDefinition Width="*"/> DefaultTopIcon="↑" DefaultBottomIcon="↓"
</Grid.ColumnDefinitions> LeftButtonTopIcon="Src↑" LeftButtonBottomIcon="Src↓"
<Grid.RowDefinitions> RightButtonTopIcon="Det↑" RightButtonBottomIcon="Det↓"
<RowDefinition Height="Auto"/> Margin="2,0,8,0"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 安全高度 | Safety Height --> <!-- 双轴摇杆(圆形)-->
<TextBlock Grid.Row="0" Grid.Column="0" Text="{loc:Localization AC_SafetyHeight}" <controls:VirtualJoystick x:Name="DualJoystick"
FontSize="12" VerticalAlignment="Center" Margin="0,2"/> JoystickMode="DualAxis"
<telerik:RadNumericUpDown Grid.Row="0" Grid.Column="1" x:Name="NumSafetyHeight" Width="160" Height="160"
Value="{Binding SafetyHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding IsJoystickEnabled}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True" DefaultTopIcon="↑" DefaultBottomIcon="" DefaultLeftIcon="←" DefaultRightIcon="→"
KeyDown="NumSafetyHeight_KeyDown" LeftButtonTopIcon="Y+" LeftButtonBottomIcon="Y-" LeftButtonLeftIcon="X-" LeftButtonRightIcon="X+"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/> RightButtonTopIcon="Rot+" RightButtonBottomIcon="Rot-" RightButtonLeftIcon="Swing-" RightButtonRightIcon="Swing+"
Margin="0,0,2,0"/>
</StackPanel>
</Grid>
<!-- 校准计算值 | Calibration Value --> <!-- ========== 下部:安全参数(一行均布)+ 操作按钮(一行均布)========== -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="{loc:Localization AC_CalibrationValue}" <StackPanel Grid.Row="1" Margin="0,8,0,0">
FontSize="12" VerticalAlignment="Center" Margin="0,2"/>
<telerik:RadNumericUpDown Grid.Row="1" Grid.Column="1" x:Name="NumCalibration"
Value="{Binding CalibrationValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ValueFormat="Numeric" NumberDecimalDigits="2" SmallChange="0.1" IsEditable="True"
KeyDown="NumCalibration_KeyDown"
Margin="4,2" telerik:StyleManager.Theme="Crystal"/>
</Grid>
</GroupBox>
<!-- 错误信息显示 | Error Message Display --> <!-- 安全参数一行(均布)-->
<TextBlock Text="{Binding ErrorMessage}" FontSize="11" Foreground="#FFE53935" <Grid Margin="0,0,0,6">
TextWrapping="Wrap" Margin="0,2"> <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> <TextBlock.Style>
<Style TargetType="TextBlock"> <Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/> <Setter Property="Visibility" Value="Visible"/>
@@ -164,74 +166,5 @@
</TextBlock.Style> </TextBlock.Style>
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<!-- ========== 右侧区域:摇杆 + 操作按钮 | Right: Joysticks + Action Buttons ========== -->
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 摇杆区域 | Joystick Area -->
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- 单轴摇杆(SingleAxisY):Source_Z / Detector_Z | Single-axis joystick -->
<controls:VirtualJoystick x:Name="SingleJoystick"
JoystickMode="SingleAxisY"
Width="150" Height="200"
IsEnabled="{Binding IsJoystickEnabled}"
DefaultTopIcon="↑" DefaultBottomIcon="↓"
LeftButtonTopIcon="Src↑" LeftButtonBottomIcon="Src↓"
RightButtonTopIcon="Det↑" RightButtonBottomIcon="Det↓"
Margin="0,0,16,0"/>
<!-- 双轴摇杆(DualAxis):Stage X/Y + Swing/Rotation | Dual-axis joystick -->
<controls:VirtualJoystick x:Name="DualJoystick"
JoystickMode="DualAxis"
Width="200" Height="200"
IsEnabled="{Binding IsJoystickEnabled}"
DefaultTopIcon="↑" DefaultBottomIcon="↓" DefaultLeftIcon="←" DefaultRightIcon="→"
LeftButtonTopIcon="Y+" LeftButtonBottomIcon="Y-" LeftButtonLeftIcon="X-" LeftButtonRightIcon="X+"
RightButtonTopIcon="Rot+" RightButtonBottomIcon="Rot-" RightButtonLeftIcon="Swing-" RightButtonRightIcon="Swing+"/>
</StackPanel>
<!-- 操作按钮区域 | Action Buttons Area -->
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,8,0,0">
<!-- 使能开关 | Enable Toggle -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,12,0">
<TextBlock Text="{loc:Localization AC_Enable}" FontSize="12" VerticalAlignment="Center" Margin="0,0,6,0"/>
<telerik:RadToggleSwitchButton IsChecked="{Binding IsJoystickEnabled, Mode=TwoWay}"
Command="{Binding ToggleEnableCommand}"
telerik:StyleManager.Theme="Crystal"/>
</StackPanel>
<!-- 保存按钮 | Save Button -->
<telerik:RadButton Content="{loc:Localization AC_Save}"
Command="{Binding SavePositionsCommand}"
Width="70" Height="28" Margin="4,0" FontSize="12"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FF62B8E0" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
<!-- 恢复按钮 | Restore Button -->
<telerik:RadButton Content="{loc:Localization AC_Restore}"
Command="{Binding RestorePositionsCommand}"
Width="70" Height="28" Margin="4,0" FontSize="12"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FFFFD54F" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
</StackPanel>
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>
+1 -1
View File
@@ -491,7 +491,7 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<views1:RaySourceOperateView Grid.Row="0" Grid.ColumnSpan="2" /> <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" /> <views:NavigationPropertyPanelView Grid.Row="2" Grid.ColumnSpan="2" />
</Grid> </Grid>
</Grid> </Grid>