c#学习WPF笔记(一)

WPF 入门实战笔记 --- 监控面板项目

一、项目背景

一个基于 WPF .NET Framework 4.7.2 的 PLC 监控面板雏形,通过 ItemsControl 批量生成轴控制行(Label + TextBox + Button),为后续写入 PLC 做准备。

二、项目文件结构

复制代码
Monitor_Plc/
├── App.xaml              ← 应用程序入口 (XAML)
├── App.xaml.cs           ← 应用程序入口 (C# 代码后置)
├── MainWindow.xaml       ← 主窗口界面 (XAML)
├── MainWindow.xaml.cs    ← 主窗口逻辑 (C# 代码后置)
├── Monitor_Plc.csproj    ← 项目配置 (.NET Framework 4.7.2)
└── Images/               ← 图片资源文件夹

注意 :WPF (.NET Framework) 项目没有 Program.cs ,入口点由编译系统自动生成,内部会创建 App 实例并调用 Run()


三、App.xaml --- 应用程序入口(完整注释)

xml 复制代码
<!--
  Application 是 WPF 应用程序的根对象
  x:Class 指向 C# 中的 App 类,两者通过 partial class 合并
  StartupUri 指定启动时自动打开哪个窗口
-->
<Application x:Class="Monitor_Plc.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Monitor_Plc"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <!-- 全局资源定义处,当前为空 -->
    </Application.Resources>
</Application>

四、App.xaml.cs --- 应用程序入口代码后置

csharp 复制代码
using System;
using System.Windows;

namespace Monitor_Plc
{
    /// <summary>
    /// App.xaml 的交互逻辑
    /// </summary>
    public partial class App : Application
    {
        // WPF 项目没有 Program.cs,入口点由编译系统自动生成
        // 编译时自动生成 Main() 方法:
        //   [STAThread]
        //   static void Main()
        //   {
        //       App app = new App();
        //       app.InitializeComponent();
        //       app.Run();  // 启动消息循环
        //   }
    }
}

五、MainWindow.xaml --- 主窗口界面(完整注释)

xml 复制代码
<!--
  Window 是 WPF 窗口的根元素
  x:Class 绑定到 C# 中的 MainWindow 类
  xmlns 声明 XAML 命名空间:
    - presentation  : WPF 核心控件 (Button, Label, Grid 等)
    - x             : XAML 语言特性 (x:Class, x:Name, x:Type 等)
    - local         : 当前项目的 C# 命名空间 (Monitor_Plc)
-->
<Window x:Class="Monitor_Plc.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Monitor_Plc"
        mc:Ignorable="d"
        Title="Home" Height="800" Width="1000">

    <!-- ===== 窗口背景:图片背景 ===== -->
    <Window.Background>
        <!--
          ImageBrush: 用图片填充背景区域
          ImageSource: 图片路径(相对路径,生成操作需设为 Resource)
          Opacity: 透明度 (0~1), 1=完全不透明, 0=完全透明
          Stretch: 图片拉伸方式
        -->
        <ImageBrush ImageSource="Images/杀戮天使.jpg" Opacity="0.9"
                    Stretch="UniformToFill" />
        <!--
          Stretch 取值说明:
            Fill           → 拉伸填满整个区域(可能变形)
            Uniform        → 等比例缩放,完整显示(可能有黑边)
            UniformToFill  → 等比例缩放,填满区域(可能裁剪边缘,最常用)
        -->
    </Window.Background>

    <!-- ===== 根布局容器 Grid ===== -->
    <Grid>
        <!--
          ItemsControl --- 数据项列表容器
          核心机制:根据 ItemsSource 绑定的数据列表,自动为每条数据生成 UI
          内部默认用 StackPanel 垂直排列每一条数据项
          不需要手动写 RowDefinitions,每行是 ItemsControl 自动生成的
        -->
        <ItemsControl Name="AxisItems">
            <!--
              ItemTemplate --- 项模板
              定义每条数据的显示样式,所有数据共用这一个模板
              注意拼写:ItemsControl.ItemTemplate(有 s)
            -->
            <ItemsControl.ItemTemplate>
                <!--
                  DataTemplate --- 数据模板
                  里面写的控件会为每条数据重复生成一份
                -->
                <DataTemplate>
                    <!--
                      Margin: 外边距,语法 "左,上,右,下" 或 "上下,左右"
                      这里 Margin="0,5" → 上下各留 5px,行与行之间不紧贴
                    -->
                    <Grid Margin="0,5">
                        <Grid.ColumnDefinitions>
                            <!--
                              ColumnDefinition 定义列:
                                Auto → 宽度由内容撑开
                                *    → 占满剩余空间(可写 2* 表示两倍)
                                数字  → 固定像素,如 Width="200"
                            -->
                            <ColumnDefinition Width="Auto"/>  <!-- 第0列:Label -->
                            <ColumnDefinition Width="Auto"/>  <!-- 第1列:TextBox -->
                            <ColumnDefinition Width="Auto"/>  <!-- 第2列:Button -->
                        </Grid.ColumnDefinitions>

                        <!--
                          Grid.Column="0" → 放在第 0 列
                          Grid.ColumnSpan="2" → 跨 2 列合并(从当前列向右数)
                          Grid.RowSpan="2"    → 跨 2 行合并(从当前行向下数)
                        -->
                        <Label Grid.Column="0"
                               Content="{Binding LabelText}"   <!-- {Binding} 绑定 AxisItem.LabelText 属性 -->
                               Foreground="Wheat" />           <!-- 文字颜色:Wheat 小麦色 -->

                        <TextBox Grid.Column="1"
                                 Margin="0,0,5,0"              <!-- 右边留 5px 间距 -->
                                 TextAlignment="Center"        <!-- 文字水平居中 -->
                                 VerticalContentAlignment="Center"  <!-- 文字垂直居中(内容在控件内居中) -->
                                 Width="80" Height="30"
                                 Text="{Binding Value}" />     <!-- 双向绑定 AxisItem.Value,修改后同步回数据 -->

                        <Button Grid.Column="2"
                                Content="Confirm"
                                Width="60" Height="30"
                                Click="Button_Click" />        <!-- 点击事件绑定到 C# 方法 -->
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

六、MainWindow.xaml.cs --- 主窗口逻辑(完整注释)

csharp 复制代码
using System;
using System.Collections.Generic;  // List<T>
using System.Windows;              // Window, MessageBox, RoutedEventArgs
using System.Windows.Controls;     // Button, TextBox, Label, ItemsControl

namespace Monitor_Plc
{
    /// <summary>
    /// 数据模型类
    /// 表示 ItemsControl 中每一条轴的数据
    /// 属性名必须与 XAML 中 {Binding 属性名} 完全一致
    /// 位置:放在 MainWindow 类外面、命名空间里面
    /// </summary>
    public class AxisItem
    {
        // 自动属性 (Auto-Implemented Property)
        // { get; set; } 编译器自动生成私有字段
        public string LabelText { get; set; }  // 标签文字,如 "Axis1Pos:"
        public string Value { get; set; }       // 输入框的值
    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// partial 关键字:这个类的另一部分由 XAML 编译自动生成
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 构造函数 --- 窗口创建时调用
        /// </summary>
        public MainWindow()
        {
            // 必须调用!初始化 XAML 中定义的控件(加载编译后的 BAML 资源)
            // 如果不调用,所有控件都不会被创建
            InitializeComponent();

            // 给 ItemsControl 绑定数据源
            // ItemsSource 必须是一个实现了 System.Collections.IEnumerable 的集合
            // List<T> 是最常用的集合类型
            AxisItems.ItemsSource = new List<AxisItem>()
            {
                // 每一条 AxisItem → ItemsControl 生成一行 UI
                // Value 留空,用户在 TextBox 中输入
                new AxisItem{ LabelText = "Axis1Pos:", Value = "" },
                new AxisItem{ LabelText = "Axis2Pos:", Value = "" },
                new AxisItem{ LabelText = "Axis3Pos:", Value = "" },
                new AxisItem{ LabelText = "Axis4Pos:", Value = "" }
            };
        }

        /// <summary>
        /// Confirm 按钮点击事件
        /// DataTemplate 中所有 Button 点击都触发此方法
        /// </summary>
        /// <param name="sender">触发事件的控件(就是用户点的那一个 Button)</param>
        /// <param name="e">路由事件参数</param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // sender 是 object 类型,需要转换为 Button
            // as 安全转换:失败返回 null,不会抛异常
            var btn = sender as Button;

            // 关键机制:获取当前行的数据
            // 在 ItemsControl 中,每条数据的 DataContext 自动设为对应的 AxisItem 对象
            // Button 没有设置 DataContext,就继承父级 Grid 的 DataContext
            // 所以 btn.DataContext 就是这一行的 AxisItem
            var item = btn.DataContext as AxisItem;

            // 显示当前行的标签和值
            // $ 字符串插值:{item.LabelText} 替换为属性值
            MessageBox.Show($"{item.LabelText} = {item.Value}");

            // 后续可替换为 PLC 写入逻辑:
            // PLC.Write(item.LabelText, item.Value);
        }
    }
}

七、核心概念详解

7.1 Grid 布局系统

Grid 是 WPF 最强大的布局容器,通过行和列的二维表格来排列控件。

xml 复制代码
<Grid>
    <!-- 先定义行和列 -->
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>      <!-- 第0行:高度由内容决定 -->
        <RowDefinition Height="2*"/>        <!-- 第1行:占剩余空间的 2/3 -->
        <RowDefinition Height="*"/>         <!-- 第2行:占剩余空间的 1/3 -->
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>    <!-- 第0列:宽度由内容决定 -->
        <ColumnDefinition Width="*"/>       <!-- 第1列:占满剩余空间 -->
        <ColumnDefinition Width="100"/>     <!-- 第2列:固定 100px -->
    </Grid.ColumnDefinitions>

    <!-- 通过附加属性指定位置 -->
    <Label Grid.Row="0" Grid.Column="0" Content="左上" />
    <TextBox Grid.Row="0" Grid.Column="1" />
    <Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="跨两列" />
</Grid>

RowDefinition.Height / ColumnDefinition.Width 取值:

说明 场景
Auto 由内容撑开 标题栏、标签、工具栏
* 按比例占满剩余空间 主要内容区域
2* 占两倍剩余空间(比 * 宽一倍) 需要按比例分割时
100 固定 100 像素 侧边栏、固定宽度的列

Grid 附加属性:

属性 含义 示例
Grid.Row="0" 放在第 0 行(索引从 0 开始)
Grid.Column="0" 放在第 0 列
Grid.RowSpan="2" 跨 2 行合并 合并后的控件占两行高度
Grid.ColumnSpan="2" 跨 2 列合并 合并后的控件占两列宽度

7.2 ItemsControl --- 数据驱动的 UI 生成器

ItemsControl 是 WPF 中根据数据列表自动生成 UI 的核心控件。这是 MVVM 模式的基础。

工作机制:

复制代码
AxisItems.ItemsSource = List<AxisItem> (4 条数据)
  └── ItemsPanelTemplate (默认是 StackPanel,垂直排列)
       ├── 数据[0]: AxisItem{LabelText="Axis1Pos:", Value=""}
       │   └── DataTemplate → Grid(Label | TextBox | Button)
       │                      └── DataContext = 这条 AxisItem
       │
       ├── 数据[1]: AxisItem{LabelText="Axis2Pos:", Value=""}
       │   └── DataTemplate → Grid(Label | TextBox | Button)
       │                      └── DataContext = 这条 AxisItem
       │
       ├── 数据[2]: AxisItem{LabelText="Axis3Pos:", Value=""}
       │   └── DataTemplate → Grid(Label | TextBox | Button)
       │                      └── DataContext = 这条 AxisItem
       │
       └── 数据[3]: AxisItem{LabelText="Axis4Pos:", Value=""}
           └── DataTemplate → Grid(Label | TextBox | Button)
                              └── DataContext = 这条 AxisItem

核心要点:

  1. ItemsSource 绑定数据源(List<T>ObservableCollection<T> 等)
  2. ItemTemplate 定义每条数据长什么样
  3. DataTemplate 里面的 {Binding} 从当前数据的 DataContext 中取值
  4. 每一条数据 生成的控件组的 DataContext 自动设为该数据对象本身
  5. 不需要写行定义,ItemsControl 内部自动处理排列

7.3 数据绑定 {Binding}

xml 复制代码
<Label Content="{Binding LabelText}" />

含义: Label 的 Content 属性从当前 DataContextLabelText 属性中取值。

双向绑定:

xml 复制代码
<TextBox Text="{Binding Value}" />

用户在 TextBox 中输入时,Value 属性也会同步更新(需数据类实现 INotifyPropertyChanged,但简单场景下默认双向绑定对 TextBox.Text 已生效)。


7.4 事件处理 --- 获取当前行数据

csharp 复制代码
private void Button_Click(object sender, RoutedEventArgs e)
{
    // sender 是触发事件的 Button 控件
    Button btn = sender as Button;

    // 获取这一行绑定的数据对象
    AxisItem item = btn.DataContext as AxisItem;

    // 现在可以访问这一行的所有数据
    string label = item.LabelText;
    string value = item.Value;
}

为什么这样可以拿到当前行的数据?

  • Button 在 DataTemplate 中,没有设置自己的 DataContext
  • 它继承父级 Grid 的 DataContext
  • 而 Grid 是由 ItemsControl 根据 AxisItem 生成的,DataContext 就是那个 AxisItem
  • 所以 btn.DataContext 就是点击那一行的数据

7.5 VerticalAlignment vs VerticalContentAlignment

很多初学者容易混淆这两个属性,这里做详细对比:

属性 作用对象 控制什么 可选值
VerticalAlignment 控件自身 控件在父容器中的垂直位置 Top, Center, Bottom, Stretch
VerticalContentAlignment 控件内部的内容 文本/内容在控件内部的垂直位置 Top, Center, Bottom, Stretch
HorizontalAlignment 控件自身 控件在父容器中的水平位置 Left, Center, Right, Stretch
HorizontalContentAlignment 控件内部的内容 内容在控件内部的水平位置 Left, Center, Right, Stretch
TextAlignment 文本 文字在控件中的水平对齐 Left, Center, Right, Justify

示例对比:

xml 复制代码
<!-- 错误:文字还在顶部 -->
<TextBox VerticalAlignment="Center" Text="Hello" />

<!-- 正确:文字在 TextBox 内部居中 -->
<TextBox VerticalContentAlignment="Center" TextAlignment="Center" Text="Hello" />

7.6 常用 WPF 控件一览

控件 命名空间 用途 主要属性
Label System.Windows.Controls 只读标签,支持快捷键 Content, Foreground, Background
TextBlock System.Windows.Controls 只读文本(轻量,无背景/边框) Text, TextWrapping
TextBox System.Windows.Controls 可编辑输入框 Text, MaxLength, IsReadOnly
Button System.Windows.Controls 按钮,可触发命令/事件 Content, Click, Command
Grid System.Windows.Controls 表格布局容器 RowDefinitions, ColumnDefinitions
StackPanel System.Windows.Controls 水平或垂直堆叠排列 Orientation
ItemsControl System.Windows.Controls 数据驱动列表容器 ItemsSource, ItemTemplate
Image System.Windows.Controls 显示图片 Source, Stretch
Rectangle System.Windows.Shapes 矩形(用于背景/分割线) Fill, Opacity

7.7 背景设置

纯色背景:

xml 复制代码
<Window Background="#FF2D2D2D">      <!-- 十六进制颜色:#AARRGGBB -->
<Window Background="AliceBlue">      <!-- 命名颜色 -->
<Window Background="{StaticResource MyBrush}">  <!-- 资源引用 -->

图片背景:

xml 复制代码
<Window.Background>
    <ImageBrush ImageSource="Images/background.jpg" Stretch="UniformToFill" Opacity="0.5" />
</Window.Background>

图片 + 半透明遮罩层(推荐,保留亮度降低饱和度):

xml 复制代码
<Grid>
    <!-- 图片层 -->
    <Image Source="Images/background.jpg" Stretch="UniformToFill" />

    <!-- 半透明白色覆盖层 -->
    <Rectangle Fill="White" Opacity="0.3" />
    <!-- Opacity 越大颜色越淡:0.2 → 稍微柔和, 0.4 → 明显变淡, 0.6 → 很淡 -->

    <!-- 也可以使用黑色覆盖层来变暗 -->
    <!-- <Rectangle Fill="Black" Opacity="0.3" /> -->

    <!-- 控件层 -->
    <Grid>
        <ItemsControl Name="AxisItems"> ... </ItemsControl>
    </Grid>
</Grid>

注意 :WPF 原生支持的图片格式:.bmp .jpg .jpeg .png .gif .tiff .ico .wmf不支持 .avif


7.8 XAML 注释语法

xml 复制代码
<!-- 单行注释 -->
<!--
    多行注释
    多行注释
-->

注意:Ctrl+/ 快捷键是 C# 的注释快捷键,XAML 不适用 ,需要手写 <!-- -->


7.9 TextBox 限制输入

xml 复制代码
<!-- 限制字符数 -->
<TextBox MaxLength="5" />

<!-- 只允许输入数字(需代码后置配合) -->
<TextBox PreviewTextInput="TextBox_PreviewTextInput" />
csharp 复制代码
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    foreach (char c in e.Text)
    {
        if (!char.IsDigit(c))
        {
            e.Handled = true;  // 阻止输入
            return;
        }
    }
}

八、常见问题排查

8.1 XAML 编译/运行错误

错误现象 原因 解决方法
"类型 'ItemsControl' 不存在" 拼写错误 检查 ItemsControl(有 s)
"未找到 ItemTemplate" 标签写错了 写成 <ItemsControl.ItemTemplate>
设计器不显示,但能运行 XAML 语法错误 查看错误列表窗口
{Binding} 不显示内容 属性名拼写不一致 检查 LabelText 是否和 C# 类一致

8.2 布局问题

现象 原因 解决方法
按钮跑到窗口最右边 中间列是 *,占满剩余空间 改为 Auto 或加 HorizontalAlignment="Left"
控件重叠在一起 放在了同一个 Grid 格子 检查 Grid.Row/Grid.Column 索引
TextBox 不显示 误用了 TextBlock 改成 <TextBox>
文字不居中 用了 VerticalAlignment 不是 VerticalContentAlignment 改用 VerticalContentAlignment="Center"
图片不显示 格式不支持(如 .avif)或路径错误 转成 .jpg/.png,生成操作设为 Resource
控件位置不对 索引从 0 开始,忘记 +1 第 0 行/列是第一个

8.3 代码后置问题

现象 原因 解决方法
InitializeComponent() 报错 XAML 有语法错误 先修 XAML 错误
ItemsSource 赋值后不显示 忘记调 InitializeComponent() 确保先调 InitializeComponent()
sender as Button 返回 null sender 不是 Button 检查事件是否绑定在 Button 上
DataContext as AxisItem 返回 null DataContext 未正确绑定 检查 ItemsSource 是否赋值

九、后续扩展方向

当前项目是一个基础的 WPF 监控面板模板,后续可以:

  1. PLC 通信 :引入 HslCommunicationS7.Net 库,在 Button_Click 中写入 PLC
  2. 数据刷新 :用 ObservableCollection<AxisItem> 替代 List<AxisItem>,实现 UI 自动更新
  3. MVVM 模式 :引入 INotifyPropertyChangedICommand,将业务逻辑与 UI 完全分离
  4. 样式统一 :在 App.xaml<Application.Resources> 中定义全局样式
  5. 多窗口:添加更多窗口,用导航框架切换
  6. 国际化:支持中英文切换(通过资源字典)
相关推荐
AOwhisky8 小时前
Redis 学习笔记(第三期):持久化与主从复制
运维·数据库·redis·笔记·学习·云计算
Tbisnic10 小时前
AI大模型学习第十一天:技术选型、安全防护与金融实战
python·学习·ai·大模型·提示词工程
xmtxz11 小时前
计算机网络基础课程学习心得:从理论抽象到硬核实战的进阶之路
运维·学习
YM52e12 小时前
男孩子在外自我保护指南——用鸿蒙 ArkTS 构建交互式安全教育应用
学习·安全·华为·harmonyos·鸿蒙·鸿蒙系统
aXin_ya13 小时前
Ai Vibecoding学习(各个AI的讲解)
学习
fanged13 小时前
Linux内核学习16--I2C子系统(TODO)
学习
.千余13 小时前
【C++】C++继承入门(下):友元、静态成员与菱形继承的底层逻辑
开发语言·c++·笔记·学习·其他
YJlio14 小时前
《Sysinternals实战指南》16.5 Ctrl2Cap 工具详解:把 Caps Lock 变成 Ctrl 的键盘改造与回退方法
linux·运维·服务器·网络·python·学习·计算机外设
花北城14 小时前
【C#】ABP框架服务端开发
开发语言·c#·abp