WPF 学习总结
一、Canvas 绘图
1.1 概述
Canvas 是 WPF 中的一个布局容器,允许通过绝对坐标(Canvas.Left、Canvas.Top)精确定位子元素,适合绘制自定义图形和仪表盘等场景。
1.2 常用绘图元素
Ellipse(椭圆/圆)
xml
<!-- 外圆 -->
<Ellipse Canvas.Left="0" Canvas.Top="0" Width="300" Height="300"
Fill="Black" Stroke="Black" StrokeThickness="20" />
<!-- 内圆 -->
<Ellipse Canvas.Left="20" Canvas.Top="20" Width="260" Height="260"
Fill="Black" Stroke="Yellow" StrokeThickness="2" />
Line(线条)
xml
<!-- 绘制刻度线 -->
<Line Stroke="White" StrokeThickness="3"
X1="150" X2="150" Y1="30" Y2="50" />
Path + ArcSegment(弧形)
xml
<!-- 使用 Path 绘制弧形 -->
<Path Fill="Transparent" Stroke="Red" StrokeThickness="30">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="150,30">
<ArcSegment IsLargeArc="False" Point="270,150"
Size="135,135" SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
- Path:绘制任意线条和图形的控件
- PathGeometry:描述几何形状数据,由多段路径组合而成
- PathFigure:实现一段或多段路径
- ArcSegment:绘制弧形的段
- 关键属性:
StartPoint(起始点)、Point(弧形结束位置)、Size(半径)、SweepDirection(方向:Clockwise顺时针 /Counterclockwise逆时针)、IsLargeArc(大弧/小弧)
二、静态资源与动态资源
2.1 概念对比
| 类型 | 关键字 | 加载时机 | 运行时更新 | 适用场景 |
|---|---|---|---|---|
| 静态资源 | StaticResource |
窗口加载时 | 不更新 | 固定不变的资源 |
| 动态资源 | DynamicResource |
实际使用时 | 运行时更新 | 需要动态切换的资源 |
三、数据绑定
3.1 绑定基础
绑定到元素
xml
<Slider x:Name="slider2" />
<TextBox Text="{Binding ElementName=slider2, Path=Value, Mode=OneWay}" />
绑定到数据源(非元素)
方式一:创建数据上下文(XAML 中,推荐,有智能提示)
xml
<!-- 引用命名空间 -->
xmlns:local="clr-namespace:_3数据绑定"
<!-- 创建上下文对象 -->
<Window.DataContext>
<local:Student />
</Window.DataContext>
方式二:代码中设置 DataContext
csharp
// MainWindow.xaml.cs
Personcs p1 = new Personcs();
this.DataContext = p1;
xml
<!-- 配合设计时数据上下文获取智能提示 -->
d:DataContext="{d:DesignInstance Type=local:Personcs}"
3.2 五种绑定模式
| 模式 | 描述 | 使用场景 |
|---|---|---|
Default |
默认绑定,根据目标属性决定 | 常规绑定 |
OneWay |
单向:数据源 → UI | 数据变化需更新界面 |
TwoWay |
双向:数据源 ↔ UI | 表单输入等双向同步 |
OneTime |
一次性:数据源 → UI(仅初始化时) | 只需初始值的静态展示 |
OneWayToSource |
单向:UI → 数据源 | UI 变化需更新数据源 |
3.3 INotifyPropertyChanged 接口
实现该接口后,当属性值变化时通知 UI 更新。
方式一:手动传递属性名
csharp
public class Personcs : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string name = "张三";
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name"); // 手动传属性名
}
}
}
方式二:使用 [CallerMemberName](推荐,更简洁)
csharp
public class Personcs1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// CallerMemberName 让编译器自动传入属性名
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string name = "张三";
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(); // 无需传参
}
}
}
3.4 绑定实体类属性
xml
<StackPanel>
<WrapPanel>
<Label Content="用户" />
<TextBox Width="100" Height="30" Text="{Binding Path=Name}" />
</WrapPanel>
<WrapPanel>
<Label Content="年龄" />
<TextBox Width="100" Height="30" Text="{Binding Age}" />
</WrapPanel>
<WrapPanel>
<Label Content="性别" />
<ComboBox Width="100" ItemsSource="{Binding Sex}" />
</WrapPanel>
<WrapPanel>
<Label Content="出生日期" />
<DatePicker Width="150" Text="{Binding BirthDay}" />
</WrapPanel>
</StackPanel>
四、MVVM 模式
4.1 MVVM 理论
MVVM 分为三层,各司其职,完全解耦:
┌──────────┐ 数据绑定 ┌────────────┐ 操作/通知 ┌──────────┐
│ View │ ◄──────────► │ ViewModel │ ◄────────────► │ Model │
│ (界面) │ │ (视图模型) │ │ (数据模型) │
└──────────┘ └────────────┘ └──────────┘
- View(视图):XAML 界面,负责展示和交互,不写业务逻辑
- ViewModel(视图模型):中间桥梁,封装数据、命令、业务逻辑,不持有 View 引用
- Model(模型):数据实体、数据访问、业务模型
数据流向 :Model → ViewModel → View(数据展示)
用户操作:View → ViewModel → Model(数据修改)
完整流程:
- 用户操作界面 View → Binding 将数据传给 ViewModel
- ViewModel 做业务处理
- ViewModel 执行 Update,将新数据写入 Model
- Model 数据改变,Notify 通知 ViewModel
- ViewModel 触发
PropertyChanged事件,通知绑定,刷新 View 界面
4.2 ViewModelBase(基类)
csharp
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
4.3 RelayCommand(ICommand 实现)
csharp
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute; // 控制按钮是否可执行
private Action<object> _excute; // 按钮点击回调逻辑
// 构造函数:传入控制条件和执行逻辑
public RelayCommand(Predicate<object> canExecute, Action<object> excute)
{
this._excute = excute;
this._canExecute = canExecute;
}
// 构造函数:始终可执行
public RelayCommand(Action<object> excute)
{
this._canExecute = (o) => true;
this._excute = excute;
}
// 使用 CommandManager.RequerySuggested 自动刷新按钮状态
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _excute(parameter);
}
4.4 Model(数据模型)
csharp
public class Student : ViewModelBase
{
public int Id { get; set; }
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged(); }
}
public int Age { get; set; }
}
4.5 ViewModel(核心逻辑)
csharp
public class MainViewModel : ViewModelBase
{
private ObservableCollection<Student> _allStudents;
private ObservableCollection<Student> students;
public ObservableCollection<Student> Students
{
get { return students; }
set { students = value; OnPropertyChanged(); }
}
// 构造时初始化数据
public MainViewModel()
{
students = new ObservableCollection<Student>(MockDataHelper.initData);
_allStudents = new ObservableCollection<Student>(MockDataHelper.initData);
}
// 添加命令
public RelayCommand AddCommand => new RelayCommand((obj) =>
{
Student stu = new Student() { Id = _nextId++, Name = "Foo", Age = 20 };
_allStudents.Add(stu);
DoFitter();
});
// 带条件的查询命令
public RelayCommand SearchCommand => new RelayCommand(
(obj) => !string.IsNullOrEmpty(searchName), // 仅在搜索框不为空时可执行
(obj) => { DoFitter(); }
);
// 筛选方法
public void DoFitter()
{
IEnumerable<Student> list = _allStudents;
if (!string.IsNullOrEmpty(SearchName))
list = _allStudents.Where(s => s.Name.Contains(SearchName));
Students = new ObservableCollection<Student>(list);
}
}
4.6 View 绑定(XAML)
xml
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<!-- 绑定命令 -->
<Button Content="查询" Command="{Binding SearchCommand}" />
<Button Content="添加" Command="{Binding AddCommand}" />
<!-- 绑定输入框,实时更新 -->
<TextBox Text="{Binding SearchName, UpdateSourceTrigger=PropertyChanged}" />
<!-- DataGrid 绑定集合 -->
<DataGrid ItemsSource="{Binding Students}" AutoGenerateColumns="False">
<DataGridTemplateColumn Header="编号">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}" HorizontalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
4.7 DataGrid 单元格内命令绑定(RelativeSource)
xml
<!-- 在 DataGrid 单元格模板中绑定 ViewModel 的命令 -->
<Button Content="修改"
Command="{Binding DataContext.CellEditCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" />
RelativeSource AncestorType=DataGrid:向上查找父级 DataGrid,获取其 DataContext(即 MainViewModel)CommandParameter="{Binding}":传递当前行绑定的 Student 对象作为参数
4.8 ObservableCollection(可观察集合)
ObservableCollection<T>是 WPF 中可被监听的集合- 当添加或删除元素时,UI 会自动更新
- 普通
List<T>的 Add/Remove 操作不会被 UI 监听
五、值转换器
5.1 单值转换器(IValueConverter)
实现 IValueConverter 接口,将 bool 转为 string 显示文本。
csharp
public class BoolToTextConvert : IValueConverter
{
// 数据源 → UI(bool → string)
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool b)
return b ? "正常" : "禁用";
return "未知";
}
// UI → 数据源(string → bool)
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (text == "正常") return true;
if (text == "禁用") return false;
return Binding.DoNothing;
}
}
XAML 中使用:
xml
<!-- 引入转换器资源 -->
<Window.Resources>
<local:BoolToTextConvert x:Key="BoolToText" />
</Window.Resources>
<!-- 使用转换器 -->
<TextBox Text="{Binding IsNormal, Converter={StaticResource BoolToText}}" />
5.2 多值转换器(IMultiValueConverter)
实现 IMultiValueConverter 接口,将多个值合并为一个。
csharp
public class NameAgeMul : IMultiValueConverter
{
// 多个值 → 合并为一个字符串
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string name = values[0].ToString();
int age = (int)values[1];
return $"姓名:{name},年龄:{age}";
}
// 字符串 → 拆分为多个值
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string str = value.ToString();
string[] arr = str.Split(',');
string name = arr[0].Split(':')[1];
string age = arr[1].Split(':')[1];
return new object[] { name, int.Parse(age) };
}
}
XAML 中使用 MultiBinding:
xml
<Window.Resources>
<local:NameAgeMul x:Key="NameAge" />
</Window.Resources>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource NameAge}">
<Binding Path="Name" />
<Binding Path="Age" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
5.3 转换器使用步骤总结
- 定义转换器 :实现
IValueConverter(单值)或IMultiValueConverter(多值)接口 - 引入 XAML:先导入命名空间,再定义为资源
- 应用转换器 :在标签中使用
Converter="{StaticResource 资源Key}"