WPF 实现可复用晶圆 n*n 网格自定义控件(支持选中与圆形裁剪)

在半导体设备、工业自动化等场景的 WPF 上位机开发中,晶圆的可视化展示是常见需求。本文将详细讲解如何封装一个支持 n*n 规格配置、圆形边缘裁剪、网格选中、自适应尺寸 的独立WaferGridControl用户控件,实现高内聚、低耦合的可复用组件开发,满足工业场景下的晶圆可视化需求。

一、需求分析

本次需要实现的晶圆网格控件需满足以下核心需求:

  1. 自定义网格规格:支持外部动态设置 n 值,绘制 n*n 的晶圆网格。
  2. 圆形边缘裁剪:边缘网格完整绘制,超出晶圆圆形的部分自动裁剪,而非直接隐藏。
  3. 精准中心对齐:网格中心、晶圆中心、控件中心三者完全重合,无偏移错位。
  4. 网格选中功能:鼠标点击网格可切换选中状态,提供清晰的视觉反馈。
  5. 高可复用性:封装为独立 UserControl,可在任意 WPF 窗口 / 页面中直接引用。
  6. 自适应尺寸:控件大小变化时,网格自动重绘并保持对齐。

二、核心实现思路

  1. 控件封装选型 :基于 WPF UserControl封装,将 UI 布局与业务逻辑内聚在控件内部,对外暴露极简的配置接口。
  2. 依赖属性设计 :定义GridCount依赖属性,支持外部绑定与动态修改,属性变更时自动触发网格重绘。
  3. 中心对齐计算:以控件内部 Canvas 的中心为基准,计算晶圆边框、网格容器的位置,确保三者中心重合。
  4. 圆形裁剪机制 :通过EllipseGeometry设置网格容器的Clip属性,实现圆形区域裁剪,保留边缘网格的可见部分。
  5. 交互逻辑内聚:将鼠标点击选中的逻辑封装在控件内部,可选暴露选中事件供外部业务响应。
  6. 自适应处理 :监听控件LoadedSizeChanged事件,确保初始化与尺寸变化时网格精准重绘。

三、完整实现步骤

3.1 创建 WaferGridControl 用户控件

首先新建一个 WPF UserControl,命名为WaferGridControl,其 XAML 布局仅包含一个承载晶圆元素的 Canvas,所有核心元素(晶圆边框、网格容器)均在后台代码动态创建。

XML 复制代码
<UserControl x:Class="WaferGridDemo.WaferGridControl"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="600" d:DesignWidth="600"
             Loaded="WaferGridControl_Loaded"
             SizeChanged="WaferGridControl_SizeChanged">
    <!-- 控件内部核心容器:承载晶圆所有元素 -->
    <Canvas x:Name="canvasWafer" 
            Background="#F5F5F5" 
            MouseDown="CanvasWafer_MouseDown">
        <!-- 动态元素将在后台代码创建 -->
    </Canvas>
</UserControl>

3.2 后台逻辑实现

后台逻辑是控件的核心,包含依赖属性定义、元素初始化、网格绘制、选中交互四大模块。

3.2.1 定义核心数据结构与私有变量

首先定义网格单元格的数据模型,以及控件内部所需的私有变量,用于存储网格数据和 UI 元素引用。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WaferGridDemo
{
    public partial class WaferGridControl : UserControl
    {
        #region 私有变量与数据模型
        // 存储所有网格单元格数据
        private List<WaferGridCell> _waferGridCells = new List<WaferGridCell>();
        // 晶圆核心UI元素
        private Ellipse _ellipseWaferBorder;
        private Canvas _canvasGridContainer;
        // 晶圆尺寸参数
        private double _waferDiameter;
        private double _gridSize;

        /// <summary>
        /// 网格单元格数据模型
        /// </summary>
        private class WaferGridCell
        {
            public int Row { get; set; }          // 行索引
            public int Column { get; set; }       // 列索引
            public double X { get; set; }         // 容器内X坐标
            public double Y { get; set; }         // 容器内Y坐标
            public double Size { get; set; }      // 网格边长
            public bool IsSelected { get; set; }  // 选中状态
        }
        #endregion
    }
}
3.2.2 定义 GridCount 依赖属性

依赖属性是 WPF 控件的核心特性,通过定义GridCount依赖属性,实现外部对网格规格的动态配置,并在属性变更时自动重绘网格。

cs 复制代码
#region 公共依赖属性:GridCount(网格规格n*n)
/// <summary>
/// 网格规格(n*n)依赖属性
/// </summary>
public static readonly DependencyProperty GridCountProperty =
    DependencyProperty.Register(
        nameof(GridCount),
        typeof(int),
        typeof(WaferGridControl),
        new PropertyMetadata(
            10, // 默认值:10*10网格
            GridCountPropertyChanged, // 属性变更回调
            CoerceGridCountValue // 属性值校验
        ));

/// <summary>
/// 网格规格(n*n),外部可直接设置/绑定
/// </summary>
public int GridCount
{
    get => (int)GetValue(GridCountProperty);
    set => SetValue(GridCountProperty, value);
}

/// <summary>
/// 依赖属性值校验:限制n在1-50之间,避免性能问题
/// </summary>
private static object CoerceGridCountValue(DependencyObject d, object baseValue)
{
    if (baseValue is int n)
    {
        return Math.Clamp(n, 1, 50);
    }
    return 10;
}

/// <summary>
/// 网格规格变更时自动重绘网格
/// </summary>
private static void GridCountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is WaferGridControl control && e.NewValue is int newN)
    {
        control.DrawWaferGrid(newN);
    }
}
#endregion
3.2.3 控件初始化与自适应处理

在控件构造函数中初始化晶圆 UI 元素,监听Loaded事件完成首次绘制,监听SizeChanged事件实现尺寸自适应。

cs 复制代码
#region 控件初始化与自适应
public WaferGridControl()
{
    InitializeComponent();
    InitWaferInnerElements();
}

/// <summary>
/// 初始化晶圆内部UI元素(边框、网格容器)
/// </summary>
private void InitWaferInnerElements()
{
    // 初始化晶圆边框
    _ellipseWaferBorder = new Ellipse
    {
        Stroke = Brushes.Black,
        StrokeThickness = 2
    };

    // 初始化网格容器(用于圆形裁剪)
    _canvasGridContainer = new Canvas();

    // 添加到控件内部Canvas
    canvasWafer.Children.Add(_ellipseWaferBorder);
    canvasWafer.Children.Add(_canvasGridContainer);
}

/// <summary>
/// 控件加载完成后首次绘制网格
/// </summary>
private void WaferGridControl_Loaded(object sender, RoutedEventArgs e)
{
    DrawWaferGrid(GridCount);
}

/// <summary>
/// 控件尺寸变化时重绘网格,保持对齐
/// </summary>
private void WaferGridControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
    DrawWaferGrid(GridCount);
}
#endregion
3.2.4 核心绘制方法(中心对齐 + 圆形裁剪)

DrawWaferGrid方法是控件的核心,实现了中心对齐计算、圆形裁剪设置、网格批量绘制三大功能,确保网格与晶圆完全居中,边缘网格裁剪平滑。

cs 复制代码
#region 核心:绘制晶圆网格(中心对齐+圆形裁剪)
/// <summary>
/// 绘制n*n晶圆网格
/// </summary>
/// <param name="n">网格规格</param>
private void DrawWaferGrid(int n)
{
    // 清空历史数据
    _waferGridCells.Clear();
    _canvasGridContainer.Children.Clear();

    // 冗余校验
    if (n < 1 || n > 50) return;

    // 1. 获取控件内部Canvas的准确尺寸
    double canvasWidth = canvasWafer.ActualWidth;
    double canvasHeight = canvasWafer.ActualHeight;
    // 计算晶圆直径(留20px边距)
    _waferDiameter = Math.Min(canvasWidth, canvasHeight) - 40;
    double waferRadius = _waferDiameter / 2;
    // 计算网格边长
    _gridSize = _waferDiameter / n;

    // 2. 计算晶圆中心(布局基准:Canvas中心)
    Point waferCenter = new Point(canvasWidth / 2, canvasHeight / 2);

    // 3. 设置晶圆边框(中心对齐)
    _ellipseWaferBorder.Width = _waferDiameter;
    _ellipseWaferBorder.Height = _waferDiameter;
    Canvas.SetLeft(_ellipseWaferBorder, waferCenter.X - waferRadius);
    Canvas.SetTop(_ellipseWaferBorder, waferCenter.Y - waferRadius);

    // 4. 设置网格容器(与晶圆边框完全重合,中心对齐)
    _canvasGridContainer.Width = _waferDiameter;
    _canvasGridContainer.Height = _waferDiameter;
    Canvas.SetLeft(_canvasGridContainer, waferCenter.X - waferRadius);
    Canvas.SetTop(_canvasGridContainer, waferCenter.Y - waferRadius);

    // 5. 设置圆形裁剪(核心:仅显示晶圆内的网格部分)
    var waferClip = new EllipseGeometry
    {
        Center = new Point(waferRadius, waferRadius), // 容器内相对中心
        RadiusX = waferRadius,
        RadiusY = waferRadius
    };
    _canvasGridContainer.Clip = waferClip;

    // 6. 计算网格偏移量(确保网格中心与容器中心重合)
    double gridTotalSize = n * _gridSize;
    double offsetX = (_canvasGridContainer.Width - gridTotalSize) / 2;
    double offsetY = (_canvasGridContainer.Height - gridTotalSize) / 2;

    // 7. 批量绘制n*n网格
    for (int row = 0; row < n; row++)
    {
        for (int col = 0; col < n; col++)
        {
            double gridX = offsetX + col * _gridSize;
            double gridY = offsetY + row * _gridSize;

            // 创建网格数据模型
            var gridCell = new WaferGridCell
            {
                Row = row,
                Column = col,
                X = gridX,
                Y = gridY,
                Size = _gridSize,
                IsSelected = false
            };
            _waferGridCells.Add(gridCell);

            // 创建可视化网格矩形
            var gridRectangle = new Rectangle
            {
                Width = _gridSize,
                Height = _gridSize,
                Stroke = Brushes.Gray,
                StrokeThickness = 0.5,
                Fill = Brushes.White,
                Tag = gridCell // 绑定数据模型,方便交互时获取
            };
            Canvas.SetLeft(gridRectangle, gridX);
            Canvas.SetTop(gridRectangle, gridY);
            _canvasGridContainer.Children.Add(gridRectangle);

            // 可选:添加网格索引文本(调试/展示用)
            var indexText = new TextBlock
            {
                Text = $"{row},{col}",
                FontSize = 8,
                Foreground = Brushes.DarkGray
            };
            Canvas.SetLeft(indexText, gridX + 2);
            Canvas.SetTop(indexText, gridY + 2);
            _canvasGridContainer.Children.Add(indexText);
        }
    }
}
#endregion
3.2.5 网格选中交互与事件暴露

实现鼠标点击选中逻辑,并可选暴露GridCellSelected事件,供外部业务响应选中状态变化。

cs 复制代码
#region 网格选中交互与事件暴露
/// <summary>
/// 鼠标点击切换网格选中状态
/// </summary>
private void CanvasWafer_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    // 转换鼠标坐标到网格容器内,提升命中准确性
    Point containerPos = e.GetPosition(_canvasGridContainer);

    foreach (var child in _canvasGridContainer.Children)
    {
        if (child is Rectangle gridRectangle)
        {
            double rectLeft = Canvas.GetLeft(gridRectangle);
            double rectTop = Canvas.GetTop(gridRectangle);
            double rectRight = rectLeft + gridRectangle.Width;
            double rectBottom = rectTop + gridRectangle.Height;

            // 判断鼠标是否点击在网格可见区域
            if (containerPos.X >= rectLeft && containerPos.X <= rectRight &&
                containerPos.Y >= rectTop && containerPos.Y <= rectBottom)
            {
                if (gridRectangle.Tag is WaferGridCell gridCell)
                {
                    // 切换选中状态并更新视觉反馈
                    gridCell.IsSelected = !gridCell.IsSelected;
                    gridRectangle.Fill = gridCell.IsSelected
                        ? Brushes.LightSkyBlue
                        : Brushes.White;

                    // 触发选中事件,通知外部
                    OnGridCellSelected(gridCell.Row, gridCell.Column, gridCell.IsSelected);
                    break;
                }
            }
        }
    }
}

/// <summary>
/// 网格选中状态变更事件(外部可订阅)
/// </summary>
public event EventHandler<GridCellSelectedEventArgs> GridCellSelected;

/// <summary>
/// 触发选中事件
/// </summary>
private void OnGridCellSelected(int row, int column, bool isSelected)
{
    GridCellSelected?.Invoke(this, new GridCellSelectedEventArgs
    {
        Row = row,
        Column = column,
        IsSelected = isSelected
    });
}
#endregion
}

/// <summary>
/// 网格选中事件参数类
/// </summary>
public class GridCellSelectedEventArgs : EventArgs
{
    public int Row { get; set; }
    public int Column { get; set; }
    public bool IsSelected { get; set; }
}

3.3 控件使用示例

封装完成的WaferGridControl可在任意 WPF 窗口中直接引用,通过GridCount属性动态配置网格规格,订阅GridCellSelected事件响应选中变化。

3.3.1 MainWindow.xaml(引用控件)
XML 复制代码
<Window x:Class="WaferGridDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WaferGridDemo"
        Title="晶圆网格控件使用示例" Height="700" Width="700"
        WindowStartupLocation="CenterScreen">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- 外部配置区域 -->
        <StackPanel Orientation="Horizontal" Margin="0,0,0,10" VerticalAlignment="Center">
            <Label Content="网格规格(n*n):" VerticalAlignment="Center" Margin="0,0,10,0"/>
            <TextBox x:Name="txtGridCount" Width="100" Text="10" VerticalAlignment="Center"
                     InputMethod.IsInputMethodEnabled="False"/>
            <Button x:Name="btnUpdateGrid" Content="更新网格" Margin="10,0,0,0"
                    Click="BtnUpdateGrid_Click" Width="100"/>
            <TextBlock x:Name="txtTip" Margin="20,0,0,0" VerticalAlignment="Center"
                       Foreground="Red" FontSize="12"/>
        </StackPanel>

        <!-- 引用晶圆网格控件 -->
        <local:WaferGridControl x:Name="waferGridControl" Grid.Row="1"
                                GridCount="10"
                                GridCellSelected="WaferGridControl_GridCellSelected"/>
    </Grid>
</Window>
3.3.2 MainWindow.xaml.cs(控制控件)
cs 复制代码
using System.Windows;

namespace WaferGridDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 外部按钮更新网格规格
        /// </summary>
        private void BtnUpdateGrid_Click(object sender, RoutedEventArgs e)
        {
            if (int.TryParse(txtGridCount.Text, out int n))
            {
                if (n >= 1 && n <= 50)
                {
                    waferGridControl.GridCount = n;
                    txtTip.Text = string.Empty;
                }
                else
                {
                    txtTip.Text = "请输入1-50之间的正整数";
                }
            }
            else
            {
                txtTip.Text = "请输入有效的正整数";
            }
        }

        /// <summary>
        /// 订阅网格选中事件
        /// </summary>
        private void WaferGridControl_GridCellSelected(object sender, GridCellSelectedEventArgs e)
        {
            // 此处可添加外部业务逻辑,如记录选中网格的行列索引
            txtTip.Text = $"网格[{e.Row},{e.Column}] {(e.IsSelected ? "选中" : "取消选中")}";
        }
    }
}

四、功能验证与效果展示

  1. 初始化效果:窗口加载后,控件自动绘制 10*10 网格,网格与晶圆完全居中,边缘网格裁剪平滑。
  2. 动态配置:输入 n 值(如 15、20)点击 "更新网格",控件自动重绘对应规格的网格,无偏移错位。
  3. 选中功能:点击网格可见区域,网格颜色切换为浅蓝色,外部可通过事件获取选中状态。
  4. 自适应尺寸:缩放窗口大小,控件内的晶圆网格自动适配,保持中心对齐。

五、扩展优化方向

  1. 样式自定义 :新增WaferBorderColorGridStrokeColorSelectedFillColor等依赖属性,支持外部自定义控件样式。
  2. 选中状态持久化:控件内部保存选中网格的索引,在尺寸变化或网格重绘后自动恢复选中状态。
  3. 批量选中功能 :支持Ctrl键多选、Shift键连续选中,封装对应的交互逻辑。
  4. 性能优化 :对于 n>30 的大规格网格,使用DrawingVisual替代Rectangle,降低 UI 元素数量,提升绘制性能。
  5. 数据导出 :添加GetSelectedCells方法,导出所有选中网格的行列索引,方便与业务系统对接。

六、总结

本文实现的WaferGridControl控件,通过 WPF 依赖属性、几何裁剪、坐标计算等技术,完美解决了晶圆网格绘制的核心需求。控件具有高内聚、低耦合、可复用的特点,可直接集成到工业自动化上位机项目中,也可根据实际需求扩展更多功能。该控件的开发思路也适用于其他自定义图形控件的封装,为 WPF 工业控件开发提供了参考范式。

相关推荐
无心水7 小时前
分布式定时任务与SELECT FOR UPDATE:从致命陷阱到优雅解决方案(实战案例+架构演进)
服务器·人工智能·分布式·后端·spring·架构·wpf
LZL_SQ9 小时前
HCCL测试框架中AllReduce边界条件测试设计深度剖析
wpf·cann
User_芊芊君子1 天前
【分布式训练】CANN SHMEM跨设备内存通信库:构建高效多机多卡训练的关键组件
分布式·深度学习·神经网络·wpf
就是有点傻2 天前
WPF按钮走马灯效果
wpf
zuozewei2 天前
虚拟电厂聚合商平台安全技术体系深度解读
安全·wpf
极客智造2 天前
WPF 自定义控件:AutoGrid 实现灵活自动布局的网格控件
wpf
极客智造2 天前
WPF Grid 布局高效扩展:GridHelpers 附加属性工具类全解析
wpf
张人玉2 天前
WPF 多语言实现完整笔记(.NET 4.7.2)
笔记·.net·wpf·多语言实现·多语言适配
暖馒2 天前
深度剖析串口通讯(232/485)
开发语言·c#·wpf·智能硬件
我要打打代码3 天前
WPF控件(2)
wpf