目录
简介
Windows Presentation Foundation (WPF) 是 Microsoft 提供的一个图形子系统,用于开发具有丰富用户界面的桌面应用程序。它通过提供强大的功能和灵活的设计,成为了开发 Windows 应用程序的首选框架之一。WPF 支持高度可定制化的 UI 设计,功能包括但不限于数据绑定、样式、控件模板、命令以及通知更改等,这些都帮助开发者轻松实现灵活而美观的用户界面。
在这篇文章中,我们将深入探讨一些 WPF 的基础概念,帮助初学者了解如何使用这些功能来构建应用程序。我们将涵盖以下几个重要的方面:
- 基础样式 (Styles):如何使用样式统一控件的外观。
- 控件模板 (Control Templates):如何自定义控件的结构和外观。
- 数据模板 (Data Templates):如何控制数据的展示方式。
- 控件绑定 (Data Binding):如何绑定控件的属性与数据源,保持 UI 与数据的同步。
- 命令 (Commands):如何使用命令模式来处理用户的操作。
- 通知更改 (INotifyPropertyChanged):如何通知 UI 属性发生了变化,进行实时更新。
一、样式基础
目的:给某些具有相同样式的控件,设置一个可以统一使用的样式。
1. 一个WPF的页面在普通设计实现的过程是:
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<StackPanel>
<Button Content="button1"
FontSize="18"
Foreground="White"
Background="Blue"></Button>
<Button Content="button2"
FontSize="18"
Foreground="White"
Background="Blue"></Button>
<Button Content="button3"
FontSize="18"
Foreground="White"
Background="Blue"></Button>
</StackPanel>
</Grid>
</Window>
2. 这三个按钮除了内容不同,其他属性都是相同的,那么就可以设计一个统一的样式,使得这三个按钮只需要应用同一个样式,而不需要针对于单个按钮进行设计。
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="FontSize"
Value="18" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="Background"
Value="Blue" />
<Setter Property="Content"
Value="Button1" />
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button></Button>
<Button></Button>
<Button></Button>
</StackPanel>
</Grid>
</Window>
3. 可以看到,在Button的属性列表中,并没有设计样式,但是页面上,样式已经显示出来了。因为没有给这个样式设置固定的名称,默认作用域是全局的。
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="FontSize"
Value="18" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="Background"
Value="Blue" />
<Setter Property="Content"
Value="Button1" />
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button Style="{StaticResource ButtonStyle}"></Button>
<Button Style="{StaticResource ButtonStyle}"></Button>
<Button Style="{StaticResource ButtonStyle}"></Button>
</StackPanel>
</Grid>
</Window>
4. 同样,样式集也可以有继承关系,BaseON。
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<Style x:Key="BaseButtonStyle"
TargetType="Button">
<Setter Property="FontSize"
Value="18" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="Background"
Value="Blue" />
</Style>
<Style x:Key="ButtonStyle"
TargetType="Button"
BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Content"
Value="Button1" />
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="button1"
Style="{StaticResource ButtonStyle}"></Button>
<Button Content="button2"
Style="{StaticResource ButtonStyle}"></Button>
<Button Content="button3"
Style="{StaticResource ButtonStyle}"></Button>
</StackPanel>
</Grid>
</Window>
元素的属性值优先级是最高的,无论在样式表中如何设计,在元素的属性中设置值之后,元素的属性值优先显示.
二、控件模板
文本大纲==》选择Button并右键==》编辑模板==》编辑副本==》确定
Button中能显示很多内容的主要原因是有ContentPresenter
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<Style x:Key="FocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" StrokeDashArray="1 2" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" StrokeThickness="1"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
<SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
<SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button Style="{DynamicResource ButtonStyle1}">
<StackPanel Orientation="Horizontal">
<Button Content="1"></Button>
<Button Content="2"></Button>
<Button Content="3"></Button>
<Button Content="4"></Button>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Window>
三、数据模板
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<DataGrid Name="list"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Code}"
Header="Code" />
<DataGridTextColumn Binding="{Binding Name}"
Header="Name" />
<DataGridTemplateColumn Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--<StackPanel Orientation="Horizontal">
<Button Content="删除" />
<Button Content="复制" />
<Button Content="保存" />
</StackPanel>-->
<StackPanel Orientation="Horizontal">
<Border Width="10"
Height="10"
Background="{Binding Code}" />
<TextBlock Margin="10,0"
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System.Windows;
namespace Style_base
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Color> test = new List<Color>();
test.Add(new Color() { Code = "#FFB6C1", Name = "浅粉红" });
test.Add(new Color() { Code = "#FFC0CB", Name = "粉红" });
test.Add(new Color() { Code = "#DC143C", Name = "深红(猩红)" });
test.Add(new Color() { Code = "#FFF0F5", Name = "淡紫红" });
list.ItemsSource = test;
}
}
public class Color
{
public string Code { get; set; }
public string Name { get; set; }
}
}
四、WPF绑定相关
1.绑定
绑定控件上的值以及绑定属性值.
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<StackPanel>
<Slider Name="slider"
Margin="5" />
<TextBox Text="{Binding ElementName=slider,Path=Value,Mode=Default}"
Margin="5" />
<TextBox Text="{Binding ElementName=slider,Path=Value,Mode=OneTime}"
Margin="5" />
<TextBox Text="{Binding ElementName=slider,Path=Value,Mode=OneWay}"
Margin="5" />
<TextBox Text="{Binding ElementName=slider,Path=Value,Mode=OneWayToSource}"
Margin="5" />
<TextBox Text="{Binding ElementName=slider,Path=Value,Mode=TwoWay}"
Margin="5" />
<TextBox Text="{Binding Name}"
Height="30"
Margin="5" />
</StackPanel>
</Grid>
</Window>
using System.Reflection.Metadata.Ecma335;
using System.Windows;
namespace Style_base
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Test() { Name = "张三" };
}
}
public class Test
{
public string Name { get; set; }
}
}
2. 命令(ICommand)
<Window x:Class="Style_base.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:Style_base"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Button Content="Click_Me" Height="46" Width="120" Command="{Binding ShowCommand}" />
</Grid>
</Window>
using System.Reflection.Metadata.Ecma335;
using System.Windows;
namespace Style_base
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
继承ICommand类,实现自己的Command类。
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Style_base
{
public class MyCommand : ICommand
{
Action executeAction;
public MyCommand(Action action)
{
executeAction = action;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
executeAction();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Style_base
{
public class MainViewModel
{
public MainViewModel()
{
ShowCommand = new MyCommand(Show);
}
public MyCommand ShowCommand { get; set; }
public void Show()
{
MessageBox.Show("点击按钮命令执行成功");
}
}
}
3. 通知更改
在2的MainViewModel中简单修改
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Style_base
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Name = "Hello WPF!";
ShowCommand = new MyCommand(Show);
}
public MyCommand ShowCommand { get; set; }
private string name;
public string Name
{
get { return name; }
set { name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); }
}
public event PropertyChangedEventHandler? PropertyChanged;
public void Show()
{
Name = "点击按钮命令执行成功";
MessageBox.Show(Name);
}
}
}
即可实现
封装成方法
新建一个基类View ModelBase
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Style_base
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
在MainViewModel中使用
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Style_base
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Name = "Hello WPF!";
ShowCommand = new MyCommand(Show);
}
public MyCommand ShowCommand { get; set; }
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
public void Show()
{
Name = "点击按钮命令执行成功";
MessageBox.Show(Name);
}
}
}
让方法自动识别是哪个属性发生了改变
对ViewModelBase进行改变
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Style_base
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName ="")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
在MainViewModel中使用
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Style_base
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Name = "Hello WPF!";
ShowCommand = new MyCommand(Show);
}
public MyCommand ShowCommand { get; set; }
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged(); }
}
public void Show()
{
Name = "点击按钮命令执行成功";
MessageBox.Show(Name);
}
}
}