csharp:wpf基本语法

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
csharp:wpf基本语法 [2025/11/26 15:40] – 移除 - 外部编辑 (未知日期) 127.0.0.1csharp:wpf基本语法 [2025/12/15 15:25] (当前版本) – [核心概念讲解] 张叶安
行 1: 行 1:
 +====== WPF MVVM 标准示例教程 ======
  
 +理解 WPF 的核心就在于理解 **MVVM (Model-View-ViewModel)** 模式。
 +
 +在这个例子中,我们将创建一个简单的 **“用户列表管理”** 应用。
 +  * **功能**:输入姓名和年龄,点击添加按钮,列表会实时更新。
 +  * **特点**:完全**不使用**传统的事件处理器(如 `Button_Click`),而是使用 **数据绑定 (Data Binding)** 和 **命令 (Command)**。
 +
 +===== 1. 基础架构 (Infrastructure) =====
 +
 +在标准的 MVVM 中,我们需要两个辅助类:
 +  - **ViewModelBase**: 实现 `INotifyPropertyChanged` 接口,用于通知 UI 数据变了。
 +  - **RelayCommand**: 实现 `ICommand` 接口,用于处理按钮点击逻辑。
 +
 +<code csharp>
 +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; }
 +        }
 +    }
 +}
 +</code>
 +
 +===== 2. 模型 (Model) =====
 +
 +这是纯粹的数据对象,不包含任何 UI 逻辑。
 +
 +<code csharp>
 +namespace WpfMvvmExample
 +{
 +    public class User
 +    {
 +        public string Name { get; set; }
 +        public int Age { get; set; }
 +        
 +        // 用于显示的格式化字符串
 +        public string DisplayInfo => $"{Name} ({Age}岁)";
 +    }
 +}
 +</code>
 +
 +===== 3. 视图模型 (ViewModel) =====
 +
 +这是 MVVM 的核心。它是 View(界面)和 Model(数据)的桥梁。
 +  * 它持有数据列表 (`ObservableCollection`)。
 +  * 它持有当前输入的字段。
 +  * 它持有命令 (`AddUserCommand`)。
 +
 +<code csharp>
 +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);
 +        }
 +    }
 +}
 +</code>
 +
 +===== 4. 视图 (View - XAML) =====
 +
 +在 XAML 中,我们**不写任何 C# 代码**来处理逻辑,全部通过 `Binding` 连接到 ViewModel。
 +
 +<code xml>
 +<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>
 +</code>
 +
 +===== 5. 胶水代码 (Code-Behind) =====
 +
 +最后,我们需要把 View 和 ViewModel 连接起来。这通常在 `MainWindow.xaml.cs` 中完成。
 +
 +<code csharp>
 +using System.Windows;
 +
 +namespace WpfMvvmExample
 +{
 +    public partial class MainWindow : Window
 +    {
 +        public MainWindow()
 +        {
 +            InitializeComponent();
 +
 +            // 关键步骤:设置 DataContext
 +            // 这告诉 View:“你的数据源和逻辑都在 MainViewModel 里”
 +            this.DataContext = new MainViewModel();
 +        }
 +    }
 +}
 +</code>
 +
 +===== 核心概念讲解 =====
 +
 +  - **DataContext (数据上下文)**:
 +    * 在 `MainWindow.xaml.cs` 中,我们把 `MainViewModel` 赋值给了 `DataContext`。
 +    * 这使得 XAML 中的 `{Binding InputName}` 知道去 `MainViewModel` 里找 `InputName` 属性。
 +
 +  - **INotifyPropertyChanged**:
 +    * 当你在输入框打字时,`set` 访问器被调用,`OnPropertyChanged` 被触发。
 +    * WPF 界面收到通知,知道数据变了。
 +
 +  - **ICommand (命令)**:
 +    * 注意 XAML 中的 `<Button Command="{Binding AddUserCommand}" ... />`。
 +    * WPF 会自动调用 `AddUserCommand.Execute` 来运行逻辑。
 +    * WPF 还会自动调用 `AddUserCommand.CanExecute`。如果我们在 `CanAddUser` 方法里返回 `false`(比如名字为空),**按钮会自动变灰(禁用)**。
 +
 +  - **ObservableCollection**:
 +    * 普通的 `List<T>` 添加数据时,界面不会知道。
 +    * `ObservableCollection<T>` 在添加/删除元素时会发出通知,列表控件(ListBox)会自动刷新显示新数据。
 +
 +
 +
 +
 +=== Tips  WPF Binding 中 数据绑定Path 的用法详解 ===
 +
 +在 WPF 的数据绑定表达式 `{Binding ...}` 中,`Path` 是最常用的属性之一,用于指定数据源中具体的属性名称。
 +
 +==== 1. 显式使用 Path ====
 +
 +这是最标准的写法,明确指定了要绑定的属性路径。
 +
 +  * **语法**: `{Binding Path=PropertyName}`
 +  * **含义**: 告诉绑定引擎,去 DataContext(数据上下文)中找名为 `PropertyName` 的属性。
 +
 +**示例代码:**
 +<code xml>
 +<!-- 绑定到 ViewModel 的 UserName 属性 -->
 +<TextBlock Text="{Binding Path=UserName}" />
 +
 +<!-- 绑定到子属性 (例如 ViewModel 有个 User 对象,User 对象有 Name 属性) -->
 +<TextBlock Text="{Binding Path=User.Name}" />
 +</code>
 +
 +==== 2. 隐式使用 Path (省略 Path=) ====
 +
 +WPF 的绑定语法允许省略 `Path=` 关键字。如果绑定表达式中的第一个参数没有指定属性名,WPF 默认将其视为 `Path`。
 +
 +  * **语法**: `{Binding PropertyName}`
 +  * **含义**: 等同于 `{Binding Path=PropertyName}`。这是最常见的简写方式。
 +
 +**示例代码:**
 +<code xml>
 +<!-- 简写方式,效果同上 -->
 +<TextBlock Text="{Binding UserName}" />
 +
 +<!-- 嵌套属性简写 -->
 +<TextBlock Text="{Binding User.Name}" />
 +</code>
 +
 +==== 3. Path 为 "." (或省略 Path 且为空) ====
 +
 +有时候我们需要绑定到**数据源对象本身**,而不是它的某个属性。这在列表控件(如 ListBox、ItemsControl)的模板中非常常见,特别是当数据源是简单的字符串列表或整数列表时。
 +
 +  * **语法**: `{Binding Path=.}` 或 `{Binding .}` 或 简单的 `{Binding}`
 +  * **含义**: 绑定到当前的 DataContext 对象本身。
 +
 +**示例代码:**
 +<code xml>
 +<!-- 假设 DataContext 是一个字符串 "Hello World" -->
 +
 +<!-- 写法一:显式点号 -->
 +<TextBlock Text="{Binding Path=.}" />
 +
 +<!-- 写法二:隐式点号 -->
 +<TextBlock Text="{Binding .}" />
 +
 +<!-- 写法三:完全省略 (最推荐) -->
 +<TextBlock Text="{Binding}" />
 +</code>
 +
 +**应用场景 (ItemsControl):**
 +<code xml>
 +<ListBox ItemsSource="{Binding StringList}">
 +    <ListBox.ItemTemplate>
 +        <DataTemplate>
 +            <!-- 这里 Binding 绑定的是 StringList 中的每一个字符串本身 -->
 +            <TextBlock Text="{Binding}" /> 
 +        </DataTemplate>
 +    </ListBox.ItemTemplate>
 +</ListBox>
 +</code>
 +
 +==== 4. 索引器语法 ====
 +
 +`Path` 支持使用索引器来访问集合或字典中的元素。
 +
 +  * **语法**: `{Binding Path=[Index]}`
 +  * **含义**: 访问集合中特定位置的元素。
 +
 +**示例代码:**
 +<code xml>
 +<!-- 绑定到某列表属性的第一个元素 -->
 +<TextBlock Text="{Binding Path=MyList[0]}" />
 +
 +<!-- 如果 DataContext 本身就是列表,访问其第二个元素 -->
 +<TextBlock Text="{Binding Path=[1]}" />
 +
 +<!-- 字典查表:绑定到 Key 为 "FirstName" 的值 -->
 +<TextBlock Text="{Binding Path=MyDictionary[FirstName]}" />
 +</code>
 +
 +==== 5. 特殊字符转义 ====
 +
 +如果绑定的属性名称中包含特殊字符(虽然不推荐属性名包含特殊字符),或者使用了附加属性,通常需要用括号包裹。
 +
 +  * **语法**: `{Binding Path=(OwnerType.AttachedProperty)}`
 +
 +**示例代码:**
 +<code xml>
 +<!-- 绑定到 Grid 的 Row 附加属性 -->
 +<TextBlock Text="{Binding Path=(Grid.Row)}" />
 +</code>
 +
 +=== 总结 ===
 +
 +^ 写法 ^ 解释 ^ 备注 ^
 +| `{Binding Path=Name}` | 标准写法 | 最清晰,适合初学者理解 |
 +| `{Binding Name}` | 省略 Path | 最常用,简洁 |
 +| `{Binding}` | 绑定到对象本身 | 常用于简单数据类型的列表模板 |
 +| `{Binding .}` | 绑定到对象本身 | 同上,显式写法 |

该主题尚不存在

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