在半导体设备、工业自动化等场景的 WPF 上位机开发中,晶圆的可视化展示是常见需求。本文将详细讲解如何封装一个支持 n*n 规格配置、圆形边缘裁剪、网格选中、自适应尺寸 的独立WaferGridControl用户控件,实现高内聚、低耦合的可复用组件开发,满足工业场景下的晶圆可视化需求。
一、需求分析
本次需要实现的晶圆网格控件需满足以下核心需求:
- 自定义网格规格:支持外部动态设置 n 值,绘制 n*n 的晶圆网格。
- 圆形边缘裁剪:边缘网格完整绘制,超出晶圆圆形的部分自动裁剪,而非直接隐藏。
- 精准中心对齐:网格中心、晶圆中心、控件中心三者完全重合,无偏移错位。
- 网格选中功能:鼠标点击网格可切换选中状态,提供清晰的视觉反馈。
- 高可复用性:封装为独立 UserControl,可在任意 WPF 窗口 / 页面中直接引用。
- 自适应尺寸:控件大小变化时,网格自动重绘并保持对齐。
二、核心实现思路
- 控件封装选型 :基于 WPF
UserControl封装,将 UI 布局与业务逻辑内聚在控件内部,对外暴露极简的配置接口。 - 依赖属性设计 :定义
GridCount依赖属性,支持外部绑定与动态修改,属性变更时自动触发网格重绘。 - 中心对齐计算:以控件内部 Canvas 的中心为基准,计算晶圆边框、网格容器的位置,确保三者中心重合。
- 圆形裁剪机制 :通过
EllipseGeometry设置网格容器的Clip属性,实现圆形区域裁剪,保留边缘网格的可见部分。 - 交互逻辑内聚:将鼠标点击选中的逻辑封装在控件内部,可选暴露选中事件供外部业务响应。
- 自适应处理 :监听控件
Loaded和SizeChanged事件,确保初始化与尺寸变化时网格精准重绘。
三、完整实现步骤
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 ? "选中" : "取消选中")}";
}
}
}
四、功能验证与效果展示
- 初始化效果:窗口加载后,控件自动绘制 10*10 网格,网格与晶圆完全居中,边缘网格裁剪平滑。
- 动态配置:输入 n 值(如 15、20)点击 "更新网格",控件自动重绘对应规格的网格,无偏移错位。
- 选中功能:点击网格可见区域,网格颜色切换为浅蓝色,外部可通过事件获取选中状态。
- 自适应尺寸:缩放窗口大小,控件内的晶圆网格自动适配,保持中心对齐。
五、扩展优化方向
- 样式自定义 :新增
WaferBorderColor、GridStrokeColor、SelectedFillColor等依赖属性,支持外部自定义控件样式。 - 选中状态持久化:控件内部保存选中网格的索引,在尺寸变化或网格重绘后自动恢复选中状态。
- 批量选中功能 :支持
Ctrl键多选、Shift键连续选中,封装对应的交互逻辑。 - 性能优化 :对于 n>30 的大规格网格,使用
DrawingVisual替代Rectangle,降低 UI 元素数量,提升绘制性能。 - 数据导出 :添加
GetSelectedCells方法,导出所有选中网格的行列索引,方便与业务系统对接。
六、总结
本文实现的WaferGridControl控件,通过 WPF 依赖属性、几何裁剪、坐标计算等技术,完美解决了晶圆网格绘制的核心需求。控件具有高内聚、低耦合、可复用的特点,可直接集成到工业自动化上位机项目中,也可根据实际需求扩展更多功能。该控件的开发思路也适用于其他自定义图形控件的封装,为 WPF 工业控件开发提供了参考范式。