拓冰建站拓冰建站
首页 / 资讯中心 / 正文

WPF图形绘制基础:掌握Rectangle与Ellipse的Stretch属性控制

这次我们来看一个 WPF 二维绘图的基础但核心的操作如何绘制矩形和椭圆以及如何通过Stretch枚举值来控制它们的填充行为。对于刚接触 WPF 图形绘制的开发者来说理解Shape派生类如Rectangle和Ellipse的尺寸、位置特别是Stretch属性的影响是构建自定义控件、数据可视化图表或复杂 UI 装饰效果的关键一步。本文将直接切入主题先说明Rectangle和Ellipse是什么、怎么用然后重点拆解Stretch枚举的四个值None,Fill,Uniform,UniformToFill在绘制时的具体表现和差异。我们会通过清晰的 XAML 和 C# 代码示例演示从基础绘制到高级控制的完整流程并分析在不同布局容器如Canvas,Grid下的行为变化。无论你是需要绘制一个简单的按钮背景还是构建一个动态的图表元素掌握这些知识都能让你更精准地控制图形输出。1. 核心能力速览在深入细节之前我们先通过一个表格快速了解Rectangle、Ellipse和Stretch属性的核心信息能力项说明图形类型System.Windows.Shapes.Rectangle(矩形) 和System.Windows.Shapes.Ellipse(椭圆)。它们是FrameworkElement和Shape的派生类可以作为控件直接放入可视化树。核心属性Width/Height定义图形的逻辑尺寸。Fill内部填充画刷。Stroke轮廓线画刷。StrokeThickness轮廓线粗细。Stretch属性决定图形如何拉伸以填充其分配的布局空间。它是System.Windows.Media.Stretch枚举类型是本文的重点。布局影响图形的最终渲染大小和位置由Width/Height、Stretch属性值以及其父容器如Grid的单元格、Canvas的Left/Top共同决定。适用场景UI 装饰元素、数据可视化中的柱状图/饼图、自定义按钮和边框、进度指示器、简单的几何图形绘制等。硬件/环境门槛无特殊要求。任何支持 .NET Framework 或 .NET Core/.NET 5 并运行 WPF 的 Windows 开发环境均可。性能取决于图形复杂度和数量但对于基础形状现代硬件毫无压力。2. 适用场景与使用边界Rectangle和Ellipse是 WPF 中用于绘制基本形状的“控件化”元素。它们不同于DrawingContext的直接绘制也不同于Path定义的复杂几何图形其优势在于易用性和与 WPF 属性系统、数据绑定、动画故事板的无缝集成。适合谁用WPF 初学者学习 WPF 图形系统的基础。UI/UX 开发者需要快速创建装饰性图形如圆角背景、指示器、图标。数据可视化开发者构建简单的柱状图矩形、饼图椭圆扇形需配合Path等图表元素。自定义控件开发者在控件模板中使用这些形状来定义视觉外观。能解决什么问题快速可视化无需复杂几何计算几行 XAML 即可呈现图形。样式与模板轻松通过Style、Trigger和TemplateBinding改变图形外观实现动态效果如鼠标悬停变色、进度条填充。布局兼容作为FrameworkElement它们可以完美融入 WPF 的布局系统Grid,StackPanel等并响应尺寸变化。不适合什么场景超高性能绘制当需要绘制成千上万个动态变化的形状时使用DrawingVisual或WriteableBitmap会有更好的性能。复杂矢量图形对于星形、多边形、不规则曲线等应使用Path元素配合Geometry如PathGeometry来定义。纯代码动态生成大量图形虽然可以在 C# 后台代码中实例化和添加但对于复杂场景考虑使用更底层的绘制 API。使用边界与注意版权与设计用于 UI 的图形设计应尊重原创避免直接复制受版权保护的特定图标或图案。性能考量虽然单个形状开销小但过度使用例如在ItemsControl的DataTemplate中为大量数据项创建复杂形状可能影响 UI 响应速度。合理使用虚拟化等技术。3. 环境准备与前置条件开始绘制前确保你的开发环境已就绪。操作系统Windows 7 SP1 或更高版本推荐 Windows 10/11。开发环境Visual Studio 2022社区版免费安装时勾选“.NET 桌面开发”工作负载它会包含 WPF 项目模板和所需 SDK。.NET SDK如果你使用其他编辑器如 VS Code需要安装对应版本的 .NET SDK例如 .NET 6.0 SDK 或 .NET 8.0 SDK。WPF 项目类型在 .NET Core 3.1 及更高版本中可用。项目创建打开 Visual Studio选择“创建新项目”。搜索“WPF”选择“WPF 应用程序”模板C#。指定项目名称和位置点击“创建”。一个标准的 WPF 项目结构包含App.xaml和MainWindow.xaml将被创建。基础知识对 XAML 语法和 C# 有基本了解。知道如何编辑MainWindow.xaml文件和在后台代码MainWindow.xaml.cs中编写事件处理程序。4. 基础绘制创建矩形和椭圆让我们从最简单的绘制开始。在MainWindow.xaml的Grid标签内添加以下 XAML 代码StackPanel !-- 绘制一个简单的蓝色矩形 -- Rectangle Width100 Height60 FillLightBlue StrokeDarkBlue StrokeThickness2 Margin5/ !-- 绘制一个简单的红色椭圆 -- Ellipse Width100 Height60 FillLightCoral StrokeDarkRed StrokeThickness2 Margin5/ !-- 绘制一个圆Width等于Height的椭圆 -- Ellipse Width80 Height80 FillGold StrokeOrange StrokeThickness3 Margin5/ !-- 绘制一个带圆角的矩形 -- Rectangle Width120 Height80 FillLightGreen StrokeDarkGreen StrokeThickness2 RadiusX15 RadiusY15 Margin5/ /StackPanel代码解析Width和Height定义了图形的“逻辑”尺寸单位是设备无关像素DIP。Fill设置图形内部的填充颜色或画刷这里用了纯色。Stroke和StrokeThickness设置图形轮廓线的颜色和粗细。RadiusX和RadiusYRectangle特有的属性用于设置圆角半径。MarginWPF 布局属性为图形周围添加边距。运行项目你将看到四个图形垂直排列。此时Stretch属性默认为Stretch.None图形严格按照Width和Height指定的尺寸绘制。5. 深入理解 Stretch 枚举Stretch属性是控制图形如何适应其“可用空间”的关键。可用空间由父容器的布局逻辑决定。例如在一个Grid单元格中如果HorizontalAlignment和VerticalAlignment是Stretch默认那么图形的可用空间就是整个单元格。Stretch枚举有四个值我们通过一个对比示例来直观感受它们的区别。在 XAML 中创建一个Grid并在每个单元格中放置一个应用了不同Stretch值的Rectangle。Grid Grid.RowDefinitions RowDefinition HeightAuto/ RowDefinition Height*/ /Grid.RowDefinitions Grid.ColumnDefinitions ColumnDefinition Width*/ ColumnDefinition Width*/ ColumnDefinition Width*/ ColumnDefinition Width*/ /Grid.ColumnDefinitions TextBlock Grid.Column0 Grid.Row0 TextStretch.None HorizontalAlignmentCenter/ TextBlock Grid.Column1 Grid.Row0 TextStretch.Fill HorizontalAlignmentCenter/ TextBlock Grid.Column2 Grid.Row0 TextStretch.Uniform HorizontalAlignmentCenter/ TextBlock Grid.Column3 Grid.Row0 TextStretch.UniformToFill HorizontalAlignmentCenter/ !-- 每个单元格都是一个Border用于清晰显示可用空间 -- Border Grid.Column0 Grid.Row1 BorderBrushGray BorderThickness1 Margin10 Rectangle Width80 Height40 FillLightBlue StrokeBlue StretchNone/ /Border Border Grid.Column1 Grid.Row1 BorderBrushGray BorderThickness1 Margin10 Rectangle Width80 Height40 FillLightCoral StrokeRed StretchFill/ /Border Border Grid.Column2 Grid.Row1 BorderBrushGray BorderThickness1 Margin10 Rectangle Width80 Height40 FillLightGreen StrokeGreen StretchUniform/ /Border Border Grid.Column3 Grid.Row1 BorderBrushGray BorderThickness1 Margin10 Rectangle Width80 Height40 FillGold StrokeOrange StretchUniformToFill/ /Border /Grid效果验证与核心逻辑Stretch.None(默认值)行为图形不进行任何拉伸。它严格按照Width和Height属性定义的尺寸绘制。示例中蓝色矩形保持 80x40 的大小位于Border的左上角因为Rectangle的HorizontalAlignment和VerticalAlignment默认是Center不在Border中作为子元素默认是Stretch但StretchNone时Alignment生效。这里为了演示我们假设Rectangle的HorizontalAlignment和VerticalAlignment为默认的Stretch但在StretchNone时Alignment属性会决定其在可用空间中的位置。为了清晰我们可以在代码中显式设置HorizontalAlignmentCenter VerticalAlignmentCenter。关键点图形的视觉尺寸等于其Width/Height不受父容器大小影响。Stretch.Fill行为图形被拉伸以完全填满分配给它的可用空间。不保持宽高比。示例中红色矩形被拉伸到充满整个Border的内部区域扣除Margin和BorderThickness。如果Border是正方形矩形会被拉伸成正方形如果是长方形矩形就被拉伸成长方形。关键点图形变形宽高比被破坏。Stretch.Uniform行为图形被等比例缩放以尽可能大地适应可用空间同时完全被包含在该空间内并保持原始宽高比。图形可能不会填满整个空间会在某一方向留白。示例中绿色矩形会等比例缩放直到其宽度或高度触及Border的边界另一边则小于边界。例如原始 80x40 (2:1)如果Border是 100x100 (1:1)矩形会缩放到 80x80高度先触顶左右留白。关键点保持比例图形完整可见可能有黑边/留白。Stretch.UniformToFill行为图形被等比例缩放以完全填满可用空间同时保持原始宽高比。这可能导致图形的一部分被裁剪因为为了填满图形必须超出边界。示例中金色矩形会等比例缩放直到其宽度和高度都至少等于Border的尺寸。例如同样 80x40 (2:1) 和 100x100 (1:1) 的Border矩形会缩放到 100x50宽度先触边这样高度50小于Border高度100为了填满它会继续按比例缩放直到高度触边即 200x100但这会超出宽度。实际上WPF 会计算一个缩放因子使得缩放后的图形能完全覆盖可用空间。最终图形的 200x100 版本中只有中间 100x100 的部分被显示出来上下或左右的部分被裁剪。关键点保持比例无留白但图形内容可能被裁剪。为了更精确地观察Alignment与Stretch的交互我们可以修改None的例子Border Grid.Column0 Grid.Row1 BorderBrushGray BorderThickness1 Margin10 Rectangle Width80 Height40 FillLightBlue StrokeBlue StretchNone HorizontalAlignmentCenter VerticalAlignmentCenter/ /Border这样当StretchNone时矩形会居中显示在Border中。6. 在 Canvas 中绘制与 Stretch 的影响Canvas是一个基于绝对坐标的布局容器它不为其子元素提供布局尺寸协商。子元素必须通过Canvas.Left,Canvas.Top等附加属性定位并且其Width/Height是显式定义的。在这种情况下Stretch属性的行为有所不同。Canvas Width400 Height200 BackgroundLightGray !-- 在Canvas中Stretch属性通常无效因为Canvas不约束子元素大小 -- !-- 此矩形有固定位置和大小Stretch不起作用 -- Rectangle Canvas.Left50 Canvas.Top30 Width100 Height60 FillBlue StretchFill/ !-- 如果没有指定Width/Height但指定了Stretch会怎样 -- !-- 实际上Shape需要Width/Height来定义其自然尺寸否则尺寸为0Stretch无意义 -- !-- 下面这个矩形将不可见因为Width和Height为0 -- Rectangle Canvas.Left200 Canvas.Top30 FillRed StretchFill/ !-- 正确用法在Canvas中通常直接设置Width/HeightStretch用None即可 -- !-- 或者结合RenderTransform进行缩放 -- Rectangle Canvas.Left50 Canvas.Top120 Width80 Height40 FillGreen StretchNone Rectangle.RenderTransform ScaleTransform ScaleX1.5 ScaleY1.5/ /Rectangle.RenderTransform /Rectangle /Canvas关键结论在Canvas中由于子元素没有来自容器的“可用空间”约束除了Canvas自身的裁剪区域Stretch属性通常不会产生预期效果。图形的尺寸主要由显式设置的Width和Height决定。如果你需要在Canvas中缩放图形应使用RenderTransform如ScaleTransform或LayoutTransform。7. 动态控制与代码示例在实际应用中我们经常需要根据数据或用户交互动态改变图形的尺寸、位置或Stretch模式。下面通过一个 C# 后台代码示例演示如何响应按钮点击来改变Ellipse的Stretch属性。首先在MainWindow.xaml中添加 UI 元素StackPanel Ellipse x:NameMyDemoEllipse Width100 Height50 FillLightSkyBlue StrokeRoyalBlue StrokeThickness2 Margin10/ WrapPanel Margin10 Button ContentStretch: None ClickStretchButton_Click Margin2 TagNone/ Button ContentStretch: Fill ClickStretchButton_Click Margin2 TagFill/ Button ContentStretch: Uniform ClickStretchButton_Click Margin2 TagUniform/ Button ContentStretch: UniformToFill ClickStretchButton_Click Margin2 TagUniformToFill/ /WrapPanel Border x:NameEllipseContainer BorderBrushDarkGray BorderThickness1 Height150 Margin10 !-- 将椭圆放入一个Border中以便观察Stretch效果 -- Ellipse x:NameMyDynamicEllipse FillLightCoral StrokeIndianRed StrokeThickness2/ /Border TextBlock x:NameStatusText Margin10/ /StackPanel然后在MainWindow.xaml.cs文件中添加事件处理逻辑using System.Windows; using System.Windows.Media; // 需要引入此命名空间以使用 Stretch 枚举 namespace YourProjectName { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // 初始化椭圆不设置Width/Height让其依赖Stretch和容器 MyDynamicEllipse.Width double.NaN; // 等同于在XAML中不设置或设为Auto MyDynamicEllipse.Height double.NaN; UpdateStatus(); } private void StretchButton_Click(object sender, RoutedEventArgs e) { if (sender is Button btn btn.Tag is string stretchMode) { switch (stretchMode) { case None: MyDynamicEllipse.Stretch Stretch.None; // 当Stretch为None时必须显式设置尺寸否则不可见 MyDynamicEllipse.Width 80; MyDynamicEllipse.Height 40; break; case Fill: MyDynamicEllipse.Stretch Stretch.Fill; // 当Stretch为Fill/Uniform/UniformToFill时可以依赖容器尺寸 MyDynamicEllipse.Width double.NaN; // 清除固定尺寸 MyDynamicEllipse.Height double.NaN; break; case Uniform: MyDynamicEllipse.Stretch Stretch.Uniform; MyDynamicEllipse.Width double.NaN; MyDynamicEllipse.Height double.NaN; break; case UniformToFill: MyDynamicEllipse.Stretch Stretch.UniformToFill; MyDynamicEllipse.Width double.NaN; MyDynamicEllipse.Height double.NaN; break; } UpdateStatus(); } } private void UpdateStatus() { StatusText.Text $当前Stretch模式: {MyDynamicEllipse.Stretch}, Width: {MyDynamicEllipse.Width}, Height: {MyDynamicEllipse.Height}; } } }操作与验证运行程序。点击不同的按钮观察底部Border中的椭圆变化。点击“None”时椭圆恢复固定大小80x40。点击“Fill”、“Uniform”、“UniformToFill”时椭圆会清除固定尺寸并尝试填充Border容器表现出不同的拉伸行为。“Fill”会变形填满“Uniform”会等比例缩放并居中可能留白“UniformToFill”会等比例缩放并填满可能被裁剪。注意StatusText显示当前属性和尺寸。当Width/Height为NaN时表示它们由布局系统决定。8. 性能观察与最佳实践虽然绘制简单的Rectangle和Ellipse性能开销极低但在复杂场景下仍需注意。数量与虚拟化如果在ListBox、DataGrid或ItemsControl的DataTemplate中大量使用复杂形状例如每个数据项包含多个带有渐变填充和阴影的图形务必启用 UI 虚拟化VirtualizingStackPanel。画刷与效果使用SolidColorBrush性能最好。LinearGradientBrush、RadialGradientBrush、ImageBrush以及BitmapEffect已过时或Effect如DropShadowEffect会显著增加渲染负载。仅在必要时使用。动画对Width、Height、RenderTransform进行动画是高效的。避免对StrokeDashArray等复杂属性进行高性能要求的连续动画。尺寸与Stretch明确需求如果图形需要固定大小使用Stretch.None并设置Width/Height。响应式布局如果图形需要随容器缩放使用Stretch.Uniform保持比例或Stretch.Fill填充变形并将Width/Height设置为Auto或NaN。UniformToFill慎用除非你明确需要裁剪效果如作为某个区域的背景图否则可能不是最佳选择。结合Viewbox有时你希望一个包含多个图形的整体如一个图标能够等比例缩放而不是单个图形。这时可以将这些图形放在一个Canvas或Grid中然后外层包裹一个Stretch”Uniform”的Viewbox控件。Viewbox的拉伸逻辑与Shape的Stretch属性类似但作用于其整个子内容。!-- 使用Viewbox控制一组图形的整体缩放 -- Viewbox StretchUniform Width100 Height100 Canvas Width24 Height24 Ellipse Canvas.Left2 Canvas.Top2 Width20 Height20 FillRed/ Rectangle Canvas.Left8 Canvas.Top8 Width8 Height8 FillWhite/ /Canvas /Viewbox9. 常见问题与排查方法问题现象可能原因排查方式解决方案图形不显示1.Width或Height为 0 或未设置。2.Fill属性未设置或设置为Transparent/null。3. 图形在布局范围之外如Canvas中坐标过大。4. 父容器背景色与图形颜色相同。1. 检查 XAML 或代码中Width/Height值。2. 检查Fill属性。3. 检查Canvas.Left/Top或Margin。4. 临时设置父容器为显眼背景色。1. 设置有效的Width/Height。2. 设置一个可见的Fill画刷。3. 调整位置或容器尺寸。4. 修改颜色。图形拉伸变形不符合预期1.Stretch属性理解错误。2. 父容器的布局约束与预期不符。3. 同时设置了Width/Height和Stretch为Fill/Uniform等产生冲突。1. 回顾Stretch四种模式的区别。2. 检查父容器如Grid行/列定义的尺寸。3. 检查图形自身的Width/Height、HorizontalAlignment/VerticalAlignment。1. 根据需求选择正确的Stretch值。2. 调整父容器布局或使用Canvas。3. 若希望图形随容器缩放将Width/Height设为Auto或NaN。Stretch”Uniform”时图形未居中图形的HorizontalAlignment和VerticalAlignment默认是Stretch在Uniform模式下留白会平均分布在两侧。若想图形紧贴某一边需修改Alignment。检查图形的HorizontalAlignment和VerticalAlignment属性。将HorizontalAlignment和VerticalAlignment设置为Center、Left、Right、Top或Bottom以控制图形在留白区域内的位置。圆角矩形 (RadiusX/Y) 在拉伸时变形RadiusX和RadiusY是绝对像素值不会随图形拉伸而等比例缩放。观察拉伸后圆角的视觉比例。如果需要圆角随图形大小等比例变化可以考虑使用Rectangle结合RenderTransform缩放或者使用Border控件其CornerRadius属性是设备无关的但Border本身不是Shape。在Canvas中设置Stretch无效Canvas不提供布局尺寸给子元素Stretch属性需要“可用空间”才能生效。确认图形是否放置在Canvas中。在Canvas中直接使用Width/Height和RenderTransform来控制图形大小避免依赖Stretch。10. 总结与下一步掌握Rectangle、Ellipse和Stretch属性是高效利用 WPF 进行二维图形绘制的基石。核心在于理解Stretch如何与布局系统交互None用于固定尺寸Fill用于充满变形Uniform用于等比例缩放适配UniformToFill用于等比例缩放裁剪。最值得尝试的下一步是将这些形状与 WPF 的其他强大功能结合数据绑定与动画将Rectangle的Height绑定到数据模型的进度值并添加动画故事板制作平滑的进度条。控件模板在自定义按钮或ToggleButton的ControlTemplate中使用这些形状定义其各种状态Normal,MouseOver,Pressed下的视觉外观。结合Path与Geometry当基础形状不够用时学习使用Path和LineGeometry、RectangleGeometry、EllipseGeometry甚至PathGeometry来创建更复杂的矢量图形。探索Drawing和Visual对于需要极致性能的动态绘图场景可以深入研究DrawingContext、DrawingVisual和RenderTargetBitmap。先从在一个Grid或Border中通过按钮动态切换Stretch模式开始实验直观感受其差异。然后尝试将图形的Fill属性绑定到一个Slider的值转换器上实现动态颜色变化。这些实践能帮你牢固建立 WPF 图形渲染的核心概念。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门