Browse Source

图像清晰度分析控件重构,支持旋转ROI与MVVM

重构 FocusAnalyzerControl,分离 ViewModel,支持旋转矩形 ROI 的绘制、拖动、调整大小与旋转,界面全面美化。新增 RotatedRect 类及相关枚举、转换器,完善依赖属性和命令绑定。机器人手动操作界面同步美化。优化数据库服务当前用户获取逻辑。VisionSolution 集成新版分析控件,支持图片选择。整体提升交互体验和可维护性。
孝锋 徐 10 months ago
parent
commit
2fe25ef920

+ 345 - 209
TeamAAS-VM/Controls/FocusAnalyzerControl.xaml

@@ -1,14 +1,18 @@
 <UserControl x:Class="TeamAAS_VP.Controls.FocusAnalyzerControl"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:c="clr-namespace:TeamAAS_VP.ValueConverter"
              xmlns:local="clr-namespace:TeamAAS_VP.Controls"
+             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
              xmlns:oxy="http://oxyplot.org/wpf"
              mc:Ignorable="d"
              d:DesignHeight="600"
              d:DesignWidth="800">
     <UserControl.Resources>
+        <!-- 样式 -->
         <Style x:Key="CardStyle"
                TargetType="Border">
             <Setter Property="Background"
@@ -25,102 +29,286 @@
                     Value="4" />
         </Style>
 
-        <Style x:Key="ValueTextStyle"
-               TargetType="TextBlock">
-            <Setter Property="FontSize"
-                    Value="14" />
-            <Setter Property="FontWeight"
-                    Value="SemiBold" />
+        <Style x:Key="ToolButtonStyle"
+               TargetType="RadioButton">
+            <Setter Property="Width"
+                    Value="30" />
+            <Setter Property="Height"
+                    Value="30" />
             <Setter Property="Margin"
-                    Value="0,2,0,4" />
+                    Value="2" />
+            <Setter Property="Cursor"
+                    Value="Hand" />
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="RadioButton">
+                        <Border Background="{TemplateBinding Background}"
+                                BorderBrush="{TemplateBinding BorderBrush}"
+                                BorderThickness="1"
+                                CornerRadius="4">
+                            <ContentPresenter HorizontalAlignment="Center"
+                                              VerticalAlignment="Center" />
+                        </Border>
+                        <ControlTemplate.Triggers>
+                            <Trigger Property="IsChecked"
+                                     Value="True">
+                                <Setter Property="Background"
+                                        Value="#007ACC" />
+                                <Setter Property="Foreground"
+                                        Value="White" />
+                            </Trigger>
+                            <Trigger Property="IsMouseOver"
+                                     Value="True">
+                                <Setter Property="Background"
+                                        Value="#F0F0F0" />
+                            </Trigger>
+                        </ControlTemplate.Triggers>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
         </Style>
+
+        <!-- ROI控制点样式 -->
+        <Style x:Key="RoiHandleStyle"
+               TargetType="Ellipse">
+            <Setter Property="Width"
+                    Value="8" />
+            <Setter Property="Height"
+                    Value="8" />
+            <Setter Property="Fill"
+                    Value="White" />
+            <Setter Property="Stroke"
+                    Value="Red" />
+            <Setter Property="StrokeThickness"
+                    Value="1" />
+            <Setter Property="Cursor"
+                    Value="SizeAll" />
+        </Style>
+
+        <!-- 旋转控制点样式 -->
+        <Style x:Key="RotateHandleStyle"
+               TargetType="Path">
+            <Setter Property="Fill"
+                    Value="Red" />
+            <Setter Property="Stroke"
+                    Value="White" />
+            <Setter Property="StrokeThickness"
+                    Value="1" />
+            <Setter Property="Cursor"
+                    Value="SizeAll" />
+            <Setter Property="Data"
+                    Value="M 0,0 L 5,0 L 2.5,5 Z" />
+        </Style>
+        <c:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
     </UserControl.Resources>
 
     <Grid>
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*" />
-            <ColumnDefinition Width="300" />
+            <ColumnDefinition Width="280" />
         </Grid.ColumnDefinitions>
 
-        <!-- 左侧:图像显示和图表 -->
-        <Grid Grid.Column="0">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="*" />
-                <RowDefinition Height="200" />
-            </Grid.RowDefinitions>
-
-            <!-- 状态栏 -->
-            <Border Grid.Row="0"
-                    Style="{StaticResource CardStyle}">
-                <StackPanel Orientation="Horizontal">
-                    <TextBlock Text="状态: "
-                               FontWeight="Bold" />
-                    <TextBlock x:Name="StatusText"
-                               Text="就绪"
-                               Margin="5,0,0,0" />
-                    <TextBlock x:Name="FrameCountText"
-                               Text="帧数: 0"
-                               Margin="20,0,0,0" />
-                    <TextBlock x:Name="BestSharpnessText"
-                               Text="最佳: 0.00"
-                               Margin="20,0,0,0" />
-
-                    <Button x:Name="StartButton"
-                            Content="开始分析"
-                            Command="{Binding StartCommand}"
-                            Margin="20,0,5,0"
-                            Padding="10,4" />
-                    <Button x:Name="StopButton"
-                            Content="停止分析"
-                            Command="{Binding StopCommand}"
-                            Margin="5,0,0,0"
-                            Padding="10,4" />
-                </StackPanel>
-            </Border>
-
-            <!-- 图像显示 -->
-            <Border Grid.Row="1"
-                    Style="{StaticResource CardStyle}"
-                    Margin="0,4,0,4">
-                <Grid>
+        <!-- 左侧:图像显示区 -->
+        <Border Grid.Column="0"
+                Style="{StaticResource CardStyle}"
+                Margin="10">
+            <Grid>
+                <!-- 图像容器 -->
+                <Grid x:Name="ImageContainer">
+                    <!-- 图像显示 -->
                     <Image x:Name="ImageDisplay"
                            Source="{Binding CurrentImage}"
-                           Stretch="Uniform" />
+                           Stretch="Uniform" 
+                           MouseLeftButtonDown="ImageDisplay_MouseLeftButtonDown"
+                           MouseLeftButtonUp="ImageDisplay_MouseLeftButtonUp"
+                           MouseMove="ImageDisplay_MouseMove">
+                        <!--<b:Interaction.Triggers>
+                            <b:EventTrigger EventName="MouseLeftButtonDown">
+                                <b:InvokeCommandAction Command="{Binding ImageMouseDownCommand}" />
+                            </b:EventTrigger>
+                            <b:EventTrigger EventName="MouseMove">
+                                <b:InvokeCommandAction Command="{Binding ImageMouseMoveCommand}" />
+                            </b:EventTrigger>
+                            <b:EventTrigger EventName="MouseLeftButtonUp">
+                                <b:InvokeCommandAction Command="{Binding ImageMouseUpCommand}" />
+                            </b:EventTrigger>
+                        </b:Interaction.Triggers>-->
+                    </Image>
 
                     <!-- ROI绘制层 -->
                     <Canvas x:Name="RoiCanvas"
                             IsHitTestVisible="False">
+                        <!-- ROI矩形(带旋转) -->
                         <Rectangle x:Name="RoiRectangle"
-                                   Stroke="Red"
-                                   StrokeThickness="2"
-                                   StrokeDashArray="4,4"
-                                   Visibility="Collapsed" />
+                                   Visibility="{Binding ShowRoi, Converter={StaticResource BooleanToVisibilityConverter}}">
+                            <Rectangle.Style>
+                                <Style TargetType="Rectangle">
+                                    <Setter Property="Stroke"
+                                            Value="Red" />
+                                    <Setter Property="StrokeThickness"
+                                            Value="2" />
+                                    <Setter Property="StrokeDashArray"
+                                            Value="4,4" />
+                                    <Setter Property="RenderTransformOrigin"
+                                            Value="0.5,0.5" />
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding RoiMode}"
+                                                     Value="Draw">
+                                            <Setter Property="StrokeDashArray"
+                                                    Value="2,2" />
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Rectangle.Style>
+                            <Rectangle.RenderTransform>
+                                <RotateTransform Angle="{Binding CurrentRoi.Angle}" />
+                            </Rectangle.RenderTransform>
+                        </Rectangle>
+
+                        <!-- ROI控制点 -->
+                        <Canvas Visibility="{Binding ShowRoi, Converter={StaticResource BooleanToVisibilityConverter}}">
+                            <!-- 角点控制点 -->
+                            <Ellipse x:Name="TopLeftHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+                            <Ellipse x:Name="TopRightHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+                            <Ellipse x:Name="BottomLeftHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+                            <Ellipse x:Name="BottomRightHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+
+                            <!-- 边中点控制点 -->
+                            <Ellipse x:Name="TopHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+                            <Ellipse x:Name="RightHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+                            <Ellipse x:Name="BottomHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+                            <Ellipse x:Name="LeftHandle"
+                                     Style="{StaticResource RoiHandleStyle}" />
+
+                            <!-- 旋转控制点 -->
+                            <Path x:Name="RotateHandle"
+                                  Style="{StaticResource RotateHandleStyle}" />
+                        </Canvas>
                     </Canvas>
-                </Grid>
-            </Border>
 
-            <!-- 曲线图表 -->
-            <Border Grid.Row="2"
-                    Style="{StaticResource CardStyle}">
-                <oxy:PlotView x:Name="SharpnessPlot"
-                              Model="{Binding PlotModel}" />
-            </Border>
-        </Grid>
+                    <!-- 操作提示 -->
+                    <Border Background="#80000000"
+                            CornerRadius="4"
+                            Padding="8"
+                            VerticalAlignment="Bottom"
+                            HorizontalAlignment="Center"
+                            Margin="0,0,0,10">
+                        <StackPanel>
+                            <TextBlock Text="ROI操作提示"
+                                       Foreground="White"
+                                       FontWeight="Bold"
+                                       Margin="0,0,0,4" />
+                            <TextBlock Text="拖动: 移动ROI"
+                                       Foreground="White"
+                                       FontSize="11" />
+                            <TextBlock Text="角点拖动: 调整大小"
+                                       Foreground="White"
+                                       FontSize="11" />
+                            <TextBlock Text="旋转点拖动: 旋转"
+                                       Foreground="White"
+                                       FontSize="11" />
+                        </StackPanel>
+                    </Border>
+                </Grid>
+            </Grid>
+        </Border>
 
-        <!-- 右侧:参数和结果显示 -->
+        <!-- 右侧:控制面板 -->
         <Grid Grid.Column="1"
-              Margin="4,0,0,0">
+              Margin="0,10,10,10">
             <Grid.RowDefinitions>
                 <RowDefinition Height="Auto" />
                 <RowDefinition Height="Auto" />
                 <RowDefinition Height="Auto" />
+                <RowDefinition Height="Auto" />
                 <RowDefinition Height="*" />
             </Grid.RowDefinitions>
 
-            <!-- 参数设置 -->
+            <!-- ROI工具栏 -->
             <Border Grid.Row="0"
                     Style="{StaticResource CardStyle}">
+                <StackPanel>
+                    <TextBlock Text="ROI工具"
+                               FontWeight="Bold"
+                               Margin="0,0,0,8" />
+
+                    <StackPanel Orientation="Horizontal"
+                                HorizontalAlignment="Center">
+                        <!-- 绘制模式 -->
+                        <RadioButton Content="📐"
+                                     Style="{StaticResource ToolButtonStyle}"
+                                     ToolTip="绘制ROI"
+                                     IsChecked="{Binding RoiMode, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Draw}"
+                                     Command="{Binding SetRoiModeCommand}"
+                                     CommandParameter="{x:Static local:RoiOperationMode.Draw}" />
+
+                        <!-- 移动模式 -->
+                        <RadioButton Content="↔"
+                                     Style="{StaticResource ToolButtonStyle}"
+                                     ToolTip="移动ROI"
+                                     IsChecked="{Binding RoiMode, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Move}"
+                                     Command="{Binding SetRoiModeCommand}"
+                                     CommandParameter="{x:Static local:RoiOperationMode.Move}" />
+
+                        <!-- 调整大小模式 -->
+                        <RadioButton Content="⤢"
+                                     Style="{StaticResource ToolButtonStyle}"
+                                     ToolTip="调整大小"
+                                     IsChecked="{Binding RoiMode, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Resize}"
+                                     Command="{Binding SetRoiModeCommand}"
+                                     CommandParameter="{x:Static local:RoiOperationMode.Resize}" />
+
+                        <!-- 旋转模式 -->
+                        <RadioButton Content="↻"
+                                     Style="{StaticResource ToolButtonStyle}"
+                                     ToolTip="旋转ROI"
+                                     IsChecked="{Binding RoiMode, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Rotate}"
+                                     Command="{Binding SetRoiModeCommand}"
+                                     CommandParameter="{x:Static local:RoiOperationMode.Rotate}" />
+
+                        <!-- 清除ROI -->
+                        <Button Width="30"
+                                Height="30"
+                                Margin="10,0,0,0"
+                                Padding="0"
+                                ToolTip="清除ROI"
+                                Command="{Binding ClearRoiCommand}">
+                            <materialDesign:PackIcon Kind="RemoveCircle"
+                                                     Width="26"
+                                                     Height="26" />
+                        </Button>
+                    </StackPanel>
+
+                    <!-- ROI信息 -->
+                    <Border Background="#F5F5F5"
+                            CornerRadius="3"
+                            Padding="6"
+                            Margin="0,8,0,0">
+                        <StackPanel>
+                            <TextBlock Text="ROI信息"
+                                       FontWeight="SemiBold"
+                                       FontSize="11" />
+                            <TextBlock Text="{Binding CurrentRoi}"
+                                       FontSize="10"
+                                       Foreground="#666"
+                                       TextWrapping="Wrap"
+                                       Margin="0,2,0,0" />
+                        </StackPanel>
+                    </Border>
+                </StackPanel>
+            </Border>
+
+            <!-- 参数设置 -->
+            <Border Grid.Row="1"
+                    Style="{StaticResource CardStyle}"
+                    Margin="0,8,0,0">
                 <StackPanel>
                     <TextBlock Text="分析参数"
                                FontWeight="Bold"
@@ -131,7 +319,6 @@
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
                         </Grid.RowDefinitions>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="80" />
@@ -145,7 +332,7 @@
                         <ComboBox Grid.Row="0"
                                   Grid.Column="1"
                                   ItemsSource="{Binding FocusMethods}"
-                                  SelectedItem="{Binding Parameters.FocusMethod}"
+                                  SelectedValue="{Binding Parameters.FocusMethod}"
                                   Margin="0,0,0,4" />
 
                         <TextBlock Grid.Row="1"
@@ -155,178 +342,127 @@
                         <ComboBox Grid.Row="1"
                                   Grid.Column="1"
                                   ItemsSource="{Binding AnalysisModes}"
-                                  SelectedItem="{Binding Parameters.AnalysisMode}"
+                                  SelectedValue="{Binding Parameters.AnalysisMode}"
                                   Margin="0,0,0,4" />
 
                         <TextBlock Grid.Row="2"
                                    Grid.Column="0"
-                                   Text="亮度:"
+                                   Text="实时分析:"
                                    VerticalAlignment="Center" />
-                        <Slider Grid.Row="2"
-                                Grid.Column="1"
-                                Minimum="0"
-                                Maximum="255"
-                                Value="{Binding Parameters.TargetBrightness}"
-                                Margin="0,0,0,4" />
-
-                        <CheckBox Grid.Row="3"
-                                  Grid.Column="0"
-                                  Grid.ColumnSpan="2"
-                                  Content="实时分析"
+                        <CheckBox Grid.Row="2"
+                                  Grid.Column="1"
                                   IsChecked="{Binding Parameters.EnableRealtimeAnalysis}"
-                                  Margin="0,4,0,0" />
+                                  VerticalAlignment="Center" />
                     </Grid>
-
-                    <Button Content="恢复默认"
-                            Command="{Binding ResetCommand}"
-                            Margin="0,8,0,0"
-                            Padding="8,4" />
                 </StackPanel>
             </Border>
 
-            <!-- 清晰度结果 -->
-            <Border Grid.Row="1"
+            <!-- 控制按钮 -->
+            <Border Grid.Row="2"
                     Style="{StaticResource CardStyle}"
-                    Margin="0,4,0,0">
-                <Grid>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="*" />
-                        <ColumnDefinition Width="*" />
-                    </Grid.ColumnDefinitions>
-
-                    <StackPanel Grid.Column="0">
-                        <TextBlock Text="当前清晰度"
-                                   FontSize="11"
-                                   Foreground="#666" />
-                        <TextBlock Text="{Binding CurrentResult.Sharpness, StringFormat=F2}"
-                                   Style="{StaticResource ValueTextStyle}"
-                                   Foreground="#007ACC" />
-                        <ProgressBar Value="{Binding CurrentResult.Sharpness}"
-                                     Maximum="50"
-                                     Height="6"
-                                     Margin="0,2,4,0" />
+                    Margin="0,8,0,0">
+                <StackPanel>
+                    <StackPanel Orientation="Horizontal"
+                                HorizontalAlignment="Center">
+                        <Button Content="开始分析"
+                                Command="{Binding StartAnalysisCommand}"
+                                Padding="20,8"
+                                Margin="0,0,8,0" />
+                        <Button Content="停止分析"
+                                Command="{Binding StopAnalysisCommand}"
+                                Padding="20,8" />
                     </StackPanel>
 
-                    <StackPanel Grid.Column="1">
-                        <TextBlock Text="质量评分"
-                                   FontSize="11"
-                                   Foreground="#666" />
-                        <TextBlock Text="{Binding CurrentResult.QualityScore, StringFormat=F1}"
-                                   Style="{StaticResource ValueTextStyle}"
-                                   FontSize="18" />
-                        <TextBlock Text="/100"
-                                   FontSize="11"
-                                   Foreground="#999" />
+                    <StackPanel Orientation="Horizontal"
+                                HorizontalAlignment="Center"
+                                Margin="0,8,0,0">
+                        <Button Content="重置"
+                                Command="{Binding ResetCommand}"
+                                Padding="15,4"
+                                Margin="0,0,8,0" />
+                        <Button Content="导出报告"
+                                Command="{Binding ExportCommand}"
+                                Padding="15,4" />
                     </StackPanel>
-                </Grid>
+                </StackPanel>
             </Border>
 
-            <!-- 棋盘格分析 -->
-            <Border Grid.Row="2"
+            <!-- 实时结果 -->
+            <Border Grid.Row="3"
                     Style="{StaticResource CardStyle}"
-                    Margin="0,4,0,0">
+                    Margin="0,8,0,0">
                 <StackPanel>
-                    <TextBlock Text="棋盘格分析"
+                    <TextBlock Text="实时结果"
                                FontWeight="Bold"
                                Margin="0,0,0,8" />
 
                     <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                        </Grid.RowDefinitions>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="*" />
                             <ColumnDefinition Width="*" />
                         </Grid.ColumnDefinitions>
 
-                        <TextBlock Grid.Row="0"
-                                   Grid.Column="0"
-                                   Text="状态:"
-                                   FontSize="11"
-                                   Foreground="#666" />
-                        <TextBlock Grid.Row="0"
-                                   Grid.Column="1"
-                                   Text="黑色均值:"
-                                   FontSize="11"
-                                   Foreground="#666" />
-
-                        <TextBlock Grid.Row="1"
-                                   Grid.Column="0"
-                                   Text="{Binding CurrentResult.Checkerboard.Detected, 
-                                          Converter={StaticResource BoolToTextConverter},
-                                          ConverterParameter='已检测|未检测'}"
-                                   Style="{StaticResource ValueTextStyle}" />
-
-                        <TextBlock Grid.Row="1"
-                                   Grid.Column="1"
-                                   Text="{Binding CurrentResult.Checkerboard.BlackMean, StringFormat=F1}"
-                                   Style="{StaticResource ValueTextStyle}" />
-
-                        <TextBlock Grid.Row="2"
-                                   Grid.Column="0"
-                                   Text="对比度:"
-                                   FontSize="11"
-                                   Foreground="#666" />
-                        <TextBlock Grid.Row="2"
-                                   Grid.Column="1"
-                                   Text="白色均值:"
-                                   FontSize="11"
-                                   Foreground="#666" />
-
-                        <TextBlock Grid.Row="3"
-                                   Grid.Column="0"
-                                   Text="{Binding CurrentResult.Checkerboard.Contrast, StringFormat=F1}"
-                                   Style="{StaticResource ValueTextStyle}" />
+                        <StackPanel Grid.Column="0">
+                            <TextBlock Text="清晰度"
+                                       FontSize="11"
+                                       Foreground="#666" />
+                            <TextBlock Text="{Binding CurrentResult.Sharpness, StringFormat=F2}"
+                                       FontSize="18"
+                                       FontWeight="Bold"
+                                       Foreground="#007ACC" />
+                            <TextBlock Text="{Binding StatusText}"
+                                       FontSize="10"
+                                       Foreground="#666"
+                                       Margin="0,2,0,0" />
+                        </StackPanel>
 
-                        <TextBlock Grid.Row="3"
-                                   Grid.Column="1"
-                                   Text="{Binding CurrentResult.Checkerboard.WhiteMean, StringFormat=F1}"
-                                   Style="{StaticResource ValueTextStyle}" />
+                        <StackPanel Grid.Column="1">
+                            <TextBlock Text="质量评分"
+                                       FontSize="11"
+                                       Foreground="#666" />
+                            <TextBlock Text="{Binding CurrentResult.QualityScore, StringFormat=F1}"
+                                       FontSize="18"
+                                       FontWeight="Bold"
+                                       Foreground="#4CAF50" />
+                            <TextBlock Text="/100"
+                                       FontSize="11"
+                                       Foreground="#999" />
+                        </StackPanel>
                     </Grid>
 
-                    <TextBlock Text="建议亮度:"
-                               FontSize="11"
-                               Foreground="#666"
-                               Margin="0,8,0,0" />
-                    <TextBlock Text="{Binding CurrentResult.Checkerboard.OptimalBrightness}"
-                               Style="{StaticResource ValueTextStyle}"
-                               Foreground="#4CAF50" />
+                    <ProgressBar Value="{Binding CurrentResult.Sharpness}"
+                                 Maximum="50"
+                                 Height="6"
+                                 Margin="0,8,0,0" />
+
+                    <!-- 最佳值显示 -->
+                    <Border Background="#F8F9FA"
+                            CornerRadius="3"
+                            Padding="6"
+                            Margin="0,8,0,0">
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock Text="最佳清晰度:"
+                                       FontSize="10"
+                                       Foreground="#666" />
+                            <TextBlock Text="{Binding BestSharpnessText}"
+                                       FontSize="10"
+                                       FontWeight="Bold"
+                                       Margin="5,0,0,0" />
+                            <TextBlock Text="{Binding FrameCountText}"
+                                       FontSize="10"
+                                       Foreground="#666"
+                                       Margin="10,0,0,0" />
+                        </StackPanel>
+                    </Border>
                 </StackPanel>
             </Border>
 
-            <!-- 建议列表 -->
-            <Border Grid.Row="3"
+            <!-- 图表 -->
+            <Border Grid.Row="4"
                     Style="{StaticResource CardStyle}"
-                    Margin="0,4,0,0">
-                <StackPanel>
-                    <TextBlock Text="调整建议"
-                               FontWeight="Bold"
-                               Margin="0,0,0,8" />
-
-                    <ScrollViewer VerticalScrollBarVisibility="Auto"
-                                  MaxHeight="200">
-                        <ItemsControl ItemsSource="{Binding CurrentResult.Suggestions}">
-                            <ItemsControl.ItemTemplate>
-                                <DataTemplate>
-                                    <Border Background="#F5F5F5"
-                                            CornerRadius="3"
-                                            Padding="6"
-                                            Margin="0,0,0,4">
-                                        <TextBlock Text="{Binding}"
-                                                   TextWrapping="Wrap"
-                                                   FontSize="11" />
-                                    </Border>
-                                </DataTemplate>
-                            </ItemsControl.ItemTemplate>
-                        </ItemsControl>
-                    </ScrollViewer>
-
-                    <Button Content="导出报告"
-                            Command="{Binding ExportCommand}"
-                            Margin="0,8,0,0"
-                            Padding="8,4" />
-                </StackPanel>
+                    Margin="0,8,0,0">
+                <oxy:PlotView Model="{Binding PlotModel}"
+                              Height="200" />
             </Border>
         </Grid>
     </Grid>

+ 190 - 447
TeamAAS-VM/Controls/FocusAnalyzerControl.xaml.cs

@@ -1,568 +1,311 @@
-using OpenCvSharp;
+using CSScripting;
+using OpenCvSharp;
+using OpenCvSharp.Extensions;
 using OpenCvSharp.WpfExtensions;
 using OxyPlot;
 using OxyPlot.Axes;
 using OxyPlot.Series;
+using Prism.Commands;
+using Prism.Mvvm;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Linq;
 using System.Runtime.CompilerServices;
-using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
 using TeamAAS_VP.Core;
 using TeamAAS_VP.Enums;
 using TeamAAS_VP.Models;
-using OpenCvSharp.Extensions;
-using Rect = OpenCvSharp.Rect;
-using DataPoint = TeamAAS_VP.Models.DataPoint;
 
 namespace TeamAAS_VP.Controls
 {
-    /// <summary>
-    /// FocusAnalyzerControl.xaml 的交互逻辑
-    /// </summary>
-    public partial class FocusAnalyzerControl : UserControl, INotifyPropertyChanged
+    public partial class FocusAnalyzerControl : UserControl
     {
-        // 命令
-        public ICommand StartCommand { get; set; }
-        public ICommand StopCommand { get; set; }
-        public ICommand ResetCommand { get; set; }
-        public ICommand ExportCommand { get; set; }
-
-        // 可绑定属性
-        private ImageSource _currentImage;
-        private AnalysisParameters _parameters;
-        private AnalysisResult _currentResult;
-        private AnalysisReport _finalReport;
-        private bool _isAnalyzing;
-        private Rect? _currentRoi;
-        private PlotModel _plotModel;
-
-        // 内部状态
-        private List<DataPoint> _historyData = new List<DataPoint>();
-        private int _frameCounter = 0;
-        private double _bestSharpness = 0;
-        private DateTime _startTime;
-
-        // 枚举值列表(用于ComboBox绑定)
-        public Array FocusMethods => Enum.GetValues(typeof(FocusMethod));
-        public Array AnalysisModes => Enum.GetValues(typeof(AnalysisMode));
-
-        public ImageSource CurrentImage
-        {
-            get => _currentImage;
-            set => SetField(ref _currentImage, value);
-        }
-
-        public AnalysisParameters Parameters
-        {
-            get => _parameters;
-            set => SetField(ref _parameters, value);
-        }
-
-        public AnalysisResult CurrentResult
-        {
-            get => _currentResult;
-            set => SetField(ref _currentResult, value);
-        }
-
-        public AnalysisReport FinalReport
-        {
-            get => _finalReport;
-            set => SetField(ref _finalReport, value);
-        }
-
-        public bool IsAnalyzing
-        {
-            get => _isAnalyzing;
-            set => SetField(ref _isAnalyzing, value);
-        }
-
-        public Rect? CurrentRoi
-        {
-            get => _currentRoi;
-            set => SetField(ref _currentRoi, value);
-        }
-
-        public PlotModel PlotModel
-        {
-            get => _plotModel;
-            set => SetField(ref _plotModel, value);
-        }
+        private FocusAnalyzerViewModel ViewModel;
 
+        // 鼠标状态
+        private bool _isMouseDown = false;
+        private System.Windows.Point _mouseDownPoint;
 
         public FocusAnalyzerControl()
         {
             InitializeComponent();
-            InitializeCommands();
-            InitializeProperties();
-            InitializePlot();
-
-            DataContext = this;
-        }
-        private void InitializeCommands()
-        {
-            StartCommand = new RelayCommand(
-                execute: StartAnalysis,
-                canExecute: () => !IsAnalyzing);
-
-            StopCommand = new RelayCommand(
-                execute: StopAnalysis,
-                canExecute: () => IsAnalyzing);
-
-            ResetCommand = new RelayCommand(
-                execute: ResetAnalysis);
-
-            ExportCommand = new RelayCommand(
-                execute: ExportReport);
+            Loaded += OnLoaded;
         }
 
-        private void InitializeProperties()
+        private void OnLoaded(object sender, RoutedEventArgs e)
         {
-            Parameters = new AnalysisParameters();
-            CurrentResult = new AnalysisResult();
-            FinalReport = new AnalysisReport();
-
-            // 设置默认ROI
-            CurrentRoi = null;
+            // 初始化ViewModel
+            if (DataContext == null)
+                DataContext = new FocusAnalyzerViewModel();
+            ViewModel=DataContext as FocusAnalyzerViewModel;
         }
 
-        private void InitializePlot()
+        #region 鼠标事件处理
+
+        private void ImageDisplay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
         {
-            PlotModel = new PlotModel
-            {
-                Title = "清晰度变化曲线",
-                TitleFontSize = 12,
-                PlotMargins = new OxyThickness(40, 10, 10, 40)
-            };
+            if (ViewModel == null) return;
 
-            // X轴
-            PlotModel.Axes.Add(new LinearAxis
-            {
-                Position = AxisPosition.Bottom,
-                Title = "帧序列",
-                MajorGridlineStyle = LineStyle.Solid,
-                MinorGridlineStyle = LineStyle.Dot,
-                MajorGridlineColor = OxyColor.FromArgb(40, 0, 0, 0),
-                MinorGridlineColor = OxyColor.FromArgb(20, 0, 0, 0)
-            });
-
-            // Y轴
-            PlotModel.Axes.Add(new LinearAxis
-            {
-                Position = AxisPosition.Left,
-                Title = "清晰度",
-                MajorGridlineStyle = LineStyle.Solid,
-                MinorGridlineStyle = LineStyle.Dot,
-                MajorGridlineColor = OxyColor.FromArgb(40, 0, 0, 0),
-                MinorGridlineColor = OxyColor.FromArgb(20, 0, 0, 0)
-            });
-
-            // 清晰度曲线
-            var sharpnessSeries = new LineSeries
-            {
-                Title = "清晰度",
-                Color = OxyColors.DodgerBlue,
-                StrokeThickness = 2,
-                MarkerType = MarkerType.Circle,
-                MarkerSize = 3,
-                MarkerFill = OxyColors.DodgerBlue,
-                MarkerStroke = OxyColors.White,
-                MarkerStrokeThickness = 1
-            };
-
-            PlotModel.Series.Add(sharpnessSeries);
-        }
+            _isMouseDown = true;
+            _mouseDownPoint = e.GetPosition(ImageDisplay);
 
-        /// <summary>
-        /// 开始分析(由外部调用)
-        /// </summary>
-        public void StartAnalysis()
-        {
-            try
+            // 处理ROI操作
+            if (ViewModel.CurrentRoi != null)
             {
-                IsAnalyzing = true;
-                _frameCounter = 0;
-                _bestSharpness = 0;
-                _historyData.Clear();
-                _startTime = DateTime.Now;
-
-                // 清空图表
-                var series = PlotModel.Series[0] as LineSeries;
-                series?.Points.Clear();
-
-                // 初始化最终报告
-                FinalReport = new AnalysisReport
+                // 检查是否点击了ROI
+                if (ViewModel.StartRoiDrag(_mouseDownPoint))
                 {
-                    Parameters = Parameters,
-                    StartTime = _startTime
-                };
-
-                // 更新UI
-                UpdateStatus("分析中...");
-                CommandManager.InvalidateRequerySuggested();
+                    CaptureMouse();
+                    return;
+                }
             }
-            catch (Exception ex)
+
+            // 开始绘制新ROI
+            if (ViewModel.RoiMode == RoiOperationMode.Draw)
             {
-                MessageBox.Show($"开始分析失败: {ex.Message}", "错误",
-                    MessageBoxButton.OK, MessageBoxImage.Error);
+                ViewModel.StartRoiDrawing(_mouseDownPoint);
+                CaptureMouse();
             }
         }
 
-        /// <summary>
-        /// 停止分析(由外部调用)
-        /// </summary>
-        public void StopAnalysis()
+        private void ImageDisplay_MouseMove(object sender, MouseEventArgs e)
         {
-            try
-            {
-                IsAnalyzing = false;
-
-                // 完成报告
-                FinalReport.EndTime = DateTime.Now;
-                FinalReport.FinalResult = CurrentResult;
-                FinalReport.HistoryData = new List<DataPoint>(_historyData);
+            if (ViewModel == null || !_isMouseDown) return;
 
-                // 更新UI
-                UpdateStatus("分析完成");
-                CommandManager.InvalidateRequerySuggested();
+            var currentPoint = e.GetPosition(ImageDisplay);
 
-                // 触发分析完成事件
-                OnAnalysisCompleted();
+            // 处理ROI拖动
+            if (ViewModel.RoiMode != RoiOperationMode.Draw && ViewModel.CurrentRoi != null)
+            {
+                ViewModel.UpdateRoiDrag(currentPoint);
+                UpdateRoiVisual();
             }
-            catch (Exception ex)
+            // 处理ROI绘制
+            else if (ViewModel.RoiMode == RoiOperationMode.Draw)
             {
-                MessageBox.Show($"停止分析失败: {ex.Message}", "错误",
-                    MessageBoxButton.OK, MessageBoxImage.Error);
+                ViewModel.UpdateRoiDrawing(currentPoint);
+                UpdateRoiVisual();
             }
         }
 
-        /// <summary>
-        /// 重置分析
-        /// </summary>
-        public void ResetAnalysis()
+        private void ImageDisplay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
         {
-            Parameters.ResetToDefaults();
-            CurrentResult = new AnalysisResult();
-            FinalReport = new AnalysisReport();
-
-            _historyData.Clear();
-            _frameCounter = 0;
-            _bestSharpness = 0;
-
-            // 清空图表
-            var series = PlotModel.Series[0] as LineSeries;
-            series?.Points.Clear();
-            PlotModel.InvalidatePlot(true);
+            if (ViewModel == null || !_isMouseDown) return;
 
-            // 清空图像
-            CurrentImage = null;
+            _isMouseDown = false;
+            ReleaseMouseCapture();
 
-            UpdateStatus("已重置");
-        }
-
-        /// <summary>
-        /// 导出报告
-        /// </summary>
-        private void ExportReport()
-        {
-            try
+            if (ViewModel.RoiMode == RoiOperationMode.Draw)
             {
-                var saveDialog = new Microsoft.Win32.SaveFileDialog
-                {
-                    Filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*",
-                    FileName = $"相机分析报告_{DateTime.Now:yyyyMMdd_HHmmss}.txt"
-                };
-
-                if (saveDialog.ShowDialog() == true)
-                {
-                    var report = FinalReport.GenerateTextReport();
-                    System.IO.File.WriteAllText(saveDialog.FileName, report);
-
-                    MessageBox.Show($"报告已保存到:{saveDialog.FileName}", "成功",
-                        MessageBoxButton.OK, MessageBoxImage.Information);
-                }
+                ViewModel.FinishRoiDrawing();
             }
-            catch (Exception ex)
+            else
             {
-                MessageBox.Show($"导出失败:{ex.Message}", "错误",
-                    MessageBoxButton.OK, MessageBoxImage.Error);
+                ViewModel.FinishRoiDrag();
             }
         }
 
-        /// <summary>
-        /// 处理新图像帧(由外部调用)
-        /// </summary>
-        public void ProcessImageFrame(Mat frame)
+        #endregion
+
+        #region ROI可视化更新
+
+        private void UpdateRoiVisual()
         {
-            if (!IsAnalyzing || frame == null || frame.Empty())
-                return;
+            if (ViewModel?.CurrentRoi == null) return;
 
-            try
-            {
-                // 更新UI图像
-                Application.Current.Dispatcher.Invoke(() =>
-                {
-                    CurrentImage = BitmapSourceConverter.ToBitmapSource(frame);
-                });
+            // 更新ROI矩形位置和大小
+            Canvas.SetLeft(RoiRectangle, ViewModel.CurrentRoi.X);
+            Canvas.SetTop(RoiRectangle, ViewModel.CurrentRoi.Y);
+            RoiRectangle.Width = ViewModel.CurrentRoi.Width;
+            RoiRectangle.Height = ViewModel.CurrentRoi.Height;
 
-                // 执行分析
-                PerformAnalysis(frame);
-                _frameCounter++;
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"处理图像帧失败: {ex.Message}");
-            }
+            // 更新控制点位置
+            UpdateRoiHandles();
         }
 
-        /// <summary>
-        /// 执行单次图像分析
-        /// </summary>
-        public void PerformAnalysis(Mat frame)
+        private void UpdateRoiHandles()
         {
-            if (frame == null || frame.Empty())
-                return;
+            if (ViewModel?.CurrentRoi == null) return;
 
-            try
-            {
-                // 1. 计算清晰度
-                double sharpness = FocusAnalysisEngine.CalculateImageSharpness(
-                    frame, Parameters.FocusMethod, CurrentRoi);
+            var roi = ViewModel.CurrentRoi;
 
-                // 2. 分析棋盘格
-                CheckerboardResult checkerboard;
-                if (Parameters.AnalysisMode == AnalysisMode.AutoCheckerboard)
-                {
-                    checkerboard = FocusAnalysisEngine.AnalyzeCheckerboardContrast(frame, CurrentRoi);
-                }
-                else
-                {
-                    checkerboard = new CheckerboardResult();
-                }
+            // 更新角点
+            UpdateHandlePosition(TopLeftHandle, roi.X, roi.Y);
+            UpdateHandlePosition(TopRightHandle, roi.X + roi.Width, roi.Y);
+            UpdateHandlePosition(BottomLeftHandle, roi.X, roi.Y + roi.Height);
+            UpdateHandlePosition(BottomRightHandle, roi.X + roi.Width, roi.Y + roi.Height);
 
-                // 3. 计算质量评分
-                double qualityScore = FocusAnalysisEngine.CalculateQualityScore(sharpness, checkerboard);
-
-                // 4. 生成建议
-                var suggestions = FocusAnalysisEngine.GenerateSuggestions(
-                    sharpness, checkerboard,
-                    Parameters.SharpnessThreshold,
-                    Parameters.ContrastThreshold);
-
-                // 5. 更新最佳清晰度
-                if (sharpness > _bestSharpness)
-                {
-                    _bestSharpness = sharpness;
-                }
+            // 更新边中点
+            UpdateHandlePosition(TopHandle, roi.X + roi.Width / 2, roi.Y);
+            UpdateHandlePosition(RightHandle, roi.X + roi.Width, roi.Y + roi.Height / 2);
+            UpdateHandlePosition(BottomHandle, roi.X + roi.Width / 2, roi.Y + roi.Height);
+            UpdateHandlePosition(LeftHandle, roi.X, roi.Y + roi.Height / 2);
 
-                // 6. 更新结果
-                Application.Current.Dispatcher.Invoke(() =>
-                {
-                    CurrentResult.Sharpness = sharpness;
-                    CurrentResult.Checkerboard = checkerboard;
-                    CurrentResult.QualityScore = qualityScore;
-                    CurrentResult.FrameCount = _frameCounter + 1;
-                    CurrentResult.BestSharpness = _bestSharpness;
-                    CurrentResult.AnalysisTime = DateTime.Now;
-                    CurrentResult.Suggestions = new ObservableCollection<string>(suggestions);
-
-                    // 更新历史数据
-                    var dataPoint = new DataPoint(_frameCounter, sharpness,
-                        checkerboard.Detected ? checkerboard.Contrast : 0);
-                    _historyData.Add(dataPoint);
-
-                    // 更新图表
-                    UpdatePlot(dataPoint);
-
-                    // 更新状态文本
-                    UpdateStatusText();
-                });
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"图像分析失败: {ex.Message}");
-            }
+            // 更新旋转控制点
+            UpdateRotateHandlePosition(roi);
         }
 
-        private void UpdatePlot(DataPoint dataPoint)
+        private void UpdateHandlePosition(FrameworkElement handle, double x, double y)
         {
-            var series = PlotModel.Series[0] as LineSeries;
-            if (series != null)
-            {
-                series.Points.Add(new OxyPlot.DataPoint(dataPoint.FrameIndex, dataPoint.Sharpness));
+            if (handle == null) return;
 
-                // 限制显示的点数(最多200个点)
-                if (series.Points.Count > 200)
-                {
-                    series.Points.RemoveAt(0);
-                }
-
-                PlotModel.InvalidatePlot(true);
-            }
+            Canvas.SetLeft(handle, x - handle.Width / 2);
+            Canvas.SetTop(handle, y - handle.Height / 2);
         }
 
-        private void UpdateStatusText()
+        private void UpdateRotateHandlePosition(RotatedRect roi)
         {
-            StatusText.Text = $"分析中... 第 {_frameCounter + 1} 帧";
-            FrameCountText.Text = $"帧数: {_frameCounter + 1}";
-            BestSharpnessText.Text = $"最佳: {_bestSharpness:F2}";
-        }
-
-        private void UpdateStatus(string status)
-        {
-            StatusText.Text = status;
-        }
+            if (RotateHandle == null) return;
 
-        // ROI绘制相关
-        private bool _isDrawingRoi = false;
-        private System.Windows.Point _roiStartPoint;
+            // 旋转控制点在矩形顶部中心上方
+            double centerX = roi.X + roi.Width / 2;
+            double centerY = roi.Y + roi.Height / 2;
 
-        private void ImageDisplay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
-        {
-            if (CurrentImage == null) return;
+            // 计算旋转后的位置
+            double angleRad = roi.Angle * Math.PI / 180;
+            double radius = Math.Max(roi.Width, roi.Height) / 2 + 20; // 距离中心20像素
 
-            _roiStartPoint = e.GetPosition(ImageDisplay);
-            _isDrawingRoi = true;
+            double x = centerX + radius * Math.Sin(angleRad);
+            double y = centerY - radius * Math.Cos(angleRad);
 
-            RoiRectangle.Visibility = Visibility.Visible;
-            Canvas.SetLeft(RoiRectangle, _roiStartPoint.X);
-            Canvas.SetTop(RoiRectangle, _roiStartPoint.Y);
-            RoiRectangle.Width = 0;
-            RoiRectangle.Height = 0;
+            // 设置位置
+            RotateHandle.RenderTransform = new RotateTransform(roi.Angle);
+            Canvas.SetLeft(RotateHandle, x - 5); // 5是三角形宽度的一半
+            Canvas.SetTop(RotateHandle, y - 5); // 5是三角形高度的一半
         }
 
-        private void ImageDisplay_MouseMove(object sender, MouseEventArgs e)
-        {
-            if (!_isDrawingRoi) return;
+        #endregion
 
-            var currentPoint = e.GetPosition(ImageDisplay);
+        #region 依赖属性
 
-            double left = Math.Min(_roiStartPoint.X, currentPoint.X);
-            double top = Math.Min(_roiStartPoint.Y, currentPoint.Y);
-            double width = Math.Abs(currentPoint.X - _roiStartPoint.X);
-            double height = Math.Abs(currentPoint.Y - _roiStartPoint.Y);
+        // CurrentImage依赖属性
+        public static readonly DependencyProperty CurrentImageProperty =
+            DependencyProperty.Register("CurrentImage", typeof(ImageSource), typeof(FocusAnalyzerControl),
+                new PropertyMetadata(null, OnCurrentImageChanged));
 
-            Canvas.SetLeft(RoiRectangle, left);
-            Canvas.SetTop(RoiRectangle, top);
-            RoiRectangle.Width = width;
-            RoiRectangle.Height = height;
+        public ImageSource CurrentImage
+        {
+            get => (ImageSource)GetValue(CurrentImageProperty);
+            set => SetValue(CurrentImageProperty, value);
         }
 
-        private void ImageDisplay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+        private static void OnCurrentImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
-            if (!_isDrawingRoi) return;
+            var control = d as FocusAnalyzerControl;
+            if (control.ViewModel != null)
+                control.ViewModel.CurrentImage = e.NewValue as ImageSource;
+        }
 
-            _isDrawingRoi = false;
-            var endPoint = e.GetPosition(ImageDisplay);
+        // Parameters依赖属性
+        public static readonly DependencyProperty ParametersProperty =
+            DependencyProperty.Register("Parameters", typeof(AnalysisParameters), typeof(FocusAnalyzerControl),
+                new PropertyMetadata(new AnalysisParameters(), OnParametersChanged));
 
-            // 转换为图像坐标
-            if (CurrentImage is BitmapSource bitmapSource)
-            {
-                double scaleX = bitmapSource.PixelWidth / ImageDisplay.ActualWidth;
-                double scaleY = bitmapSource.PixelHeight / ImageDisplay.ActualHeight;
+        public AnalysisParameters Parameters
+        {
+            get => (AnalysisParameters)GetValue(ParametersProperty);
+            set => SetValue(ParametersProperty, value);
+        }
 
-                int x = (int)(Math.Min(_roiStartPoint.X, endPoint.X) * scaleX);
-                int y = (int)(Math.Min(_roiStartPoint.Y, endPoint.Y) * scaleY);
-                int width = (int)(Math.Abs(endPoint.X - _roiStartPoint.X) * scaleX);
-                int height = (int)(Math.Abs(endPoint.Y - _roiStartPoint.Y) * scaleY);
+        private static void OnParametersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var control = d as FocusAnalyzerControl;
+            if (control.ViewModel != null)
+                control.ViewModel.Parameters = e.NewValue as AnalysisParameters;
+        }
 
-                // 确保ROI在图像范围内
-                x = Math.Max(0, Math.Min(x, bitmapSource.PixelWidth - 1));
-                y = Math.Max(0, Math.Min(y, bitmapSource.PixelHeight - 1));
-                width = Math.Max(1, Math.Min(width, bitmapSource.PixelWidth - x));
-                height = Math.Max(1, Math.Min(height, bitmapSource.PixelHeight - y));
+        // CurrentResult依赖属性
+        public static readonly DependencyProperty CurrentResultProperty =
+            DependencyProperty.Register("CurrentResult", typeof(AnalysisResult), typeof(FocusAnalyzerControl),
+                new PropertyMetadata(new AnalysisResult(), OnCurrentResultChanged));
 
-                CurrentRoi = new Rect(x, y, width, height);
-            }
+        public AnalysisResult CurrentResult
+        {
+            get => (AnalysisResult)GetValue(CurrentResultProperty);
+            set => SetValue(CurrentResultProperty, value);
         }
 
-        private void ImageDisplay_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
+        private static void OnCurrentResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
-            // 清除ROI
-            CurrentRoi = null;
-            RoiRectangle.Visibility = Visibility.Collapsed;
+            var control = d as FocusAnalyzerControl;
+            if (control.ViewModel != null)
+                control.ViewModel.CurrentResult = e.NewValue as AnalysisResult;
         }
 
-        // INotifyPropertyChanged 实现
-        public event PropertyChangedEventHandler PropertyChanged;
+        // StartAnalysisCommand依赖属性
+        public static readonly DependencyProperty StartAnalysisCommandProperty =
+            DependencyProperty.Register("StartAnalysisCommand", typeof(ICommand), typeof(FocusAnalyzerControl),
+                new PropertyMetadata(null, OnStartAnalysisCommandChanged));
 
-        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+        public ICommand StartAnalysisCommand
         {
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+            get => (ICommand)GetValue(StartAnalysisCommandProperty);
+            set => SetValue(StartAnalysisCommandProperty, value);
         }
 
-        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+        private static void OnStartAnalysisCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
-            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
-            field = value;
-            OnPropertyChanged(propertyName);
-            return true;
+            var control = d as FocusAnalyzerControl;
+            if (control.ViewModel != null)
+                control.ViewModel.StartAnalysisCommand = e.NewValue as ICommand;
         }
 
-        // 事件
-        public event EventHandler AnalysisCompleted;
+        // 其他命令和属性的依赖属性...
 
-        protected virtual void OnAnalysisCompleted()
-        {
-            AnalysisCompleted?.Invoke(this, EventArgs.Empty);
-        }
-    }
+        #endregion
 
-    /// <summary>
-    /// RelayCommand实现
-    /// </summary>
-    public class RelayCommand : ICommand
-    {
-        private readonly Action _execute;
-        private readonly Func<bool> _canExecute;
+        #region 公共方法
 
-        public event EventHandler CanExecuteChanged
+        /// <summary>
+        /// 处理图像帧
+        /// </summary>
+        public async Task ProcessImageFrameAsync(OpenCvSharp.Mat frame)
         {
-            add => CommandManager.RequerySuggested += value;
-            remove => CommandManager.RequerySuggested -= value;
+            if (ViewModel != null)
+                await ViewModel.ProcessImageFrameAsync(frame);
         }
 
-        public RelayCommand(Action execute, Func<bool> canExecute = null)
+        /// <summary>
+        /// 执行单次分析
+        /// </summary>
+        public void PerformAnalysis(OpenCvSharp.Mat frame)
         {
-            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
-            _canExecute = canExecute;
+            ViewModel?.PerformAnalysis(frame);
         }
 
-        public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
-
-        public void Execute(object parameter) => _execute();
-    }
-
-    /// <summary>
-    /// 布尔值到文本转换器
-    /// </summary>
-    public class BoolToTextConverter : System.Windows.Data.IValueConverter
-    {
-        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        /// <summary>
+        /// 获取当前ROI区域
+        /// </summary>
+        public OpenCvSharp.Rect? GetCurrentRoiRect()
         {
-            if (value is bool boolValue && parameter is string param)
-            {
-                var options = param.Split('|');
-                return boolValue ? options[0] : options[1];
-            }
-            return string.Empty;
+            if (ViewModel?.CurrentRoi == null)
+                return null;
+
+            return new OpenCvSharp.Rect(
+                (int)ViewModel.CurrentRoi.X,
+                (int)ViewModel.CurrentRoi.Y,
+                (int)ViewModel.CurrentRoi.Width,
+                (int)ViewModel.CurrentRoi.Height);
         }
 
-        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        /// <summary>
+        /// 获取旋转ROI
+        /// </summary>
+        public OpenCvSharp.RotatedRect? GetCurrentRotatedRoi()
         {
-            throw new NotImplementedException();
+            if (ViewModel?.CurrentRoi == null)
+                return null;
+
+            return ViewModel.CurrentRoi.ToCvRotatedRect();
         }
+
+        #endregion
     }
-}
+}

+ 611 - 0
TeamAAS-VM/Controls/FocusAnalyzerViewModel.cs

@@ -0,0 +1,611 @@
+using OpenCvSharp;
+using OpenCvSharp.WpfExtensions;
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using Prism.Commands;
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using System.Windows.Media;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Models;
+
+namespace TeamAAS_VP.Controls
+{
+    /// <summary>
+    /// FocusAnalyzer用户控件ViewModel
+    /// </summary>
+    public class FocusAnalyzerViewModel : BindableBase
+    {
+        #region 可绑定属性
+
+        // 图像源
+        private ImageSource _currentImage;
+        public ImageSource CurrentImage
+        {
+            get => _currentImage;
+            set => SetProperty(ref _currentImage, value);
+        }
+
+        // 分析参数
+        private AnalysisParameters _parameters = new AnalysisParameters();
+        public AnalysisParameters Parameters
+        {
+            get => _parameters;
+            set => SetProperty(ref _parameters, value);
+        }
+
+        // 当前结果
+        private AnalysisResult _currentResult = new AnalysisResult();
+        public AnalysisResult CurrentResult
+        {
+            get => _currentResult;
+            set => SetProperty(ref _currentResult, value);
+        }
+
+        // 最终报告
+        private AnalysisReport _finalReport = new AnalysisReport();
+        public AnalysisReport FinalReport
+        {
+            get => _finalReport;
+            set => SetProperty(ref _finalReport, value);
+        }
+
+        // 分析状态
+        private bool _isAnalyzing;
+        public bool IsAnalyzing
+        {
+            get => _isAnalyzing;
+            set => SetProperty(ref _isAnalyzing, value);
+        }
+
+        // ROI区域
+        private RotatedRect _currentRoi;
+        public RotatedRect CurrentRoi
+        {
+            get => _currentRoi;
+            set => SetProperty(ref _currentRoi, value);
+        }
+
+        // 是否显示ROI
+        private bool _showRoi = true;
+        public bool ShowRoi
+        {
+            get => _showRoi;
+            set => SetProperty(ref _showRoi, value);
+        }
+
+        // ROI操作模式
+        private RoiOperationMode _roiMode = RoiOperationMode.Draw;
+        public RoiOperationMode RoiMode
+        {
+            get => _roiMode;
+            set => SetProperty(ref _roiMode, value);
+        }
+
+        // 图表模型
+        private PlotModel _plotModel;
+        public PlotModel PlotModel
+        {
+            get => _plotModel;
+            set => SetProperty(ref _plotModel, value);
+        }
+
+        // 状态文本
+        private string _statusText = "就绪";
+        public string StatusText
+        {
+            get => _statusText;
+            set => SetProperty(ref _statusText, value);
+        }
+
+        // 帧数文本
+        private string _frameCountText = "帧数: 0";
+        public string FrameCountText
+        {
+            get => _frameCountText;
+            set => SetProperty(ref _frameCountText, value);
+        }
+
+        // 最佳清晰度文本
+        private string _bestSharpnessText = "最佳: 0.00";
+        public string BestSharpnessText
+        {
+            get => _bestSharpnessText;
+            set => SetProperty(ref _bestSharpnessText, value);
+        }
+
+        // 枚举列表
+        public Array FocusMethods => Enum.GetValues(typeof(FocusMethod));
+        public Array AnalysisModes => Enum.GetValues(typeof(AnalysisMode));
+
+        #endregion
+
+        #region 命令
+
+        // 控制命令(绑定到外部)
+        public ICommand StartAnalysisCommand { get; set; }
+        public ICommand StopAnalysisCommand { get; set; }
+        public ICommand ResetCommand { get; set; }
+        public ICommand ExportCommand { get; set; }
+
+        // 内部命令
+        public DelegateCommand<object> SetRoiModeCommand { get; private set; }
+        public DelegateCommand ClearRoiCommand { get; private set; }
+
+        #endregion
+
+        #region 事件
+
+        // 控件事件(供外部订阅)
+        public event EventHandler AnalysisStarted;
+        public event EventHandler AnalysisStopped;
+        public event EventHandler RoiChanged;
+        public event EventHandler<ErrorEventArgs> ErrorOccurred;
+
+        #endregion
+
+        #region 私有字段
+
+        private int _frameCounter = 0;
+        private double _bestSharpness = 0;
+        private DateTime _startTime;
+        private ObservableCollection<Models.DataPoint> _historyData = new ObservableCollection<Models.DataPoint>();
+
+        // ROI绘制状态
+        private System.Windows.Point _roiStartPoint;
+        private System.Windows.Point _roiDragStartPoint;
+        private RotatedRect _roiBeforeDrag;
+        private DragHandleType _currentDragHandle = DragHandleType.None;
+
+        #endregion
+
+        #region 构造函数
+
+        public FocusAnalyzerViewModel()
+        {
+            InitializeCommands();
+            InitializeProperties();
+            InitializePlot();
+        }
+
+        #endregion
+
+        #region 初始化
+
+        private void InitializeCommands()
+        {
+            // ROI模式切换命令
+            SetRoiModeCommand = new DelegateCommand<object>(mode =>
+            {
+                RoiMode = (RoiOperationMode)mode;
+            });
+
+            // 清除ROI命令
+            ClearRoiCommand = new DelegateCommand(() =>
+            {
+                CurrentRoi = null;
+                OnRoiChanged();
+            });
+        }
+
+        private void InitializeProperties()
+        {
+            Parameters = new AnalysisParameters();
+            CurrentResult = new AnalysisResult();
+            FinalReport = new AnalysisReport();
+
+            // 初始化默认ROI
+            CurrentRoi = new RotatedRect(100, 100, 200, 200, 0);
+        }
+
+        private void InitializePlot()
+        {
+            PlotModel = new PlotModel
+            {
+                Title = "清晰度变化曲线",
+                TitleFontSize = 12,
+                PlotMargins = new OxyThickness(40, 10, 10, 40)
+            };
+
+            PlotModel.Axes.Add(new LinearAxis
+            {
+                Position = AxisPosition.Bottom,
+                Title = "帧序列",
+                MajorGridlineStyle = LineStyle.Solid,
+                MinorGridlineStyle = LineStyle.Dot
+            });
+
+            PlotModel.Axes.Add(new LinearAxis
+            {
+                Position = AxisPosition.Left,
+                Title = "清晰度",
+                MajorGridlineStyle = LineStyle.Solid,
+                MinorGridlineStyle = LineStyle.Dot
+            });
+
+            PlotModel.Series.Add(new LineSeries
+            {
+                Title = "清晰度",
+                Color = OxyColors.DodgerBlue,
+                StrokeThickness = 2
+            });
+        }
+
+        #endregion
+
+        #region 公共方法
+
+        /// <summary>
+        /// 处理图像帧
+        /// </summary>
+        public async Task ProcessImageFrameAsync(Mat frame)
+        {
+            if (!IsAnalyzing || frame == null || frame.Empty())
+                return;
+
+            try
+            {
+                // 更新图像
+                await App.Current.Dispatcher.InvokeAsync(() =>
+                {
+                    CurrentImage = BitmapSourceConverter.ToBitmapSource(frame);
+                });
+
+                // 执行分析
+                await Task.Run(() => PerformAnalysis(frame));
+                _frameCounter++;
+
+                // 更新UI
+                UpdateStatusText();
+            }
+            catch (Exception ex)
+            {
+                OnErrorOccurred(new ErrorEventArgs($"处理图像帧失败: {ex.Message}", ex));
+            }
+        }
+
+        /// <summary>
+        /// 执行单次分析
+        /// </summary>
+        public void PerformAnalysis(Mat frame)
+        {
+            if (frame == null || frame.Empty())
+                return;
+
+            try
+            {
+                // 将RotatedRect转换为Rect(简化处理)
+                Rect? roiRect = null;
+                if (CurrentRoi != null)
+                {
+                    roiRect = new Rect((int)CurrentRoi.X, (int)CurrentRoi.Y, (int)CurrentRoi.Width, (int)CurrentRoi.Height);
+                }
+
+                // 计算清晰度
+                double sharpness = FocusAnalysisEngine.CalculateImageSharpness(
+                    frame, Parameters.FocusMethod, roiRect);
+
+                // 分析棋盘格
+                CheckerboardResult checkerboard;
+                if (Parameters.AnalysisMode == AnalysisMode.AutoCheckerboard)
+                {
+                    checkerboard = FocusAnalysisEngine.AnalyzeCheckerboardContrast(frame, roiRect);
+                }
+                else
+                {
+                    checkerboard = new CheckerboardResult();
+                }
+
+                // 更新结果
+                App.Current.Dispatcher.Invoke(() =>
+                {
+                    UpdateAnalysisResult(sharpness, checkerboard);
+                    UpdateHistoryData(sharpness, checkerboard);
+                    UpdatePlot();
+                });
+            }
+            catch (Exception ex)
+            {
+                OnErrorOccurred(new ErrorEventArgs($"图像分析失败: {ex.Message}", ex));
+            }
+        }
+
+        #endregion
+
+        #region ROI绘制方法
+
+        /// <summary>
+        /// 开始ROI绘制
+        /// </summary>
+        public void StartRoiDrawing(System.Windows.Point startPoint)
+        {
+            if (RoiMode != RoiOperationMode.Draw)
+                return;
+
+            _roiStartPoint = startPoint;
+            CurrentRoi = new RotatedRect(startPoint.X, startPoint.Y, 0, 0, 0);
+        }
+
+        /// <summary>
+        /// 更新ROI绘制
+        /// </summary>
+        public void UpdateRoiDrawing(System.Windows.Point currentPoint)
+        {
+            if (RoiMode != RoiOperationMode.Draw || CurrentRoi == null)
+                return;
+
+            double x = Math.Min(_roiStartPoint.X, currentPoint.X);
+            double y = Math.Min(_roiStartPoint.Y, currentPoint.Y);
+            double width = Math.Abs(currentPoint.X - _roiStartPoint.X);
+            double height = Math.Abs(currentPoint.Y - _roiStartPoint.Y);
+
+            CurrentRoi.X = x;
+            CurrentRoi.Y = y;
+            CurrentRoi.Width = width;
+            CurrentRoi.Height = height;
+        }
+
+        /// <summary>
+        /// 完成ROI绘制
+        /// </summary>
+        public void FinishRoiDrawing()
+        {
+            OnRoiChanged();
+        }
+
+        /// <summary>
+        /// 开始ROI拖动
+        /// </summary>
+        public bool StartRoiDrag(System.Windows.Point point)
+        {
+            if (CurrentRoi == null || !CurrentRoi.ContainsPoint(point))
+                return false;
+
+            // 检查是否点击了控制点
+            _currentDragHandle = GetDragHandleAtPoint(point);
+
+            if (_currentDragHandle != DragHandleType.None)
+            {
+                _roiDragStartPoint = point;
+                _roiBeforeDrag = new RotatedRect(
+                    CurrentRoi.X, CurrentRoi.Y,
+                    CurrentRoi.Width, CurrentRoi.Height,
+                    CurrentRoi.Angle);
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// 更新ROI拖动
+        /// </summary>
+        public void UpdateRoiDrag(System.Windows.Point currentPoint)
+        {
+            if (CurrentRoi == null || _roiBeforeDrag == null)
+                return;
+
+            double deltaX = currentPoint.X - _roiDragStartPoint.X;
+            double deltaY = currentPoint.Y - _roiDragStartPoint.Y;
+
+            switch (_currentDragHandle)
+            {
+                case DragHandleType.TopLeft:
+                    CurrentRoi.X = _roiBeforeDrag.X + deltaX;
+                    CurrentRoi.Y = _roiBeforeDrag.Y + deltaY;
+                    CurrentRoi.Width = _roiBeforeDrag.Width - deltaX;
+                    CurrentRoi.Height = _roiBeforeDrag.Height - deltaY;
+                    break;
+
+                case DragHandleType.TopRight:
+                    CurrentRoi.Y = _roiBeforeDrag.Y + deltaY;
+                    CurrentRoi.Width = _roiBeforeDrag.Width + deltaX;
+                    CurrentRoi.Height = _roiBeforeDrag.Height - deltaY;
+                    break;
+
+                case DragHandleType.BottomLeft:
+                    CurrentRoi.X = _roiBeforeDrag.X + deltaX;
+                    CurrentRoi.Width = _roiBeforeDrag.Width - deltaX;
+                    CurrentRoi.Height = _roiBeforeDrag.Height + deltaY;
+                    break;
+
+                case DragHandleType.BottomRight:
+                    CurrentRoi.Width = _roiBeforeDrag.Width + deltaX;
+                    CurrentRoi.Height = _roiBeforeDrag.Height + deltaY;
+                    break;
+
+                case DragHandleType.Rotate:
+                    // 计算旋转角度
+                    var center = new System.Windows.Point(
+                        _roiBeforeDrag.CenterX,
+                        _roiBeforeDrag.CenterY);
+                    double startAngle = Math.Atan2(
+                        _roiDragStartPoint.Y - center.Y,
+                        _roiDragStartPoint.X - center.X);
+                    double currentAngle = Math.Atan2(
+                        currentPoint.Y - center.Y,
+                        currentPoint.X - center.X);
+                    double angleDelta = (currentAngle - startAngle) * 180 / Math.PI;
+                    CurrentRoi.Angle = (_roiBeforeDrag.Angle + angleDelta) % 360;
+                    break;
+
+                case DragHandleType.None:
+                    // 移动整个ROI
+                    CurrentRoi.X = _roiBeforeDrag.X + deltaX;
+                    CurrentRoi.Y = _roiBeforeDrag.Y + deltaY;
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// 结束ROI拖动
+        /// </summary>
+        public void FinishRoiDrag()
+        {
+            _currentDragHandle = DragHandleType.None;
+            _roiBeforeDrag = null;
+            OnRoiChanged();
+        }
+
+        /// <summary>
+        /// 获取指定点的控制点类型
+        /// </summary>
+        private DragHandleType GetDragHandleAtPoint(System.Windows.Point point)
+        {
+            if (CurrentRoi == null) return DragHandleType.None;
+
+            const double handleSize = 8;
+            var center = new System.Windows.Point(CurrentRoi.CenterX, CurrentRoi.CenterY);
+
+            // 检查旋转控制点(在矩形顶部中心上方)
+            var rotateHandle = new System.Windows.Point(center.X, CurrentRoi.Y - 20);
+            if (Distance(point, rotateHandle) <= handleSize)
+                return DragHandleType.Rotate;
+
+            // 检查角点
+            var corners = new[]
+            {
+                new System.Windows.Point(CurrentRoi.X, CurrentRoi.Y), // 左上
+                new System.Windows.Point(CurrentRoi.X + CurrentRoi.Width, CurrentRoi.Y), // 右上
+                new System.Windows.Point(CurrentRoi.X, CurrentRoi.Y + CurrentRoi.Height), // 左下
+                new System.Windows.Point(CurrentRoi.X + CurrentRoi.Width, CurrentRoi.Y + CurrentRoi.Height) // 右下
+            };
+
+            // 替换 switch 表达式为 switch 语句以兼容 C# 7.3
+            for (int i = 0; i < corners.Length; i++)
+            {
+                if (Distance(point, corners[i]) <= handleSize)
+                {
+                    switch (i)
+                    {
+                        case 0:
+                            return DragHandleType.TopLeft;
+                        case 1:
+                            return DragHandleType.TopRight;
+                        case 2:
+                            return DragHandleType.BottomLeft;
+                        case 3:
+                            return DragHandleType.BottomRight;
+                        default:
+                            return DragHandleType.None;
+                    }
+                }
+            }
+
+            return DragHandleType.None;
+        }
+
+        private double Distance(System.Windows.Point p1, System.Windows.Point p2)
+        {
+            return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
+        }
+
+        #endregion
+
+        #region 私有方法
+
+        private void UpdateAnalysisResult(double sharpness, CheckerboardResult checkerboard)
+        {
+            double qualityScore = FocusAnalysisEngine.CalculateQualityScore(sharpness, checkerboard);
+            var suggestions = FocusAnalysisEngine.GenerateSuggestions(
+                sharpness, checkerboard,
+                Parameters.SharpnessThreshold,
+                Parameters.ContrastThreshold);
+
+            if (sharpness > _bestSharpness)
+                _bestSharpness = sharpness;
+
+            CurrentResult.Sharpness = sharpness;
+            CurrentResult.Checkerboard = checkerboard;
+            CurrentResult.QualityScore = qualityScore;
+            CurrentResult.FrameCount = _frameCounter + 1;
+            CurrentResult.BestSharpness = _bestSharpness;
+            CurrentResult.AnalysisTime = DateTime.Now;
+            CurrentResult.Suggestions = new ObservableCollection<string>(suggestions);
+        }
+
+        private void UpdateHistoryData(double sharpness, CheckerboardResult checkerboard)
+        {
+            var dataPoint = new Models.DataPoint(_frameCounter, sharpness,
+                checkerboard.Detected ? checkerboard.Contrast : 0);
+            _historyData.Add(dataPoint);
+
+            // 添加到最终报告
+            FinalReport.HistoryData.Add(dataPoint);
+        }
+
+        private void UpdatePlot()
+        {
+            if (PlotModel.Series[0] is LineSeries series)
+            {
+                series.Points.Clear();
+
+                for (int i = 0; i < _historyData.Count; i++)
+                {
+                    series.Points.Add(new OxyPlot.DataPoint(i, _historyData[i].Sharpness));
+                }
+
+                // 限制显示点数
+                if (series.Points.Count > 200)
+                {
+                    series.Points.RemoveRange(0, series.Points.Count - 200);
+                }
+
+                PlotModel.InvalidatePlot(true);
+            }
+        }
+
+        private void UpdateStatusText()
+        {
+            StatusText = $"分析中... 第 {_frameCounter + 1} 帧";
+            FrameCountText = $"帧数: {_frameCounter + 1}";
+            BestSharpnessText = $"最佳: {_bestSharpness:F2}";
+        }
+
+        #endregion
+
+        #region 事件触发方法
+
+        protected virtual void OnAnalysisStarted()
+        {
+            AnalysisStarted?.Invoke(this, EventArgs.Empty);
+        }
+
+        protected virtual void OnAnalysisStopped()
+        {
+            AnalysisStopped?.Invoke(this, EventArgs.Empty);
+        }
+
+        protected virtual void OnRoiChanged()
+        {
+            RoiChanged?.Invoke(this, EventArgs.Empty);
+        }
+
+        protected virtual void OnErrorOccurred(ErrorEventArgs e)
+        {
+            ErrorOccurred?.Invoke(this, e);
+        }
+
+        #endregion
+    }
+
+    public class ErrorEventArgs : EventArgs
+    {
+        public string Message { get; }
+        public Exception Exception { get; }
+
+        public ErrorEventArgs(string message, Exception exception = null)
+        {
+            Message = message;
+            Exception = exception;
+        }
+    }
+}

+ 143 - 73
TeamAAS-VM/Controls/RobotManual.xaml

@@ -96,111 +96,181 @@
                     <RowDefinition Height="auto" />
                     <RowDefinition Height="auto" />
                 </Grid.RowDefinitions>
-                <ComboBox Grid.Column="0"
+                <Grid.Resources>
+                    <Style TargetType="Button"
+                           BasedOn="{StaticResource MaterialDesignRaisedDarkButton}">
+                        <Setter Property="Width"
+                                Value="60" />
+                        <Setter Property="Height"
+                                Value="60" />
+                        <Setter Property="FontSize"
+                                Value="13" />
+                        <Setter Property="Padding"
+                                Value="0" />
+                        <Setter Property="HorizontalAlignment"
+                                Value="Center" />
+                        <Setter Property="VerticalAlignment"
+                                Value="Center" />
+                        <Setter Property="Margin"
+                                Value="2" />
+                        <Setter Property="materialDesign:ButtonAssist.CornerRadius"
+                                Value="10" />
+                    </Style>
+                </Grid.Resources>
+                <ComboBox Grid.ColumnSpan="3"
                           Grid.Row="0"
                           Margin="10,5"
+                          HorizontalAlignment="Left"
                           materialDesign:HintAssist.Hint="{lex:Loc 选择Tool}"
                           materialDesign:HintAssist.HelperText="{lex:Loc 选择Tool}"
                           SelectedItem="{Binding SelectTool,Mode=TwoWay}"
                           ItemsSource="{Binding Tools}" />
-                <Button Content="X-"
-                        Grid.Row="2"
+                <Button Grid.Row="2"
                         Grid.Column="0"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X-}" />
-                <Button Content="X+"
-                        Grid.Row="2"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X-}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="ArrowLeftBold"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="-X"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="2"
                         Grid.Column="2"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X+}" />
-                <Button Content="Y+"
-                        Grid.Row="1"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X+}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="ArrowRightBold"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="+X"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="1"
                         Grid.Column="1"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y+}" />
-                <Button Content="Y-"
-                        Grid.Row="3"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y+}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="ArrowBottomBold"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="+Y"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="3"
                         Grid.Column="1"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y-}" />
-                <Button Content="Z+"
-                        Grid.Row="1"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y-}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="ArrowBottomBold"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="-Y"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="1"
                         Grid.Column="3"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z+}" />
-                <Button Content="Z-"
-                        Grid.Row="3"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z+}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="ArrowTopBold"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="+Z"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="3"
                         Grid.Column="3"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z-}" />
-                <Button Content="U+"
-                        Grid.Row="4"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z-}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="ArrowBottomBold"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="-Z"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="4"
                         Grid.Column="0"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U+}" />
-                <Button Content="U-"
-                        Grid.Row="5"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U+}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="AxisZRotateCounterclockwise"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="+U"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="5"
                         Grid.Column="0"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U-}" />
-                <Button Content="V+"
-                        Grid.Row="4"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U-}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="AxisZRotateClockwise"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="-U"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="4"
                         Grid.Column="1"
                         Grid.ColumnSpan="2"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V+}" />
-                <Button Content="V-"
-                        Grid.Row="5"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V+}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="AxisYRotateCounterclockwise"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="+V"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="5"
                         Grid.Column="1"
                         Grid.ColumnSpan="2"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V-}" />
-                <Button Content="W+"
-                        Grid.Row="4"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V-}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="AxisYRotateClockwise"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="-V"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="4"
                         Grid.Column="3"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W+}" />
-                <Button Content="W-"
-                        Grid.Row="5"
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W+}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="AxisXRotateCounterclockwise"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="+W"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
+                <Button Grid.Row="5"
                         Grid.Column="3"
-                        Margin="10,10,10,10"
-                        materialDesign:ButtonAssist.CornerRadius="10"
-                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                         Command="{Binding JogCommand}"
-                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W-}" />
+                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W-}">
+                    <StackPanel Orientation="Vertical">
+                        <materialDesign:PackIcon Kind="AxisXRotateClockwise"
+                                                 Width="26"
+                                                 Height="26" />
+                        <TextBlock Text="-W"
+                                   HorizontalAlignment="Center" />
+                    </StackPanel>
+                </Button>
                 <StackPanel Grid.Row="6"
                             Grid.ColumnSpan="7"
                             Margin="10,10,0,10"

+ 5 - 2
TeamAAS-VM/Controls/RobotManual.xaml.cs

@@ -40,7 +40,6 @@ namespace TeamAAS_VP.Controls
 
         public RobotManual()
         {
-            _configService=((PrismApplication)(App.Current)).Container.Resolve<IConfigService>();
             InitializeComponent();
             DataContext = this;
             this.Loaded += RobotManual_Loaded;
@@ -48,7 +47,7 @@ namespace TeamAAS_VP.Controls
 
         private void RobotManual_Loaded(object sender, RoutedEventArgs e)
         {
-            _configService = ((PrismApplication)(App.Current)).Container.Resolve<IConfigService>();
+            
         }
 
         #region 属性
@@ -62,6 +61,8 @@ namespace TeamAAS_VP.Controls
                 {
                     if (value!=null)
                     {
+                        if (_configService==null)
+                            _configService = ((PrismApplication)(App.Current)).Container.Resolve<IConfigService>();
                         this.Distance = _configService.GetRobotStepDistance(value.Id);
                     }
                     if (value is FanucRobot)
@@ -94,6 +95,8 @@ namespace TeamAAS_VP.Controls
                     {
                         return;
                     }
+                    if (_configService == null)
+                        _configService = ((PrismApplication)(App.Current)).Container.Resolve<IConfigService>();
                     _configService.UpdateRobotStepDistance(Robot.Id, value);
                 }
                 catch (Exception)

+ 129 - 0
TeamAAS-VM/Controls/RotatedRect.cs

@@ -0,0 +1,129 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Controls
+{
+    /// <summary>
+    /// 支持旋转的矩形
+    /// </summary>
+    public class RotatedRect : BindableBase
+    {
+        private double _x;
+        private double _y;
+        private double _width;
+        private double _height;
+        private double _angle; // 旋转角度,单位:度
+
+        public double X
+        {
+            get => _x;
+            set => SetProperty(ref _x, value);
+        }
+
+        public double Y
+        {
+            get => _y;
+            set => SetProperty(ref _y, value);
+        }
+
+        public double Width
+        {
+            get => _width;
+            set => SetProperty(ref _width, Math.Max(0, value));
+        }
+
+        public double Height
+        {
+            get => _height;
+            set => SetProperty(ref _height, Math.Max(0, value));
+        }
+
+        public double Angle
+        {
+            get => _angle;
+            set => SetProperty(ref _angle, NormalizeAngle(value));
+        }
+
+        public double CenterX => X + Width / 2;
+        public double CenterY => Y + Height / 2;
+
+        public RotatedRect()
+        {
+        }
+
+        public RotatedRect(double x, double y, double width, double height, double angle = 0)
+        {
+            X = x;
+            Y = y;
+            Width = width;
+            Height = height;
+            Angle = angle;
+        }
+
+        private double NormalizeAngle(double angle)
+        {
+            // 将角度规范到0-360度
+            angle %= 360;
+            if (angle < 0) angle += 360;
+            return angle;
+        }
+
+        public System.Windows.Rect ToRect()
+        {
+            return new System.Windows.Rect(X, Y, Width, Height);
+        }
+
+        public OpenCvSharp.RotatedRect ToCvRotatedRect()
+        {
+            var center = new OpenCvSharp.Point2f((float)CenterX, (float)CenterY);
+            var size = new OpenCvSharp.Size2f((float)Width, (float)Height);
+            return new OpenCvSharp.RotatedRect(center, size, (float)Angle);
+        }
+
+        public bool ContainsPoint(System.Windows.Point point)
+        {
+            // 简化版:不考虑旋转的包含检测
+            return point.X >= X && point.X <= X + Width &&
+                   point.Y >= Y && point.Y <= Y + Height;
+        }
+
+        public override string ToString()
+        {
+            return $"X={X:F1}, Y={Y:F1}, W={Width:F1}, H={Height:F1}, Angle={Angle:F1}°";
+        }
+    }
+
+    /// <summary>
+    /// ROI操作模式
+    /// </summary>
+    public enum RoiOperationMode
+    {
+        None,           // 无操作
+        Draw,           // 绘制
+        Move,           // 移动
+        Resize,         // 调整大小
+        Rotate,         // 旋转
+        CornerResize    // 角点调整
+    }
+
+    /// <summary>
+    /// 拖动控制点类型
+    /// </summary>
+    public enum DragHandleType
+    {
+        None,
+        TopLeft,
+        TopRight,
+        BottomLeft,
+        BottomRight,
+        Top,
+        Right,
+        Bottom,
+        Left,
+        Rotate
+    }
+}

+ 16 - 9
TeamAAS-VM/Data/SystemDatabaseService.cs

@@ -68,6 +68,7 @@ namespace TeamAAS_VP.Data
     public class SystemDatabaseService : ISystemDatabaseService
     {
         private readonly ISqlSugarClient _db;
+        private User CurrentUser { get; set; }
 
         /// <summary>
         /// 使用指定的 SqlSugar 客户端构造数据库服务实例。
@@ -171,6 +172,7 @@ namespace TeamAAS_VP.Data
             {
                 throw new ArgumentException($"User with id {userId} not found", nameof(userId));
             }
+            CurrentUser = user;
 
             // 记录一条成功的登录记录
             var rec = new UserLoginRecord
@@ -199,15 +201,20 @@ namespace TeamAAS_VP.Data
         /// <returns></returns>
         public User GetCurrentUser()
         {
-            var recs = _db.Queryable<UserLoginRecord>()
-                .Where(r => r.Success == true)
-                .OrderBy(r => r.Time, OrderByType.Desc)
-                .Take(1)
-                .ToList();
-            var rec = recs.FirstOrDefault();
-            //如果没有登录记录则返回一个默认的用户,即第一个用户
-            if (rec == null) return _db.Queryable<User>().First();
-            return _db.Queryable<User>().InSingle(rec.UserId);
+            //var recs = _db.Queryable<UserLoginRecord>()
+            //    .Where(r => r.Success == true)
+            //    .OrderBy(r => r.Time, OrderByType.Desc)
+            //    .Take(1)
+            //    .ToList();
+            //var rec = recs.FirstOrDefault();
+            ////如果没有登录记录则返回一个默认的用户,即第一个用户
+            //if (rec == null) return _db.Queryable<User>().First();
+            //return _db.Queryable<User>().InSingle(rec.UserId);
+            if (CurrentUser==null)
+            {
+                CurrentUser= _db.Queryable<User>().ToList().FirstOrDefault();
+            }
+            return CurrentUser;
         }
 
         /// <summary>

+ 4 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -519,6 +519,7 @@
     <Compile Include="Controls\FocusAnalyzerControl.xaml.cs">
       <DependentUpon>FocusAnalyzerControl.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Controls\FocusAnalyzerViewModel.cs" />
     <Compile Include="Controls\myNumericUpDowm.xaml.cs">
       <DependentUpon>myNumericUpDowm.xaml</DependentUpon>
     </Compile>
@@ -531,6 +532,7 @@
     <Compile Include="Controls\RobotManual.xaml.cs">
       <DependentUpon>RobotManual.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Controls\RotatedRect.cs" />
     <Compile Include="Controls\ScriptEditControl.xaml.cs">
       <DependentUpon>ScriptEditControl.xaml</DependentUpon>
     </Compile>
@@ -732,9 +734,11 @@
     <Compile Include="ValidationRules\TextValidationRule.cs" />
     <Compile Include="ValidationRules\UsernameValidationRule.cs" />
     <Compile Include="ValueConverter\BooleanToVisibilityConverter.cs" />
+    <Compile Include="ValueConverter\BoolToTextConverter.cs" />
     <Compile Include="ValueConverter\CameraMountConverter.cs" />
     <Compile Include="ValueConverter\EnumDescriptionTypeConverter.cs" />
     <Compile Include="ValueConverter\EnumLocalizationConverter.cs" />
+    <Compile Include="ValueConverter\EnumToBooleanConverter.cs" />
     <Compile Include="ValueConverter\InvertBooleanConverter.cs" />
     <Compile Include="ValueConverter\InvertBooleanToVisibilityConverter.cs" />
     <Compile Include="ValueConverter\JogConverter.cs" />

+ 29 - 0
TeamAAS-VM/ValueConverter/BoolToTextConverter.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.ValueConverter
+{
+    /// <summary>
+    /// 布尔值到文本转换器
+    /// </summary>
+    public class BoolToTextConverter : System.Windows.Data.IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            if (value is bool boolValue && parameter is string param)
+            {
+                var options = param.Split('|');
+                return boolValue ? options[0] : options[1];
+            }
+            return string.Empty;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 1 - 1
TeamAAS-VM/ValueConverter/BooleanToVisibilityConverter.cs

@@ -34,7 +34,7 @@ namespace TeamAAS_VP.ValueConverter
 
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
-            return null;
+            return value is Visibility visibility && visibility == Visibility.Visible;
         }
     }
 }

+ 26 - 0
TeamAAS-VM/ValueConverter/EnumToBooleanConverter.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace TeamAAS_VP.ValueConverter
+{
+    /// <summary>
+    /// 枚举到布尔值转换器
+    /// </summary>
+    public class EnumToBooleanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value?.ToString() == parameter?.ToString();
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value?.Equals(true) == true ? Enum.Parse(targetType, parameter.ToString()) : Binding.DoNothing;
+        }
+    }
+}

+ 104 - 1
TeamAAS-VM/ViewModels/DebugMod/VisionSolutionViewModel.cs

@@ -1,20 +1,45 @@
 using Prism.Commands;
 using Prism.Ioc;
 using Prism.Mvvm;
+using Prism.Regions;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Windows.Media;
 using Team.FFFeederService;
+using Team.FFFeederService.Interfaces;
+using TeamAAS_VP.Controls;
 using TeamAAS_VP.Core;
 using TeamAAS_VP.Interfaces;
 
 namespace TeamAAS_VP.ViewModels.DebugMod
 {
-    public class VisionSolutionViewModel : BindableBase
+    public class VisionSolutionViewModel : BindableBase, IConfirmNavigationRequest
     {
         IContainerProvider _container;
         ISystemDatabaseService _systemDatabaseService;
 
+        private ImageSource _CurrentImage;
+        public ImageSource CurrentImage
+        {
+            get { return _CurrentImage; }
+            set { SetProperty(ref _CurrentImage, value); }
+        }
+
+        private FocusAnalyzerViewModel _FocusAnalyzerViewModel;
+        public FocusAnalyzerViewModel FocusAnalyzerViewModel
+        {
+            get { return _FocusAnalyzerViewModel; }
+            set { SetProperty(ref _FocusAnalyzerViewModel, value); }
+        }
+
+        //选择图片
+        private DelegateCommand _SelectImageCommand;
+        public DelegateCommand SelectImageCommand =>
+            _SelectImageCommand ?? (_SelectImageCommand = new DelegateCommand(ExecuteSelectImageCommand));
+
+        
+
         private DelegateCommand _LoadedCommand;
         public DelegateCommand LoadedCommand =>
             _LoadedCommand ?? (_LoadedCommand = new DelegateCommand(ExecuteLoadedCommand));
@@ -45,6 +70,84 @@ namespace TeamAAS_VP.ViewModels.DebugMod
 
         }
 
+        /// <summary>
+        /// 选择一张图像
+        /// </summary>
+        void ExecuteSelectImageCommand()
+        {
+            //打开文件对话框,选择一张图片
+            Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
+            openFileDialog.Filter = "Image files (*.png;*.jpg;*.jpeg;*.bmp)|*.png;*.jpg;*.jpeg;*.bmp|All files (*.*)|*.*";
+            openFileDialog.Multiselect = true;
+            bool? result = openFileDialog.ShowDialog();
+            if (result.HasValue)
+            {
+                if (openFileDialog.FileNames.Length > 0)
+                {
+                    //加载第一张图片
+                    string filePath = openFileDialog.FileNames[0];
+                    try
+                    {
+                        System.Windows.Media.Imaging.BitmapImage bitmap = new System.Windows.Media.Imaging.BitmapImage();
+                        bitmap.BeginInit();
+                        bitmap.UriSource = new Uri(filePath, UriKind.Absolute);
+                        bitmap.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
+                        bitmap.EndInit();
+                        FocusAnalyzerViewModel.CurrentImage = bitmap;
+                    }
+                    catch (Exception ex)
+                    {
+                        //显示错误信息
+                        System.Windows.MessageBox.Show("加载图片失败:" + ex.Message);
+                    }
+                }
+            }
+        }
+
+        #region 继承
+        /// <summary>
+        /// 确认导航请求时调用。此方法允许您在导航之前执行一些操作。
+        /// </summary>
+        /// <param name="navigationContext"></param>
+        /// <param name="continuationCallback"></param>
+        public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
+        {
+            continuationCallback(true);
+        }
+
+        /// <summary>
+        /// 接收导航请求时调用。传入导航参数,包含有关导航目标的信息。
+        /// </summary>
+        /// <param name="navigationContext"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        public async void OnNavigatedTo(NavigationContext navigationContext)
+        {
+            if (FocusAnalyzerViewModel==null)
+            {
+                FocusAnalyzerViewModel = new FocusAnalyzerViewModel();
+            }
+        }
+
+        /// <summary>
+        /// 是否允许导航到此视图模型。
+        /// </summary>
+        /// <param name="navigationContext"></param>
+        /// <returns></returns>
+        public bool IsNavigationTarget(NavigationContext navigationContext)
+        {
+            return true;
+        }
+
+        /// <summary>
+        /// 导航离开此视图模型时调用。您可以在此处执行清理操作或保存状态。
+        /// </summary>
+        /// <param name="navigationContext"></param>
+        public void OnNavigatedFrom(NavigationContext navigationContext)
+        {
+
+        }
+        #endregion
+
         private bool _IsAllowEdit = false;
 
         public bool IsAllowEdit

+ 1 - 0
TeamAAS-VM/Views/Calibration/CalibrationCameraParams.xaml

@@ -61,6 +61,7 @@
         <materialDesign:DrawerHost Grid.Row="1"
                                    IsEnabled="{Binding IsAllowEdit}"
                                    OpenMode="Standard"
+                                   d:IsRightDrawerOpen="true"
                                    x:Name="DrawerHost">
             <materialDesign:DrawerHost.RightDrawerContent>
                 <uControl:RobotManual x:Name="robotManual" />

+ 8 - 0
TeamAAS-VM/Views/DebugMod/VisionSolution.xaml

@@ -9,6 +9,7 @@
              xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
              xmlns:sys="clr-namespace:System;assembly=mscorlib"
              xmlns:enums="clr-namespace:TeamAAS_VP.Enums"
+             xmlns:controls="clr-namespace:TeamAAS_VP.Controls"
              xmlns:lex="http://wpflocalizeextension.codeplex.com"
              lex:LocalizeDictionary.DesignCulture="zh-CN"
              lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
@@ -24,10 +25,17 @@
         </b:EventTrigger>
     </b:Interaction.Triggers>
     <Grid IsEnabled="{Binding IsAllowEdit}">
+        
         <Grid.RowDefinitions>
             <RowDefinition Height="auto" />
             <RowDefinition Height="*" />
         </Grid.RowDefinitions>
+        <Button Content="选择图片"
+                Margin="10,5"
+                HorizontalAlignment="Left"
+                Command="{Binding SelectImageCommand}" />
+        <controls:FocusAnalyzerControl Grid.Row="1"
+                                       DataContext="{Binding FocusAnalyzerViewModel}" />
         <!--<Release:VmGlobalToolControl Grid.Row="0"
                                      HorizontalAlignment="Left"
                                      VerticalAlignment="Top" />

+ 2 - 0
TeamAAS-VM/Views/Product/CameraParams.xaml

@@ -73,6 +73,7 @@
         <materialDesign:DrawerHost Grid.Row="1"
                                    OpenMode="Standard"
                                    x:Name="DrawerHost"
+                                   d:IsRightDrawerOpen="true"
                                    IsEnabled="{Binding IsAllowEdit}">
             <materialDesign:DrawerHost.RightDrawerContent>
                 <uControl:RobotManual x:Name="robotManual"
@@ -121,6 +122,7 @@
                             <RowDefinition Height="auto" />
                             <RowDefinition Height="auto" />
                             <RowDefinition Height="auto" />
+                            <RowDefinition Height="auto" />
                         </Grid.RowDefinitions>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="auto" />

+ 142 - 75
TeamAAS-VM/Views/Product/RobotPointParams.xaml

@@ -99,9 +99,9 @@
                                    OpenMode="Modal"
                                    x:Name="DrawerHost"
                                    IsEnabled="{Binding IsAllowEdit}">
-            <materialDesign:DrawerHost.RightDrawerContent>
+            <!--<materialDesign:DrawerHost.RightDrawerContent>
                 <local:TestProductPage Visibility="{Binding IsHaveRobot,Converter={StaticResource BooleanToVisibilityConverter}}" />
-            </materialDesign:DrawerHost.RightDrawerContent>
+            </materialDesign:DrawerHost.RightDrawerContent>-->
             <Grid>
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="auto" />
@@ -502,108 +502,175 @@
                                                     <RowDefinition Height="auto" />
                                                     <RowDefinition Height="auto" />
                                                 </Grid.RowDefinitions>
+                                                <Grid.Resources>
+                                                    <Style TargetType="Button"
+                                                           BasedOn="{StaticResource MaterialDesignRaisedDarkButton}">
+                                                        <Setter Property="Width"
+                                                                Value="60" />
+                                                        <Setter Property="Height"
+                                                                Value="60" />
+                                                        <Setter Property="FontSize"
+                                                                Value="13" />
+                                                        <Setter Property="Padding"
+                                                                Value="0" />
+                                                        <Setter Property="HorizontalAlignment"
+                                                                Value="Center" />
+                                                        <Setter Property="VerticalAlignment"
+                                                                Value="Center" />
+                                                        <Setter Property="Margin"
+                                                                Value="2" />
+                                                        <Setter Property="materialDesign:ButtonAssist.CornerRadius"
+                                                                Value="10" />
+                                                    </Style>
+                                                </Grid.Resources>
 
                                                 <Button x:Name="btn_x"
-                                                        Content="X-"
                                                         Grid.Row="1"
                                                         Grid.Column="0"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X-}" />
-                                                <Button Content="X+"
-                                                        Grid.Row="1"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X-}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="ArrowLeftBold"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="-X"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="1"
                                                         Grid.Column="2"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X+}" />
-                                                <Button Content="Y+"
-                                                        Grid.Row="0"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=X+}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="ArrowRightBold"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="+X"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="0"
                                                         Grid.Column="1"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y+}" />
-                                                <Button Content="Y-"
-                                                        Grid.Row="2"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y+}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="ArrowBottomBold"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="+Y"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="2"
                                                         Grid.Column="1"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y-}" />
-                                                <Button Content="Z+"
-                                                        Grid.Row="0"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Y-}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="ArrowBottomBold"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="-Y"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="0"
                                                         Grid.Column="3"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z+}" />
-                                                <Button Content="Z-"
-                                                        Grid.Row="2"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z+}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="ArrowTopBold"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="+Z"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="2"
                                                         Grid.Column="3"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z-}" />
-                                                <Button Content="U+"
-                                                        Grid.Row="3"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=Z-}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="ArrowBottomBold"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="-Z"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="3"
                                                         Grid.Column="0"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U+}" />
-                                                <Button Content="U-"
-                                                        Grid.Row="4"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U+}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="AxisZRotateCounterclockwise"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="+U"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="4"
                                                         Grid.Column="0"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U-}" />
-                                                <Button Content="V+"
-                                                        Grid.Row="3"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=U-}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="AxisZRotateClockwise"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="-U"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="3"
                                                         Grid.Column="1"
                                                         Grid.ColumnSpan="2"
-                                                        Width="{Binding ElementName=btn_x,Path=Width}"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V+}" />
-                                                <Button Content="V-"
-                                                        Grid.Row="4"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V+}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="AxisYRotateCounterclockwise"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="+V"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="4"
                                                         Grid.Column="1"
                                                         Grid.ColumnSpan="2"
-                                                        Width="{Binding ElementName=btn_x,Path=Width}"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V-}" />
-                                                <Button Content="W+"
-                                                        Grid.Row="3"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=V-}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="AxisYRotateClockwise"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="-V"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="3"
                                                         Grid.Column="3"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W+}" />
-                                                <Button Content="W-"
-                                                        Grid.Row="4"
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W+}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="AxisXRotateCounterclockwise"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="+W"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
+                                                <Button Grid.Row="4"
                                                         Grid.Column="3"
-                                                        Margin="10,10,10,10"
-                                                        materialDesign:ButtonAssist.CornerRadius="10"
-                                                        Style="{StaticResource MaterialDesignRaisedDarkButton}"
                                                         Command="{Binding DataContext.JogCommand,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
-                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W-}" />
+                                                        CommandParameter="{Binding ElementName=txtDistance,Path=Value,Converter={StaticResource JogConverter}, ConverterParameter=W-}">
+                                                    <StackPanel Orientation="Vertical">
+                                                        <materialDesign:PackIcon Kind="AxisXRotateClockwise"
+                                                                                 Width="26"
+                                                                                 Height="26" />
+                                                        <TextBlock Text="-W"
+                                                                   HorizontalAlignment="Center" />
+                                                    </StackPanel>
+                                                </Button>
                                                 <Grid Grid.Row="5"
                                                       Grid.ColumnSpan="7"
                                                       Margin="10">