Files
Shi.Ji e31d3560bb 添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
2026-05-18 11:43:09 +08:00

595 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MainShell.Common;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.ComponentModel;
namespace MainShell.Resources.CustomControl
{
/// <summary>
/// 高性能芯片Map图控件支持5-10万个点的动态状态更新支持鼠标拖拽和缩放
/// </summary>
public class DieMapControl : FrameworkElement
{
private int _rows;
private int _columns;
private DieState[,] _dieStates;
private Point[,] _dieCoordinates;
private double _minX, _maxX, _minY, _maxY;
private Dictionary<DieState, Brush> _stateBrushes;
// 渲染相关
private DrawingVisual _visual;
private VisualCollection _visuals;
private DispatcherTimer _renderTimer;
private bool _needsRedraw = false;
// 交互相关
private MatrixTransform _transform = new MatrixTransform();
private Point _lastMousePosition;
private Point _mouseDownPosition;
private bool _isDragging;
private bool _isViewChangedByUser;
private Popup _infoPopup;
private TextBlock _infoText;
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof(Brush), typeof(DieMapControl), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
public Brush Background
{
get { return (Brush)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public static readonly DependencyProperty MapModelProperty =
DependencyProperty.Register("MapModel", typeof(DieMapModel), typeof(DieMapControl), new PropertyMetadata(null, OnMapModelChanged));
public DieMapModel MapModel
{
get { return (DieMapModel)GetValue(MapModelProperty); }
set { SetValue(MapModelProperty, value); }
}
public static readonly DependencyProperty DieClickedCommandProperty =
DependencyProperty.Register("DieClickedCommand", typeof(ICommand), typeof(DieMapControl), new PropertyMetadata(null));
public ICommand DieClickedCommand
{
get { return (ICommand)GetValue(DieClickedCommandProperty); }
set { SetValue(DieClickedCommandProperty, value); }
}
public event EventHandler<DieClickedEventArgs> DieClicked;
public class DieClickedEventArgs : EventArgs
{
public int Row { get; }
public int Col { get; }
public DieState State { get; }
public DieClickedEventArgs(int row, int col, DieState state)
{
Row = row;
Col = col;
State = state;
}
}
private static void OnMapModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (DieMapControl)d;
if (e.OldValue is DieMapModel oldModel)
{
oldModel.MapInitialized -= control.OnMapInitialized;
oldModel.DieStateChanged -= control.OnDieStateChanged;
oldModel.DieStatesChanged -= control.OnDieStatesChanged;
}
if (e.NewValue is DieMapModel newModel)
{
newModel.MapInitialized += control.OnMapInitialized;
newModel.DieStateChanged += control.OnDieStateChanged;
newModel.DieStatesChanged += control.OnDieStatesChanged;
control.InitializeFromModel(newModel);
}
}
private void OnMapInitialized(object sender, EventArgs e)
{
InitializeFromModel(MapModel);
}
private void OnDieStateChanged(object sender, (int Row, int Col, DieState State) e)
{
_needsRedraw = true;
}
private void OnDieStatesChanged(object sender, IEnumerable<(int Row, int Col, DieState State)> e)
{
_needsRedraw = true;
}
public static readonly DependencyProperty DieSizeProperty =
DependencyProperty.Register("DieSize", typeof(double), typeof(DieMapControl), new FrameworkPropertyMetadata(4.0, FrameworkPropertyMetadataOptions.AffectsRender, OnSizeChanged));
public double DieSize
{
get { return (double)GetValue(DieSizeProperty); }
set { SetValue(DieSizeProperty, value); }
}
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register("Spacing", typeof(double), typeof(DieMapControl), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender, OnSizeChanged));
public double Spacing
{
get { return (double)GetValue(SpacingProperty); }
set { SetValue(SpacingProperty, value); }
}
private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (DieMapControl)d;
control.RedrawAll();
}
public DieMapControl()
{
_visuals = new VisualCollection(this);
_visual = new DrawingVisual();
_visuals.Add(_visual);
// 初始化画刷字典,冻结以提高性能
_stateBrushes = new Dictionary<DieState, Brush>
{
{ DieState.Available, CreateFrozenBrush(Color.FromRgb(211, 211, 211)) }, // 浅灰色
{ DieState.Used, CreateFrozenBrush(Color.FromRgb(50, 205, 50)) }, // 绿色
{ DieState.Error, CreateFrozenBrush(Color.FromRgb(255, 0, 0)) }, // 红色
{ DieState.NotExist, Brushes.Transparent }, // 透明
{ DieState.Current, CreateFrozenBrush(Color.FromRgb(0, 191, 255)) }, // 深天蓝 - 当前位置
{ DieState.Target, CreateFrozenBrush(Color.FromRgb(255, 165, 0)) } // 橙色 - 目标位置
};
// 初始化Popup
_infoPopup = new Popup
{
Placement = PlacementMode.Mouse,
StaysOpen = false,
AllowsTransparency = true
};
Border popupBorder = new Border
{
Background = new SolidColorBrush(Color.FromArgb(220, 30, 30, 30)),
BorderBrush = Brushes.Gray,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(4),
Padding = new Thickness(8)
};
_infoText = new TextBlock
{
Foreground = Brushes.White,
FontSize = 12
};
popupBorder.Child = _infoText;
_infoPopup.Child = popupBorder;
// 鼠标事件
this.MouseWheel += OnMouseWheel;
this.MouseDown += OnMouseDown;
this.MouseUp += OnMouseUp;
this.MouseMove += OnMouseMove;
this.MouseLeave += OnMouseLeave;
// 裁剪超出边界的内容
this.ClipToBounds = true;
// 初始化定时器1秒3次 (约333ms)
_renderTimer = new DispatcherTimer(DispatcherPriority.Render);
_renderTimer.Interval = TimeSpan.FromMilliseconds(333);
_renderTimer.Tick += RenderTimer_Tick;
_renderTimer.Start();
}
private void RenderTimer_Tick(object sender, EventArgs e)
{
if (_needsRedraw)
{
RedrawAll();
_needsRedraw = false;
}
}
private SolidColorBrush CreateFrozenBrush(Color color)
{
var brush = new SolidColorBrush(color);
brush.Freeze();
return brush;
}
protected override int VisualChildrenCount => _visuals.Count;
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _visuals.Count)
throw new ArgumentOutOfRangeException();
return _visuals[index];
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (Background != null)
{
drawingContext.DrawRectangle(Background, null, new Rect(0, 0, ActualWidth, ActualHeight));
}
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (!_isViewChangedByUser && _rows > 0)
{
FitToScreen();
}
}
private void InitializeFromModel(DieMapModel model)
{
if (model == null) return;
_rows = model.Rows;
_columns = model.Columns;
_dieStates = model.States;
_dieCoordinates = model.Coordinates;
if (_dieCoordinates != null)
{
CalculateBounds();
}
// 重置变换
_transform.Matrix = Matrix.Identity;
// 延迟执行FitToScreen确保控件已经布局完成
Dispatcher.BeginInvoke(new Action(() =>
{
if (ActualWidth > 0 && ActualHeight > 0)
{
FitToScreen();
}
else
{
RedrawAll();
}
}), DispatcherPriority.Loaded);
}
private void CalculateBounds()
{
_minX = double.MaxValue;
_maxX = double.MinValue;
_minY = double.MaxValue;
_maxY = double.MinValue;
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
var pt = _dieCoordinates[r, c];
if (pt.X < _minX) _minX = pt.X;
if (pt.X > _maxX) _maxX = pt.X;
if (pt.Y < _minY) _minY = pt.Y;
if (pt.Y > _maxY) _maxY = pt.Y;
}
}
}
private void RedrawAll()
{
if (_rows == 0 || _columns == 0 || _dieStates == null) return;
using (DrawingContext dc = _visual.RenderOpen())
{
// 应用当前的缩放和平移变换
dc.PushTransform(_transform);
double size = DieSize;
double spacing = Spacing;
double physicalWidth = _maxX - _minX;
double physicalHeight = _maxY - _minY;
if (physicalWidth == 0) physicalWidth = 1;
if (physicalHeight == 0) physicalHeight = 1;
// 基础绘制区域大小(未缩放前)
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
DieState state = _dieStates[r, c];
if (state == DieState.NotExist) continue;
Brush brush = _stateBrushes.ContainsKey(state) ? _stateBrushes[state] : Brushes.Transparent;
if (brush == Brushes.Transparent) continue;
double x, y;
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[r, c];
x = ((pt.X - _minX) / physicalWidth) * baseWidth;
y = (1.0 - (pt.Y - _minY) / physicalHeight) * baseHeight;
}
else
{
x = c * (size + spacing);
y = r * (size + spacing);
}
dc.DrawRectangle(brush, null, new Rect(x, y, size, size));
}
}
dc.Pop();
}
_needsRedraw = false;
}
#region ()
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
Point mousePos = e.GetPosition(this);
double scaleFactor = e.Delta > 0 ? 1.1 : 1 / 1.1;
Matrix matrix = _transform.Matrix;
matrix.ScaleAt(scaleFactor, scaleFactor, mousePos.X, mousePos.Y);
_transform.Matrix = matrix;
_isViewChangedByUser = true;
RedrawAll();
e.Handled = true;
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle)
{
FitToScreen();
}
else if (e.ChangedButton == MouseButton.Left)
{
_mouseDownPosition = e.GetPosition(this);
_lastMousePosition = _mouseDownPosition;
_isDragging = true;
this.CaptureMouse();
_infoPopup.IsOpen = false;
}
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
_isDragging = false;
this.ReleaseMouseCapture();
Point currentPosition = e.GetPosition(this);
if ((currentPosition - _mouseDownPosition).Length < 5)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
ShowDieInfo(currentPosition);
}
else
{
HandleDieClick(currentPosition);
}
}
}
}
private void HandleDieClick(Point mousePos)
{
if (_rows == 0 || _columns == 0 || _dieStates == null) return;
if (!_transform.Matrix.HasInverse) return;
Matrix inverse = _transform.Matrix;
inverse.Invert();
Point canvasPos = inverse.Transform(mousePos);
double size = DieSize;
double spacing = Spacing;
double physicalWidth = _maxX - _minX;
double physicalHeight = _maxY - _minY;
if (physicalWidth == 0) physicalWidth = 1;
if (physicalHeight == 0) physicalHeight = 1;
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
int clickedRow = -1;
int clickedCol = -1;
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
DieState state = _dieStates[r, c];
if (state == DieState.NotExist) continue;
double x, y;
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[r, c];
x = ((pt.X - _minX) / physicalWidth) * baseWidth;
y = (1.0 - (pt.Y - _minY) / physicalHeight) * baseHeight;
}
else
{
x = c * (size + spacing);
y = r * (size + spacing);
}
if (canvasPos.X >= x && canvasPos.X <= x + size &&
canvasPos.Y >= y && canvasPos.Y <= y + size)
{
clickedRow = r;
clickedCol = c;
break;
}
}
if (clickedRow != -1) break;
}
if (clickedRow != -1)
{
var dieInfo = (Row: clickedRow, Col: clickedCol, State: _dieStates[clickedRow, clickedCol]);
// 触发事件
DieClicked?.Invoke(this, new DieClickedEventArgs(clickedRow, clickedCol, _dieStates[clickedRow, clickedCol]));
// 触发命令
if (DieClickedCommand != null && DieClickedCommand.CanExecute(dieInfo))
{
DieClickedCommand.Execute(dieInfo);
}
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (_isDragging)
{
Point currentPosition = e.GetPosition(this);
Vector delta = currentPosition - _lastMousePosition;
Matrix matrix = _transform.Matrix;
matrix.Translate(delta.X, delta.Y);
_transform.Matrix = matrix;
_isViewChangedByUser = true;
_lastMousePosition = currentPosition;
RedrawAll();
}
}
private void OnMouseLeave(object sender, MouseEventArgs e)
{
if (_isDragging)
{
_isDragging = false;
this.ReleaseMouseCapture();
}
}
private void FitToScreen()
{
if (_rows == 0 || _columns == 0) return;
double size = DieSize;
double spacing = Spacing;
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
if (baseWidth == 0 || baseHeight == 0 || ActualWidth == 0 || ActualHeight == 0) return;
double scaleX = ActualWidth / baseWidth;
double scaleY = ActualHeight / baseHeight;
double scale = Math.Min(scaleX, scaleY) * 0.9; // 90% to leave some margin
if (scale <= 0) scale = 1;
_transform.Matrix = new Matrix(scale, 0, 0, scale,
(ActualWidth - baseWidth * scale) / 2,
(ActualHeight - baseHeight * scale) / 2);
_isViewChangedByUser = false;
RedrawAll();
}
private void ShowDieInfo(Point mousePos)
{
if (_rows == 0 || _columns == 0 || _dieStates == null) return;
if (!_transform.Matrix.HasInverse) return;
Matrix inverse = _transform.Matrix;
inverse.Invert();
Point canvasPos = inverse.Transform(mousePos);
double size = DieSize;
double spacing = Spacing;
double physicalWidth = _maxX - _minX;
double physicalHeight = _maxY - _minY;
if (physicalWidth == 0) physicalWidth = 1;
if (physicalHeight == 0) physicalHeight = 1;
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
int clickedRow = -1;
int clickedCol = -1;
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
DieState state = _dieStates[r, c];
if (state == DieState.NotExist) continue;
double x, y;
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[r, c];
x = ((pt.X - _minX) / physicalWidth) * baseWidth;
y = (1.0 - (pt.Y - _minY) / physicalHeight) * baseHeight;
}
else
{
x = c * (size + spacing);
y = r * (size + spacing);
}
if (canvasPos.X >= x && canvasPos.X <= x + size &&
canvasPos.Y >= y && canvasPos.Y <= y + size)
{
clickedRow = r;
clickedCol = c;
break;
}
}
if (clickedRow != -1) break;
}
if (clickedRow != -1)
{
DieState state = _dieStates[clickedRow, clickedCol];
string info = $"行 (Row): {clickedRow}\n列 (Col): {clickedCol}\n状态: {MainShell.Converter.EnumHelper.GetEnumDescription(state)}";
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[clickedRow, clickedCol];
info += $"\nX: {pt.X:F3}\nY: {pt.Y:F3}";
}
_infoText.Text = info;
_infoPopup.IsOpen = true;
}
else
{
_infoPopup.IsOpen = false;
}
}
#endregion
}
}