WPF 从零入门(上):WPF 是什么、XAML 语法、布局与样式全解
本系列基于一份完整的 WPF 教学工程拆解整理,覆盖 WPF 基础概念 → XAML 语法 → 项目结构 → 布局容器 → 样式与属性 → 事件交互,上篇讲基础,下篇讲导航控制器实战(见《WPF 从零入门(下):导航控制器实战------NavigationWindow、Page 与 Frame 页面跳转》)。
一、WPF 是什么?为什么它比 WinForm 强?
WPF(Windows Presentation Foundation,窗体渲染基础) 是 Windows 的用户界面框架(UI Framework),用来生成具有震撼视觉体验的 Windows 客户端应用程序。
| 对比项 | WPF | WinForm |
|---|---|---|
| 出身 | .NET Framework 3.0 的一部分 | 从 .NET 出现开始就有 |
| 渲染引擎 | Direct3D(利用现代图形硬件) | GDI+(软件渲染) |
| 界面描述 | XAML 标记语言(声明式) | 纯代码拖控件 |
| 控件样式 | 背景/字体/边框/边距/对齐全面可控 | 相对简陋 |
| 数据绑定 | 原生支持,可上 MVVM | 弱 |
| 适用场景 | 现代界面、动画、上位机、工业软件 | 老项目、简单工具 |
WPF 提供的核心功能:XAML、控件、数据绑定、布局、样式、模板、文本和版式、2-D/3-D 图形、动画、文档、媒体。
划重点:数据绑定是 WPF 的难点也是精髓------让界面控件显示的数据动态化,MVVM 框架(Prism 等)都建立在它之上。
二、XAML 到底是什么?
HTML(超文本标记语言) → XML(可扩展标记语言) → XAML(可扩展应用程序标记语言)
XAML 是一种声明性标记语言 ,文件扩展名 .xaml。它像 HTML/XML 一样用标签描述界面,但背后每个标签都对应一个 C# 类。
标签的三种形态
xml
<!-- 双标签:开始标签 + 内容区 + 结束标签 -->
<Button>按钮内容</Button>
<!-- 单标签:直接闭合 -->
<Button/>
<!-- 属性:键值对形式,只能写在开始标签里 -->
<Button Content="按钮" Width="100" Height="50"/>
XAML 几个必懂概念
| 概念 | 说明 |
|---|---|
| 大驼峰命名 | 标签名如 Button、StackPanel,因为背后是 C# 类 |
| xmlns | 相当于 C# 的 using,引入命名空间 |
| DOM 树 | 文档对象模型,XAML 的标签嵌套形成树状结构 |
| x:Class | 指定这个 XAML 对应的后台 C# 类 |
| 双文件分离 | XXX.xaml(界面长什么样)+ XXX.xaml.cs(怎么交互),界面与逻辑分离 |
项目结构(每个 WPF 项目都一样)
| 文件/目录 | 作用 |
|---|---|
.sln |
解决方案文件,管理整个项目 |
.csproj |
项目文件,记录引用和配置 |
App.xaml |
应用程序入口 + 全局资源配置(相当于 WinForm 的 Program.cs) |
MainWindow.xaml |
主窗口界面 |
MainWindow.xaml.cs |
主窗口后台逻辑 |
bin |
编译输出目录 |
obj |
编译中间文件 |
四大重要对象 :Application(应用程序类)、Window(窗体类)、Page(页面类,带导航)、UserControl(用户控件)。
App.xaml 里的
StartupUri="Window1.xaml"就是启动窗口,相当于 WinForm 里的new Form1()。
三、布局容器:界面排版的基石
WPF 的规则是:Window 里必须有且只有一个父容器,所有控件都放在容器里。常用容器有五种:
1. Grid(网格)------ 最常用
xml
<Grid>
<!-- 定义行:固定高 / auto自适应 / *按比例 -->
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="auto"/> <!-- auto = 剩余空间或子元素高度 -->
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/> <!-- * 是1份,2* 是2份 -->
</Grid.RowDefinitions>
<!-- 定义列 -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!-- 元素必须指定所在区域 -->
<Button Grid.Row="0" Grid.Column="1" Content="按钮1"/>
<!-- Grid.RowSpan / Grid.ColumnSpan 单元格合并 -->
<Button Grid.Row="2" Grid.RowSpan="2" Grid.ColumnSpan="2" Content="合并单元格"/>
</Grid>
auto:剩余部分 / 子元素高度*与2*:按比例分配(1 份 vs 2 份)Grid.RowSpan/Grid.ColumnSpan:跨行跨列(类似 Excel 合并单元格)
2. StackPanel(栈面板)------ 垂直/水平排
xml
<StackPanel Orientation="Vertical">
<Button>按钮1</Button>
<Button>按钮2</Button>
</StackPanel>
Orientation 控制排列方向:Horizontal 水平、Vertical 垂直。
3. WrapPanel(自动换行)
xml
<WrapPanel Orientation="Vertical">
<TextBox Width="200" Text="admin"/>
<PasswordBox Width="200" Password="123456"/>
<Button Content="登录"/>
</WrapPanel>
放不下了自动换行,适合登录框、工具栏这类场景。
4. DockPanel(停靠面板)
xml
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Top">上</Button>
<Button DockPanel.Dock="Left">左</Button>
<Button DockPanel.Dock="Right">右</Button>
<Button DockPanel.Dock="Bottom">下</Button>
</DockPanel>
子元素通过 DockPanel.Dock 锚定上下左右,LastChildFill 控制最后一个子元素是否自动填满剩余空间。典型用途:主窗体顶部菜单 + 底部状态栏 + 中间内容区。
5. Border(边框)------ 不是面板但常用
xml
<Border BorderBrush="Red" BorderThickness="2">
<StackPanel>
<TextBox Width="60"/>
<PasswordBox Width="60" Password="123456"/>
</StackPanel>
</Border>
给子内容画边框,相当于 WinForm 的 GroupBox 但更轻量。
面板可以多层嵌套:StackPanel 里套 Grid,Grid 里套 DockPanel......复杂的工业界面(上位机、医疗设备、产线监控)全是这样搭出来的。
四、样式系统:三级优先级
| 样式类型 | 定义位置 | 使用范围 | 优先级 |
|---|---|---|---|
| 全局样式 | App.xaml 的 <Application.Resources> |
整个应用程序 | 最低 |
| 局部样式 | Window.xaml 的 <Window.Resources> |
当前窗体 | 次之 |
| 行内样式 | 标签自己的 <Button.Style> |
当前标签 | 最高 |
局部样式示例(Window.Resources)
xml
<Window.Resources>
<!-- x:Key 是样式名,TargetType 是适用的控件类型 -->
<Style x:Key="ButtonDefault" TargetType="Button">
<Setter Property="Background" Value="Khaki"/>
<Setter Property="Foreground" Value="Turquoise"/>
<Setter Property="Opacity" Value="0.8"/>
</Style>
</Window.Resources>
<!-- 使用:StaticResource 引用 -->
<Button Style="{StaticResource ButtonDefault}">按钮</Button>
行内样式示例(Button.Style + 渐变背景)
xml
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="1,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.5" Color="Green"/>
<GradientStop Offset="1.0" Color="Black"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
渐变背景(线性渐变 LinearGradientBrush)
xml
<Window.Background>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset="0.2" Color="Wheat"/>
<GradientStop Offset="0.6" Color="Red"/>
<GradientStop Offset="1.0" Color="Yellow"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Window.Background>
Offset 是偏移量(0~1),颜色沿渐变方向依次"打散"。一行代码让窗体告别白底。
五、常用属性速查表
| 属性 | 作用 |
|---|---|
Width / Height |
元素的宽高 |
Title |
窗体标题 |
Icon |
窗体图标 |
WindowState |
启动状态(最大化/最小化) |
WindowStyle |
窗体样式(有无边框等) |
WindowStartupLocation |
启动位置(居中/手动) |
Background |
背景颜色 |
Foreground |
字体颜色 |
BorderBrush / BorderThickness |
边框颜色 / 厚度 |
FontFamily / FontSize / FontStyle / FontWeight |
字体族 / 大小 / 斜体 / 加粗 |
TextAlignment |
文本水平对齐 |
HorizontalAlignment / VerticalAlignment |
元素在容器中的对齐 |
HorizontalContentAlignment / VerticalContentAlignment |
内容在元素中的对齐 |
Margin |
外间距(元素与元素/容器之间) |
Opacity |
透明度(0 全透明 ~ 1 不透明),可用于控制可见性 |
Margin 多值规则(重点!)
xml
<Button Margin="10"/> <!-- 1个值:四方向都是10 -->
<Button Margin="10,20"/> <!-- 2个值:左右10,上下20 -->
<Button Margin="10,20,10,30"/> <!-- 4个值:左 上 右 下 -->
更直观的写法是直接用 Thickness 结构体:
xml
<Button.Margin>
<Thickness Bottom="30" Left="20" Right="10" Top="20"/>
</Button.Margin>
六、事件与 Name / x:Name 的区别
绑定事件
xml
<Button Content="按钮" Click="Button_Click_1"/>
<StackPanel PreviewMouseDown="StackPanel_MouseDown" Background="Black"/>
后台处理(事件路由,e.Source 拿到触发源)
csharp
private void StackPanel_MouseDown(object sender, MouseButtonEventArgs e)
{
object target = e.Source; // 事件的触发源
FrameworkElement ele = target as FrameworkElement;
if (ele is Button)
{
if (ele.Name == "btn1")
MessageBox.Show("button1使用和WinForm一样的Name属性");
else if (ele.Name == "btn2")
MessageBox.Show("button2使用WPF的x:Name机制");
else
MessageBox.Show("x:Name适用于任何XAML对象");
}
}
Name vs x:Name
| Name | x:Name | |
|---|---|---|
| 适用对象 | 只有自带 Name 属性的控件 | 任何 XAML 对象 |
| 本质 | 直接绑定属性 | 编译期生成字段引用 |
| 推荐 | WinForm 习惯 | WPF 推荐 |
事件是路由事件(RoutedEvent) :在 StackPanel 上挂
PreviewMouseDown,子按钮点击时事件会先经过父级(隧道)再回到子级(冒泡),所以父容器能统一拦截处理子元素的点击。
七、Content 内容属性:控件里还能嵌控件
WPF 的 Content 不只是字符串,可以是任何控件:
xml
<Button Width="100" Height="25">
<Button.Content>
<WrapPanel>
<TextBlock>我是</TextBlock>
<TextBlock Background="Red" Foreground="Wheat">文本标签</TextBlock>
</WrapPanel>
</Button.Content>
</Button>
注意:Content 只能设置一次,但内容可以是任意复杂的面板组合。
上篇小结
| 知识点 | 一句话记住 |
|---|---|
| WPF | 基于 Direct3D 的现代 UI 框架,.NET 3.0 起 |
| XAML | 声明式标记语言,标签 = C# 类 |
| 项目结构 | App.xaml(入口) + MainWindow.xaml(界面) + .xaml.cs(逻辑) |
| 布局 | Grid(网格) / StackPanel(栈) / WrapPanel(换行) / DockPanel(停靠) |
| 样式 | 全局 < 局部 < 行内,x:Key + TargetType + Setter |
| 属性 | Margin 1=四向 / 2=左右上下 / 4=左上右下 |
| 事件 | 路由事件,e.Source 拿触发源 |
| Name | 推荐 x:Name,任何 XAML 对象可用 |
下篇预告
窗体只是单页,真正的应用是多个页面之间跳转 。下篇《WPF 从零入门(下):导航控制器实战》带你拆解 NavigationWindow + Page + Frame 三种导航方式,以及 CheckBox、RadioButton、GroupBox、ComboBox 等常用选择类控件的实战用法。点关注不迷路!