csharp:wpf基本语法

WPF MVVM 标准示例教程

理解 WPF 的核心就在于理解 MVVM (Model-View-ViewModel) 模式。

在这个例子中,我们将创建一个简单的 “用户列表管理” 应用。

  • 功能:输入姓名和年龄,点击添加按钮,列表会实时更新。
  • 特点:完全不使用传统的事件处理器(如 `Button_Click`),而是使用 数据绑定 (Data Binding)命令 (Command)

在标准的 MVVM 中,我们需要两个辅助类:

  1. ViewModelBase: 实现 `INotifyPropertyChanged` 接口,用于通知 UI 数据变了。
  2. RelayCommand: 实现 `ICommand` 接口,用于处理按钮点击逻辑。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
 
namespace WpfMvvmExample
{
    // 1. 通知基类:当属性变化时,告诉 UI 刷新
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
 
        // 辅助方法:设置值并通知
        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }
 
    // 2. 命令类:将按钮点击行为绑定到方法
    public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;
 
        public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
        {
            _execute = execute;
            _canExecute = canExecute;
        }
 
        public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
 
        public void Execute(object parameter) => _execute(parameter);
 
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }
}

这是纯粹的数据对象,不包含任何 UI 逻辑。

namespace WpfMvvmExample
{
    public class User
    {
        public string Name { get; set; }
        public int Age { get; set; }
 
        // 用于显示的格式化字符串
        public string DisplayInfo => $"{Name} ({Age}岁)";
    }
}

这是 MVVM 的核心。它是 View(界面)和 Model(数据)的桥梁。

  • 它持有数据列表 (`ObservableCollection`)。
  • 它持有当前输入的字段。
  • 它持有命令 (`AddUserCommand`)。
using System.Collections.ObjectModel;
using System.Windows;
 
namespace WpfMvvmExample
{
    public class MainViewModel : ViewModelBase
    {
        // --- 状态字段 ---
        private string _inputName;
        private int _inputAge;
 
        // --- 绑定到界面的属性 ---
        public string InputName
        {
            get => _inputName;
            set => SetProperty(ref _inputName, value); // 值改变时通知 UI
        }
 
        public int InputAge
        {
            get => _inputAge;
            set => SetProperty(ref _inputAge, value);
        }
 
        // ObservableCollection 是 WPF 列表绑定的神器,集合变动会自动刷新界面
        public ObservableCollection<User> Users { get; set; }
 
        // --- 命令 ---
        public RelayCommand AddUserCommand { get; set; }
 
        // --- 构造函数 ---
        public MainViewModel()
        {
            Users = new ObservableCollection<User>
            {
                new User { Name = "张三", Age = 25 } // 初始数据
            };
 
            // 初始化命令:指定执行逻辑(AddUser) 和 判断逻辑(CanAddUser)
            AddUserCommand = new RelayCommand(AddUser, CanAddUser);
        }
 
        // --- 逻辑方法 ---
        private void AddUser(object obj)
        {
            // 添加到集合
            Users.Add(new User { Name = InputName, Age = InputAge });
 
            // 清空输入框
            InputName = string.Empty;
            InputAge = 0;
        }
 
        // 判断按钮是否可用(例如:名字不能为空)
        // 如果返回 false,WPF 会自动禁用按钮
        private bool CanAddUser(object obj)
        {
            return !string.IsNullOrWhiteSpace(InputName);
        }
    }
}

在 XAML 中,我们不写任何 C# 代码来处理逻辑,全部通过 `Binding` 连接到 ViewModel。

<Window x:Class="WpfMvvmExample.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:WpfMvvmExample"
        mc:Ignorable="d"
        Title="MVVM 标准示例" Height="350" Width="400">
 
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!-- 输入区域 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
            <TextBlock Text="姓名:" VerticalAlignment="Center"/>
            <!-- UpdateSourceTrigger=PropertyChanged 表示每敲一个字都同步给 ViewModel -->
            <TextBox Text="{Binding InputName, UpdateSourceTrigger=PropertyChanged}" 
                     Width="100" Margin="5,0"/>
 
            <TextBlock Text="年龄:" VerticalAlignment="Center" Margin="10,0,0,0"/>
            <TextBox Text="{Binding InputAge, UpdateSourceTrigger=PropertyChanged}" 
                     Width="50" Margin="5,0"/>
        </StackPanel>
 
        <!-- 按钮区域 -->
        <!-- Command 绑定到 ViewModel 中的 AddUserCommand -->
        <Button Grid.Row="1" Content="添加用户" 
                Command="{Binding AddUserCommand}"
                Width="100" HorizontalAlignment="Left" Margin="0,0,0,10"/>
 
        <!-- 列表区域 -->
        <!-- ItemsSource 绑定到 Users 集合 -->
        <ListBox Grid.Row="2" ItemsSource="{Binding Users}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- 显示 User 对象的 DisplayInfo 属性 -->
                    <TextBlock Text="{Binding DisplayInfo}" FontSize="14"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

最后,我们需要把 View 和 ViewModel 连接起来。这通常在 `MainWindow.xaml.cs` 中完成。

using System.Windows;
 
namespace WpfMvvmExample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
 
            // 关键步骤:设置 DataContext
            // 这告诉 View:“你的数据源和逻辑都在 MainViewModel 里”
            this.DataContext = new MainViewModel();
        }
    }
}
  1. DataContext (数据上下文):
    • 在 `MainWindow.xaml.cs` 中,我们把 `MainViewModel` 赋值给了 `DataContext`。
    • 这使得 XAML 中的 `{Binding InputName}` 知道去 `MainViewModel` 里找 `InputName` 属性。
  1. INotifyPropertyChanged:
    • 当你在输入框打字时,`set` 访问器被调用,`OnPropertyChanged` 被触发。
    • WPF 界面收到通知,知道数据变了。
  1. ICommand (命令):
    • 注意 XAML 中的 `<Button Command=“{Binding AddUserCommand}” … />`。
    • WPF 会自动调用 `AddUserCommand.Execute` 来运行逻辑。
    • WPF 还会自动调用 `AddUserCommand.CanExecute`。如果我们在 `CanAddUser` 方法里返回 `false`(比如名字为空),按钮会自动变灰(禁用)
  1. ObservableCollection:
    • 普通的 `List<T>` 添加数据时,界面不会知道。
    • `ObservableCollection<T>` 在添加/删除元素时会发出通知,列表控件(ListBox)会自动刷新显示新数据。

Tips WPF Binding 中 数据绑定Path 的用法详解

在 WPF 的数据绑定表达式 `{Binding …}` 中,`Path` 是最常用的属性之一,用于指定数据源中具体的属性名称。

这是最标准的写法,明确指定了要绑定的属性路径。

  • 语法: `{Binding Path=PropertyName}`
  • 含义: 告诉绑定引擎,去 DataContext(数据上下文)中找名为 `PropertyName` 的属性。

示例代码:

<!-- 绑定到 ViewModel 的 UserName 属性 -->
<TextBlock Text="{Binding Path=UserName}" />
 
<!-- 绑定到子属性 (例如 ViewModel 有个 User 对象,User 对象有 Name 属性) -->
<TextBlock Text="{Binding Path=User.Name}" />

WPF 的绑定语法允许省略 `Path=` 关键字。如果绑定表达式中的第一个参数没有指定属性名,WPF 默认将其视为 `Path`。

  • 语法: `{Binding PropertyName}`
  • 含义: 等同于 `{Binding Path=PropertyName}`。这是最常见的简写方式。

示例代码:

<!-- 简写方式,效果同上 -->
<TextBlock Text="{Binding UserName}" />
 
<!-- 嵌套属性简写 -->
<TextBlock Text="{Binding User.Name}" />

有时候我们需要绑定到数据源对象本身,而不是它的某个属性。这在列表控件(如 ListBox、ItemsControl)的模板中非常常见,特别是当数据源是简单的字符串列表或整数列表时。

  • 语法: `{Binding Path=.}` 或 `{Binding .}` 或 简单的 `{Binding}`
  • 含义: 绑定到当前的 DataContext 对象本身。

示例代码:

<!-- 假设 DataContext 是一个字符串 "Hello World" -->
 
<!-- 写法一:显式点号 -->
<TextBlock Text="{Binding Path=.}" />
 
<!-- 写法二:隐式点号 -->
<TextBlock Text="{Binding .}" />
 
<!-- 写法三:完全省略 (最推荐) -->
<TextBlock Text="{Binding}" />

应用场景 (ItemsControl):

<ListBox ItemsSource="{Binding StringList}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <!-- 这里 Binding 绑定的是 StringList 中的每一个字符串本身 -->
            <TextBlock Text="{Binding}" /> 
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

`Path` 支持使用索引器来访问集合或字典中的元素。

  • 语法: `{Binding Path=[Index]}`
  • 含义: 访问集合中特定位置的元素。

示例代码:

<!-- 绑定到某列表属性的第一个元素 -->
<TextBlock Text="{Binding Path=MyList[0]}" />
 
<!-- 如果 DataContext 本身就是列表,访问其第二个元素 -->
<TextBlock Text="{Binding Path=[1]}" />
 
<!-- 字典查表:绑定到 Key 为 "FirstName" 的值 -->
<TextBlock Text="{Binding Path=MyDictionary[FirstName]}" />

如果绑定的属性名称中包含特殊字符(虽然不推荐属性名包含特殊字符),或者使用了附加属性,通常需要用括号包裹。

  • 语法: `{Binding Path=(OwnerType.AttachedProperty)}`

示例代码:

<!-- 绑定到 Grid 的 Row 附加属性 -->
<TextBlock Text="{Binding Path=(Grid.Row)}" />

总结

写法 解释 备注
`{Binding Path=Name}` 标准写法 最清晰,适合初学者理解
`{Binding Name}` 省略 Path 最常用,简洁
`{Binding}` 绑定到对象本身 常用于简单数据类型的列表模板
`{Binding .}` 绑定到对象本身 同上,显式写法
请输入您的评论. 可以使用维基语法:
 

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/wpf基本语法.txt
  • 最后更改: 2025/12/15 15:25
  • 张叶安