已合并 PR 34: 合并AxisControl运动控制控件

This commit is contained in:
QI Mingxuan
2026-04-23 08:54:08 +08:00
23 changed files with 2178 additions and 3 deletions
@@ -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
}
}