211 lines
9.1 KiB
C#
211 lines
9.1 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using Microsoft.Win32;
|
|
using Prism.Ioc;
|
|
using XP.ImageProcessing.RoiControl.Controls;
|
|
using XplorePlane.Events;
|
|
using XplorePlane.ViewModels;
|
|
|
|
namespace XplorePlane.Views
|
|
{
|
|
public partial class ViewportPanelView : UserControl
|
|
{
|
|
private MainViewModel _mainVm;
|
|
|
|
private void SetStatus(string msg)
|
|
{
|
|
if (_mainVm == null)
|
|
{
|
|
try { _mainVm = ContainerLocator.Current?.Resolve<MainViewModel>(); } catch { }
|
|
}
|
|
if (_mainVm != null) _mainVm.StatusMessage = msg;
|
|
}
|
|
|
|
public ViewportPanelView()
|
|
{
|
|
InitializeComponent();
|
|
DataContextChanged += OnDataContextChanged;
|
|
|
|
// 动态创建右键菜单,支持条件性阻止弹出
|
|
var menu = new System.Windows.Controls.ContextMenu();
|
|
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "放大" });
|
|
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "缩小" });
|
|
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "适应窗口" });
|
|
menu.Items.Add(new System.Windows.Controls.Separator());
|
|
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "保存原始图像" });
|
|
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "保存结果图像" });
|
|
menu.Items.Add(new System.Windows.Controls.Separator());
|
|
menu.Items.Add(new System.Windows.Controls.MenuItem { Header = "清除所有测量" });
|
|
((System.Windows.Controls.MenuItem)menu.Items[0]).Click += ZoomIn_Click;
|
|
((System.Windows.Controls.MenuItem)menu.Items[1]).Click += ZoomOut_Click;
|
|
((System.Windows.Controls.MenuItem)menu.Items[2]).Click += ResetView_Click;
|
|
((System.Windows.Controls.MenuItem)menu.Items[4]).Click += SaveOriginalImage_Click;
|
|
((System.Windows.Controls.MenuItem)menu.Items[5]).Click += SaveResultImage_Click;
|
|
((System.Windows.Controls.MenuItem)menu.Items[7]).Click += ClearAllMeasurements_Click;
|
|
RoiCanvas.ContextMenu = menu;
|
|
RoiCanvas.ContextMenuOpening += (s, e) =>
|
|
{
|
|
if (RoiCanvas.SuppressContextMenu)
|
|
{
|
|
RoiCanvas.SuppressContextMenu = false;
|
|
e.Handled = true;
|
|
}
|
|
};
|
|
|
|
// 测量事件 → 更新主界面状态栏
|
|
RoiCanvas.MeasureCompleted += (s, e) =>
|
|
{
|
|
if (e is MeasureCompletedEventArgs args)
|
|
{
|
|
string typeLabel = args.MeasureType switch
|
|
{
|
|
"PointToLine" => "点线距",
|
|
"Angle" => "角度",
|
|
"FillRate" => "填锡率",
|
|
"BgaVoid" => "BGA空隙",
|
|
"BubbleVoid" => "气泡空隙",
|
|
_ => "点点距"
|
|
};
|
|
string valueText = args.MeasureType switch
|
|
{
|
|
"Angle" => $"{args.Distance:F2}°",
|
|
"FillRate" => $"{args.Distance:F1}%",
|
|
"BgaVoid" => $"{args.Distance:F1}%",
|
|
"BubbleVoid" => $"{args.Distance:F1}%",
|
|
_ => $"{args.Distance:F2} px"
|
|
};
|
|
SetStatus($"{typeLabel}: {valueText} | 共 {args.TotalCount} 条测量");
|
|
}
|
|
};
|
|
RoiCanvas.MeasureStatusChanged += (s, e) =>
|
|
{
|
|
if (e is MeasureStatusEventArgs args)
|
|
SetStatus(args.Message);
|
|
};
|
|
|
|
// 十字辅助线:直接订阅 Prism 事件
|
|
try
|
|
{
|
|
var ea = ContainerLocator.Current?.Resolve<Prism.Events.IEventAggregator>();
|
|
ea?.GetEvent<ToggleCrosshairEvent>().Subscribe(() =>
|
|
{
|
|
RoiCanvas.ShowCrosshair = !RoiCanvas.ShowCrosshair;
|
|
}, Prism.Events.ThreadOption.UIThread);
|
|
|
|
// 测量模式:直接订阅 Prism 事件
|
|
ea?.GetEvent<MeasurementToolEvent>().Subscribe(mode =>
|
|
{
|
|
RoiCanvas.CurrentMeasureMode = mode switch
|
|
{
|
|
MeasurementToolMode.PointDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointDistance,
|
|
MeasurementToolMode.PointLineDistance => XP.ImageProcessing.RoiControl.Models.MeasureMode.PointToLine,
|
|
MeasurementToolMode.Angle => XP.ImageProcessing.RoiControl.Models.MeasureMode.Angle,
|
|
MeasurementToolMode.ThroughHoleFillRate => XP.ImageProcessing.RoiControl.Models.MeasureMode.FillRate,
|
|
MeasurementToolMode.BgaVoid => XP.ImageProcessing.RoiControl.Models.MeasureMode.BgaVoid,
|
|
MeasurementToolMode.BubbleMeasure => XP.ImageProcessing.RoiControl.Models.MeasureMode.BubbleMeasure,
|
|
_ => XP.ImageProcessing.RoiControl.Models.MeasureMode.None
|
|
};
|
|
}, Prism.Events.ThreadOption.UIThread);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (e.OldValue is INotifyPropertyChanged oldVm)
|
|
oldVm.PropertyChanged -= OnVmPropertyChanged;
|
|
if (e.NewValue is INotifyPropertyChanged newVm)
|
|
newVm.PropertyChanged += OnVmPropertyChanged;
|
|
}
|
|
|
|
private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
// 测量模式和十字线通过 Prism 事件直接驱动,不再依赖 PropertyChanged
|
|
}
|
|
|
|
#region 右键菜单
|
|
|
|
private void ZoomIn_Click(object sender, RoutedEventArgs e) => RoiCanvas.ZoomScale = Math.Min(10.0, RoiCanvas.ZoomScale * 1.2);
|
|
private void ZoomOut_Click(object sender, RoutedEventArgs e) => RoiCanvas.ZoomScale = Math.Max(0.1, RoiCanvas.ZoomScale / 1.2);
|
|
private void ResetView_Click(object sender, RoutedEventArgs e) => RoiCanvas.ResetView();
|
|
|
|
private void ClearAllMeasurements_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
RoiCanvas.ClearMeasurements();
|
|
RoiCanvas.ROIItems?.Clear();
|
|
RoiCanvas.SelectedROI = null;
|
|
if (DataContext is ViewportPanelViewModel vm)
|
|
vm.ResetMeasurementState();
|
|
SetStatus("已清除所有测量");
|
|
}
|
|
|
|
private void SaveOriginalImage_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (DataContext is not ViewportPanelViewModel vm || vm.ImageSource is not BitmapSource bitmap)
|
|
{
|
|
MessageBox.Show("当前没有可保存的图像", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
return;
|
|
}
|
|
SaveBitmapToFile(bitmap, "保存原始图像");
|
|
}
|
|
|
|
private void SaveResultImage_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var target = FindChildByName<Canvas>(RoiCanvas, "mainCanvas");
|
|
if (target == null)
|
|
{
|
|
MessageBox.Show("当前没有可保存的图像", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
return;
|
|
}
|
|
var width = (int)target.ActualWidth;
|
|
var height = (int)target.ActualHeight;
|
|
if (width == 0 || height == 0) return;
|
|
|
|
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
|
|
rtb.Render(target);
|
|
SaveBitmapToFile(rtb, "保存结果图像");
|
|
}
|
|
|
|
private static void SaveBitmapToFile(BitmapSource bitmap, string title)
|
|
{
|
|
var dialog = new SaveFileDialog
|
|
{
|
|
Title = title,
|
|
Filter = "PNG 图像|*.png|BMP 图像|*.bmp|JPEG 图像|*.jpg",
|
|
DefaultExt = ".png"
|
|
};
|
|
if (dialog.ShowDialog() != true) return;
|
|
|
|
BitmapEncoder encoder = Path.GetExtension(dialog.FileName).ToLower() switch
|
|
{
|
|
".bmp" => new BmpBitmapEncoder(),
|
|
".jpg" or ".jpeg" => new JpegBitmapEncoder(),
|
|
_ => new PngBitmapEncoder()
|
|
};
|
|
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
|
using var fs = new FileStream(dialog.FileName, FileMode.Create);
|
|
encoder.Save(fs);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private static T FindChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
|
|
{
|
|
int count = VisualTreeHelper.GetChildrenCount(parent);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var child = VisualTreeHelper.GetChild(parent, i);
|
|
if (child is T t && t.Name == name) return t;
|
|
var result = FindChildByName<T>(child, name);
|
|
if (result != null) return result;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|