WPF学习笔记02_样式/模板

1、WPF基础简介

WPF(Windows Presentation Foundation)是微软开发的用于构建 Windows 应用程序的框架。它提供了丰富的图形用户界面(GUI)功能,使开发者能够创建具有高度定制化、现代化外观和丰富交互的应用程序。下图是对WPF概念的简单分类和总结。

为了方便读者尽可能快速的对WPF相关核心概念有一个大致理解,特意制作了以下思维脑图,方便读者阅读、理解。
在这里插入图片描述
在这里插入图片描述
对于初次接触WPF的读者而言,如果您看了上面关于WPF核心概念的思维脑图,仍然对各个概念所扮演的角色产生困惑,不妨看一下下面这幅图的阐释,希望能对您有所帮助。
下图将WPF应用程序的开发比喻为建房子,各个核心概念扮演着各自的角色。
在这里插入图片描述

**备注:**如读者需要对WPF控件的相关内容有一个详细的理解请参考博文《WPF学习笔记_01》,链接如https://blog.csdn.net/zeroCCY/article/details/135016286?spm=1001.2014.3001.5501
下面内容将详细讲解WPF样式和模板的相关内容,希望对各位读者有所帮助。

2、WPF样式

WPF(Windows Presentation Foundation)样式是一种定义控件外观的方式,它允许你为应用程序中的控件创建一致的外观和行为。样式可以包含诸如背景、边框、字体、颜色等视觉属性的定义,使得你可以轻松地应用于多个控件,确保整个应用程序具有统一的外观和风格。
为方便读者理解,特举出简单示例代码如下

<Window x:Class="TestWpf01.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:TestWpf01"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="ButtonStyle" TargetType="Button">
            <Setter Property="FontSize" Value="18"></Setter>
            <Setter Property="Foreground" Value="White"></Setter>
            <Setter Property="Background" Value="Red"></Setter>
            <Setter Property="Content" Value="Button1"></Setter>
            <Style.Triggers>
                <!--样式触发器,当鼠标悬停时改变按钮的背景色-->
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="LightGreen"></Setter>
                </Trigger>
            </Style.Triggers>

        </Style>
        
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Style="{StaticResource ButtonStyle}" ></Button>
            <Button Style="{StaticResource ButtonStyle}" Content="Button2"></Button>
            <Button Style="{StaticResource ButtonStyle}" Content="Button3"></Button>
            
        </StackPanel>

    </Grid>
</Window>

其中:
(1)Key 属性:
x:Key 属性用于为样式定义一个唯一的键。这个键可以用于在其他控件中引用这个样式。
(2)TargetType 属性:
TargetType 属性指定了这个样式所适用的控件类型。在上面的例子中,TargetType=“Button” 表示这个样式适用于 Button 控件。
(3)Setter 元素:
Setter 元素用于定义控件的属性以及属性的值。在上面的例子中,通过 Setter 元素设置了 Button 的前景色、背景色和字体大小。
(4)应用样式:
要将样式应用于控件,可以使用 Style 属性和 StaticResource 引用定义好的样式:
(5)触发器:
样式也可以包含触发器,根据控件的不同状态改变外观。例如,鼠标悬停或按下按钮时的样式变化。
(6)控件模板:
除了属性外,样式还可以包含控件模板(Template),用于定义控件的外观和布局。
总的来说,WPF 样式为开发者提供了一种方便的方式来定义和管理控件的外观,使得应用程序的界面更易于维护和统一。
在这里插入图片描述
鼠标放置在Button2上面就会呈现下图所示窗口
在这里插入图片描述
在这个例子中,当鼠标悬停在按钮上时,触发器会检测按钮的 IsMouseOver 属性,如果为 True,则会应用触发器中定义的属性变化,即改变按钮的背景色为 LightGreen。当鼠标移出按钮区域时,背景色会恢复为初始值Red。

3、WPF模板

在 WPF 中,模板是一种强大的工具,用于自定义控件的外观和行为。WPF 提供了两种主要类型的模板:控件模板(ControlTemplate)和数据模板(DataTemplate)。

3.1 控件模板

控件模板允许您重新定义控件的外观和行为,包括控件的视觉元素和交互方式。使用控件模板,可以完全改变控件的外观,使其呈现出自定义的视觉效果。
结构和组成部分:
(1)TargetType:指定模板要应用的控件类型。
(2)Visual Tree:控件模板通常由嵌套的 XAML 元素组成,用于构建控件的视觉结构。
(3)触发器(Triggers):允许在特定条件下更改控件的外观。

为方便读者理解,特举出简单示例代码如下:

<Window x:Class="TestWpf02_Templates.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:TestWpf02_Templates"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
            <Border x:Name="Border" Background="LightGreen" BorderBrush="DarkBlue" BorderThickness="2" CornerRadius="5">
                <TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontSize="14"></TextBlock>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Border"  Property="Background" Value="Red"></Setter>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="Bisque"></Setter>
                </Trigger>
            </ControlTemplate.Triggers>

        </ControlTemplate>
    </Window.Resources>

    <Grid>
        <StackPanel Background="Gray" Margin="10,10,10,10">
            <Button Content="Custom Button1" Template="{StaticResource CustomButtonTemplate}" Width="150" Height="50" HorizontalAlignment="Left"></Button>
            <Button Margin="10,10,10,10" Content="Custom Button2" Template="{StaticResource CustomButtonTemplate}" Width="150" Height="50" ></Button>
        </StackPanel>
    </Grid>
</Window>

此示例中,CustomButtonTemplate 是一个自定义按钮的控件模板。它使用 Border 和 TextBlock 元素来定义按钮的外观,并使用触发器定义鼠标悬停和按下时的不同样式。
在这里插入图片描述
鼠标放在Button2上悬停结果展示如下
在这里插入图片描述

3.2 数据模板

数据模板用于定义数据的呈现方式,通常在控件(如列表框或表格)中用于显示数据项的外观。数据模板定义了如何呈现特定类型的数据。
为方便理解,下面举出应用数据模板的示例并详细解释整个过程。详细步骤及过程如下:

第一步:定义数据模型
定义了一个简单的数据模型 Person,它具有 Name 和 Age 属性,并实现了 INotifyPropertyChanged 接口,以便在属性更改时通知界面更新。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestWpf02_DataTemplate
{
    public class Person: INotifyPropertyChanged
    {
        private string name;
        private int age;

        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                OnPropertyChanged("Name");
            }
        }

        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                OnPropertyChanged("Age");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

对Person类部分代码作出如下解释:

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

这段代码实际上是用于实现 INotifyPropertyChanged 接口的一部分,该接口用于通知界面或其他部分,特定属性的值已更改,从而触发相应的更新操作。
(1)“PropertyChanged ”事件是 INotifyPropertyChanged 接口的一部分。当属性值更改时,类会触发此事件。
(2)“OnPropertyChanged” 方法用于触发 PropertyChanged 事件。通常情况下,此方法会在属性的 setter 中被调用,以便在属性更改时通知任何监听者。
(3)“Invoke(this, new PropertyChangedEventArgs(propertyName)) ”行通过触发事件,并传递属性名称来告知任何订阅了此事件的对象,特定属性的值已经更改。
(4)“PropertyChanged?. ”这部分是 null 条件运算符,确保在事件未被订阅的情况下不会导致空引用异常。如果 PropertyChanged 事件有订阅者,则调用 Invoke 方法通知这些订阅者属性值已更改。
在实际编码中,当需要在属性更改时通知相关组件或界面进行更新时,这段代码通常用于实现数据绑定和响应式 UI 更新。

第二步:创建 XAML 界面
在 XAML 文件中创建了 MainWindow 窗口,并放置了一个 ListBox 控件。该 ListBox 用于显示 Person 对象的集合,其数据源绑定到 People 属性。
在 ListBox 中使用了 ItemTemplate,这是数据模板的一部分,用于定义如何呈现每个 Person 对象。在这个示例中,ItemTemplate 是一个简单的 StackPanel,包含两个 TextBlock 控件,分别显示人员的姓名和年龄。

<Window x:Class="TestWpf02_DataTemplate.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:TestWpf02_DataTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
 
    <Grid>
        <ListBox ItemsSource="{Binding People}" Margin="10" Background="LightBlue">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel >
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="16" Background="Gray"/>
                        <TextBlock Text="{Binding Age}" Foreground="Gray"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

    </Grid>
</Window>

第三步:连接数据模型和数据源
在 MainWindow.xaml.cs 中,MainWindow 类的构造函数初始化了一个 ObservableCollection 类型的 People 属性,并为其添加了几个 Person 对象作为示例数据。
通过设置窗口的 DataContext 属性为 this,将 MainWindow 作为数据上下文,从而使 XAML 中的数据绑定能够访问到窗口的属性和数据。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TestWpf02_DataTemplate
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ObservableCollection<Person> People { get; set; }
        public MainWindow()
        {
            InitializeComponent();

            DataContext = this;

            // 假设在窗口加载时初始化数据源
            People = new ObservableCollection<Person>
            {
                new Person { Name = "Alice", Age = 30},
                new Person { Name = "Bob", Age = 25 },
                new Person { Name = "Charlie", Age = 35 }
            };
        }
    }
}

实现结果如下:
在这里插入图片描述
4、扩展(WPF实战)
如果您已经看到这里,恭喜您已经对WPF有了初步的理解与学习,为了进一步巩固,可以在上面基础上尝试设计如下操作界面:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果你顺利的实现了如上界面及相应功能,恭喜你,对于WPF应用程序设计,可以十分肯定的说,你入门了!!!

前路任重道远,让学习成为一种态度!!!(与君共勉)

最后。如果你实现起来确实有些困难,特附上完整代码供您参考,希望能对你的WPF学习有所帮助。

XAML文件:

<Window x:Class="TestWpf02_DataTemplate.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:TestWpf02_DataTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>

        <Style x:Key="TextBlockStyle" TargetType="TextBlock">
            <Setter Property="Background" Value="Lavender"></Setter>
            <Style.Triggers>
                <!--样式触发器,当鼠标悬停时改变按钮的背景色-->
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="LightGreen"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>

        <ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
            <Border x:Name="Border" Background="LightGreen" BorderBrush="DarkBlue" BorderThickness="2" CornerRadius="5">
                <TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontSize="14"></TextBlock>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Border"  Property="Background" Value="Red"></Setter>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="Bisque"></Setter>
                </Trigger>
            </ControlTemplate.Triggers>

        </ControlTemplate>
    </Window.Resources>

    <Grid>
        <StackPanel Margin="10" Background="LightBlue">
            <StackPanel Orientation="Horizontal" Background="LightGreen" VerticalAlignment="Center">
                <TextBlock Text="输入姓名:"  VerticalAlignment="Center"></TextBlock>
                <TextBox x:Name="txtName" Width="120" Margin="5"  />
                <TextBlock Text="输入年龄:" VerticalAlignment="Center"></TextBlock>
                <TextBox x:Name="txtAge" Width="50" Margin="5" />
                <Button Content="Add Person" Click="AddPerson_Click" Margin="5" Template="{StaticResource CustomButtonTemplate}"/>
            </StackPanel>

            <ListBox ItemsSource="{Binding People}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Margin="5">
                            <TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="16" Style="{StaticResource TextBlockStyle}"/>
                            <TextBlock Text="{Binding Age}" Foreground="Gray" Style="{StaticResource TextBlockStyle}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>

    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TestWpf02_DataTemplate
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// // ObservableCollection 用于实时更新数据
        /// </summary>
        public ObservableCollection<Person> People { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            DataContext = this;

            // 假设在窗口加载时初始化数据源
            People = new ObservableCollection<Person>
            {
                new Person { Name = "Alice", Age = 30},
                new Person { Name = "Bob", Age = 25 },
                new Person { Name = "Charlie", Age = 35 }
            };
        }

        // ObservableCollection 用于实时更新数据
        

        // 添加新 Person 对象到数据源中
        private void AddPerson_Click(object sender, RoutedEventArgs e)
        {
            if (!string.IsNullOrEmpty(txtName.Text) && int.TryParse(txtAge.Text, out int age))
            {
                People.Add(new Person { Name = txtName.Text, Age = age });
                txtName.Text = ""; // 清空输入框
                txtAge.Text = "";
            }
            else
            {
                MessageBox.Show("Please enter valid Name and Age.");
            }
        }
    }
}

Person.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestWpf02_DataTemplate
{
    public class Person: INotifyPropertyChanged
    {
        private string name;
        private int age;

        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                OnPropertyChanged("Name");
            }
        }

        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                OnPropertyChanged("Age");
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }


}


  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值