WPF入门第一篇 基础布局与简单样式

首先,创建WPF项目,在自动打开的MainWindow.xaml里面,找到Grid标签,并将它替换为:

  1. <Grid>
  2. <Grid.RowDefinitions>
  3. <RowDefinition></RowDefinition>
  4. <RowDefinition></RowDefinition>
  5. </Grid.RowDefinitions>
  6. <Grid.ColumnDefinitions>
  7. <ColumnDefinition></ColumnDefinition>
  8. <ColumnDefinition></ColumnDefinition>
  9. </Grid.ColumnDefinitions>
  10. <TextBlock Name="t1" Grid.Row="0" Grid.Column="0" Text="t1"></TextBlock>
  11. <TextBlock Name="t2" Grid.Row="0" Grid.Column="1" Text="t2"></TextBlock>
  12. <Button Name="btn1" Grid.Row="1" Grid.Column="0" Content="btn1"></Button>
  13. <Button Name="btn2" Grid.Row="1" Grid.Column="1" Content="btn2"></Button>
  14. </Grid>

修改好之后,界面会变成这样:

WPF入门 - 图1

可以看到 MainWindow被分成一个“田”字,上面两个格子的左上角分别显示t1和t2,下面两个格子颜色有变化,并且中间显示btn1和btn2。

对比代码可以看出,WPF中Grid是用来布局的,我们使用RowDefinitionColumnDefinition来定义Grid有几行几列,之后再在Grid标签中定义了两个TextBlock和两个Button,它们的位置是通过Grid.RowGrid.Column确定的。同时,这四个控件的默认大小是填充了整个格子,且TextBlock中文本的显示位于左上角,Button中文本的显示位于居中位置。

下面我们写样式代码来使btn2变成我们常见的按钮样式,并且居中显示。

将btn2的代码替换为如下代码:

  1. <Button Name="btn2" Grid.Row="1" Grid.Column="1" Content="btn2" Width="100" Height="40"></Button>

为了让btn1和btn2的样式一样,且不用写重复的代码,我们可以将Width=”100”,Height=”40”单独拎出来,写成样式,并且给btn1和btn2引用该样式即可。

  1. <Window x:Class="WpfTest.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfTest"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Window.Resources>
  10. <Style TargetType="Button" x:Key="btn">
  11. <Setter Property="Width" Value="100"></Setter>
  12. <Setter Property="Height" Value="48"></Setter>
  13. </Style>
  14. </Window.Resources>
  15. <Grid>
  16. <Grid.RowDefinitions>
  17. <RowDefinition></RowDefinition>
  18. <RowDefinition></RowDefinition>
  19. </Grid.RowDefinitions>
  20. <Grid.ColumnDefinitions>
  21. <ColumnDefinition></ColumnDefinition>
  22. <ColumnDefinition></ColumnDefinition>
  23. </Grid.ColumnDefinitions>
  24. <TextBlock Name="t1" Grid.Row="0" Grid.Column="0" Text="t1"></TextBlock>
  25. <TextBlock Name="t2" Grid.Row="0" Grid.Column="1" Text="t2"></TextBlock>
  26. <Button Name="btn1" Grid.Row="1" Grid.Column="0" Content="btn1" Style="{StaticResource btn}"></Button>
  27. <Button Name="btn2" Grid.Row="1" Grid.Column="1" Content="btn2" Style="{StaticResource btn}"></Button>
  28. </Grid>
  29. </Window>

可以看出,样式可以在Window.Resources中定义,通过TargetType属性来限制适用类型,通过x:Key属性来被引用。

修改后效果是这样的:

WPF入门 - 图2

类似Web前端的css文件,我们常常将Style写在单独的文件中,并在页面中引用。在资源管理器中右键点击项目名,添加一个资源字典文件,命名为styles。

WPF入门 - 图3

将styles.xaml代码替换为以下代码:

  1. <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3. xmlns:local="clr-namespace:WpfTest">
  4. <Style TargetType="Button" x:Key="btn">
  5. <Setter Property="Width" Value="100"></Setter>
  6. <Setter Property="Height" Value="48"></Setter>
  7. </Style>
  8. </ResourceDictionary>
  1. 可以看出,我们将MainWindow.xaml中定义的btn样式复制到了styles.xaml中。为了在MainWindow中使用该样式,我们需要下面两点,二选一:
  2. 1. App.xaml中引用styles.xaml(全局样式,所有界面都适用)
  3. 2. MainWindow.xaml中引用styles.xaml(仅适用给MainWindow

WPF入门 - 图4

分别对应上面的 i 和 ii。

下面我们来分别实现上面两个步骤:

①首先,打开App.xaml,将代码替换为如下代码:

  1. <Application x:Class="WpfTest.App"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:local="clr-namespace:WpfTest"
  5. StartupUri="MainWindow.xaml">
  6. <Application.Resources>
  7. <ResourceDictionary Source="styles.xaml"></ResourceDictionary>
  8. </Application.Resources>
  9. </Application>

或者,在MainWindow.xaml文件中,将Window.Resources段代码改为如下代码:

  1. <Window.Resources>
  2. <ResourceDictionary Source="styles.xaml"></ResourceDictionary>
  3. </Window.Resources>

如果MainWindow中需要引用两个及以上的样式文件,则需要将Window.Resources段改为下面格式:

  1. <Window.Resources>
  2. <ResourceDictionary>
  3. <ResourceDictionary.MergedDictionaries>
  4. <ResourceDictionary Source="styles.xaml"></ResourceDictionary>
  5. <!-- 在这里添加更多的样式文件引用 -->
  6. </ResourceDictionary.MergedDictionaries>
  7. </ResourceDictionary>
  8. </Window.Resources>

常用的布局标签还有StackPanel(与Grid对应),它支持横向或纵向布局,我们可以将btn2的代码替换为如下代码:

  1. <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
  2. <Button Name="btn2" Content="btn2" Style="{StaticResource btn}" Margin="5"></Button>
  3. <Button Name="btn3" Content="btn3" Style="{StaticResource btn}"></Button>
  4. </StackPanel>

将刚才上面修改的代码添加到MainWindow中,效果是这样的:

WPF入门 - 图5

巧妙地使用Grid和StackPanel,以及简单的样式设置,即可设计出漂亮的WPF界面了。关于布局还有更多的标签可以使用,样式设置也可以设置更多的属性,不过用起来都类似,所以关于布局与样式,还需要更深入的学习。

WPF入门 - 图6

关于布局容器Grid、StackPanel、GroupBox、DockPanel、WrapPanel:https://www.cnblogs.com/xixixing/p/10959455.html

WPF入门第二篇 MVVM与Binding

MVVM,即Model-View-ViewModel的首字母缩写,在这种开发模式下常用binding来对View和ViewModel进行绑定

添加三个文件夹,分别命名为Models、Views、ViewModels。

WPF入门 - 图7

在Model文件夹中,添加Student类,并将Student.cs代码替换为如下代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace WpfTest2.Models
  7. {
  8. public class Student
  9. {
  10. //以下两个都是属性,不是字段;因为教程的不规范没有首字母大写
  11. public string id { get; set; }
  12. public string name { get; set; }
  13. }
  14. }

在Models文件夹中,添加Score类,并将Score.cs代码替换为如下代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace WpfTest2.Models
  7. {
  8. public class Score
  9. {
  10. public string stuId { get; set; }
  11. public string subject { get; set; }
  12. public int score { get; set; }
  13. }
  14. }

在ViewModels文件夹中添加CardViewModel类,并将代码替换为如下代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using WpfTest2.Models; //注意这里
  7. namespace WpfTest2.ViewModels
  8. {
  9. public class CardViewModel
  10. {
  11. public Student student { get; set; }
  12. public Score score { get; set; }
  13. }
  14. }

从上面的步骤可以看出,我们有了两个Model,一个ViewModel。实际开发中,Model一般是数据库表映射的实体,而ViewModel是我们自定义的与一个View完全绑定的数据模型。下面我们在Views文件夹中,添加一个用户控件,命名为Card。并将Card.xaml代码替换为:

  1. <UserControl x:Class="WpfTest2.Views.Card"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  6. xmlns:local="clr-namespace:WpfTest2.Views"
  7. mc:Ignorable="d"
  8. d:DesignHeight="300" d:DesignWidth="300">
  9. <UserControl.Resources>
  10. <Style TargetType="TextBlock">
  11. <Setter Property="FontSize" Value="20"></Setter>
  12. <Setter Property="Margin" Value="10"></Setter>
  13. <Setter Property="Foreground" Value="White"></Setter>
  14. <Setter Property="Width" Value="100"></Setter>
  15. <Setter Property="Height" Value="35"></Setter>
  16. </Style>
  17. <Style TargetType="StackPanel" x:Key="sp-h">
  18. <Setter Property="Background" Value="Black"></Setter>
  19. <Setter Property="Margin" Value="5"></Setter>
  20. </Style>
  21. </UserControl.Resources>
  22. <Grid Margin="10">
  23. <Grid.RowDefinitions>
  24. <RowDefinition Height="50"></RowDefinition>
  25. <RowDefinition></RowDefinition>
  26. </Grid.RowDefinitions>
  27. <Border Grid.RowSpan="2" BorderThickness="0" Background="Black" CornerRadius="20">
  28. </Border>
  29. <StackPanel Orientation="Horizontal" Grid.Row="0" Style="{StaticResource sp-h}" Margin="10,5,10,5">
  30. <TextBlock Text="{Binding student.name}"></TextBlock>
  31. </StackPanel>
  32. <StackPanel Orientation="Vertical" Grid.Row="1" Margin="5,10,5,10">
  33. <StackPanel Orientation="Horizontal" Style="{StaticResource sp-h}">
  34. <TextBlock Text="id:"></TextBlock>
  35. <TextBlock Text="{Binding score.stuId}"></TextBlock>
  36. </StackPanel>
  37. <StackPanel Orientation="Horizontal" Style="{StaticResource sp-h}">
  38. <TextBlock Text="subject:"></TextBlock>
  39. <TextBlock Text="{Binding score.subject}"></TextBlock>
  40. </StackPanel>
  41. <StackPanel Orientation="Horizontal" Style="{StaticResource sp-h}">
  42. <TextBlock Text="score:"></TextBlock>
  43. <TextBlock Text="{Binding score.score}"></TextBlock>
  44. </StackPanel>
  45. </StackPanel>
  46. </Grid>
  47. </UserControl>

将Card.xaml.cs代码替换为以下代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Navigation;
  14. using System.Windows.Shapes;
  15. using WpfTest2.ViewModels; //注意这里
  16. namespace WpfTest2.Views
  17. {
  18. /// <summary>
  19. /// Card.xaml 的交互逻辑
  20. /// </summary>
  21. public partial class Card : UserControl
  22. {
  23. public Card()
  24. {
  25. InitializeComponent();
  26. }
  27. //这里是构造函数的重载,有参数的构造函数需要执行无参构造器中的代码,为了省去重复代码的编写,这里就继承了。
  28. public Card(CardViewModel vm) : this()
  29. {
  30. //DataContext属性是绑定的默认源。它允许指定一个绑定的基。
  31. //详细的在这里:https://blog.csdn.net/seanbei/article/details/53207620
  32. this.DataContext = vm;
  33. }
  34. }
  35. }

此时,我们的解决方案视图如下:

WPF入门 - 图8

下面我们将在MainWindow中使用我们的Card。

打开MainWindow.xaml,将代码替换为:

  1. <Window x:Class="WpfTest2.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfTest2"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Grid>
  10. <WrapPanel Name="wpCardList">
  11. </WrapPanel>
  12. </Grid>
  13. </Window>

WrapPanel用法:https://blog.csdn.net/seanbei/article/details/52893767

打开MainWindow.xaml.cs,将代码替换为:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Navigation;
  14. using System.Windows.Shapes;
  15. namespace WpfTest2
  16. {
  17. /// <summary>
  18. /// MainWindow.xaml 的交互逻辑
  19. /// </summary>
  20. public partial class MainWindow : Window
  21. {
  22. public MainWindow()
  23. {
  24. InitializeComponent();
  25. Models.Student stu = new Models.Student() { id = "111", name = "吴凡" };
  26. Models.Score sc = new Models.Score() { stuId = "111", subject = "语文", score = 100 };
  27. ViewModels.CardViewModel cardVm = new ViewModels.CardViewModel() { student = stu, score = sc };
  28. Views.Card card = new Views.Card(cardVm);
  29. wpCardList.Children.Add(card);
  30. Models.Score sc1 = new Models.Score() { stuId = "111", subject = "数学", score = 99 };
  31. ViewModels.CardViewModel cardVm1 = new ViewModels.CardViewModel() { student = stu, score = sc1 };
  32. Views.Card card1 = new Views.Card(cardVm1);
  33. wpCardList.Children.Add(card1);
  34. }
  35. }
  36. }

运行效果:

WPF入门 - 图9

分析以上代码,不难发现,通过MVVM的开发模式,使用数据库表映射实体作为Models,使用UserControl作为Views,使用自定义类作为ViewModel,可以让代码整体结构非常清晰,而且通过UserControl的动态创建、删除等操作,可以很容易的实现子页面的跳转、复杂的自定义列表等操作

关于Binding的使用,在上面的代码中也有体现。对于用户控件Card,首先在后台代码中设置this.DataContext = vm,然后在xaml页面中使用{Binding 属性值}进行数据绑定。这其实是简写方式,写全后为{Binding Path=属性值}。除了使用这种方法绑定后台设置的DataContext的值外,还可以通过{Binding ElementName=name Path=prop}来绑定页面内的其他控件的指定属性值。如:

  1. <TextBlock Text={Binding ElementName="btn1" Path="Content"}></TextBlock>

比如,使MainWindow的title显示为wpCardList的Children的数量,则可以将MainWindow.xaml替换为如下代码:

  1. <Window x:Class="WpfTest2.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfTest2"
  7. mc:Ignorable="d"
  8. Title="{Binding ElementName=wpCardList, Path=Children.Count}" Height="800" Width="1000">//这里是新增的
  9. <Grid>
  10. <WrapPanel Name="wpCardList">
  11. </WrapPanel>
  12. </Grid>
  13. </Window>

WPF入门第三篇 ControlTemplate、Trigger与Storyboard

ControlTemplate通常用在Style中,Trigger通常作为ControlTemplate的一部分,StoryBoard表示动画效果,下面将通过对Button按钮设置这几项来简单说明这几项的用法。

在MainWindow中添加一个Button按钮;在Window段添加resource段,并在其中添加一个Style:

  1. <Window x:Class="WpfControlTemplateTest.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfControlTemplateTest"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Window.Resources>
  10. <ResourceDictionary>
  11. <Style TargetType="Button">
  12. <Setter Property="Width" Value="100"></Setter>
  13. <Setter Property="Height" Value="40"></Setter>
  14. <Setter Property="Background" Value="LightGreen"></Setter>
  15. <Setter Property="BorderThickness" Value="0"></Setter>
  16. <Setter Property="Foreground" Value="White"></Setter>
  17. </Style>
  18. </ResourceDictionary>
  19. </Window.Resources>
  20. <Grid>
  21. <Button Name="btn1" Content="btn1"></Button>
  22. </Grid>
  23. </Window>

此时的显示效果是这样的:

WPF入门 - 图10

按钮样式改变了很多,但是很多时候我们都需要圆角按钮,为了让按钮变成圆角,我们需要设置按钮的Template属性。将下面的setter添加到我们的style中即可。

  1. <Window x:Class="WpfControlTemplateTest.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfControlTemplateTest"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Window.Resources>
  10. <ResourceDictionary>
  11. <Style TargetType="Button">
  12. <Setter Property="Width" Value="100"></Setter>
  13. <Setter Property="Height" Value="40"></Setter>
  14. <Setter Property="Background" Value="LightGreen"></Setter>
  15. <Setter Property="BorderThickness" Value="0"></Setter>
  16. <Setter Property="Foreground" Value="White"></Setter>
  17. //添加的代码
  18. <Setter Property="Template">
  19. <Setter.Value>
  20. <ControlTemplate TargetType="Button">
  21. <Grid>
  22. <Border CornerRadius="10" Background="LightGreen"></Border>
  23. <ContentPresenter
  24. Content="{TemplateBinding Content}"
  25. HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  26. Margin="{TemplateBinding Padding}"
  27. VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  28. />
  29. </Grid>
  30. </ControlTemplate>
  31. </Setter.Value>
  32. </Setter>
  33. //到此为止
  34. </Style>
  35. </ResourceDictionary>
  36. </Window.Resources>
  37. <Grid>
  38. <Button Name="btn1" Content="btn1"></Button>
  39. </Grid>
  40. </Window>

现在的显示效果:

WPF入门 - 图11

通过使用ControlTemplate,可以定制化控件的模板样式。

下面我们来看Triggers。

Trigger通常在ControlTemplate中定义,用来实现Hover、Press等效果。

比如,我们希望btn1在hover时,width变为200,(翻译成人话:鼠标悬停在按钮上的时候,按钮长度变为200)则可以在ControlTemplate段中添加下面代码:

  1. <Window x:Class="WpfControlTemplateTest.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WpfControlTemplateTest"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Window.Resources>
  10. <ResourceDictionary>
  11. <Style TargetType="Button">
  12. <Setter Property="Width" Value="100"></Setter>
  13. <Setter Property="Height" Value="40"></Setter>
  14. <Setter Property="Background" Value="LightGreen"></Setter>
  15. <Setter Property="BorderThickness" Value="0"></Setter>
  16. <Setter Property="Foreground" Value="White"></Setter>
  17. <Setter Property="Template">
  18. <Setter.Value>
  19. <ControlTemplate TargetType="Button">
  20. <Grid>
  21. <Border CornerRadius="10" Background="LightGreen"></Border>
  22. <ContentPresenter
  23. Content="{TemplateBinding Content}"
  24. HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  25. Margin="{TemplateBinding Padding}"
  26. VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  27. />
  28. </Grid>
  29. //添加的代码
  30. <ControlTemplate.Triggers>
  31. <Trigger Property="IsMouseOver" Value="True">
  32. <Setter Property="Width" Value="200"></Setter>
  33. </Trigger>
  34. </ControlTemplate.Triggers>
  35. //到此为止
  36. </ControlTemplate>
  37. </Setter.Value>
  38. </Setter>
  39. </Style>
  40. </ResourceDictionary>
  41. </Window.Resources>
  42. <Grid>
  43. <Button Name="btn1" Content="btn1"></Button>
  44. </Grid>
  45. </Window>

运行后的悬停前、悬停后效果对比:

WPF入门 - 图12WPF入门 - 图13

此时,如果想为按钮的变化添加动画效果,就需要设置Storyboard。我们需要将ControlTemplate.Triggers段替换为如下代码:

  1. <ControlTemplate.Triggers>
  2. <Trigger Property="IsMouseOver" Value="True">
  3. <Trigger.EnterActions>
  4. <BeginStoryboard>
  5. <Storyboard>
  6. <DoubleAnimation Storyboard.TargetProperty="Width" To="200" Duration="0:0:0.15"></DoubleAnimation>
  7. </Storyboard>
  8. </BeginStoryboard>
  9. </Trigger.EnterActions>
  10. <Trigger.ExitActions>
  11. <BeginStoryboard>
  12. <Storyboard>
  13. <DoubleAnimation Storyboard.TargetProperty="Width" To="100" Duration="0:0:0.1"></DoubleAnimation>
  14. </Storyboard>
  15. </BeginStoryboard>
  16. </Trigger.ExitActions>
  17. </Trigger>
  18. </ControlTemplate.Triggers>

这里的DoubleAnimation指的是数字变化的动画,若需要颜色变化,则需要ColorAnimation。DoubleAnimation的Duration属性是一个时间,分别被设置了0.15秒和0.1秒。

运行效果:

WPF入门 - 图14

WPF入门第六篇 界面绑定属性刷新 INotifyPropertyChanged

新建WPF项目,命名为VMTest(VM是ViewModel的简称)。右击项目添加一个类,命名为Person。将Person类进行修改:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace VMTest
  7. {
  8. public class Person
  9. {
  10. public int Id { get; set; }
  11. public string Name { get; set; }
  12. public int Age { get; set; }
  13. }
  14. }

将MainWindow.xaml代码替换为下面代码:

  1. <Window x:Class="VMTest.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:VMTest"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Grid>
  10. <StackPanel Orientation="Vertical">
  11. <StackPanel Orientation="Horizontal">
  12. <TextBlock Text="Id:"></TextBlock>
  13. <TextBlock Text="{Binding Id}"></TextBlock>
  14. </StackPanel>
  15. <StackPanel Orientation="Horizontal">
  16. <TextBlock Text="Name:"></TextBlock>
  17. <TextBlock Text="{Binding Name}"></TextBlock>
  18. </StackPanel>
  19. <StackPanel Orientation="Horizontal">
  20. <TextBlock Text="Age:"></TextBlock>
  21. <TextBlock Text="{Binding Age}"></TextBlock>
  22. </StackPanel>
  23. <StackPanel Orientation="Vertical">
  24. <Button Content="btn1" Width="100" HorizontalAlignment="Left" Click="Button_Click"></Button>
  25. </StackPanel>
  26. </StackPanel>
  27. </Grid>
  28. </Window>

将MainWindow.xaml.cs代码替换为:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Navigation;
  14. using System.Windows.Shapes;
  15. namespace VMTest
  16. {
  17. /// <summary>
  18. /// MainWindow.xaml 的交互逻辑
  19. /// </summary>
  20. public partial class MainWindow : Window
  21. {
  22. Person p;
  23. public MainWindow()
  24. {
  25. InitializeComponent();
  26. p = new Person()
  27. {
  28. Id = 1,
  29. Name = "Wufan",
  30. Age = 25
  31. };
  32. this.DataContext = p;
  33. }
  34. private void Button_Click(object sender,RoutedEventArgs e)
  35. {
  36. p.Age = 26;
  37. }
  38. }
  39. }

测试项目简单的准备完毕了,分析一下代码:

MainWindow作为View,被它内部定义的Person p控制着显示的数据,在MainWindow的初始化代码中,我们new了一个Person赋值给p,并设置MainWindow的DataContext等于p。在MainWindow的ButtonClick事件中,我们改变了p.Age的值,期望界面上绑定值也能随着绑定模型的属性的变化而变化。

此时运行程序,可以看到界面显示了Person的三个属性值,但是点击btn1时,Age却没有随着变化,未达到我们的预期。

WPF入门 - 图15

为了解决这个问题,最笨的方法就是对DataContext重新赋值,来刷新整个界面的所有Binding值。这种方法的具体做法是将ButtonClick代码改为:

  1. private void Button_Click(object sender, RoutedEventArgs e)
  2. {
  3. p.Age = 26;
  4. Person temp = new Person()
  5. {
  6. Id = p.Id,
  7. Name = p.Name,
  8. Age = p.Age
  9. };
  10. this.DataContext = temp;
  11. p = temp;
  12. }
  1. 这样做确实可以解决问题,但却不是一个好的方法。因为重新给DataContext复制后,界面的所有Binding值都要刷新,这不符合我们的开发要求,也不符合设计者的初衷。

为了避免这种方法,设计者提供了INotifyPropertyChanged。

INotifyPropertyChanged的作用和用法

对于INotifyPropertyChanged的作用说明:向客户端发出某一属性值已更改的通知。简单来说,可以在不给DataContext重新赋值的情况下,界面Binding值可以随着绑定模型的属性的变化而变化。

将ButtonClick按钮的代码改为:

  1. private void Button_Click(object sender, RoutedEventArgs e)
  2. {
  3. p.Age = 26;
  4. }

为Person类添加接口继承,并添加相应的引用及接口实现;修改Age属性的实现方式,并使用PropertyChanged来将Age属性的改变通知到界面:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;//新增的引用
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace VMTest
  8. {
  9. public class Person:INotifyPropertyChanged
  10. {
  11. public int Id { get; set; }
  12. public string Name { get; set; }
  13. //Age属性的改变
  14. private int _age;
  15. public int Age
  16. {
  17. get => _age;
  18. set
  19. {
  20. _age = value;
  21. if(PropertyChanged != null)
  22. {
  23. PropertyChanged(this, new PropertyChangedEventArgs("Age"));
  24. }
  25. }
  26. }
  27. //新增代码
  28. public event PropertyChangedEventHandler PropertyChanged;
  29. }
  30. }
  1. 此时,点击btn1后,Age属性的改变被通知到了界面。

为了让Id和Name属性也能实现这种效果,则需要将这两个属性的写法也改成上面介绍的Age属性的写法。

如果直接修改会让我们的代码非常繁琐,我们可以将INotifyPropertyChanged相关代码封装起来使用,使得ViewModel(本文中的Person类)的代码看起来更加简洁。

INotifyPropertyChanged的封装使用

我们对INotifyPropertyChanged做一个封装,封装到VM类,其他的ViewModel类只需要继承VM类,即可方便的实现属性变更通知的效果。

VM类代码参考了开源框架ReactiveUI,git地址:https://github.com/reactiveui/reactiveui

在项目中添加一个类,命名为VM。VM类代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Runtime.CompilerServices;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace VMTest
  9. {
  10. public class VM : INotifyPropertyChanged
  11. {
  12. public event PropertyChangedEventHandler PropertyChanged;
  13. public void RaisePropertyChanged(string p)
  14. {
  15. if (PropertyChanged != null)
  16. {
  17. PropertyChanged(this, new PropertyChangedEventArgs(p));
  18. }
  19. }
  20. public void RaiseAndSetIfChanged<T>(ref T a, T v, [CallerMemberName] string propertyName = null)
  21. {
  22. a = v;
  23. if (propertyName != null)
  24. {
  25. RaisePropertyChanged(propertyName);
  26. }
  27. }
  28. }
  29. }

将Person类代码替换为下面的代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace VMTest
  8. {
  9. public class Person : VM
  10. {
  11. private int _id;
  12. public int Id
  13. {
  14. get => _id;
  15. set => this.RaiseAndSetIfChanged(ref _id, value);//说明:这里没有把方法定义时的第三个参数也放进来,原因是:系统特性。
  16. //因为[CallerMemberName]这样的特性,系统会自动帮你获取到调用堆栈的对象名称。
  17. //所以这里的代码等价于:
  18. // set => this.RaiseAndSetIfChanged(ref _id, value,"Id");
  19. }
  20. private string _name;
  21. public string Name
  22. {
  23. get => _name;
  24. set => this.RaiseAndSetIfChanged(ref _name, value);//同理
  25. }
  26. private int _age;
  27. public int Age
  28. {
  29. get => _age;
  30. set => this.RaiseAndSetIfChanged(ref _age, value);//同理
  31. }
  32. }
  33. }

此时,Id、Name、Age属性的改变都可以被通知到界面。

修改Button按钮的点击事件如下:

  1. private void Button_Click(object sender, RoutedEventArgs e)
  2. {
  3. p.Age = 26;
  4. p.Name = "wf";
  5. p.Id = 2;
  6. }

到此为止,代码已经可以实现预期的效果了。

(了解)WPF入门第四篇 VisualState

https://blog.csdn.net/wf824284257/article/details/88989991

(了解)WPF入门第五篇 WPF图表与ECharts

https://blog.csdn.net/wf824284257/article/details/89002133

(了解)WPF入门第七篇 使用Squirrel自动更新应用

https://blog.csdn.net/wf824284257/article/details/89164525