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
{
///
/// 高性能芯片Map图控件,支持5-10万个点的动态状态更新,支持鼠标拖拽和缩放
///
public class DieMapControl : FrameworkElement
{
private int _rows;
private int _columns;
private DieState[,] _dieStates;
private Point[,] _dieCoordinates;
private double _minX, _maxX, _minY, _maxY;
private Dictionary _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 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.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
}
}