添加 MX-PD-盘古 项目文件

将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
Shi.Ji
2026-05-18 11:43:09 +08:00
parent 03632a379d
commit e31d3560bb
739 changed files with 99783 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
using System;
using System.Windows;
using System.Windows.Input;
namespace MainShell.Common.Behaviors
{
public static class WindowDragMoveBehavior
{
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(WindowDragMoveBehavior),
new PropertyMetadata(false, OnIsEnabledChanged));
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element == null)
{
return;
}
if ((bool)e.NewValue)
{
element.MouseLeftButtonDown += OnMouseLeftButtonDown;
return;
}
element.MouseLeftButtonDown -= OnMouseLeftButtonDown;
}
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as DependencyObject;
var window = element != null ? Window.GetWindow(element) : null;
if (window == null || e.ButtonState != MouseButtonState.Pressed)
{
return;
}
try
{
window.DragMove();
e.Handled = true;
}
catch (InvalidOperationException)
{
}
}
}
}

View File

@@ -0,0 +1,59 @@
using MainShell.Log;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace MainShell.Common
{
public static class CommonUti
{
public static void RunOnUi(Action action)
{
if (action == null)
{
return;
}
try
{
if (Application.Current == null || Application.Current.Dispatcher.CheckAccess())
{
// 同步在当前线程执行
action();
}
else
{
// 在 UI 线程同步执行
Application.Current.Dispatcher.Invoke(action);
}
}
catch (Exception ex)
{
MaxwellControl.Controls.MessageBox.Show($"方法执行异常:{ex.ToString()}", icon: MessageBoxImage.Error);
}
}
public static Brush BrushFromString(string s)
{
if (string.IsNullOrWhiteSpace(s))
return Brushes.Transparent;
try
{
var brush = (Brush)new BrushConverter().ConvertFromString(s);
return brush ?? Brushes.Transparent;
}
catch
{
// 如果解析失败,返回默认颜色(可根据需要改为 Brushes.Black 或抛异常)
return Brushes.Transparent;
}
}
}
}

View File

@@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using MainShell.Resources.CustomControl;
namespace MainShell.Common.ControlAttribute
{
public static class ControlBehavior
{
public static bool GetIsEnable(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnableProperty);
}
public static void SetIsEnable(DependencyObject obj, bool value)
{
obj.SetValue(IsEnableProperty, value);
}
// Using a DependencyProperty as the backing store for IsEnable. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnableProperty =
DependencyProperty.RegisterAttached("IsEnable", typeof(bool), typeof(ControlBehavior), new PropertyMetadata(true, OnProppertyChanged));
public static bool GetIsNumericOnly(DependencyObject obj)
{
return (bool)obj.GetValue(IsNumericOnlyProperty);
}
public static void SetIsNumericOnly(DependencyObject obj, bool value)
{
obj.SetValue(IsNumericOnlyProperty, value);
}
public static readonly DependencyProperty IsNumericOnlyProperty =
DependencyProperty.RegisterAttached("IsNumericOnly", typeof(bool), typeof(ControlBehavior), new PropertyMetadata(false, OnIsNumericOnlyChanged));
public static bool GetRouteDieMapMouseWheel(DependencyObject obj)
{
return (bool)obj.GetValue(RouteDieMapMouseWheelProperty);
}
public static void SetRouteDieMapMouseWheel(DependencyObject obj, bool value)
{
obj.SetValue(RouteDieMapMouseWheelProperty, value);
}
public static readonly DependencyProperty RouteDieMapMouseWheelProperty =
DependencyProperty.RegisterAttached("RouteDieMapMouseWheel", typeof(bool), typeof(ControlBehavior), new PropertyMetadata(false, OnRouteDieMapMouseWheelChanged));
private static void OnProppertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TabControl tabControl)
{
if (e.NewValue is bool isenable)
{
if (isenable)
foreach (var item in tabControl.Items)
{
if (tabControl.ItemContainerGenerator.ContainerFromItem(item) is TabItem tabItem)
{
tabItem.IsEnabled = isenable;
}
}
else
foreach (var item in tabControl.Items)
{
if (tabControl.ItemContainerGenerator.ContainerFromItem(item) is TabItem tabItem)
{
if (tabItem.IsSelected)
{
tabItem.IsEnabled = true;
}
else
tabItem.IsEnabled = isenable;
}
}
}
}
}
private static void OnIsNumericOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is TextBox textBox))
return;
if ((bool)e.NewValue)
{
InputMethod.SetIsInputMethodEnabled(textBox, false);
textBox.PreviewTextInput += TextBox_PreviewTextInput;
DataObject.AddPastingHandler(textBox, OnTextBoxPaste);
}
else
{
InputMethod.SetIsInputMethodEnabled(textBox, true);
textBox.PreviewTextInput -= TextBox_PreviewTextInput;
DataObject.RemovePastingHandler(textBox, OnTextBoxPaste);
}
}
private static void OnRouteDieMapMouseWheelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer == null)
{
return;
}
bool isEnabled = e.NewValue is bool && (bool)e.NewValue;
MouseWheelEventHandler mouseWheelEventHandler = new MouseWheelEventHandler(ScrollViewer_PreviewMouseWheel);
if (isEnabled)
{
scrollViewer.RemoveHandler(UIElement.PreviewMouseWheelEvent, mouseWheelEventHandler);
scrollViewer.AddHandler(UIElement.PreviewMouseWheelEvent, mouseWheelEventHandler, true);
}
else
{
scrollViewer.RemoveHandler(UIElement.PreviewMouseWheelEvent, mouseWheelEventHandler);
}
}
private static void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = sender as ScrollViewer;
if (scrollViewer == null)
{
return;
}
DieMapControl dieMapControl = FindDieMapControl(scrollViewer, e);
if (dieMapControl == null)
{
return;
}
e.Handled = true;
MouseWheelEventArgs mouseWheelEventArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
mouseWheelEventArgs.RoutedEvent = UIElement.MouseWheelEvent;
mouseWheelEventArgs.Source = dieMapControl;
dieMapControl.RaiseEvent(mouseWheelEventArgs);
}
private static DieMapControl FindDieMapControl(ScrollViewer scrollViewer, MouseWheelEventArgs e)
{
DependencyObject dependencyObject = e.OriginalSource as DependencyObject;
DieMapControl dieMapControl = FindAncestor<DieMapControl>(dependencyObject);
if (dieMapControl != null)
{
return dieMapControl;
}
dependencyObject = Mouse.DirectlyOver as DependencyObject;
dieMapControl = FindAncestor<DieMapControl>(dependencyObject);
if (dieMapControl != null)
{
return dieMapControl;
}
IInputElement inputElement = scrollViewer.InputHitTest(e.GetPosition(scrollViewer));
dependencyObject = inputElement as DependencyObject;
return FindAncestor<DieMapControl>(dependencyObject);
}
private static T FindAncestor<T>(DependencyObject dependencyObject) where T : DependencyObject
{
while (dependencyObject != null)
{
T target = dependencyObject as T;
if (target != null)
{
return target;
}
DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null && dependencyObject is FrameworkContentElement)
{
parent = ((FrameworkContentElement)dependencyObject).Parent;
}
dependencyObject = parent;
}
return null;
}
private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (sender is TextBox textBox)
{
e.Handled = !IsValidNumericText(GetPreviewText(textBox, e.Text));
}
}
private static void OnTextBoxPaste(object sender, DataObjectPastingEventArgs e)
{
if (!(sender is TextBox textBox))
return;
if (!e.DataObject.GetDataPresent(typeof(string)))
{
e.CancelCommand();
return;
}
var pasteText = e.DataObject.GetData(typeof(string)) as string ?? string.Empty;
if (!IsValidNumericText(GetPreviewText(textBox, pasteText)))
{
e.CancelCommand();
}
}
private static string GetPreviewText(TextBox textBox, string newText)
{
var currentText = textBox.Text ?? string.Empty;
if (textBox.SelectionLength > 0)
{
currentText = currentText.Remove(textBox.SelectionStart, textBox.SelectionLength);
}
return currentText.Insert(textBox.CaretIndex, newText);
}
private static bool IsValidNumericText(string text)
{
if (string.IsNullOrWhiteSpace(text) || text == "-" || text == "." || text == "-.")
return true;
return double.TryParse(text, out _);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using MessageBox = MaxwellControl.Controls.MessageBox;
namespace MainShell.Common.ControlAttribute
{
public class NumericValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string strValue = value as string;
if (string.IsNullOrEmpty(strValue))
{
// 空值是允许的,如果需要必填,则返回 new ValidationResult(false, "不能为空");
return new ValidationResult(false, "不能为空");
}
// 尝试将字符串转换为double
if (double.TryParse(strValue, out _))
{
return ValidationResult.ValidResult;
}
else
{
return new ValidationResult(false, "请输入有效的数字。");
}
}
}
}

View File

@@ -0,0 +1,97 @@
using MaxwellFramework.Core.Common;
using MwFramework.Controls.ControlCanvas.Model;
using MwFramework.ManagerService;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Common.Display.Models
{
public class CameraAxisDevices : ViewModelBase
{
List<HardwareDevice> _hardwareDeviceList = new List<HardwareDevice>();
HardwareDevice _selectedHardwareDevice = new HardwareDevice();
private ObservableCollection<object> _shapes = new ObservableCollection<object>();
private int _selectedIndex = 0;
private bool _cameraSwitchEnable = true;
public List<HardwareDevice> HardwareDeviceList
{
get { return _hardwareDeviceList; }
set
{
if (_hardwareDeviceList == value)
{
return;
}
_hardwareDeviceList = value;
if (_hardwareDeviceList != null && _hardwareDeviceList.Count > 0)
{
_selectedIndex = 0;
SelectedHardwareDevice = _hardwareDeviceList[_selectedIndex];
RaisePropertyChanged(nameof(SelectedIndex));
}
RaisePropertyChanged(nameof(HardwareDeviceList));
}
}
public HardwareDevice SelectedHardwareDevice
{
get { return _selectedHardwareDevice; }
set
{
if (_selectedHardwareDevice != null && _selectedHardwareDevice.Camera != null && value != null && value.Camera != null)
{
}
_selectedHardwareDevice = value; RaisePropertyChanged(nameof(SelectedHardwareDevice));
}
}
public ObservableCollection<object> Shapes
{
get { return _shapes; }
set
{
if (_shapes != value) { _shapes = value; RaisePropertyChanged(nameof(Shapes)); }
}
}
private ObservableCollection<PrimShape> _regions = new ObservableCollection<PrimShape>();
public ObservableCollection<PrimShape> Regions
{
get { return _regions; }
set
{
SetAndNotify(ref _regions, value);
}
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
if (HardwareDeviceList != null && HardwareDeviceList.Count > 0 && _selectedIndex >= 0)
{
if (_selectedIndex < HardwareDeviceList.Count)
{
SelectedHardwareDevice = HardwareDeviceList[_selectedIndex];
}
}
RaisePropertyChanged(nameof(SelectedIndex));
}
}
public bool CameraSwitchEnable
{
get { return _cameraSwitchEnable; }
set { _cameraSwitchEnable = value; RaisePropertyChanged(nameof(CameraSwitchEnable)); }
}
}
}

View File

@@ -0,0 +1,30 @@
<UserControl x:Class="MainShell.Common.Display.View.CameraAxisView"
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:local="clr-namespace:MainShell.Common.Display.View"
xmlns:control="http://www.maxwell.com/controls"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<CheckBox Panel.ZIndex="2" IsChecked="{Binding IsOpen, UpdateSourceTrigger=PropertyChanged}" Cursor="Hand" Margin="2,2" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Top">
<TextBlock Text="移动"/>
</CheckBox>
<control:CameraAxisControlA Name="cameraAxisControl"
IsShowDoubleLine="False"
IsAxisControlLDBVisible2="Visible"
Cameras="{Binding CameraAxisDevices.HardwareDeviceList}"
TabSelectedIndex ="{Binding CameraAxisDevices.SelectedIndex}"
Items="{Binding CameraAxisDevices.Shapes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Regions="{Binding CameraAxisDevices.Regions}" ShapeThickness="1" IsShowSolidLine="True" DrawInConcurrency="False"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="RecordPix">
<i:InvokeCommandAction Command="{Binding OnLeftMouseDownCommand}" CommandParameter="{Binding ElementName=cameraAxisControl}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</control:CameraAxisControlA>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
namespace MainShell.Common.Display.View
{
/// <summary>
/// CameraAxisView.xaml 的交互逻辑
/// </summary>
public partial class CameraAxisView : UserControl
{
public CameraAxisView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,22 @@
<UserControl x:Class="MainShell.Common.Display.View.CameraView"
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:local="clr-namespace:MainShell.Common.Display.View"
xmlns:mw="http://www.maxwell.com/controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Border BorderThickness="1" BorderBrush ="#B0B9C4">
<mw:DesignPanel x:Name="cameraDesignPanel"
Source="{Binding DisplayImage}"
Items="{Binding Shapes}"
IsVirtualPix="Visible"
IsShowSolidLine="True"
IsCrossingLineVisible="False"
IsNonRectCrossingLineVisible="False"
IsImagePix="Collapsed" />
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
namespace MainShell.Common.Display.View
{
/// <summary>
/// CameraView.xaml 的交互逻辑
/// </summary>
public partial class CameraView : UserControl
{
public CameraView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,323 @@
using MainShell.Common;
using MainShell.Common.Display.Models;
using MainShell.Log;
using MainShell.Motion;
using Maxwell.SemiFramework.DefaultConfig.Vision;
using MwFramework.Controls.Components;
using MwFramework.Controls.ControlCanvas.Model;
using MwFramework.Controls.UIControl;
using MwFramework.Device;
using MwFramework.ManagerService;
using Stylet;
using StyletIoC;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace MainShell.Common.Display.ViewModel
{
public class CameraAxisViewModel : Screen
{
private const double DefaultFovX = 14.1 * 10;
private const double DefaultFovY = 10.3 * 10;
private const string SafeAxisMotionNotInjectedMessage = "SafeAxisMotion is not injected.";
private readonly object _moveLock = new object();
private CameraAxisDevices _cameraAxisDevices = new CameraAxisDevices();
private double _xPixelPos;
private double _yPixelPos;
private double _cameraXIndex;
private double _cameraYIndex;
private GetConvertValue _convertFunXDelegate;
private GetConvertValue _convertFunYDelegate;
private ObservableCollection<PrimShape> _regions = new ObservableCollection<PrimShape>();
private bool _isCameraEnable = true;
private Visibility _controlVisibility = Visibility.Visible;
private bool _isOpen;
[Inject]
public SafeAxisMotion SafeAxisMotion { get; set; }
public CameraAxisViewModel()
{
ConvertFunXDelegate = new GetConvertValue(ConvertFunX);
ConvertFunYDelegate = new GetConvertValue(ConvertFunY);
OnLeftMouseDownCommand = new DelegateCommand<object>(OnLeftMouseDown);
}
public CameraAxisDevices CameraAxisDevices
{
get { return _cameraAxisDevices; }
set { SetAndNotify(ref _cameraAxisDevices, value); }
}
public double XPixelPos
{
get { return _xPixelPos; }
set { SetAndNotify(ref _xPixelPos, value); }
}
public double YPixelPos
{
get { return _yPixelPos; }
set { SetAndNotify(ref _yPixelPos, value); }
}
public double CameraXIndex
{
get { return _cameraXIndex; }
set { SetAndNotify(ref _cameraXIndex, value); }
}
public double CameraYIndex
{
get { return _cameraYIndex; }
set { SetAndNotify(ref _cameraYIndex, value); }
}
public GetConvertValue ConvertFunXDelegate
{
get { return _convertFunXDelegate; }
set { SetAndNotify(ref _convertFunXDelegate, value); }
}
public GetConvertValue ConvertFunYDelegate
{
get { return _convertFunYDelegate; }
set { SetAndNotify(ref _convertFunYDelegate, value); }
}
public ObservableCollection<PrimShape> Regions
{
get { return _regions; }
set { SetAndNotify(ref _regions, value); }
}
public bool IsCameraEnable
{
get { return _isCameraEnable; }
set { SetAndNotify(ref _isCameraEnable, value); }
}
public Visibility ControlVisibility
{
get { return _controlVisibility; }
set { SetAndNotify(ref _controlVisibility, value); }
}
public ICommand OnLeftMouseDownCommand { get; private set; }
public bool IsOpen
{
get { return _isOpen; }
set { SetAndNotify(ref _isOpen, value); }
}
public void OnLeftMouseDown(object sender)
{
try
{
HardwareDevice selectedDevice;
if (!TryGetSelectedDevice(out selectedDevice))
{
return;
}
CameraAxisControlA controlA = sender as CameraAxisControlA;
if (controlA == null)
{
return;
}
Point pixelPos = new Point(controlA.YPixPos, controlA.XPixPos);
Task.Run(() => MoveToPixelPoint(selectedDevice, pixelPos, false));
}
catch (Exception ex)
{
LogManager.LogSysError(ex);
ShowOperationFailed(ex.Message);
}
}
public void MovePosAction()
{
try
{
HardwareDevice selectedDevice;
if (!TryGetSelectedDevice(out selectedDevice))
{
return;
}
Point pixelPos = new Point(YPixelPos, XPixelPos);
Task.Run(() => MoveToPixelPoint(selectedDevice, pixelPos, true));
}
catch (Exception ex)
{
LogManager.LogSysError(ex);
ShowOperationFailed(ex.Message);
}
}
public double ConvertFunX(double interval)
{
try
{
HardwareDevice selectedDevice;
if (!TryGetSelectedDevice(out selectedDevice))
{
return 0;
}
string name = selectedDevice.Camera.Id;
double dx;
double dy;
VisionProcess.Instance.GetCameraResolution(name, out dx, out dy);
double pixelX = interval / Math.Abs(dx);
return pixelX;
}
catch (Exception ex)
{
LogManager.LogSysError(ex);
ShowOperationFailed(ex.Message);
return 0;
}
}
public double ConvertFunY(double interval)
{
try
{
HardwareDevice selectedDevice;
if (!TryGetSelectedDevice(out selectedDevice))
{
return 0;
}
string name = selectedDevice.Camera.Id;
double dx;
double dy;
VisionProcess.Instance.GetCameraResolution(name, out dx, out dy);
double pixelY = interval / Math.Abs(dy);
return pixelY;
}
catch (Exception ex)
{
LogManager.LogSysError(ex);
ShowOperationFailed(ex.Message);
return 0;
}
}
private bool TryGetSelectedDevice(out HardwareDevice selectedDevice)
{
selectedDevice = CameraAxisDevices != null ? CameraAxisDevices.SelectedHardwareDevice : null;
if (!IsOpen || selectedDevice == null)
{
return false;
}
if (selectedDevice.Camera == null || selectedDevice.Camera.Camera == null)
{
ShowDeviceNotInitialized();
return false;
}
if (!selectedDevice.Camera.Camera.IsGrabbing)
{
LocalizedMessageBox.Show(MessageKey.VisionCameraNotGrabbing, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
return false;
}
if (selectedDevice.AxisX == null || selectedDevice.AxisY == null)
{
ShowDeviceNotInitialized();
return false;
}
return true;
}
private void MoveToPixelPoint(HardwareDevice selectedDevice, Point pixelPos, bool requireConfirmation)
{
lock (_moveLock)
{
try
{
IAxis axisX = selectedDevice.AxisX;
IAxis axisY = selectedDevice.AxisY;
Point rulerPos = new Point(axisX.State.ActualPos, axisY.State.ActualPos);
Point newRulerPoint = Algorithm.Instance.MoveToPixelPoint(selectedDevice.Camera.Id, rulerPos, pixelPos);
if (requireConfirmation)
{
double dx = newRulerPoint.X - rulerPos.X;
double dy = newRulerPoint.Y - rulerPos.Y;
if (!ValidateMoveRange(dx, dy))
{
return;
}
if (!ConfirmMove(newRulerPoint))
{
return;
}
}
if (SafeAxisMotion == null)
{
InvalidOperationException exception = new InvalidOperationException(SafeAxisMotionNotInjectedMessage);
LogManager.LogSysError(exception);
ShowOperationFailed(SafeAxisMotionNotInjectedMessage);
return;
}
SafeAxisMotion.SafeMove(
MotionMoveRequest.ForAxis(axisX, newRulerPoint.X),
MotionMoveRequest.ForAxis(axisY, newRulerPoint.Y));
}
catch (Exception ex)
{
LogManager.LogSysError(ex);
ShowOperationFailed(ex.Message);
}
}
}
private static bool ValidateMoveRange(double dx, double dy)
{
if (Math.Abs(dx) > DefaultFovX || Math.Abs(dy) > DefaultFovY)
{
LocalizedMessageBox.Show(MessageKey.ParamOutOfRange, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
return false;
}
return true;
}
private static bool ConfirmMove(Point newRulerPoint)
{
string message = string.Format("新点的坐标({0},{1}),是否移动?", newRulerPoint.X, newRulerPoint.Y);
return MwMessageBox.Show(message, LanguageResourceHelper.GetString(MessageKey.TitleConfirm), MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.Cancel) == MessageBoxResult.OK;
}
private static void ShowDeviceNotInitialized()
{
LocalizedMessageBox.Show(MessageKey.DeviceNotInitialized, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
private static void ShowOperationFailed(string message)
{
if (string.IsNullOrWhiteSpace(message))
{
LocalizedMessageBox.Show(MessageKey.CommonOperationFailed, MessageKey.TitleError, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
MwMessageBox.Show(message, LanguageResourceHelper.GetString(MessageKey.TitleError), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}

View File

@@ -0,0 +1,317 @@
using MainShell.EventArgsFolder;
using MainShell.Log;
using MainShell.Vision;
using MwFramework.Device.Model;
using MwFramework.Controls.ControlCanvas.DrawingControl;
using Stylet;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using AppCameraType = MainShell.Common.CameraType;
using System.Windows.Media;
namespace MainShell.Common.Display.ViewModel
{
public class CameraViewModel : Screen, IHandle<DisplayImageEventArgs>, IHandle<DisplayFindTemplateResultEventArgs>
{
private readonly IEventAggregator _eventAggregator;
public AppCameraType CameraType { get; }
private WriteableBitmap currentImage;
private WriteableBitmap _displayImage;
public WriteableBitmap DisplayImage
{
get { return _displayImage; }
set
{
SetAndNotify(ref _displayImage, value);
}
}
private ObservableCollection<object> _shapes = new ObservableCollection<object>();
public ObservableCollection<object> Shapes
{
get { return _shapes; }
set
{
if (_shapes != value) { _shapes = value; OnPropertyChanged(nameof(Shapes)); }
}
}
/// <summary>
/// 根据 CameraData 更新并返回 WriteableBitmap。
/// 优化要点:
/// - 重用已有 WriteableBitmap当尺寸与像素格式相同时以减少分配。
/// - 校验参数与缓冲区长度。
/// - 捕获并记录异常,失败时返回 null不抛出
/// </summary>
public WriteableBitmap UpdateBitmap(CameraData cameraData)
{
if (cameraData == null)
{
"图片数据为空".LogSysError();
return null;
}
try
{
int width = (int)cameraData.Width;
int height = (int)cameraData.Height;
int bitCnt = cameraData.Bit;
byte[] buffer = cameraData.BufferData;
if (width <= 0 || height <= 0)
{
$"CameraViewModel.UpdateBitmap: invalid size {width}x{height}".LogSysError();
return null;
}
// 选择像素格式
System.Windows.Media.PixelFormat pixelFormat = bitCnt == 1
? System.Windows.Media.PixelFormats.Gray8
: (bitCnt == 3 ? System.Windows.Media.PixelFormats.Bgr24 : System.Windows.Media.PixelFormats.Bgra32);
// 计算每行字节数stride
int bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8;
int stride = width * bytesPerPixel;
long requiredLength = (long)stride * height;
if (buffer == null)
{
"CameraViewModel.UpdateBitmap: buffer is null".LogSysError();
return null;
}
if (buffer.Length < requiredLength)
{
($"CameraViewModel.UpdateBitmap: buffer length {buffer.Length} is less than required {requiredLength}").LogSysError();
return null;
}
// 如果现有 bitmap 可复用(尺寸和格式一致),就重用以减少 GC/分配
if (currentImage == null ||
currentImage.PixelWidth != width ||
currentImage.PixelHeight != height ||
!currentImage.Format.Equals(pixelFormat))
{
// 新建 WriteableBitmap
currentImage = new WriteableBitmap(width, height, 96, 96, pixelFormat, null);
}
// 将字节拷贝到 BackBufferUI 线程中调用)
currentImage.Lock();
try
{
// BackBuffer 为 IntPtr使用 Marshal.Copy 从 byte[] 拷贝
System.Runtime.InteropServices.Marshal.Copy(buffer, 0, currentImage.BackBuffer, (int)requiredLength);
currentImage.AddDirtyRect(new Int32Rect(0, 0, width, height));
}
finally
{
currentImage.Unlock();
}
return currentImage;
}
catch (Exception ex)
{
($"CameraViewModel.UpdateBitmap exception: {ex}").LogSysError();
return null;
}
}
public CameraViewModel(IEventAggregator eventAggregator, AppCameraType cameraType)
{
_eventAggregator = eventAggregator;
CameraType = cameraType;
_eventAggregator?.Subscribe(this);
}
public void Handle(DisplayImageEventArgs message)
{
if (message == null || message.CameraType != CameraType)
{
return;
}
CommonUti.RunOnUi(() =>
{
WriteableBitmap bmp = UpdateBitmap(message.CameraData);
// 只有在成功生成 bitmap 时才更新 DisplayImage避免把 null 赋值导致 UI 清空
if (bmp != null)
{
DisplayImage = bmp;
}
});
}
public void Handle(DisplayFindTemplateResultEventArgs message)
{
if (message == null || message.CameraType != CameraType)
{
return;
}
CommonUti.RunOnUi(() =>
{
ApplyTemplateResultShapes(message);
});
}
private void ApplyTemplateResultShapes(DisplayFindTemplateResultEventArgs message)
{
if (message.ClearShapesBeforeDisplay)
{
Shapes.Clear();
}
if (message.Result == null)
{
return;
}
AppendCenterShapes(message.Result);
AppendContourShape(message.Result.Contour);
AppendCircleShapes(message.Result.Circles);
AppendLineShapes(message.Result.Lines);
AppendRectangleShapes(message.Result.Rectangles);
}
private void AppendCenterShapes(FindTemplateResult result)
{
if (result == null || !AreFinite(result.CenterX, result.CenterY, result.Score))
{
return;
}
ShapeDrawing pointShape = new ShapeDrawing();
pointShape.X = result.CenterX;
pointShape.Y = result.CenterY;
Shapes.Add(pointShape);
TextShapeDrawing textShape = new TextShapeDrawing();
textShape.X = result.CenterX;
textShape.Y = result.CenterY;
textShape.Text = $"Score:{result.Score:F3}";
Shapes.Add(textShape);
}
private void AppendContourShape(FindTemplateContourResult contour)
{
if (contour == null || !AreFinite(contour.StartX, contour.StartY, contour.EndX, contour.EndY))
{
return;
}
RectShapeDrawing contourShape = new RectShapeDrawing();
contourShape.X = contour.StartX;
contourShape.Y = contour.StartY;
contourShape.X1 = contour.EndX;
contourShape.Y1 = contour.EndY;
Shapes.Add(contourShape);
}
private void AppendCircleShapes(IList<FindTemplateCircleResult> circles)
{
if (circles == null)
{
return;
}
foreach (FindTemplateCircleResult circle in circles)
{
if (circle == null || !AreFinite(circle.CenterX, circle.CenterY, circle.Radius))
{
continue;
}
CircleShapeDrawing circleShape = new CircleShapeDrawing();
circleShape.X = circle.CenterX;
circleShape.Y = circle.CenterY;
circleShape.CenterPoint = new Point(circle.CenterX, circle.CenterY);
circleShape.RadiusX = circle.Radius;
circleShape.RadiusY = circle.Radius;
circleShape.LineWidth = 1;
circleShape.FillColor = Brushes.Blue;
Shapes.Add(circleShape);
}
}
private void AppendLineShapes(IList<FindTemplateLineResult> lines)
{
if (lines == null)
{
return;
}
foreach (FindTemplateLineResult line in lines)
{
if (line == null || !AreFinite(line.StartX, line.StartY, line.EndX, line.EndY))
{
continue;
}
LineShapeDrawing lineShape = new LineShapeDrawing();
lineShape.X = line.StartX;
lineShape.Y = line.StartY;
lineShape.X1 = line.EndX;
lineShape.Y1 = line.EndY;
Shapes.Add(lineShape);
}
}
private void AppendRectangleShapes(IList<FindTemplateRectangleResult> rectangles)
{
if (rectangles == null)
{
return;
}
foreach (FindTemplateRectangleResult rectangle in rectangles)
{
if (rectangle == null || !AreFinite(rectangle.StartX, rectangle.StartY, rectangle.Width, rectangle.Height, rectangle.Angle))
{
continue;
}
double halfWidth = rectangle.Width / 2d;
double halfHeight = rectangle.Height / 2d;
double cosAngle = Math.Cos(rectangle.Angle);
double sinAngle = Math.Sin(rectangle.Angle);
double centerX = rectangle.StartX + (halfWidth * cosAngle) - (halfHeight * sinAngle);
double centerY = rectangle.StartY + (halfWidth * sinAngle) + (halfHeight * cosAngle);
RectShape2Drawing rectangleShape = new RectShape2Drawing();
rectangleShape.X = centerX;
rectangleShape.Y = centerY;
rectangleShape.CenterX = centerX;
rectangleShape.CenterY = centerY;
rectangleShape.HalfWidth = halfWidth;
rectangleShape.HalfHeigth = halfHeight;
rectangleShape.Angle = rectangle.Angle;
Shapes.Add(rectangleShape);
}
}
private static bool AreFinite(params double[] values)
{
if (values == null)
{
return false;
}
foreach (double value in values)
{
if (double.IsNaN(value) || double.IsInfinity(value))
{
return false;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Common
{
/// <summary>
/// 一个简单的包装类,在对象被 Dispose 时执行指定的 Action。
/// </summary>
public class DisposableAction : IDisposable
{
private readonly Action _action;
private bool _isDisposed;
public DisposableAction(Action action)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
}
public void Dispose()
{
if (!_isDisposed)
{
_action();
_isDisposed = true;
}
}
}
}

View File

@@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Common
{
public enum MachineMode { Manual, Auto, Maintenance }
public enum SpeedType
{
[Description("低速")]
Low,
[Description("中速")]
Medium,
[Description("高速")]
High
}
public enum DieTransferRowTraversalStrategy
{
[Description("全部正向")]
AllPositive = 0,
[Description("全部反向")]
AllNegative = 1,
[Description("蛇形")]
Serpentine = 2
}
public enum BondingPathMode
{
S形打件 = 1,
Z形正向打件,
Z形反向打件,
}
public enum BondingStagePathMode
{
S型区域打件 = 1,
Stage正向区域打件,
Stage负向区域打件,
}
public enum BondingWSPathMode
{
S型打件 = 1,
WS正向区域打件,
WS负向区域打件,
}
public enum RunningMode
{
= 1,
,
}
public enum CameraType
{
TopPositionCamera,
TopWsCamera,
TopWideCamera,
TopWideWsCamera,
MapCamera
}
public enum TransPathType
{
[Description("就近")]
Nearest,
[Description("顺序")]
Sequence,
}
public enum TransPathDirection
{
[Description("正向")]
Positive,
[Description("反向")]
Negative,
}
public enum DieState
{
[Description("可使用")]
Available,
[Description("已使用")]
Used,
[Description("错误")]
Error,
[Description("不存在")]
NotExist,
[Description("当前")]
Current,
[Description("目标")]
Target
}
public enum ProcessExecutionStatus
{
Unknown = 0,
Running,
Paused,
Canceled,
Faulted,
Completed
}
public enum FlowDisplayStage
{
Load = 0,
Align = 1,
Bond = 2,
Inspect = 3,
Unload = 4
}
public enum WaferScanMode
{
[Description("按需扫描")]
ScanOnDemand,
[Description("全扫")]
FullScan,
}
public enum BondingSection
{
/// <summary>
/// A区
/// </summary>
A,
/// <summary>
/// B区
/// </summary>
B
}
public enum ComSupMotionType
{
/// <summary>
/// 干涉写入
/// </summary>
Interfere = 102,
/// <summary>
/// 回零写入
/// </summary>
Home = 103,
/// <summary>
/// 快打执行
/// </summary>
FastBonding = 200,
/// <summary>
/// 慢打执行
/// </summary>
SlowBonding = 201,
/// <summary>
/// 报警清楚
/// </summary>
AlarmClear = 104,
/// <summary>
/// 切PID指定
/// </summary>
ChangePid = 301,
/// <summary>
/// 触发报警
/// </summary>
OccurAlarm = 105,
/// <summary>
/// 同步PID参数
/// </summary>
SyncPIDParam = 106,
/// <summary>
/// 手动设置刺针次数
/// </summary>
NeedleWorkCount = 302,
}
}

View File

@@ -0,0 +1,25 @@
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Common.Extension
{
public static class DeviceExtension
{
private static readonly int _taskNum = 3;
public static void GetLastSamples(this IDiastimeter diastimeter, out double distance)
{
var doubles = new List<double>();
for (int i = 0; i < _taskNum; i++)
{
diastimeter.GetLastSample(out double val);
if (val != double.NaN)
doubles.Add(val);
}
distance = doubles.Average();
}
}
}

View File

@@ -0,0 +1,78 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace MainShell.Common
{
public static class FocusedEditorCommitHelper
{
public static void CommitFocusedEditorChanges()
{
var focusedElement = Keyboard.FocusedElement as DependencyObject;
if (focusedElement == null)
return;
CommitBinding(focusedElement);
var dataGrid = FindAncestor<DataGrid>(focusedElement);
if (dataGrid != null)
{
dataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
}
private static void CommitBinding(DependencyObject dependencyObject)
{
if (dependencyObject is TextBox textBox)
{
textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
return;
}
if (dependencyObject is ComboBox comboBox)
{
comboBox.GetBindingExpression(Selector.SelectedItemProperty)?.UpdateSource();
comboBox.GetBindingExpression(ComboBox.TextProperty)?.UpdateSource();
return;
}
if (dependencyObject is ToggleButton toggleButton)
{
toggleButton.GetBindingExpression(ToggleButton.IsCheckedProperty)?.UpdateSource();
}
}
private static T FindAncestor<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var current = dependencyObject;
while (current != null)
{
if (current is T target)
return target;
current = GetParent(current);
}
return null;
}
private static DependencyObject GetParent(DependencyObject dependencyObject)
{
if (dependencyObject is FrameworkElement frameworkElement)
{
return frameworkElement.Parent ?? VisualTreeHelper.GetParent(frameworkElement);
}
if (dependencyObject is FrameworkContentElement frameworkContentElement)
{
return frameworkContentElement.Parent;
}
return null;
}
}
}

View File

@@ -0,0 +1,380 @@
using MainShell.Resources.CustomControl;
using MaxwellFramework.Core.Events;
using Stylet;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
namespace MainShell.Common
{
public class HeaderDeviceStatusController : PropertyChangedBase, IHandle<MainViewSelectedPageChangeEvent>
{
private enum HeaderDeviceStatusTone
{
Auto,
Manual,
Maintenance,
Recipe,
Settings,
Calibration,
Unknown
}
private readonly IEventAggregator _eventAggregator;
private bool _hasLoadingStarted;
private bool _isVisible;
private string _labelText = string.Empty;
private string _statusText = string.Empty;
private Brush _badgeBackgroundBrush;
private Brush _badgeBorderBrush;
private Brush _labelForegroundBrush;
private Brush _statusForegroundBrush;
private Brush _indicatorBrush;
public HeaderDeviceStatusController(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_eventAggregator.Subscribe(this);
LoadingService.Instance.PropertyChanged += OnLoadingServicePropertyChanged;
UpdateStatusText("Home");
IsVisible = false;
}
public bool IsVisible
{
get
{
return _isVisible;
}
private set
{
SetAndNotify(ref _isVisible, value);
}
}
public string LabelText
{
get
{
return _labelText;
}
private set
{
SetAndNotify(ref _labelText, value);
}
}
public string StatusText
{
get
{
return _statusText;
}
private set
{
SetAndNotify(ref _statusText, value);
}
}
public Brush BadgeBackgroundBrush
{
get
{
return _badgeBackgroundBrush;
}
private set
{
SetAndNotify(ref _badgeBackgroundBrush, value);
}
}
public Brush BadgeBorderBrush
{
get
{
return _badgeBorderBrush;
}
private set
{
SetAndNotify(ref _badgeBorderBrush, value);
}
}
public Brush LabelForegroundBrush
{
get
{
return _labelForegroundBrush;
}
private set
{
SetAndNotify(ref _labelForegroundBrush, value);
}
}
public Brush StatusForegroundBrush
{
get
{
return _statusForegroundBrush;
}
private set
{
SetAndNotify(ref _statusForegroundBrush, value);
}
}
public Brush IndicatorBrush
{
get
{
return _indicatorBrush;
}
private set
{
SetAndNotify(ref _indicatorBrush, value);
}
}
public void Handle(MainViewSelectedPageChangeEvent message)
{
if (message == null)
{
return;
}
UpdateStatusText(message.SelectePageName);
if (!LoadingService.Instance.IsBusy)
{
IsVisible = true;
}
}
private void OnLoadingServicePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!string.Equals(e.PropertyName, nameof(LoadingService.IsBusy), StringComparison.Ordinal))
{
return;
}
if (LoadingService.Instance.IsBusy)
{
_hasLoadingStarted = true;
IsVisible = false;
return;
}
if (_hasLoadingStarted)
{
IsVisible = true;
}
}
private void UpdateStatusText(string pageName)
{
string resourceKey;
HeaderDeviceStatusTone tone;
ResolveStatusDefinition(pageName, out resourceKey, out tone);
LabelText = GetResourceText("HeaderDeviceStatusLabel", "<22><>ǰ״̬:");
StatusText = GetResourceText(resourceKey, GetFallbackStatusText(tone));
ApplyTone(tone);
}
private static void ResolveStatusDefinition(string pageName, out string resourceKey, out HeaderDeviceStatusTone tone)
{
if (string.Equals(pageName, "Home", StringComparison.OrdinalIgnoreCase))
{
resourceKey = "HeaderDeviceModeAuto";
tone = HeaderDeviceStatusTone.Auto;
return;
}
if (string.Equals(pageName, "ManualOperate", StringComparison.OrdinalIgnoreCase))
{
resourceKey = "HeaderDeviceModeManual";
tone = HeaderDeviceStatusTone.Manual;
return;
}
if (string.Equals(pageName, "MenuDeviceMaint", StringComparison.OrdinalIgnoreCase))
{
resourceKey = "HeaderDeviceModeMaintenance";
tone = HeaderDeviceStatusTone.Maintenance;
return;
}
if (string.Equals(pageName, "Recipe", StringComparison.OrdinalIgnoreCase))
{
resourceKey = "HeaderDeviceModeRecipe";
tone = HeaderDeviceStatusTone.Recipe;
return;
}
if (string.Equals(pageName, "PageSettings", StringComparison.OrdinalIgnoreCase))
{
resourceKey = "HeaderDeviceModeSettings";
tone = HeaderDeviceStatusTone.Settings;
return;
}
if (string.Equals(pageName, "PageCalib", StringComparison.OrdinalIgnoreCase))
{
resourceKey = "HeaderDeviceModeCalibration";
tone = HeaderDeviceStatusTone.Calibration;
return;
}
resourceKey = "HeaderDeviceModeUnknown";
tone = HeaderDeviceStatusTone.Unknown;
}
private static string GetFallbackStatusText(HeaderDeviceStatusTone tone)
{
switch (tone)
{
case HeaderDeviceStatusTone.Auto:
return "<22>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD> (AUTO)";
case HeaderDeviceStatusTone.Manual:
return "<22>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD> (MANUAL)";
case HeaderDeviceStatusTone.Maintenance:
return "<22>豸ά<E8B1B8><CEAC> (MAINT)";
case HeaderDeviceStatusTone.Recipe:
return "<22><EFBFBD>༭ (RECIPE)";
case HeaderDeviceStatusTone.Settings:
return "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (SETUP)";
case HeaderDeviceStatusTone.Calibration:
return "<22>У׼ (CALIB)";
default:
return "״̬<D7B4><CCAC><EFBFBD><EFBFBD> (PENDING)";
}
}
private void ApplyTone(HeaderDeviceStatusTone tone)
{
BadgeBackgroundBrush = CreateBackgroundBrush(tone);
BadgeBorderBrush = CreateBorderBrush(tone);
LabelForegroundBrush = CreateSolidBrush(198, 221, 255);
StatusForegroundBrush = Brushes.White;
IndicatorBrush = CreateIndicatorBrush(tone);
}
private static Brush CreateBackgroundBrush(HeaderDeviceStatusTone tone)
{
switch (tone)
{
case HeaderDeviceStatusTone.Auto:
return CreateGradientBrush(Color.FromRgb(42, 111, 84), Color.FromRgb(24, 79, 60));
case HeaderDeviceStatusTone.Manual:
return CreateGradientBrush(Color.FromRgb(148, 103, 37), Color.FromRgb(104, 72, 24));
case HeaderDeviceStatusTone.Maintenance:
return CreateGradientBrush(Color.FromRgb(46, 104, 164), Color.FromRgb(31, 74, 125));
case HeaderDeviceStatusTone.Recipe:
return CreateGradientBrush(Color.FromRgb(95, 76, 153), Color.FromRgb(68, 54, 111));
case HeaderDeviceStatusTone.Settings:
return CreateGradientBrush(Color.FromRgb(88, 103, 124), Color.FromRgb(62, 74, 92));
case HeaderDeviceStatusTone.Calibration:
return CreateGradientBrush(Color.FromRgb(34, 124, 141), Color.FromRgb(23, 90, 102));
default:
return CreateGradientBrush(Color.FromRgb(50, 102, 177), Color.FromRgb(35, 78, 140));
}
}
private static Brush CreateBorderBrush(HeaderDeviceStatusTone tone)
{
switch (tone)
{
case HeaderDeviceStatusTone.Auto:
return CreateSolidBrush(94, 169, 128);
case HeaderDeviceStatusTone.Manual:
return CreateSolidBrush(201, 151, 76);
case HeaderDeviceStatusTone.Maintenance:
return CreateSolidBrush(94, 148, 206);
case HeaderDeviceStatusTone.Recipe:
return CreateSolidBrush(130, 112, 192);
case HeaderDeviceStatusTone.Settings:
return CreateSolidBrush(126, 143, 166);
case HeaderDeviceStatusTone.Calibration:
return CreateSolidBrush(79, 170, 187);
default:
return CreateSolidBrush(96, 145, 214);
}
}
private static Brush CreateIndicatorBrush(HeaderDeviceStatusTone tone)
{
switch (tone)
{
case HeaderDeviceStatusTone.Auto:
return CreateSolidBrush(63, 227, 123);
case HeaderDeviceStatusTone.Manual:
return CreateSolidBrush(255, 183, 66);
case HeaderDeviceStatusTone.Maintenance:
return CreateSolidBrush(72, 196, 255);
case HeaderDeviceStatusTone.Recipe:
return CreateSolidBrush(173, 121, 255);
case HeaderDeviceStatusTone.Settings:
return CreateSolidBrush(158, 172, 196);
case HeaderDeviceStatusTone.Calibration:
return CreateSolidBrush(70, 231, 219);
default:
return CreateSolidBrush(255, 93, 93);
}
}
private static Brush CreateSolidBrush(byte red, byte green, byte blue)
{
return new SolidColorBrush(Color.FromRgb(red, green, blue));
}
private static Brush CreateGradientBrush(Color startColor, Color endColor)
{
return new LinearGradientBrush(startColor, endColor, 0.0);
}
private static string GetResourceText(string resourceKey, string fallback)
{
Application application = Application.Current;
if (application == null)
{
return fallback;
}
object resource = application.TryFindResource(resourceKey);
string text = resource as string;
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
return fallback;
}
}
}

View File

@@ -0,0 +1,607 @@
using MainShell.Resources.CustomControl;
using Newtonsoft.Json;
using Stylet;
using System;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace MainShell.Common
{
public static class HeaderDeviceStatusOverlayHost
{
private static readonly object OverlayRootTag = new object();
private static readonly HeaderDeviceStatusOverlaySettings OverlaySettings = new HeaderDeviceStatusOverlaySettings();
public static void Attach(Window window, HeaderDeviceStatusController controller)
{
if (window == null)
{
throw new ArgumentNullException(nameof(window));
}
if (controller == null)
{
throw new ArgumentNullException(nameof(controller));
}
window.Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() => AttachCore(window, controller)));
}
private static void AttachCore(Window window, HeaderDeviceStatusController controller)
{
Grid existingOverlayRoot = window.Content as Grid;
if (existingOverlayRoot != null && ReferenceEquals(existingOverlayRoot.Tag, OverlayRootTag))
{
return;
}
UIElement currentContent = window.Content as UIElement;
if (currentContent == null)
{
return;
}
Grid overlayRoot = new Grid();
overlayRoot.Tag = OverlayRootTag;
window.Content = null;
overlayRoot.Children.Add(currentContent);
overlayRoot.Children.Add(CreateOverlayElement(controller));
overlayRoot.Children.Add(CreateDebugPanelElement());
window.Content = overlayRoot;
}
private static FrameworkElement CreateOverlayElement(HeaderDeviceStatusController controller)
{
HeaderDeviceStatusView statusView = new HeaderDeviceStatusView();
statusView.DataContext = controller;
statusView.IsHitTestVisible = false;
statusView.HorizontalAlignment = HorizontalAlignment.Left;
statusView.VerticalAlignment = VerticalAlignment.Top;
Binding marginBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.Margin));
marginBinding.Source = OverlaySettings;
BindingOperations.SetBinding(statusView, FrameworkElement.MarginProperty, marginBinding);
Panel.SetZIndex(statusView, short.MaxValue);
return statusView;
}
private static FrameworkElement CreateDebugPanelElement()
{
Border panelBorder = new Border();
panelBorder.Background = new SolidColorBrush(Color.FromArgb(235, 18, 26, 35));
panelBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(72, 93, 118));
panelBorder.BorderThickness = new Thickness(1.0);
panelBorder.CornerRadius = new CornerRadius(6.0);
panelBorder.Padding = new Thickness(10.0, 8.0, 10.0, 8.0);
panelBorder.HorizontalAlignment = HorizontalAlignment.Left;
panelBorder.VerticalAlignment = VerticalAlignment.Top;
Binding panelMarginBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.DebugPanelMargin));
panelMarginBinding.Source = OverlaySettings;
BindingOperations.SetBinding(panelBorder, FrameworkElement.MarginProperty, panelMarginBinding);
Binding panelVisibilityBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.IsDebugPanelVisible));
panelVisibilityBinding.Source = OverlaySettings;
panelVisibilityBinding.Converter = new BooleanToVisibilityConverter();
BindingOperations.SetBinding(panelBorder, UIElement.VisibilityProperty, panelVisibilityBinding);
StackPanel rootPanel = new StackPanel();
rootPanel.Orientation = Orientation.Vertical;
DockPanel headerPanel = new DockPanel();
TextBlock titleText = new TextBlock();
titleText.Text = "Overlay Debug";
titleText.Foreground = Brushes.White;
titleText.FontSize = 13.0;
titleText.FontWeight = FontWeights.SemiBold;
titleText.Margin = new Thickness(0.0, 0.0, 8.0, 6.0);
DockPanel.SetDock(titleText, Dock.Left);
headerPanel.Children.Add(titleText);
Button closeButton = CreateDebugButton("<22><><EFBFBD><EFBFBD>", 44.0);
closeButton.Margin = new Thickness(6.0, 0.0, 0.0, 6.0);
closeButton.Click += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.HideDebugPanel();
};
DockPanel.SetDock(closeButton, Dock.Right);
headerPanel.Children.Add(closeButton);
rootPanel.Children.Add(headerPanel);
TextBlock summaryText = new TextBlock();
summaryText.Foreground = new SolidColorBrush(Color.FromRgb(210, 221, 236));
summaryText.FontSize = 12.0;
summaryText.Margin = new Thickness(0.0, 0.0, 0.0, 8.0);
Binding summaryBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.PositionSummary));
summaryBinding.Source = OverlaySettings;
BindingOperations.SetBinding(summaryText, TextBlock.TextProperty, summaryBinding);
rootPanel.Children.Add(summaryText);
rootPanel.Children.Add(CreateDebugInputRow("Left", nameof(HeaderDeviceStatusOverlaySettings.LeftMarginText)));
rootPanel.Children.Add(CreateDebugInputRow("Top", nameof(HeaderDeviceStatusOverlaySettings.TopMarginText)));
rootPanel.Children.Add(CreateDebugInputRow("Step", nameof(HeaderDeviceStatusOverlaySettings.StepText)));
UniformGrid actionGrid = new UniformGrid();
actionGrid.Columns = 4;
actionGrid.Rows = 1;
actionGrid.Margin = new Thickness(0.0, 0.0, 0.0, 8.0);
Button leftButton = CreateDebugButton("<22><>", 40.0);
leftButton.Click += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.AdjustMargin(-OverlaySettings.Step, 0.0);
};
actionGrid.Children.Add(leftButton);
Button rightButton = CreateDebugButton("<22><>", 40.0);
rightButton.Click += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.AdjustMargin(OverlaySettings.Step, 0.0);
};
actionGrid.Children.Add(rightButton);
Button upButton = CreateDebugButton("<22><>", 40.0);
upButton.Click += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.AdjustMargin(0.0, -OverlaySettings.Step);
};
actionGrid.Children.Add(upButton);
Button downButton = CreateDebugButton("<22><>", 40.0);
downButton.Click += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.AdjustMargin(0.0, OverlaySettings.Step);
};
actionGrid.Children.Add(downButton);
rootPanel.Children.Add(actionGrid);
Button applyButton = CreateDebugButton(<><D3A6>", 0.0);
applyButton.Width = 178.0;
applyButton.Margin = new Thickness(0.0, 0.0, 0.0, 0.0);
applyButton.Click += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.ApplyTextValues();
};
rootPanel.Children.Add(applyButton);
panelBorder.Child = rootPanel;
Panel.SetZIndex(panelBorder, short.MaxValue);
return panelBorder;
}
private static FrameworkElement CreateDebugInputRow(string labelText, string bindingPath)
{
Grid rowGrid = new Grid();
rowGrid.Margin = new Thickness(0.0, 0.0, 0.0, 6.0);
rowGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(44.0) });
rowGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(92.0) });
rowGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(56.0) });
TextBlock label = new TextBlock();
label.Text = labelText;
label.Foreground = new SolidColorBrush(Color.FromRgb(210, 221, 236));
label.FontSize = 12.0;
label.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(label, 0);
rowGrid.Children.Add(label);
TextBox inputBox = CreateDebugTextBox(bindingPath);
Grid.SetColumn(inputBox, 1);
rowGrid.Children.Add(inputBox);
TextBlock hint = new TextBlock();
hint.Text = "<22>س<EFBFBD>";
hint.Foreground = new SolidColorBrush(Color.FromRgb(150, 167, 188));
hint.FontSize = 11.0;
hint.VerticalAlignment = VerticalAlignment.Center;
hint.HorizontalAlignment = HorizontalAlignment.Right;
Grid.SetColumn(hint, 2);
rowGrid.Children.Add(hint);
return rowGrid;
}
private static TextBox CreateDebugTextBox(string bindingPath)
{
TextBox inputBox = new TextBox();
inputBox.Height = 26.0;
inputBox.Margin = new Thickness(0.0);
inputBox.Padding = new Thickness(6.0, 2.0, 6.0, 2.0);
inputBox.Background = new SolidColorBrush(Color.FromRgb(33, 46, 60));
inputBox.BorderBrush = new SolidColorBrush(Color.FromRgb(96, 122, 149));
inputBox.Foreground = Brushes.White;
inputBox.FontSize = 12.0;
inputBox.VerticalContentAlignment = VerticalAlignment.Center;
Binding textBinding = new Binding(bindingPath);
textBinding.Source = OverlaySettings;
textBinding.Mode = BindingMode.TwoWay;
textBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(inputBox, TextBox.TextProperty, textBinding);
inputBox.LostFocus += delegate(object sender, RoutedEventArgs e)
{
OverlaySettings.ApplyTextValues();
};
inputBox.KeyDown += delegate(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter)
{
return;
}
OverlaySettings.ApplyTextValues();
e.Handled = true;
};
return inputBox;
}
private static Button CreateDebugButton(string content, double width)
{
Button button = new Button();
button.Content = content;
if (width > 0.0)
{
button.Width = width;
}
button.Height = 28.0;
button.Margin = new Thickness(0.0, 0.0, 6.0, 0.0);
button.Padding = new Thickness(4.0, 0.0, 4.0, 0.0);
button.Background = new SolidColorBrush(Color.FromRgb(43, 62, 81));
button.BorderBrush = new SolidColorBrush(Color.FromRgb(96, 122, 149));
button.Foreground = Brushes.White;
button.FontSize = 12.0;
return button;
}
private sealed class HeaderDeviceStatusOverlaySettings : PropertyChangedBase, IDisposable
{
private const double DefaultTopMargin = 8.0;
private const double DefaultLeftMargin = 8.0;
private const double DefaultDebugPanelTopOffset = 50.0;
private const double DefaultAdjustStep = 1.0;
private const string ConfigurationDirectoryName = "Configuration";
private const string ConfigurationFileName = "HeaderDeviceStatusOverlay.json";
private readonly string _configurationDirectoryPath;
private readonly string _configurationFilePath;
private readonly FileSystemWatcher _fileWatcher;
private double _topMargin = DefaultTopMargin;
private double _leftMargin = DefaultLeftMargin;
private bool _isDebugPanelVisible;
private double _step = DefaultAdjustStep;
private string _leftMarginText = DefaultLeftMargin.ToString("0.##", CultureInfo.InvariantCulture);
private string _topMarginText = DefaultTopMargin.ToString("0.##", CultureInfo.InvariantCulture);
private string _stepText = DefaultAdjustStep.ToString("0.##", CultureInfo.InvariantCulture);
public HeaderDeviceStatusOverlaySettings()
{
_configurationDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationDirectoryName);
_configurationFilePath = Path.Combine(_configurationDirectoryPath, ConfigurationFileName);
EnsureConfigurationFile();
LoadFromFile();
_fileWatcher = new FileSystemWatcher(_configurationDirectoryPath, ConfigurationFileName);
_fileWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.CreationTime;
_fileWatcher.Changed += OnConfigurationFileChanged;
_fileWatcher.Created += OnConfigurationFileChanged;
_fileWatcher.Renamed += OnConfigurationFileRenamed;
_fileWatcher.EnableRaisingEvents = true;
}
public Thickness Margin
{
get
{
return new Thickness(_leftMargin, _topMargin, 0.0, 0.0);
}
}
public Thickness DebugPanelMargin
{
get
{
return new Thickness(_leftMargin, _topMargin + DefaultDebugPanelTopOffset, 0.0, 0.0);
}
}
public bool IsDebugPanelVisible
{
get
{
return _isDebugPanelVisible;
}
}
public double Step
{
get
{
return _step;
}
}
public string LeftMarginText
{
get
{
return _leftMarginText;
}
set
{
SetAndNotify(ref _leftMarginText, value);
}
}
public string TopMarginText
{
get
{
return _topMarginText;
}
set
{
SetAndNotify(ref _topMarginText, value);
}
}
public string StepText
{
get
{
return _stepText;
}
set
{
SetAndNotify(ref _stepText, value);
}
}
public string PositionSummary
{
get
{
return string.Format("Left={0:0.##}, Top={1:0.##}, Step={2:0.##}", _leftMargin, _topMargin, _step);
}
}
public void Dispose()
{
_fileWatcher.Changed -= OnConfigurationFileChanged;
_fileWatcher.Created -= OnConfigurationFileChanged;
_fileWatcher.Renamed -= OnConfigurationFileRenamed;
_fileWatcher.Dispose();
}
public void AdjustMargin(double leftOffset, double topOffset)
{
double leftMargin = _leftMargin + leftOffset;
double topMargin = _topMargin + topOffset;
UpdateSettings(leftMargin, topMargin, _isDebugPanelVisible, _step);
PersistCurrentConfiguration();
}
public void HideDebugPanel()
{
UpdateSettings(_leftMargin, _topMargin, false, _step);
PersistCurrentConfiguration();
}
public void ApplyTextValues()
{
double leftMargin;
double topMargin;
double step;
bool canParseLeft = TryParseInput(LeftMarginText, _leftMargin, out leftMargin);
bool canParseTop = TryParseInput(TopMarginText, _topMargin, out topMargin);
bool canParseStep = TryParseInput(StepText, _step, out step);
if (!canParseStep)
{
step = _step;
}
step = NormalizeStep(step);
UpdateSettings(leftMargin, topMargin, _isDebugPanelVisible, step);
PersistCurrentConfiguration();
}
private void OnConfigurationFileChanged(object sender, FileSystemEventArgs e)
{
ReloadOnUiThread();
}
private void OnConfigurationFileRenamed(object sender, RenamedEventArgs e)
{
ReloadOnUiThread();
}
private void ReloadOnUiThread()
{
Application currentApplication = Application.Current;
if (currentApplication == null)
{
LoadFromFile();
return;
}
currentApplication.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(LoadFromFile));
}
private void EnsureConfigurationFile()
{
if (!Directory.Exists(_configurationDirectoryPath))
{
Directory.CreateDirectory(_configurationDirectoryPath);
}
if (File.Exists(_configurationFilePath))
{
return;
}
HeaderDeviceStatusOverlayPositionConfiguration configuration = new HeaderDeviceStatusOverlayPositionConfiguration();
configuration.TopMargin = DefaultTopMargin;
configuration.LeftMargin = DefaultLeftMargin;
configuration.ShowDebugPanel = false;
configuration.AdjustStep = DefaultAdjustStep;
SaveConfiguration(configuration);
}
private void LoadFromFile()
{
HeaderDeviceStatusOverlayPositionConfiguration configuration = TryReadConfiguration();
if (configuration == null)
{
return;
}
double topMargin = NormalizeMargin(configuration.TopMargin, DefaultTopMargin);
double leftMargin = NormalizeMargin(configuration.LeftMargin, DefaultLeftMargin);
double adjustStep = NormalizeStep(configuration.AdjustStep);
UpdateSettings(leftMargin, topMargin, configuration.ShowDebugPanel, adjustStep);
}
private HeaderDeviceStatusOverlayPositionConfiguration TryReadConfiguration()
{
for (int attempt = 0; attempt < 3; attempt++)
{
try
{
string content = File.ReadAllText(_configurationFilePath);
if (string.IsNullOrWhiteSpace(content))
{
return new HeaderDeviceStatusOverlayPositionConfiguration();
}
HeaderDeviceStatusOverlayPositionConfiguration configuration = JsonConvert.DeserializeObject<HeaderDeviceStatusOverlayPositionConfiguration>(content);
return configuration ?? new HeaderDeviceStatusOverlayPositionConfiguration();
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
catch (JsonException)
{
return null;
}
}
return null;
}
private void SaveConfiguration(HeaderDeviceStatusOverlayPositionConfiguration configuration)
{
string content = JsonConvert.SerializeObject(configuration, Formatting.Indented);
File.WriteAllText(_configurationFilePath, content);
}
private void PersistCurrentConfiguration()
{
HeaderDeviceStatusOverlayPositionConfiguration configuration = new HeaderDeviceStatusOverlayPositionConfiguration();
configuration.TopMargin = _topMargin;
configuration.LeftMargin = _leftMargin;
configuration.ShowDebugPanel = _isDebugPanelVisible;
configuration.AdjustStep = _step;
SaveConfiguration(configuration);
}
private static double NormalizeMargin(double value, double fallbackValue)
{
if (double.IsNaN(value) || double.IsInfinity(value))
{
return fallbackValue;
}
return value;
}
private static double NormalizeStep(double value)
{
if (double.IsNaN(value) || double.IsInfinity(value) || value <= 0.0)
{
return DefaultAdjustStep;
}
return value;
}
private void UpdateSettings(double leftMargin, double topMargin, bool isDebugPanelVisible, double step)
{
bool hasChanged = !leftMargin.Equals(_leftMargin)
|| !topMargin.Equals(_topMargin)
|| !isDebugPanelVisible.Equals(_isDebugPanelVisible)
|| !step.Equals(_step);
if (!hasChanged)
{
return;
}
_leftMargin = leftMargin;
_topMargin = topMargin;
_isDebugPanelVisible = isDebugPanelVisible;
_step = step;
LeftMarginText = FormatValue(_leftMargin);
TopMarginText = FormatValue(_topMargin);
StepText = FormatValue(_step);
NotifyOfPropertyChange(nameof(Margin));
NotifyOfPropertyChange(nameof(DebugPanelMargin));
NotifyOfPropertyChange(nameof(IsDebugPanelVisible));
NotifyOfPropertyChange(nameof(Step));
NotifyOfPropertyChange(nameof(PositionSummary));
}
private static bool TryParseInput(string text, double fallbackValue, out double value)
{
string trimmedText = string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim();
if (string.IsNullOrEmpty(trimmedText))
{
value = fallbackValue;
return false;
}
return double.TryParse(trimmedText, NumberStyles.Float, CultureInfo.InvariantCulture, out value)
|| double.TryParse(trimmedText, NumberStyles.Float, CultureInfo.CurrentCulture, out value);
}
private static string FormatValue(double value)
{
return value.ToString("0.##", CultureInfo.InvariantCulture);
}
}
private sealed class HeaderDeviceStatusOverlayPositionConfiguration
{
public double TopMargin { get; set; }
public double LeftMargin { get; set; }
public bool ShowDebugPanel { get; set; }
public double AdjustStep { get; set; }
}
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace MainShell.Common
{
/// <summary>
/// Localization resource helper.
/// </summary>
public static class LanguageResourceHelper
{
private static readonly IReadOnlyDictionary<MessageKey, string> ResourceKeyMap =
new Dictionary<MessageKey, string>
{
{ MessageKey.TitleInfo, "Msg_Title_Info" },
{ MessageKey.TitleWarning, "Msg_Title_Warning" },
{ MessageKey.TitleError, "Msg_Title_Error" },
{ MessageKey.TitleConfirm, "Msg_Title_Confirm" },
{ MessageKey.VisionCameraNotFound, "Msg_Vision_CameraNotFound" },
{ MessageKey.VisionCameraNotOpen, "Msg_Vision_CameraNotOpen" },
{ MessageKey.VisionCameraNotGrabbing, "Msg_Vision_CameraNotGrabbing" },
{ MessageKey.VisionCaptureTimeout, "Msg_Vision_CaptureTimeout" },
{ MessageKey.VisionNoFrameReturned, "Msg_Vision_NoFrameReturned" },
{ MessageKey.VisionSoftTriggerFailed, "Msg_Vision_SoftTriggerFailed" },
{ MessageKey.VisionDriverError, "Msg_Vision_DriverError" },
{ MessageKey.VisionImageIsNull, "Msg_Vision_ImageIsNull" },
{ MessageKey.VisionRequestInvalid, "Msg_Vision_RequestInvalid" },
{ MessageKey.VisionTimeoutInvalid, "Msg_Vision_TimeoutInvalid" },
{ MessageKey.VisionOperationCancelled, "Msg_Vision_OperationCancelled" },
{ MessageKey.VisionTemplatePathEmpty, "Msg_Vision_TemplatePathEmpty" },
{ MessageKey.VisionTemplateRoiInvalid, "Msg_Vision_TemplateRoiInvalid" },
{ MessageKey.VisionTemplateMinScoreInvalid, "Msg_Vision_TemplateMinScoreInvalid" },
{ MessageKey.VisionTemplateAlgorithmNotImplemented, "Msg_Vision_TemplateAlgorithmNotImplemented" },
{ MessageKey.VisionTemplateMatchFailed, "Msg_Vision_TemplateMatchFailed" },
{ MessageKey.VisionCommonAlgorithmNotSupported, "Msg_Vision_CommonAlgorithmNotSupported" },
{ MessageKey.VisionCommonAlgorithmExecutionFailed, "Msg_Vision_CommonAlgorithmExecutionFailed" },
{ MessageKey.VisionChipMapSortInputInvalid, "Msg_Vision_ChipMapSortInputInvalid" },
{ MessageKey.CommonUnknownError, "Msg_Common_UnknownError" },
{ MessageKey.CommonOperationSucceeded, "Msg_Common_OperationSucceeded" },
{ MessageKey.CommonOperationFailed, "Msg_Common_OperationFailed" },
{ MessageKey.CommonSaveSucceeded, "Msg_Common_SaveSucceeded" },
{ MessageKey.CommonSaveFailed, "Msg_Common_SaveFailed" },
{ MessageKey.AxisSoftLimitSaveConfirm, "Msg_AxisSoftLimit_SaveConfirm" },
{ MessageKey.AxisSoftLimitSaveFailedWithReason, "Msg_AxisSoftLimit_SaveFailedWithReason" },
{ MessageKey.AxisSoftLimitLoadFailedWithReason, "Msg_AxisSoftLimit_LoadFailedWithReason" },
{ MessageKey.AxisSoftLimitRangeInvalid, "Msg_AxisSoftLimit_RangeInvalid" },
{ MessageKey.AxisSoftLimitApplySucceeded, "Msg_AxisSoftLimit_ApplySucceeded" },
{ MessageKey.AxisSoftLimitApplyFailedWithReason, "Msg_AxisSoftLimit_ApplyFailedWithReason" },
{ MessageKey.AxisSoftLimitApplyAllSucceeded, "Msg_AxisSoftLimit_ApplyAllSucceeded" },
{ MessageKey.AxisSoftLimitApplyAllFailedWithReason, "Msg_AxisSoftLimit_ApplyAllFailedWithReason" },
{ MessageKey.AxisSoftLimitNoAxisData, "Msg_AxisSoftLimit_NoAxisData" },
{ MessageKey.AxisSoftLimitSelectedAxisRequired, "Msg_AxisSoftLimit_SelectedAxisRequired" },
{ MessageKey.CommonDeleteConfirm, "Msg_Common_DeleteConfirm" },
{ MessageKey.CommonExitConfirm, "Msg_Common_ExitConfirm" },
{ MessageKey.DeviceNotInitialized, "Msg_Device_NotInitialized" },
{ MessageKey.DeviceBusy, "Msg_Device_Busy" },
{ MessageKey.DeviceDisconnected, "Msg_Device_Disconnected" },
{ MessageKey.DeviceHomeRequired, "Msg_Device_HomeRequired" },
{ MessageKey.ParamInvalid, "Msg_Param_Invalid" },
{ MessageKey.ParamEmpty, "Msg_Param_Empty" },
{ MessageKey.ParamOutOfRange, "Msg_Param_OutOfRange" },
{ MessageKey.StartProcessConfirm, "Msg_Process_StartConfirm" },
{ MessageKey.StartProcessFailed, "Msg_Process_StartFailed" },
{ MessageKey.StopProcessConfirm, "Msg_Process_StopConfirm" },
{ MessageKey.StopProcessFailed, "Msg_Process_StopFailed" },
{ MessageKey.ProcessFailed, "Msg_Process_Failed" },
{ MessageKey.ProcessFailedWithReason, "Msg_Process_FailedWithReason" },
{ MessageKey.ProcessStepFailedWithReason, "Msg_Process_StepFailedWithReason" },
{ MessageKey.ProcessSubstratePositionFailedWithReason, "Msg_Process_SubstratePositionFailedWithReason" },
{ MessageKey.ProcessSubstratePositionRecipeNotLoaded, "Msg_Process_SubstratePositionRecipeNotLoaded" },
{ MessageKey.ProcessSubstratePositionMarkParameterMissing, "Msg_Process_SubstratePositionMarkParameterMissing" },
{ MessageKey.ProcessSubstratePositionNoEnabledMarks, "Msg_Process_SubstratePositionNoEnabledMarks" },
{ MessageKey.ProcessSubstratePositionAlignmentFailedWithReason, "Msg_Process_SubstratePositionAlignmentFailedWithReason" },
{ MessageKey.ProcessDiePositionFailedWithReason, "Msg_Process_DiePositionFailedWithReason" },
{ MessageKey.ProcessDiePositionRecipeNotLoaded, "Msg_Process_DiePositionRecipeNotLoaded" },
{ MessageKey.ProcessDiePositionCoordinateGenerationMissing, "Msg_Process_DiePositionCoordinateGenerationMissing" },
{ MessageKey.ProcessDiePositionWaferInfoMissing, "Msg_Process_DiePositionWaferInfoMissing" },
{ MessageKey.ProcessSubstrateHeightMeasureFailedWithReason, "Msg_Process_SubstrateHeightMeasureFailedWithReason" },
{ MessageKey.ProcessSubstrateHeightMeasureRecipeNotLoaded, "Msg_Process_SubstrateHeightMeasureRecipeNotLoaded" },
{ MessageKey.ProcessSubstrateHeightMeasureSettingMissing, "Msg_Process_SubstrateHeightMeasureSettingMissing" },
{ MessageKey.ProcessSubstrateHeightMeasureNoPoints, "Msg_Process_SubstrateHeightMeasureNoPoints" },
{ MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid, "Msg_Process_SubstrateHeightMeasurePointPositionInvalid" },
{ MessageKey.NeedleCalibrationLoadFailed, "Msg_NeedleCalibration_LoadFailed" },
{ MessageKey.NeedleCalibrationTouchCountMustGreaterThanZero, "Msg_NeedleCalibration_TouchCountMustGreaterThanZero" },
{ MessageKey.NeedleCalibrationCompleted, "Msg_NeedleCalibration_Completed" },
{ MessageKey.NeedleCalibrationStopped, "Msg_NeedleCalibration_Stopped" },
{ MessageKey.NeedleCalibrationCanceled, "Msg_NeedleCalibration_Canceled" },
{ MessageKey.OriginCalibOpenVisionTemplateFailed, "Msg_OriginCalib_OpenVisionTemplateFailed" },
{ MessageKey.OriginCalibAxisNotFound, "Msg_OriginCalib_AxisNotFound" },
{ MessageKey.OriginCalibReadPositionFailed, "Msg_OriginCalib_ReadPositionFailed" },
{ MessageKey.OriginCalibMoveFailed, "Msg_OriginCalib_MoveFailed" },
{ MessageKey.OriginCalibDeleteAxisFailed, "Msg_OriginCalib_DeleteAxisFailed" },
{ MessageKey.OriginCalibDeleteAxisDuringCalibration, "Msg_OriginCalib_DeleteAxisDuringCalibration" },
{ MessageKey.OriginCalibCalibrationCanceled, "Msg_OriginCalib_CalibrationCanceled" },
{ MessageKey.OriginCalibCalibrationFailed, "Msg_OriginCalib_CalibrationFailed" },
{ MessageKey.OriginCalibConfigSaved, "Msg_OriginCalib_ConfigSaved" },
{ MessageKey.OriginCalibConfigSaveFailed, "Msg_OriginCalib_ConfigSaveFailed" },
{ MessageKey.OriginCalibNoCalibratedModules, "Msg_OriginCalib_NoCalibratedModules" },
{ MessageKey.OriginCalibControllerWriteCompleted, "Msg_OriginCalib_ControllerWriteCompleted" },
{ MessageKey.OriginCalibControllerWriteNotReady, "Msg_OriginCalib_ControllerWriteNotReady" },
{ MessageKey.OriginCalibControllerWritePartialSuccess, "Msg_OriginCalib_ControllerWritePartialSuccess" },
{ MessageKey.OriginCalibControllerWriteNotImplemented, "Msg_OriginCalib_ControllerWriteNotImplemented" },
{ MessageKey.PidNoProfileSelected, "Msg_Pid_NoProfileSelected" },
{ MessageKey.PidNoFilteringParameterSelected, "Msg_Pid_NoFilteringParameterSelected" },
{ MessageKey.PidReadSucceeded, "Msg_Pid_ReadSucceeded" },
{ MessageKey.PidReadFailed, "Msg_Pid_ReadFailed" },
{ MessageKey.PidWriteSucceeded, "Msg_Pid_WriteSucceeded" },
{ MessageKey.PidWriteFailed, "Msg_Pid_WriteFailed" },
{ MessageKey.DieRecheckSelectedPointRequired, "Msg_DieRecheck_SelectedPointRequired" },
{ MessageKey.DieRecheckMoveDeviceUnavailable, "Msg_DieRecheck_MoveDeviceUnavailable" },
{ MessageKey.DieRecheckStatusOk, "Msg_DieRecheck_StatusOk" },
{ MessageKey.DieRecheckStatusMissingBond, "Msg_DieRecheck_StatusMissingBond" },
{ MessageKey.DieRecheckStatusXExceeded, "Msg_DieRecheck_StatusXExceeded" },
{ MessageKey.DieRecheckStatusYExceeded, "Msg_DieRecheck_StatusYExceeded" }
};
public static string GetString(MessageKey messageKey)
{
return GetString(messageKey, messageKey.ToString());
}
public static string GetString(MessageKey messageKey, string fallback)
{
if (messageKey == MessageKey.None)
{
return fallback ?? string.Empty;
}
if (!ResourceKeyMap.TryGetValue(messageKey, out var resourceKey))
{
return fallback ?? messageKey.ToString();
}
var application = Application.Current;
if (application == null)
{
return fallback ?? resourceKey;
}
var resource = application.TryFindResource(resourceKey);
if (resource is string text && !string.IsNullOrWhiteSpace(text))
{
return text;
}
return fallback ?? resourceKey;
}
public static string Format(MessageKey messageKey, params object[] args)
{
var format = GetString(messageKey);
return args == null || args.Length == 0
? format
: string.Format(format, args);
}
}
}

View File

@@ -0,0 +1,104 @@
using MainShell.Resources.CustomControl;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace MainShell.Common
{
public static class Loading
{
private static LoadingWaitView _view;
// 全局初始化:在主程序启动或插件加载时调用一次
public static void Initialize(Window mainWindow)
{
if (_view != null) return; // 避免重复加载
_view = new LoadingWaitView();
// 获取窗口的根内容
var rootContent = mainWindow.Content;
if (rootContent is Panel rootPanel)
{
// 如果根部本来就是 Panel (Grid/Canvas等),直接添加
rootPanel.Children.Add(_view);
}
else if (rootContent is ContentControl contentControl)
{
// 如果根部是 ContentControl我们需要把原本的内容包在一个新的 Grid 里
var originalContent = contentControl.Content as UIElement;
contentControl.Content = null;
var wrapperGrid = new Grid();
wrapperGrid.Children.Add(originalContent); // 原有内容
wrapperGrid.Children.Add(_view); // 我们的遮罩层
contentControl.Content = wrapperGrid;
}
// 确保遮罩层铺满并置于顶层
Grid.SetColumnSpan(_view, 99);
Grid.SetRowSpan(_view, 99);
Panel.SetZIndex(_view, 9999);
}
// 基础调用Loading.Show("计算中...");
public static void Show(string msg, bool isIndeterminate, bool canCancel = true) => LoadingService.Instance.Show(msg, canCancel, isIndeterminate);
public static void Hide() => LoadingService.Instance.Hide();
public static void Report(double val) => LoadingService.Instance.Report(val);
// 获取当前令牌Loading.Token
public static CancellationToken Token => LoadingService.Instance.Token;
// 高级用法:使用 using 块自动开启和关闭
public static IDisposable Manager(string msg, bool canCancel = true)
{
Show(msg, canCancel);
return new DisposableAction(() => Hide());
}
public static async Task<bool> RunAsync(
Func<CancellationToken, IProgress<double>, Task> work,
string message,
bool canCancel = true, bool isIndeterminate = false,
Action<Exception> onError = null)
{
using (Manager(message, canCancel))
{
try
{
LoadingService.Instance.Show(message, isIndeterminate, canCancel);
var progress = new Progress<double>(val =>
Application.Current.Dispatcher.Invoke(() => LoadingService.Instance.ProgressValue = val));
await Task.Run(() => work(LoadingService.Instance.Token, progress), LoadingService.Instance.Token);
return true; // 执行成功
}
catch (OperationCanceledException)
{
// 用户点击了取消,通常不需要弹窗,直接返回 false
return false;
}
catch (Exception ex)
{
// 捕获其他所有异常(硬件错误、逻辑错误等)
if (onError != null)
onError(ex);
else
MessageBox.Show($"操作失败: {ex.Message}", "系统错误", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Windows;
namespace MainShell.Common
{
/// <summary>
/// <20><><EFBFBD>ڶ<EFBFBD><DAB6><EFBFBD><EFBFBD><EFBFBD>ö<EFBFBD><C3B6><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><D7B0>
/// </summary>
public static class LocalizedMessageBox
{
public static MessageBoxResult Show(MessageKey messageKey)
{
return Show(null, messageKey, MessageKey.TitleInfo, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None);
}
public static MessageBoxResult Show(MessageKey messageKey, MessageKey captionKey)
{
return Show(null, messageKey, captionKey, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None);
}
public static MessageBoxResult Show(MessageKey messageKey, MessageKey captionKey, MessageBoxButton button, MessageBoxImage icon)
{
return Show(null, messageKey, captionKey, button, icon, MessageBoxResult.None);
}
public static MessageBoxResult Show(Window owner, MessageKey messageKey, MessageKey captionKey, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult = MessageBoxResult.None)
{
return MwMessageBox.InvokeOnUiThread(() => ShowCore(owner, messageKey, captionKey, button, icon, defaultResult));
}
public static MessageBoxResult ShowFormat(MessageKey messageKey, MessageKey captionKey, MessageBoxButton button, MessageBoxImage icon, params object[] args)
{
return MwMessageBox.InvokeOnUiThread(() => ShowFormatCore(messageKey, captionKey, button, icon, args));
}
private static MessageBoxResult ShowCore(Window owner, MessageKey messageKey, MessageKey captionKey, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
{
string message = LanguageResourceHelper.GetString(messageKey);
string caption = captionKey == MessageKey.None
? string.Empty
: LanguageResourceHelper.GetString(captionKey);
return MwMessageBox.Show(owner, message, caption, button, icon, defaultResult);
}
private static MessageBoxResult ShowFormatCore(MessageKey messageKey, MessageKey captionKey, MessageBoxButton button, MessageBoxImage icon, object[] args)
{
string message = LanguageResourceHelper.Format(messageKey, args);
string caption = captionKey == MessageKey.None
? string.Empty
: LanguageResourceHelper.GetString(captionKey);
return MwMessageBox.Show(message, caption, button, icon);
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace MainShell.Common
{
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD> MessageKey <20><>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD>ܿ<EFBFBD>ʧ<EFBFBD>ܣ<EFBFBD><DCA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class LocalizedProcessException : Exception
{
public MessageKey FailureMessageKey { get; }
public string[] FailureMessageArguments { get; }
public LocalizedProcessException(MessageKey failureMessageKey, params string[] failureMessageArguments)
: base(LanguageResourceHelper.Format(failureMessageKey, ConvertArguments(failureMessageArguments)))
{
FailureMessageKey = failureMessageKey;
FailureMessageArguments = failureMessageArguments ?? Array.Empty<string>();
}
public static object[] ConvertArguments(string[] failureMessageArguments)
{
if (failureMessageArguments == null || failureMessageArguments.Length == 0)
{
return Array.Empty<object>();
}
object[] convertedArguments = new object[failureMessageArguments.Length];
for (int i = 0; i < failureMessageArguments.Length; i++)
{
convertedArguments[i] = failureMessageArguments[i];
}
return convertedArguments;
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
namespace MainShell.Common
{
/// <summary>
/// Message keys used by the localization system.
/// </summary>
public enum MessageKey
{
None = 0,
TitleInfo,
TitleWarning,
TitleError,
TitleConfirm,
VisionCameraNotFound,
VisionCameraNotOpen,
VisionCameraNotGrabbing,
VisionCaptureTimeout,
VisionNoFrameReturned,
VisionSoftTriggerFailed,
VisionDriverError,
VisionImageIsNull,
VisionRequestInvalid,
VisionTimeoutInvalid,
VisionOperationCancelled,
VisionTemplatePathEmpty,
VisionTemplateRoiInvalid,
VisionTemplateMinScoreInvalid,
VisionTemplateAlgorithmNotImplemented,
VisionTemplateMatchFailed,
VisionCommonAlgorithmNotSupported,
VisionCommonAlgorithmExecutionFailed,
VisionChipMapSortInputInvalid,
CommonUnknownError,
CommonOperationSucceeded,
CommonOperationFailed,
CommonSaveSucceeded,
CommonSaveFailed,
AxisSoftLimitSaveConfirm,
AxisSoftLimitSaveFailedWithReason,
AxisSoftLimitLoadFailedWithReason,
AxisSoftLimitRangeInvalid,
AxisSoftLimitApplySucceeded,
AxisSoftLimitApplyFailedWithReason,
AxisSoftLimitApplyAllSucceeded,
AxisSoftLimitApplyAllFailedWithReason,
AxisSoftLimitNoAxisData,
AxisSoftLimitSelectedAxisRequired,
CommonDeleteConfirm,
CommonExitConfirm,
DeviceNotInitialized,
DeviceBusy,
DeviceDisconnected,
DeviceHomeRequired,
ParamInvalid,
ParamEmpty,
ParamOutOfRange,
StartProcessConfirm,
StartProcessFailed,
StopProcessConfirm,
StopProcessFailed,
ProcessFailed,
ProcessFailedWithReason,
ProcessStepFailedWithReason,
ProcessSubstratePositionFailedWithReason,
ProcessSubstratePositionRecipeNotLoaded,
ProcessSubstratePositionMarkParameterMissing,
ProcessSubstratePositionNoEnabledMarks,
ProcessSubstratePositionAlignmentFailedWithReason,
ProcessDiePositionFailedWithReason,
ProcessDiePositionRecipeNotLoaded,
ProcessDiePositionCoordinateGenerationMissing,
ProcessDiePositionWaferInfoMissing,
ProcessSubstrateHeightMeasureFailedWithReason,
ProcessSubstrateHeightMeasureRecipeNotLoaded,
ProcessSubstrateHeightMeasureSettingMissing,
ProcessSubstrateHeightMeasureNoPoints,
ProcessSubstrateHeightMeasurePointPositionInvalid,
NeedleCalibrationLoadFailed,
NeedleCalibrationTouchCountMustGreaterThanZero,
NeedleCalibrationCompleted,
NeedleCalibrationStopped,
NeedleCalibrationCanceled,
OriginCalibOpenVisionTemplateFailed,
OriginCalibAxisNotFound,
OriginCalibReadPositionFailed,
OriginCalibMoveFailed,
OriginCalibDeleteAxisFailed,
OriginCalibDeleteAxisDuringCalibration,
OriginCalibCalibrationCanceled,
OriginCalibCalibrationFailed,
OriginCalibConfigSaved,
OriginCalibConfigSaveFailed,
OriginCalibNoCalibratedModules,
OriginCalibControllerWriteCompleted,
OriginCalibControllerWriteNotReady,
OriginCalibControllerWritePartialSuccess,
OriginCalibControllerWriteNotImplemented,
PidNoProfileSelected,
PidNoFilteringParameterSelected,
PidReadSucceeded,
PidReadFailed,
PidWriteSucceeded,
PidWriteFailed,
DieRecheckSelectedPointRequired,
DieRecheckMoveDeviceUnavailable,
DieRecheckStatusOk,
DieRecheckStatusMissingBond,
DieRecheckStatusXExceeded,
DieRecheckStatusYExceeded
}
}

View File

@@ -0,0 +1,639 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using MainShell.Common.View;
using MainShell.Common.ViewModel;
namespace MainShell.Common
{
public static class MwMessageBoxGlyphs
{
public const string None = "\uE686";
public const string Information = "\uE625";
public const string Warning = "\uE7BD";
public const string Error = "\uE76C";
public const string Question = "\uE686";
}
public sealed class MwMessageBoxButtonText
{
public string Ok { get; set; } = <><C8B7>";
public string Cancel { get; set; } = <><C8A1>";
public string Yes { get; set; } = "<22><>";
public string No { get; set; } = "<22><>";
public string Retry { get; set; } = "<22><><EFBFBD><EFBFBD>";
public string Abort { get; set; } = "<22><>ֹ";
public string Ignore { get; set; } = "<22><><EFBFBD><EFBFBD>";
public static MwMessageBoxButtonText CreateDefault()
{
return new MwMessageBoxButtonText();
}
}
public sealed class MwMessageBoxButtonDefinition
{
public string Text { get; set; }
public MessageBoxResult Result { get; set; }
public bool IsDefault { get; set; }
public bool IsCancel { get; set; }
public bool IsPrimary { get; set; }
public Style ButtonStyle { get; set; }
}
public sealed class MwMessageBoxIconTheme
{
public string NoneGlyph { get; set; } = MwMessageBoxGlyphs.None;
public string InformationGlyph { get; set; } = MwMessageBoxGlyphs.Information;
public string WarningGlyph { get; set; } = MwMessageBoxGlyphs.Warning;
public string ErrorGlyph { get; set; } = MwMessageBoxGlyphs.Error;
public string QuestionGlyph { get; set; } = MwMessageBoxGlyphs.Question;
public static MwMessageBoxIconTheme CreateDefault()
{
return new MwMessageBoxIconTheme();
}
}
public sealed class MwMessageBoxColorPalette
{
public MwMessageBoxColorPalette()
{
}
public MwMessageBoxColorPalette(string accentColor, string iconBackgroundColor, string iconForegroundColor)
{
AccentColor = accentColor;
IconBackgroundColor = iconBackgroundColor;
IconForegroundColor = iconForegroundColor;
}
public string AccentColor { get; set; }
public string IconBackgroundColor { get; set; }
public string IconForegroundColor { get; set; }
}
public sealed class MwMessageBoxButtonColorTheme
{
public MwMessageBoxButtonColorTheme()
{
}
public MwMessageBoxButtonColorTheme(string backgroundColor, string hoverBackgroundColor, string pressedBackgroundColor, string borderColor, string foregroundColor)
{
BackgroundColor = backgroundColor;
HoverBackgroundColor = hoverBackgroundColor;
PressedBackgroundColor = pressedBackgroundColor;
BorderColor = borderColor;
ForegroundColor = foregroundColor;
}
public string BackgroundColor { get; set; }
public string HoverBackgroundColor { get; set; }
public string PressedBackgroundColor { get; set; }
public string BorderColor { get; set; }
public string ForegroundColor { get; set; }
}
public sealed class MwMessageBoxVisualTheme
{
public MwMessageBoxColorPalette NonePalette { get; set; } = new MwMessageBoxColorPalette("#9CA3AF", "#F3F4F6", "#6B7280");
public MwMessageBoxColorPalette InformationPalette { get; set; } = new MwMessageBoxColorPalette("#38BDF8", "#E0F2FE", "#0284C7");
public MwMessageBoxColorPalette WarningPalette { get; set; } = new MwMessageBoxColorPalette("#F59E0B", "#FEF3C7", "#D97706");
public MwMessageBoxColorPalette ErrorPalette { get; set; } = new MwMessageBoxColorPalette("#EF4444", "#FEE2E2", "#DC2626");
public MwMessageBoxColorPalette QuestionPalette { get; set; } = new MwMessageBoxColorPalette("#3B82F6", "#DBEAFE", "#2563EB");
public MwMessageBoxButtonColorTheme PrimaryButtonTheme { get; set; } = new MwMessageBoxButtonColorTheme("#0E6AE8", "#0B5AD0", "#094BAC", "#0E6AE8", "#FFFFFF");
public MwMessageBoxButtonColorTheme SecondaryButtonTheme { get; set; } = new MwMessageBoxButtonColorTheme("#F3F4F6", "#E5E7EB", "#D1D5DB", "#D1D5DB", "#374151");
public static MwMessageBoxVisualTheme CreateDefault()
{
return new MwMessageBoxVisualTheme();
}
}
public sealed class MwMessageBoxState
{
public string Caption { get; set; }
public string Message { get; set; }
public string IconGlyph { get; set; }
public string IconPathData { get; set; }
public string TimeText { get; set; }
public Brush AccentBrush { get; set; }
public Brush IconBackgroundBrush { get; set; }
public Brush IconForegroundBrush { get; set; }
public IReadOnlyList<MwMessageBoxButtonDefinition> Buttons { get; set; }
public MessageBoxResult DefaultResult { get; set; }
public TextAlignment MessageTextAlignment { get; set; } = TextAlignment.Left;
public FlowDirection WindowFlowDirection { get; set; } = FlowDirection.LeftToRight;
public FlowDirection MessageFlowDirection { get; set; } = FlowDirection.LeftToRight;
}
public static class MwMessageBox
{
public static MwMessageBoxButtonText DefaultButtonText { get; set; } = MwMessageBoxButtonText.CreateDefault();
public static MwMessageBoxIconTheme IconTheme { get; set; } = MwMessageBoxIconTheme.CreateDefault();
public static MwMessageBoxVisualTheme VisualTheme { get; set; } = MwMessageBoxVisualTheme.CreateDefault();
public static MessageBoxResult Show(string messageBoxText)
{
return Show(null, messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(string messageBoxText, string caption)
{
return Show(null, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button)
{
return Show(null, messageBoxText, caption, button, MessageBoxImage.None, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
{
return Show(null, messageBoxText, caption, button, icon, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
{
return Show(null, messageBoxText, caption, button, icon, defaultResult, 0, null);
}
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options)
{
return Show(null, messageBoxText, caption, button, icon, defaultResult, options, null);
}
public static MessageBoxResult Show(Window owner, string messageBoxText)
{
return Show(owner, messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption)
{
return Show(owner, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button)
{
return Show(owner, messageBoxText, caption, button, MessageBoxImage.None, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
{
return Show(owner, messageBoxText, caption, button, icon, MessageBoxResult.None, 0, null);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
{
return Show(owner, messageBoxText, caption, button, icon, defaultResult, 0, null);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options)
{
return Show(owner, messageBoxText, caption, button, icon, defaultResult, options, null);
}
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MwMessageBoxButtonText buttonText)
{
return Show(null, messageBoxText, caption, button, icon, MessageBoxResult.None, 0, buttonText);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MwMessageBoxButtonText buttonText)
{
return Show(owner, messageBoxText, caption, button, icon, MessageBoxResult.None, 0, buttonText);
}
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options, MwMessageBoxButtonText buttonText)
{
return InvokeOnUiThread(() => ShowCore(owner, messageBoxText, caption, button, icon, defaultResult, options, buttonText));
}
public static MessageBoxResult ShowCustom(string messageBoxText, string caption, MessageBoxImage icon, params MwMessageBoxButtonDefinition[] buttons)
{
return ShowCustom(null, messageBoxText, caption, icon, MessageBoxResult.None, 0, buttons);
}
public static MessageBoxResult ShowCustom(Window owner, string messageBoxText, string caption, MessageBoxImage icon, params MwMessageBoxButtonDefinition[] buttons)
{
return ShowCustom(owner, messageBoxText, caption, icon, MessageBoxResult.None, 0, buttons);
}
public static MessageBoxResult ShowCustom(Window owner, string messageBoxText, string caption, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options, params MwMessageBoxButtonDefinition[] buttons)
{
return InvokeOnUiThread(() => ShowCustomCore(owner, messageBoxText, caption, icon, defaultResult, options, buttons));
}
internal static T InvokeOnUiThread<T>(Func<T> callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
Application application = Application.Current;
if (application == null)
{
return callback();
}
if (application.Dispatcher == null || application.Dispatcher.CheckAccess())
{
return callback();
}
return application.Dispatcher.Invoke(callback);
}
private static MessageBoxResult ShowCore(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options, MwMessageBoxButtonText buttonText)
{
owner = owner ?? GetActiveWindow();
List<MwMessageBoxButtonDefinition> definitions = CreateButtonDefinitions(button, defaultResult, buttonText ?? DefaultButtonText).ToList();
MwMessageBoxState state = CreateState(messageBoxText, caption, icon, definitions, defaultResult, options);
MwMessageBoxWindowViewModel viewModel = new MwMessageBoxWindowViewModel(state);
MwMessageBoxWindow dialog = new MwMessageBoxWindow(viewModel);
if (owner != null)
{
dialog.Owner = owner;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
dialog.ShowDialog();
return viewModel.Result;
}
private static MessageBoxResult ShowCustomCore(Window owner, string messageBoxText, string caption, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options, params MwMessageBoxButtonDefinition[] buttons)
{
owner = owner ?? GetActiveWindow();
List<MwMessageBoxButtonDefinition> definitions = NormalizeButtonDefinitions(buttons, defaultResult).ToList();
MwMessageBoxState state = CreateState(messageBoxText, caption, icon, definitions, defaultResult, options);
MwMessageBoxWindowViewModel viewModel = new MwMessageBoxWindowViewModel(state);
MwMessageBoxWindow dialog = new MwMessageBoxWindow(viewModel);
if (owner != null)
{
dialog.Owner = owner;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
dialog.ShowDialog();
return viewModel.Result;
}
private static IEnumerable<MwMessageBoxButtonDefinition> CreateButtonDefinitions(MessageBoxButton button, MessageBoxResult defaultResult, MwMessageBoxButtonText buttonText)
{
var labels = buttonText ?? MwMessageBoxButtonText.CreateDefault();
var definitions = new List<MwMessageBoxButtonDefinition>();
switch (button)
{
case MessageBoxButton.OK:
definitions.Add(CreateDefinition(labels.Ok, MessageBoxResult.OK, true));
break;
case MessageBoxButton.OKCancel:
definitions.Add(CreateDefinition(labels.Ok, MessageBoxResult.OK, true));
definitions.Add(CreateDefinition(labels.Cancel, MessageBoxResult.Cancel, false, true));
break;
case MessageBoxButton.YesNo:
definitions.Add(CreateDefinition(labels.Yes, MessageBoxResult.Yes, true));
definitions.Add(CreateDefinition(labels.No, MessageBoxResult.No));
break;
case MessageBoxButton.YesNoCancel:
definitions.Add(CreateDefinition(labels.Yes, MessageBoxResult.Yes, true));
definitions.Add(CreateDefinition(labels.No, MessageBoxResult.No));
definitions.Add(CreateDefinition(labels.Cancel, MessageBoxResult.Cancel, false, true));
break;
default:
definitions.Add(CreateDefinition(labels.Ok, MessageBoxResult.OK, true));
break;
}
return NormalizeButtonDefinitions(definitions, defaultResult);
}
private static IEnumerable<MwMessageBoxButtonDefinition> NormalizeButtonDefinitions(IEnumerable<MwMessageBoxButtonDefinition> buttons, MessageBoxResult defaultResult)
{
var definitions = (buttons ?? Enumerable.Empty<MwMessageBoxButtonDefinition>())
.Where(item => item != null)
.Select(item => new MwMessageBoxButtonDefinition
{
Text = string.IsNullOrWhiteSpace(item.Text) ? item.Result.ToString() : item.Text,
Result = item.Result,
IsCancel = item.IsCancel,
IsDefault = item.IsDefault,
IsPrimary = item.IsPrimary,
ButtonStyle = item.ButtonStyle
})
.ToList();
if (definitions.Count == 0)
{
definitions.Add(CreateDefinition(DefaultButtonText.Ok, MessageBoxResult.OK, true));
}
var targetDefault = defaultResult == MessageBoxResult.None
? definitions.FirstOrDefault(item => item.IsDefault)?.Result ?? definitions[0].Result
: defaultResult;
foreach (var item in definitions)
{
item.IsDefault = item.Result == targetDefault;
}
if (!definitions.Any(item => item.IsCancel))
{
var cancelButton = definitions.FirstOrDefault(item => item.Result == MessageBoxResult.Cancel);
if (cancelButton != null)
{
cancelButton.IsCancel = true;
}
}
if (!definitions.Any(item => item.IsPrimary))
{
var primary = definitions.FirstOrDefault(item => item.IsDefault) ?? definitions[0];
primary.IsPrimary = true;
}
foreach (var item in definitions)
{
item.ButtonStyle = item.ButtonStyle ?? GetButtonStyle(item.IsPrimary);
}
return definitions;
}
private static MwMessageBoxButtonDefinition CreateDefinition(string text, MessageBoxResult result, bool isPrimary = false, bool isCancel = false)
{
return new MwMessageBoxButtonDefinition
{
Text = text,
Result = result,
IsPrimary = isPrimary,
IsDefault = isPrimary,
IsCancel = isCancel,
ButtonStyle = GetButtonStyle(isPrimary)
};
}
internal static Style GetButtonStyle(bool isPrimary)
{
if (Application.Current != null)
{
var key = isPrimary ? "SaveButtonStyle" : "SettingsButtonStyle";
var resourceStyle = Application.Current.TryFindResource(key) as Style;
if (resourceStyle != null)
{
return resourceStyle;
}
}
var visualTheme = VisualTheme ?? MwMessageBoxVisualTheme.CreateDefault();
var buttonTheme = isPrimary ? visualTheme.PrimaryButtonTheme : visualTheme.SecondaryButtonTheme;
var style = new Style(typeof(System.Windows.Controls.Button));
style.Setters.Add(new Setter(System.Windows.Controls.Control.BackgroundProperty, CreateBrush(buttonTheme.BackgroundColor)));
style.Setters.Add(new Setter(System.Windows.Controls.Control.BorderBrushProperty, CreateBrush(buttonTheme.BorderColor)));
style.Setters.Add(new Setter(System.Windows.Controls.Control.BorderThicknessProperty, new Thickness(1)));
style.Setters.Add(new Setter(System.Windows.Controls.Control.ForegroundProperty, CreateBrush(buttonTheme.ForegroundColor)));
style.Setters.Add(new Setter(System.Windows.Controls.Control.FontSizeProperty, 14d));
style.Setters.Add(new Setter(System.Windows.Controls.Control.FontWeightProperty, FontWeights.SemiBold));
style.Setters.Add(new Setter(System.Windows.Controls.Control.CursorProperty, System.Windows.Input.Cursors.Hand));
var hoverTrigger = new Trigger { Property = UIElement.IsMouseOverProperty, Value = true };
hoverTrigger.Setters.Add(new Setter(System.Windows.Controls.Control.BackgroundProperty, CreateBrush(buttonTheme.HoverBackgroundColor)));
style.Triggers.Add(hoverTrigger);
var pressedTrigger = new Trigger { Property = System.Windows.Controls.Primitives.ButtonBase.IsPressedProperty, Value = true };
pressedTrigger.Setters.Add(new Setter(System.Windows.Controls.Control.BackgroundProperty, CreateBrush(buttonTheme.PressedBackgroundColor)));
style.Triggers.Add(pressedTrigger);
return style;
}
internal static MessageBoxResult ResolveFallbackResult(MwMessageBoxState state)
{
if (state == null)
{
return MessageBoxResult.None;
}
if (state.DefaultResult != MessageBoxResult.None)
{
return state.DefaultResult;
}
var cancelButton = state.Buttons != null ? state.Buttons.FirstOrDefault(item => item.IsCancel) : null;
if (cancelButton != null)
{
return cancelButton.Result;
}
var defaultButton = state.Buttons != null ? state.Buttons.FirstOrDefault(item => item.IsDefault) ?? state.Buttons.FirstOrDefault() : null;
return defaultButton != null ? defaultButton.Result : MessageBoxResult.None;
}
private static Window GetActiveWindow()
{
if (Application.Current == null)
{
return null;
}
var active = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.IsActive);
if (active != null)
{
return active;
}
if (Application.Current.MainWindow != null && Application.Current.MainWindow.IsVisible)
{
return Application.Current.MainWindow;
}
return Application.Current.Windows.OfType<Window>()
.Where(w => w.IsVisible)
.OrderByDescending(w => w.IsLoaded)
.FirstOrDefault();
}
private static MwMessageBoxState CreateState(string message, string caption, MessageBoxImage icon, IReadOnlyList<MwMessageBoxButtonDefinition> buttons, MessageBoxResult defaultResult, MessageBoxOptions options)
{
var palette = CreatePaletteBrushes(GetPalette(icon));
return new MwMessageBoxState
{
Caption = ResolveCaption(caption, icon),
Message = message,
IconGlyph = GetIconText(icon, IconTheme),
IconPathData = GetIconPathData(icon),
TimeText = DateTime.Now.ToString("HH:mm:ss"),
AccentBrush = palette.AccentBrush,
IconBackgroundBrush = palette.IconBackgroundBrush,
IconForegroundBrush = palette.IconForegroundBrush,
Buttons = buttons,
DefaultResult = defaultResult,
MessageTextAlignment = (options & MessageBoxOptions.RightAlign) == MessageBoxOptions.RightAlign ? TextAlignment.Right : TextAlignment.Left,
WindowFlowDirection = (options & MessageBoxOptions.RtlReading) == MessageBoxOptions.RtlReading ? FlowDirection.RightToLeft : FlowDirection.LeftToRight,
MessageFlowDirection = (options & MessageBoxOptions.RtlReading) == MessageBoxOptions.RtlReading ? FlowDirection.RightToLeft : FlowDirection.LeftToRight
};
}
private static string ResolveCaption(string caption, MessageBoxImage icon)
{
if (!string.IsNullOrWhiteSpace(caption))
{
return caption;
}
switch (icon)
{
case MessageBoxImage.Warning:
return "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ";
case MessageBoxImage.Error:
return "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ";
case MessageBoxImage.Information:
return "<22><>Ϣ<EFBFBD><CFA2>ʾ";
case MessageBoxImage.Question:
return "<22><><EFBFBD><EFBFBD>ȷ<EFBFBD><C8B7>";
default:
return "<22><>ʾ";
}
}
private static string GetIconText(MessageBoxImage icon, MwMessageBoxIconTheme iconTheme)
{
var theme = iconTheme ?? MwMessageBoxIconTheme.CreateDefault();
switch (icon)
{
case MessageBoxImage.None:
return theme.NoneGlyph;
case MessageBoxImage.Warning:
return theme.WarningGlyph;
case MessageBoxImage.Error:
return theme.ErrorGlyph;
case MessageBoxImage.Information:
return theme.InformationGlyph;
case MessageBoxImage.Question:
return theme.QuestionGlyph;
default:
return theme.NoneGlyph;
}
}
private static string GetIconPathData(MessageBoxImage icon)
{
switch (icon)
{
case MessageBoxImage.Warning:
return "M11,4 H13 V14 H11 Z M11,16 H13 V18 H11 Z";
case MessageBoxImage.Error:
return "M7.76,6.34 L12,10.59 L16.24,6.34 L17.66,7.76 L13.41,12 L17.66,16.24 L16.24,17.66 L12,13.41 L7.76,17.66 L6.34,16.24 L10.59,12 L6.34,7.76 Z";
case MessageBoxImage.Information:
return "M11,5 H13 V7 H11 Z M11,9 H13 V19 H11 Z";
case MessageBoxImage.Question:
return "M10.2,8.8 C10.27,7.69 11.17,6.82 12.3,6.82 C13.46,6.82 14.36,7.66 14.36,8.75 C14.36,9.52 13.96,10.12 13.02,10.82 C11.74,11.78 11.1,12.65 11.1,14.35 L12.9,14.35 C12.9,13.26 13.27,12.74 14.1,12.09 C15.29,11.17 16,10.14 16,8.72 C16,6.69 14.37,5 12.3,5 C10.23,5 8.56,6.63 8.4,8.8 Z M11.05,16.2 H13.15 V18.3 H11.05 Z";
default:
return "M8,11 H16 V13 H8 Z";
}
}
private static MwMessageBoxColorPalette GetPalette(MessageBoxImage icon)
{
var visualTheme = VisualTheme ?? MwMessageBoxVisualTheme.CreateDefault();
switch (icon)
{
case MessageBoxImage.Warning:
return CreatePalette(visualTheme.WarningPalette, visualTheme.NonePalette);
case MessageBoxImage.Error:
return CreatePalette(visualTheme.ErrorPalette, visualTheme.NonePalette);
case MessageBoxImage.Information:
return CreatePalette(visualTheme.InformationPalette, visualTheme.NonePalette);
case MessageBoxImage.Question:
return CreatePalette(visualTheme.QuestionPalette, visualTheme.NonePalette);
default:
return CreatePalette(visualTheme.NonePalette, MwMessageBoxVisualTheme.CreateDefault().NonePalette);
}
}
private static MwMessageBoxPaletteBrushes CreatePaletteBrushes(MwMessageBoxColorPalette palette)
{
return new MwMessageBoxPaletteBrushes(palette);
}
private static MwMessageBoxColorPalette CreatePalette(MwMessageBoxColorPalette palette, MwMessageBoxColorPalette fallbackPalette)
{
var resolvedFallback = fallbackPalette ?? MwMessageBoxVisualTheme.CreateDefault().NonePalette;
var resolvedPalette = palette ?? resolvedFallback;
return new MwMessageBoxColorPalette(
resolvedPalette.AccentColor ?? resolvedFallback.AccentColor,
resolvedPalette.IconBackgroundColor ?? resolvedFallback.IconBackgroundColor,
resolvedPalette.IconForegroundColor ?? resolvedFallback.IconForegroundColor);
}
private static SolidColorBrush CreateBrush(string color)
{
return (SolidColorBrush)new BrushConverter().ConvertFromString(color);
}
private sealed class MwMessageBoxPaletteBrushes
{
public MwMessageBoxPaletteBrushes(MwMessageBoxColorPalette palette)
{
var resolvedPalette = palette ?? MwMessageBoxVisualTheme.CreateDefault().NonePalette;
AccentBrush = CreateBrush(resolvedPalette.AccentColor ?? "#9CA3AF");
IconBackgroundBrush = CreateBrush(resolvedPalette.IconBackgroundColor ?? "#F3F4F6");
IconForegroundBrush = CreateBrush(resolvedPalette.IconForegroundColor ?? "#6B7280");
}
public Brush AccentBrush { get; }
public Brush IconBackgroundBrush { get; }
public Brush IconForegroundBrush { get; }
private static SolidColorBrush CreateBrush(string color)
{
return (SolidColorBrush)new BrushConverter().ConvertFromString(color);
}
}
}
}

View File

@@ -0,0 +1,296 @@
<Window x:Class="MainShell.Common.View.MwMessageBoxWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:common="clr-namespace:MainShell.Common"
xmlns:behaviors="clr-namespace:MainShell.Common.Behaviors"
mc:Ignorable="d"
MinWidth="360"
MinHeight="220"
MaxWidth="720"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
WindowStyle="None"
Background="Transparent"
ShowInTaskbar="False"
Title="{Binding State.Caption}"
FlowDirection="{Binding State.WindowFlowDirection}"
UseLayoutRounding="True"
SnapsToDevicePixels="True"
common:WindowService.DialogResult="{Binding DialogResult}">
<Window.Resources>
<Style x:Key="MwMessageBoxButtonStyle" TargetType="Button">
<Setter Property="Foreground" Value="#374151"/>
<Setter Property="Background" Value="#FFFFFF"/>
<Setter Property="BorderBrush" Value="#C9D5E5"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding DataContext.IsPrimary, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter Property="Background" Value="#2E5FA7"/>
<Setter Property="BorderBrush" Value="#2E5FA7"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DataContext.IsPrimary, RelativeSource={RelativeSource TemplatedParent}}" Value="False"/>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#F5F8FC"/>
<Setter Property="BorderBrush" Value="#AFC3DD"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DataContext.IsPrimary, RelativeSource={RelativeSource TemplatedParent}}" Value="False"/>
<Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#E8EEF6"/>
<Setter Property="BorderBrush" Value="#9FB7D6"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DataContext.IsPrimary, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#254F8D"/>
<Setter Property="BorderBrush" Value="#254F8D"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DataContext.IsPrimary, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
<Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#1E4174"/>
<Setter Property="BorderBrush" Value="#1E4174"/>
</MultiDataTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="#9CA3AF"/>
<Setter Property="Background" Value="#F3F4F6"/>
<Setter Property="BorderBrush" Value="#E5E7EB"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MwMessageBoxCaptionStyle" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding State.Caption}"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="MwMessageBoxCloseButtonStyle" TargetType="Button">
<Setter Property="Width" Value="44"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="Background" Value="#2E5FA7"/>
<Setter Property="BorderBrush" Value="#2E5FA7"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#C94A4A"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#A53B3B"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding WindowClosingCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Border Background="#FFFFFF"
CornerRadius="2"
BorderBrush="#C6D1DF"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect Color="#1F3A5A"
BlurRadius="18"
ShadowDepth="2"
Opacity="0.18"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="#2E5FA7">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0"
behaviors:WindowDragMoveBehavior.IsEnabled="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Width="16"
Height="16"
Margin="10,0,8,0"
VerticalAlignment="Center"
BorderBrush="#D8E6F7"
BorderThickness="1"
CornerRadius="8">
<TextBlock Text="i"
Foreground="#FFFFFF"
FontSize="11"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Foreground="#FFFFFF"
FontSize="13"
FontWeight="Normal"
TextTrimming="CharacterEllipsis">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding State.Caption}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding State.Caption}" Value="{x:Null}">
<Setter Property="Text" Value="{DynamicResource Msg_Title_Info}"/>
</DataTrigger>
<DataTrigger Binding="{Binding State.Caption}" Value="">
<Setter Property="Text" Value="{DynamicResource Msg_Title_Info}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Button Grid.Column="1"
x:Name="CloseButton"
Style="{StaticResource MwMessageBoxCloseButtonStyle}"
Click="OnCloseButtonClick"/>
</Grid>
<Grid Grid.Row="1" Margin="28,28,28,22">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Width="62"
Height="62"
HorizontalAlignment="Center"
Background="#FFFFFF"
BorderBrush="{Binding State.AccentBrush}"
BorderThickness="1.5"
CornerRadius="31">
<Viewbox Width="26"
Height="26"
Stretch="Uniform">
<Path Data="{Binding State.IconPathData}"
Fill="{Binding State.IconForegroundBrush}"
Stretch="Uniform"/>
</Viewbox>
</Border>
<TextBlock Grid.Row="1"
Margin="0,18,0,0"
HorizontalAlignment="Center"
Foreground="#1F2937"
FontSize="17"
FontWeight="SemiBold"
TextAlignment="Center"
Style="{StaticResource MwMessageBoxCaptionStyle}"/>
<TextBlock Grid.Row="2"
Margin="0,14,0,0"
MaxWidth="540"
Text="{Binding State.Message}"
TextWrapping="Wrap"
Foreground="#4B5563"
FontSize="14"
LineHeight="23"
TextAlignment="Center"
FlowDirection="{Binding State.MessageFlowDirection}"/>
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="1"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0"
Background="#E7EEF7"/>
<ItemsControl Grid.Row="1"
Margin="0,18,0,20"
ItemsSource="{Binding State.Buttons}"
HorizontalAlignment="Center"
MaxWidth="560">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}"
MinWidth="96"
Height="38"
Margin="8,0,8,0"
Padding="22,0,22,0"
IsDefault="{Binding IsDefault}"
IsCancel="{Binding IsCancel}"
Style="{Binding ButtonStyle, TargetNullValue={StaticResource MwMessageBoxButtonStyle}}"
Command="{Binding DataContext.SelectButtonCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
</Border>
</Window>

View File

@@ -0,0 +1,20 @@
using System.Windows;
using MainShell.Common.ViewModel;
namespace MainShell.Common.View
{
public partial class MwMessageBoxWindow : Window
{
public MwMessageBoxWindow(MwMessageBoxWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel ?? throw new System.ArgumentNullException(nameof(viewModel));
CloseButton.Content = "\u00D7";
}
private void OnCloseButtonClick(object sender, System.Windows.RoutedEventArgs e)
{
this.Close();
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Windows;
using System.Windows.Input;
using MaxwellFramework.Core.Common.Command;
using Stylet;
namespace MainShell.Common.ViewModel
{
public class MwMessageBoxWindowViewModel : PropertyChangedBase
{
private bool? _dialogResult;
private MessageBoxResult _result;
public MwMessageBoxWindowViewModel(MwMessageBoxState state)
{
State = state ?? throw new ArgumentNullException(nameof(state));
SelectButtonCommand = new DelegateCommand(OnSelectButton);
WindowClosingCommand = new DelegateCommand(OnWindowClosing);
}
public MwMessageBoxState State { get; }
public bool? DialogResult
{
get => _dialogResult;
set => SetAndNotify(ref _dialogResult, value);
}
public MessageBoxResult Result
{
get => _result;
private set => SetAndNotify(ref _result, value);
}
public ICommand SelectButtonCommand { get; }
public ICommand WindowClosingCommand { get; }
private void OnSelectButton(object parameter)
{
var definition = parameter as MwMessageBoxButtonDefinition;
if (definition == null)
{
return;
}
Result = definition.Result;
DialogResult = true;
}
private void OnWindowClosing(object parameter)
{
if (Result != MessageBoxResult.None)
{
return;
}
Result = MwMessageBox.ResolveFallbackResult(State);
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
namespace MainShell.Common
{
[MarkupExtensionReturnType(typeof(string))]
public class PropDescExtension : MarkupExtension
{
public Type ForType { get; set; }
public string Property { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (ForType == null || string.IsNullOrWhiteSpace(Property)) return Property;
var pd = TypeDescriptor.GetProperties(ForType)[Property];
if (pd == null) return Property;
var desc = pd.Attributes.OfType<DescriptionAttribute>().FirstOrDefault();
return string.IsNullOrWhiteSpace(desc?.Description) ? Property : desc.Description;
}
}
}

View File

@@ -0,0 +1,232 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Common
{
public class RetryMechanism
{
// 自定义异常:操作失败
public class RetryException : Exception
{
public RetryException(string message) : base(message) { }
}
public class RetryResult
{
public bool Succeeded { get; set; }
public bool IsAborted { get; set; }
public int AttemptCount { get; set; }
public int MaxAttempts { get; set; }
public string Message { get; set; }
}
/// <summary>
/// 向后兼容旧接口:默认直接继续重试直到达到最大次数,失败时抛异常。
/// 新代码建议优先使用 <see cref="RetryOrThrow(Func{bool}, Func{int, bool}, string, int)"/> 或返回结果对象的重载。
/// </summary>
public static void RetryIfNeeded(Func<bool> checkCondition, string message, int maxAttempts = 3)
{
RetryOrThrow(checkCondition, attempt => true, message, maxAttempts);
}
/// <summary>
/// 执行同步重试并返回结果对象。
/// </summary>
/// <param name="checkCondition">
/// 每次尝试时执行的检查逻辑。
/// 返回 <c>true</c> 表示成功,返回 <c>false</c> 表示本次失败并根据 <paramref name="shouldRetry"/> 决定是否继续。
/// 如果该委托抛出异常,则异常会直接向外传播,不会被当前重试机制捕获或吞掉,也不会自动进入下一次重试。
/// </param>
/// <param name="shouldRetry">当一次尝试失败后,决定是否继续重试。参数为当前已完成的尝试次数,从 1 开始计数。</param>
/// <param name="message">操作描述信息。</param>
/// <param name="maxAttempts">最大尝试次数,必须大于 0。</param>
public static RetryResult RetryIfNeeded(Func<bool> checkCondition, Func<int, bool> shouldRetry, string message, int maxAttempts = 3)
{
if (checkCondition == null)
{
throw new ArgumentNullException(nameof(checkCondition));
}
if (shouldRetry == null)
{
throw new ArgumentNullException(nameof(shouldRetry));
}
if (maxAttempts <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "maxAttempts must be greater than 0.");
}
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
bool conditionMet = checkCondition();
if (conditionMet)
{
return new RetryResult
{
Succeeded = true,
AttemptCount = attempt,
MaxAttempts = maxAttempts,
Message = message
};
}
if (attempt == maxAttempts)
{
return new RetryResult
{
Succeeded = false,
AttemptCount = attempt,
MaxAttempts = maxAttempts,
Message = message
};
}
bool continueRetry = shouldRetry(attempt);
if (!continueRetry)
{
return new RetryResult
{
Succeeded = false,
IsAborted = true,
AttemptCount = attempt,
MaxAttempts = maxAttempts,
Message = message
};
}
}
return new RetryResult
{
Succeeded = false,
AttemptCount = maxAttempts,
MaxAttempts = maxAttempts,
Message = message
};
}
// 保留抛异常风格的兼容方法
public static void RetryOrThrow(Func<bool> checkCondition, Func<int, bool> shouldRetry, string message, int maxAttempts = 3)
{
var result = RetryIfNeeded(checkCondition, shouldRetry, message, maxAttempts);
EnsureSuccess(result);
}
/// <summary>
/// 向后兼容旧接口的异步版本:默认直接继续重试直到达到最大次数,失败时抛异常。
/// 新代码建议优先使用 <see cref="RetryOrThrowAsync(Func{Task{bool}}, Func{int, Task{bool}}, string, int, CancellationToken)"/> 或返回结果对象的重载。
/// </summary>
public static async Task RetryIfNeededAsync(Func<Task<bool>> checkConditionAsync, string message, int maxAttempts = 3)
{
await RetryOrThrowAsync(checkConditionAsync, attempt => Task.FromResult(true), message, maxAttempts, CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
/// 执行异步重试并返回结果对象。
/// </summary>
/// <param name="checkConditionAsync">
/// 每次尝试时执行的异步检查逻辑。
/// 返回 <c>true</c> 表示成功,返回 <c>false</c> 表示本次失败并根据 <paramref name="shouldRetryAsync"/> 决定是否继续。
/// 如果该委托抛出异常,则异常会直接向外传播,不会被当前重试机制捕获或吞掉,也不会自动进入下一次重试。
/// </param>
/// <param name="shouldRetryAsync">当一次尝试失败后,决定是否继续重试。参数为当前已完成的尝试次数,从 1 开始计数。</param>
/// <param name="message">操作描述信息。</param>
/// <param name="maxAttempts">最大尝试次数,必须大于 0。</param>
/// <param name="cancellationToken">用于取消异步重试流程。</param>
public static async Task<RetryResult> RetryIfNeededAsync(Func<Task<bool>> checkConditionAsync, Func<int, Task<bool>> shouldRetryAsync, string message, int maxAttempts = 3, CancellationToken cancellationToken = default(CancellationToken))
{
if (checkConditionAsync == null)
{
throw new ArgumentNullException(nameof(checkConditionAsync));
}
if (shouldRetryAsync == null)
{
throw new ArgumentNullException(nameof(shouldRetryAsync));
}
if (maxAttempts <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "maxAttempts must be greater than 0.");
}
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
cancellationToken.ThrowIfCancellationRequested();
bool conditionMet = await checkConditionAsync().ConfigureAwait(false);
if (conditionMet)
{
return new RetryResult
{
Succeeded = true,
AttemptCount = attempt,
MaxAttempts = maxAttempts,
Message = message
};
}
if (attempt == maxAttempts)
{
return new RetryResult
{
Succeeded = false,
AttemptCount = attempt,
MaxAttempts = maxAttempts,
Message = message
};
}
cancellationToken.ThrowIfCancellationRequested();
bool continueRetry = await shouldRetryAsync(attempt).ConfigureAwait(false);
if (!continueRetry)
{
return new RetryResult
{
Succeeded = false,
IsAborted = true,
AttemptCount = attempt,
MaxAttempts = maxAttempts,
Message = message
};
}
}
return new RetryResult
{
Succeeded = false,
AttemptCount = maxAttempts,
MaxAttempts = maxAttempts,
Message = message
};
}
// 保留抛异常风格的异步兼容方法
public static async Task RetryOrThrowAsync(Func<Task<bool>> checkConditionAsync, Func<int, Task<bool>> shouldRetryAsync, string message, int maxAttempts = 3, CancellationToken cancellationToken = default(CancellationToken))
{
var result = await RetryIfNeededAsync(checkConditionAsync, shouldRetryAsync, message, maxAttempts, cancellationToken).ConfigureAwait(false);
EnsureSuccess(result);
}
private static void EnsureSuccess(RetryResult result)
{
if (result.Succeeded)
{
return;
}
if (result.IsAborted)
{
throw new RetryException($"{result.Message} 已中止重试,操作失败。尝试次数: {result.AttemptCount}/{result.MaxAttempts}");
}
throw new RetryException($"{result.Message} 已达到最大重试次数,操作失败。尝试次数: {result.AttemptCount}/{result.MaxAttempts}");
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace MainShell.Common
{
class TimeoutRetryMechanism
{
public class WaitForConditionResult
{
public bool Succeeded { get; set; }
public bool TimedOut { get; set; }
public bool IsAborted { get; set; }
}
/// <summary>
/// 在指定超时时间内轮询等待条件成立。
/// 当 <paramref name="timeout"/> 为 <c>null</c> 时,表示无限等待,直到条件成立或外部取消。
/// </summary>
/// <param name="condition">待检查的条件,返回 <c>true</c> 表示条件成立。</param>
/// <param name="timeout">最大等待时间。传入 <c>null</c> 表示无限等待;传入具体值时必须大于 0。</param>
/// <param name="pollingInterval">轮询间隔,必须大于 0。</param>
/// <param name="cancellationToken">用于中止等待流程。</param>
public static WaitForConditionResult WaitForConditionWithTimeout(Func<bool> condition, TimeSpan? timeout, TimeSpan pollingInterval, CancellationToken cancellationToken)
{
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
if (timeout.HasValue && timeout.Value <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(timeout), "timeout must be greater than zero when specified.");
}
if (pollingInterval <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(pollingInterval), "pollingInterval must be greater than zero.");
}
var stopwatch = Stopwatch.StartNew();
while (true)
{
if (condition())
{
return new WaitForConditionResult
{
Succeeded = true
};
}
if (timeout.HasValue && stopwatch.Elapsed > timeout.Value)
{
return new WaitForConditionResult
{
Succeeded = false,
TimedOut = true
};
}
if (cancellationToken.IsCancellationRequested)
{
return new WaitForConditionResult
{
Succeeded = false,
IsAborted = true
};
}
Thread.Sleep(pollingInterval);
}
}
}
}

View File

@@ -0,0 +1,63 @@
<Window x:Class="MainShell.Common.View.CameraSettingsView"
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:local="clr-namespace:MainShell.Common.View"
xmlns:custom="clr-namespace:MainShell.Resources.CustomControl" xmlns:mw="http://www.maxwell-gp.com/"
mc:Ignorable="d"
Title="相机设置"
Width="450" SizeToContent="Height"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
WindowStyle="ToolWindow"
d:DesignHeight="300" d:DesignWidth="350">
<StackPanel Margin="5">
<!-- 相机参数设置 -->
<GroupBox Header="{DynamicResource CameraPar}" Margin="0,0,0,5" Style="{StaticResource GroupBoxSecondary}">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 曝光时间 -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="曝光时间:" VerticalAlignment="Center" Style="{StaticResource LabelStyle}"/>
<mw:IntNumberBox Grid.Row="0" Grid.Column="1"
Value="{Binding CameraConfig.ExposureTime}"
Minimum="10" Maximum="1000000"
mw:NumericKeypadAttach.IsEnabled="True"
HorizontalContentAlignment="Right" Margin="5,2,0,2"/>
<!-- 增益 -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="增益:" VerticalAlignment="Center" Style="{StaticResource LabelStyle}"/>
<mw:IntNumberBox Grid.Row="1" Grid.Column="1"
Value="{Binding CameraConfig.Gain}"
Minimum="1" Maximum="100"
mw:NumericKeypadAttach.IsEnabled="True"
HorizontalContentAlignment="Right" Margin="5,2,0,2"/>
</Grid>
</GroupBox>
<!-- 光源参数设置 -->
<!-- 使用 ItemsControl 动态生成 LightControl需要 ViewModel 提供光源通道列表 -->
<GroupBox Header="{DynamicResource LightSetting}" Style="{StaticResource GroupBoxSecondary}">
<ItemsControl ItemsSource="{Binding LightChannels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custom:LightControl Header="{Binding Name}"
Value="{Binding Value, Mode=TwoWay}"
Min="0" Max="255" Margin="0,2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button Content="{DynamicResource GetParameter}" Style="{StaticResource TeachButtonStyle}" Margin="5" IsDefault="True" Command="{Binding ReadFromHardwareCommand}"/>
</StackPanel>
</StackPanel>
</Window>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
namespace MainShell.Common.View
{
/// <summary>
/// CameraSettingsControl.xaml 的交互逻辑
/// </summary>
public partial class CameraSettingsView : Window
{
public CameraSettingsView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,254 @@
using MainShell.Common;
using MainShell.Hardware;
using MainShell.Models;
using MwFramework.Controls.ControlCanvas;
using Stylet;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Input;
namespace MainShell.Common.ViewModel
{
/// <summary>
/// 单个光源通道的包装类
/// </summary>
public class LightChannelViewModel : PropertyChangedBase
{
private readonly object _sourceConfig;
private readonly PropertyInfo _propertyInfo;
public string Name { get; set; }
public int Value
{
get => (int)_propertyInfo.GetValue(_sourceConfig);
set
{
if (value != Value)
{
_propertyInfo.SetValue(_sourceConfig, value);
NotifyOfPropertyChange();
}
}
}
public LightChannelViewModel(object sourceConfig, PropertyInfo propertyInfo, string displayName)
{
_sourceConfig = sourceConfig;
_propertyInfo = propertyInfo;
Name = displayName;
}
}
/// <summary>
/// 相机及光源参数设置弹窗 ViewModel
/// </summary>
public class CameraSettingsViewModel : Screen
{
private readonly IWindowManager _windowManager;
private readonly HardwareManager _hardwareManager;
// 核心标志位:是否正在从硬件同步数据
private bool _isSyncingFromHardware;
private CameraType _currentCameraType;
private object _currentLightConfig;
private CameraConfig _cameraConfig;
public CameraConfig CameraConfig
{
get => _cameraConfig;
set => SetAndNotify(ref _cameraConfig, value);
}
private ObservableCollection<LightChannelViewModel> _lightChannels;
public ObservableCollection<LightChannelViewModel> LightChannels
{
get => _lightChannels;
set => SetAndNotify(ref _lightChannels, value);
}
public ICommand ReadFromHardwareCommand { get; }
public ICommand SaveAndCloseCommand { get; }
public CameraSettingsViewModel(IWindowManager windowManager, HardwareManager hardwareManager)
{
_windowManager = windowManager;
_hardwareManager = hardwareManager;
ReadFromHardwareCommand = new DelegateCommand(ReadFromHardware);
SaveAndCloseCommand = new DelegateCommand(SaveAndClose);
DisplayName = "相机光源参数设置";
}
/// <summary>
/// 初始化并绑定数据
/// </summary>
public void Initialize(CameraConfig cameraConfig, object lightConfig, CameraType cameraType)
{
// 1. 解绑旧对象的事件(如果有)
if (CameraConfig != null) CameraConfig.PropertyChanged -= OnConfigPropertyChanged;
if (_currentLightConfig is INotifyPropertyChanged oldLight) oldLight.PropertyChanged -= OnConfigPropertyChanged;
_currentCameraType = cameraType;
CameraConfig = cameraConfig;
_currentLightConfig = lightConfig;
// 2. 重新订阅事件
if (CameraConfig != null) CameraConfig.PropertyChanged += OnConfigPropertyChanged;
if (_currentLightConfig is INotifyPropertyChanged newLight) newLight.PropertyChanged += OnConfigPropertyChanged;
// 3. 生成光源通道列表
LightChannels = new ObservableCollection<LightChannelViewModel>();
if (lightConfig != null)
{
var properties = lightConfig.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
if (prop.PropertyType == typeof(int) && prop.CanRead && prop.CanWrite)
{
var descAttr = prop.GetCustomAttribute<DescriptionAttribute>();
// 必须有 Description 才认为是光源通道
if (descAttr != null)
{
var vm = new LightChannelViewModel(lightConfig, prop, descAttr.Description);
LightChannels.Add(vm);
}
}
}
}
}
/// <summary>
/// 统一处理属性变更事件 => 设置硬件
/// </summary>
private void OnConfigPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// 关键逻辑:如果是从硬件读取过程中,则忽略,不要反向设置回去
if (_isSyncingFromHardware) return;
ApplySettingToHardware(sender, e.PropertyName);
}
private void ApplySettingToHardware(object source, string propertyName)
{
// 1. 设置相机参数
if (source == CameraConfig)
{
// TODO: 替换为实际的 set 相机参数代码
/*
var camDevice = _hardwareManager.GetCamera(_currentCameraType);
if (propertyName == nameof(CameraConfig.ExposureTime))
{
camDevice.SetExposure(CameraConfig.ExposureTime);
}
else if (propertyName == nameof(CameraConfig.Gain))
{
camDevice.SetGain(CameraConfig.Gain);
}
*/
System.Diagnostics.Debug.WriteLine($"[Hardware Set] Cam: {_currentCameraType}, Prop: {propertyName}");
return;
}
// 2. 设置光源参数 (根据类型直接判断)
if (source is MapCamLightConfig mapConfig)
{
if (propertyName == nameof(MapCamLightConfig.RedLight))
{
// TODO: _hardwareManager.SetMapRedLight(mapConfig.RedLight);
System.Diagnostics.Debug.WriteLine($"[Hardware Set] MapCam Red: {mapConfig.RedLight}");
}
else if (propertyName == nameof(MapCamLightConfig.BlueLight))
{
// TODO: _hardwareManager.SetMapBlueLight(mapConfig.BlueLight);
System.Diagnostics.Debug.WriteLine($"[Hardware Set] MapCam Blue: {mapConfig.BlueLight}");
}
}
else if (source is UpCamLightConfig upConfig)
{
if (propertyName == nameof(UpCamLightConfig.PointRedLight))
{
// TODO: _hardwareManager.SetUpPointRed(upConfig.PointRedLight);
System.Diagnostics.Debug.WriteLine($"[Hardware Set] UpCam PointRed: {upConfig.PointRedLight}");
}
else if (propertyName == nameof(UpCamLightConfig.RingRedLight))
{
// TODO: ...
}
// ... 处理其他 UpCam 通道
}
else if (source is DownCamLightConfig downConfig)
{
if (propertyName == nameof(DownCamLightConfig.PointRedLight))
{
// TODO: ...
}
// ... 处理其他 DownCam 通道
}
}
/// <summary>
/// 从硬件读取当前值 => 更新 UI (不触发设置)
/// </summary>
private void ReadFromHardware()
{
try
{
_isSyncingFromHardware = true; // 开启静默模式
// 1. 读取相机实际参数
/*
var camDevice = _hardwareManager.GetCamera(_currentCameraType);
if (camDevice != null) {
CameraConfig.ExposureTime = (int)camDevice.CurrentExposure;
CameraConfig.Gain = (int)camDevice.CurrentGain;
}
*/
// 2. 读取光源实际参数
if (_currentLightConfig is MapCamLightConfig mapConfig)
{
// TODO: 读取实际硬件值
mapConfig.RedLight = _hardwareManager.GetMapRedLightIntensity();
mapConfig.BlueLight = _hardwareManager.GetMapBlueLightIntensity();
}
else if (_currentLightConfig is UpCamLightConfig upConfig)
{
upConfig.PointRedLight = _hardwareManager.GetUpPointRedLightIntensity();
upConfig.PointBlueLight=_hardwareManager.GetUpPointBlueLightIntensity();
upConfig.RingRedLight=_hardwareManager.GetUpRingRedLightIntensity();
upConfig.RingBlueLight=_hardwareManager.GetUpRingBlueLightIntensity();
upConfig.RedBackLight=_hardwareManager.GetUpRedBackLightIntensity();
// ...
}
else if (_currentLightConfig is DownCamLightConfig downConfig)
{
// downConfig.PointRedLight = ...
downConfig.PointRedLight = _hardwareManager.GetDownPointRedLightIntensity();
downConfig.PointBlueLight = _hardwareManager.GetDownPointBlueLightIntensity();
downConfig.RingRedLight = _hardwareManager.GetDownRingRedLightIntensity();
downConfig.RingBlueLight = _hardwareManager.GetDownRingBlueLightIntensity();
// ...
}
}
finally
{
foreach (var channel in LightChannels)
{
// 强制刷新 UI
channel.Refresh();
}
_isSyncingFromHardware = false; // 必须复位
}
}
private void SaveAndClose()
{
RequestClose(true);
}
}
}

View File

@@ -0,0 +1,28 @@
<UserControl x:Class="MainShell.Common.VisionTemple.View.VisionTempleView"
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:local="clr-namespace:MainShell.Common.VisionTemple.View" xmlns:control="http://www.maxwell.com/controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<control:CameraVision DataContext="{Binding Service}" Name="cameraVision">
<control:CameraVision.CameraHost>
<control:CameraAxisControlA
Items="{Binding CameraModel.CameraShowViewModel.Items}"
Source="{Binding CameraModel.CameraShowViewModel.CurrentImage, Mode=OneWay}"
TabSelectedIndex="{Binding SelectCameraIndex}"
Cameras="{Binding Cameras}"
Device="{Binding SelectItem}"
ImportFile="{Binding CameraModel.ImportFile}"
Regions="{Binding CameraModel.CameraShowViewModel.Regions}"
IsClear="{Binding CameraModel.CameraShowViewModel.IsClear}"
IsShowContext="True"
ShapeThickness="1" IsShowSolidLine="True" DrawInConcurrency = "False"/>
</control:CameraVision.CameraHost>
</control:CameraVision>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
namespace MainShell.Common.VisionTemple.View
{
/// <summary>
/// VisionTempleView.xaml 的交互逻辑
/// </summary>
public partial class VisionTempleView : UserControl
{
public VisionTempleView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,16 @@
<Window x:Class="MainShell.Common.VisionTemple.View.VisionTempleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:view="clr-namespace:MainShell.Common.VisionTemple.View"
mc:Ignorable="d"
Title="{Binding DisplayName}"
Height="875"
Width="1000"
WindowStartupLocation="CenterOwner"
Background="White">
<Grid Background="White">
<view:VisionTempleView DataContext="{Binding VisionTemple}" />
</Grid>
</Window>

View File

@@ -0,0 +1,15 @@
using System.Windows;
namespace MainShell.Common.VisionTemple.View
{
/// <summary>
/// VisionTempleWindow.xaml 的交互逻辑
/// </summary>
public partial class VisionTempleWindow : Window
{
public VisionTempleWindow()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,32 @@
using MwFramework.Controls.PatternTemplate.ViewModel;
using Stylet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Common.VisionTemple.ViewModel
{
public class VisionTempleViewModel : Screen
{
public string Name { get; set; } = "VisionTempleModel";
private MatchTemplateService _service;
public MatchTemplateService Service
{
get { return _service; }
set
{
_service = value;
OnPropertyChanged(nameof(Service));
}
}
public VisionTempleViewModel()
{
_service = new MatchTemplateService();
}
}
}

View File

@@ -0,0 +1,114 @@
using MainShell.Common.VisionTemple.View;
using MainShell.Hardware;
using Stylet;
using System.ComponentModel;
using System.Windows;
namespace MainShell.Common.VisionTemple.ViewModel
{
public class VisionTempleWindowViewModel : Screen
{
private readonly VisionTempleViewModel _visionTemple;
private VisionTempleWindow _window;
private bool _isShuttingDown;
private readonly HardwareManager _hardwareManager;
public VisionTempleWindowViewModel(HardwareManager hardwareManager)
{
DisplayName = "视觉模板制作";
_visionTemple = new VisionTempleViewModel();
_visionTemple.Service = new MwFramework.Controls.PatternTemplate.ViewModel.MatchTemplateService();
_visionTemple.Service.CameraModel.IsShowFoldDialog = false;
_hardwareManager = hardwareManager;
}
private string _templatePath;
/// <summary>
/// 模版路径
/// </summary>
public string TemplatePath
{
get { return _templatePath; }
set
{
_templatePath = value;
_visionTemple.Service.CameraModel.TemplatePath = value;
}
}
public VisionTempleViewModel VisionTemple
{
get
{
return _visionTemple;
}
}
public void SetCamera(CameraType cameraType)
{
switch (cameraType)
{
case CameraType.MapCamera:
_visionTemple.Service.Cameras = _hardwareManager.CameraAxisManager.BottomCameraAxisDevices;
break;
case CameraType.TopPositionCamera:
_visionTemple.Service.Cameras = _hardwareManager.CameraAxisManager.TopCameraAxisDevices;
break;
case CameraType.TopWideCamera:
_visionTemple.Service.Cameras = _hardwareManager.CameraAxisManager.WideCameraAxisDevices;
break;
default:
break;
}
if(_visionTemple.Service.Cameras!=null&& _visionTemple.Service.Cameras.Count>0)
{
_visionTemple.Service.SelectItem = _visionTemple.Service.Cameras[0];
}
}
public void ShowWindow()
{
if (_window == null)
{
_window = new VisionTempleWindow();
_window.DataContext = this;
_window.Closing += OnWindowClosing;
}
Window owner = Application.Current != null ? Application.Current.MainWindow : null;
if (owner != null && _window.Owner == null)
{
_window.Owner = owner;
owner.Closing += OnOwnerClosing;
}
if (_window.IsVisible)
{
if (_window.WindowState == WindowState.Minimized)
{
_window.WindowState = WindowState.Normal;
}
_window.Activate();
return;
}
_window.Show();
_window.Activate();
}
private void OnWindowClosing(object sender, CancelEventArgs e)
{
if (!_isShuttingDown)
{
e.Cancel = true;
_window.Hide();
}
}
private void OnOwnerClosing(object sender, CancelEventArgs e)
{
_isShuttingDown = true;
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Windows;
namespace MainShell.Common
{
public class WindowService
{
// 定义一个名为 DialogResult 的附加属性
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(WindowService),
new PropertyMetadata(DialogResultChanged)); // 当此属性值改变时,调用 DialogResultChanged 方法
// 提供 Set 方法供外部设置附加属性的值
public static void SetDialogResult(UIElement element, bool? value)
{
element.SetValue(DialogResultProperty, value);
}
// 提供 Get 方法供外部获取附加属性的值
public static bool? GetDialogResult(UIElement element)
{
return (bool?)element.GetValue(DialogResultProperty);
}
// 当 DialogResult 附加属性的值改变时调用的回调方法
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// 确保附加属性应用在一个 Window 控件上,并且新的值是 bool 类型
if (d is Window window && e.NewValue is bool result)
{
// 设置窗口的 DialogResult
window.DialogResult = result;
// 关闭窗口
window.Close();
}
}
}
}

View File

@@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Common
{
public static class WorkflowContextKeys
{
/// <summary>
/// 用于传递 IEventAggregator 实例
/// </summary>
public const string EventAggregator = "EventAggregator";
/// <summary>
/// 用于传递请求令牌 (Guid)
/// </summary>
public const string RequestToken = "RequestToken";
/// <summary>
/// 当前配方
/// </summary>
public const string RecipeManager = "RecipeManager";
/// <summary>
/// 流程结果管理器 Key
/// </summary>
public const string ProcessResultManager = "ProcessResultManager";
/// <summary>
/// 恢复目标活动名称
/// </summary>
public const string ResumeTargetActivityName="ResumeTargetActivityName";
/// <summary>
/// 流程名称
/// </summary>
public const string WorkflowName = "WorkflowName";
/// <summary>
/// 当前步骤ID
/// </summary>
public const string CurrentStepId = "CurrentStepId";
/// <summary>
/// 下一步骤ID
/// </summary>
public const string NextStepId = "NextStepId";
/// <summary>
/// 流程路由配置
/// </summary>
public const string WorkflowRoutes = "WorkflowRoutes";
/// <summary>
/// 晶片转移路由
/// </summary>
public const string DieTransferRoute = "DieTransferRoute";
/// <summary>
/// 自动生产路由
/// </summary>
public const string AutoProductionRoute = "AutoProductionRoute";
/// <summary>
/// 流程运行时追踪器
/// </summary>
public const string WorkflowRuntimeTracker = "WorkflowRuntimeTracker";
/// <summary>
/// 流程失败消息
/// </summary>
public const string WorkflowFailureMessage = "WorkflowFailureMessage";
/// <summary>
/// 流程失败消息键
/// </summary>
public const string WorkflowFailureMessageKey = "WorkflowFailureMessageKey";
/// <summary>
/// 流程失败消息参数
/// </summary>
public const string WorkflowFailureMessageArguments = "WorkflowFailureMessageArguments";
/// <summary>
/// 准备区服务
/// </summary>
public const string PreparationAreaService = "PreparationAreaService";
/// <summary>
/// 当前晶片状态服务
/// </summary>
public const string CurrentChipStateService = "CurrentChipStateService";
/// <summary>
/// 自动生产运行时状态服务
/// </summary>
public const string AutoProductionRuntimeStateService = "AutoProductionRuntimeStateService";
/// <summary>
/// 准备区状态
/// </summary>
public const string PreparationAreaStatus = "PreparationAreaStatus";
/// <summary>
/// 当前晶片状态
/// </summary>
public const string CurrentChipState = "CurrentChipState";
/// <summary>
/// 基板处理状态
/// </summary>
public const string SubstrateProcessState = "SubstrateProcessState";
/// <summary>
/// 待加载晶片
/// </summary>
public const string PendingChipLoad = "PendingChipLoad";
/// <summary>
/// 当前基板待处理数量
/// </summary>
public const string CurrentSubstratePendingCount = "CurrentSubstratePendingCount";
/// <summary>
/// 当前基板已处理数量
/// </summary>
public const string CurrentSubstrateProcessedCount = "CurrentSubstrateProcessedCount";
/// <summary>
/// 当前晶片剩余数量
/// </summary>
public const string CurrentChipRemainingCount = "CurrentChipRemainingCount";
/// <summary>
/// 转移需要重新检查
/// </summary>
public const string TransferNeedsRecheck = "TransferNeedsRecheck";
/// <summary>
/// 转移晶片已耗尽
/// </summary>
public const string TransferChipExhausted = "TransferChipExhausted";
/// <summary>
/// 晶片转移决策结果
/// </summary>
public const string DieTransferDecisionResult = "DieTransferDecisionResult";
/// <summary>
/// 晶片转移路径生成请求
/// </summary>
public const string DieTransferPathRequest = "DieTransferPathRequest";
/// <summary>
/// 晶片转移路径生成结果
/// </summary>
public const string DieTransferPathPlan = "DieTransferPathPlan";
/// <summary>
/// 当前父级活动/子流程名称
/// </summary>
public const string ParentActivityName = "ParentActivityName";
/// <summary>
/// 针头Z对刀次数
/// </summary>
public const string NeedleZCalibrationTouchCount = "NeedleZCalibrationTouchCount";
/// <summary>
/// 针头Z对刀完成后是否自动抬高
/// </summary>
public const string NeedleZCalibrationAutoRaiseZ1 = "NeedleZCalibrationAutoRaiseZ1";
/// <summary>
/// 针头Z对刀取消令牌
/// </summary>
public const string NeedleZCalibrationCancellationToken = "NeedleZCalibrationCancellationToken";
/// <summary>
/// 针头Z对刀工作流请求数据
/// </summary>
public const string NeedleZCalibrationWorkflowData = "NeedleZCalibrationWorkflowData";
/// <summary>
/// 针头Z对刀结果集合
/// </summary>
public const string NeedleZCalibrationResults = "NeedleZCalibrationResults";
/// <summary>
/// 针头Z对刀最后一次结果
/// </summary>
public const string NeedleZCalibrationLastResult = "NeedleZCalibrationLastResult";
/// <summary>
/// 针头Z对刀平均结果
/// </summary>
public const string NeedleZCalibrationAverageResult = "NeedleZCalibrationAverageResult";
/// <summary>
/// 基板定位结果
/// </summary>
public const string SubstratePositionResult = "SubstratePositionResult";
/// <summary>
/// 芯片定位结果
/// </summary>
public const string DiePositionResult = "DiePositionResult";
/// <summary>
/// 芯片定位运行态上下文
/// </summary>
public const string DiePositionContext = "DiePositionContext";
/// <summary>
/// 芯片定位扫描规划结果
/// </summary>
public const string DiePositionScanPlan = "DiePositionScanPlan";
/// <summary>
/// 基板测高结果
/// </summary>
public const string SubstrateHeightMeasureResult = "SubstrateHeightMeasureResult";
/// <summary>
/// 精度复检结果
/// </summary>
public const string DieRecheckResult = "DieRecheckResult";
/// <summary>
/// 基板Mark识别服务
/// </summary>
public const string SubstrateMarkRecognitionService = "SubstrateMarkRecognitionService";
}
}