添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 _);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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, "请输入有效的数字。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// 将字节拷贝到 BackBuffer(UI 线程中调用)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
181
MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Common/Enums.cs
Normal file
181
MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Common/Enums.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
104
MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Common/Loading.cs
Normal file
104
MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Common/Loading.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Common.VisionTemple.View
|
||||
{
|
||||
/// <summary>
|
||||
/// VisionTempleWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class VisionTempleWindow : Window
|
||||
{
|
||||
public VisionTempleWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user