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 } }