WPF学习笔记

初始WPF

WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架, 属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架,真正做到了 分离界面设计人员与开发人员的工作;同时它提供了全新的多媒体交互用户图形界面。

定义

WPF 为Windows Presentation Foundation的首字母缩写 ,中文译为“Windows呈现基础”, 其原来代号为“Avalon”,因与“我佩服”拼音首字母组合一样,国内有人调侃地称之为“我佩 服”。由 .NET Framework 3.0 开始引入,与 Windows Communication Foundation及 Windows Workflow Foundation并行为新一代 Windows操作系统以及 WinFX 的三个重大应 用程序开发类库。

WPF是微软新一代图形系统,运行在.NET Framework 3.0及以上版本下,为用户界 面、2D/3D 图形、文档和媒体提供了统一的描述和操作方法。

基于DirectX 9/10技术的WPF不仅带来了前所未有的3D界面,而且其图形向量渲染引擎也大 大改进了传统的2D界面,比如Vista中的半透明效果的窗体等都得益于WPF。 程序员 在WPF的帮助下,要开发出媲美Mac程序的酷炫界面已不再是遥不可及的奢望。 WPF相对 于Windows客户端的开发来说,向前跨出了巨大的一步,它提供了超丰富的.NET UI 框架, 集成了矢量图形,丰富的流动文字支持(flow text support),3D视觉效果和强大无比的控件 模型框架。

Windows Presentation Foundation(以前的代号为“Avalon”)是 Microsoft 用于 Windows 的统一显示子系统,它通过 WinFX 公开。它由显示引擎和托管代码框架组成。Windows Presentation Foundation 统一了 Windows 创建、显示和操作文档、媒体和用户界面 (UI) 的 方式,使开发人员和设计人员可以创建更好的视觉效果、不同的用户体验。Windows Presentation Foundation 发布后,Windows XP、Windows Server 2003 和以后所有的 Windows操作系统版本都可以使用它。

WPF是.Net Framework 3.0里新推出的主打功能之一,加上Vista集成.Net Framework 3.0, 改写Winform时代,可谓是影响巨大!WPF是一套API函数库,由.Net FrameWork3.0以上 版本类库运行。

WPF是Windows操作系统中一次重大变革,与早期的GDI+/GDI不同。WPF是基于DirectX 引擎的,支持GPU硬件加速,在不支持硬件加速时也可以使用软件绘制。高级别的线程绘 制可以提高使用者的体验。自动识别显示器分辨率并进行缩放。而Vista就是一个非常典型

的例子。

特点

程序人员与美工人员明确分工,美工人员可以使用Expression Studio中套装工具可视化的设 计界面。然后交给程序开发组中的XAML就可以。让程序人员直接套用到开发环境,不需要 想页面怎么切了。

对与WPF最重要的特色,矢量图的超强支持。兼容支持2D绘图,比如矩形、自定义路径, 位图等。文字显示的增强,XPS和消锯齿。三维强大的支持。包括3D控件及事件,与2D及 视频合并打造更立体效果。渐变、使用高精确的(ARGB)颜色,支持浮点类型的像素坐 标。这些对GDI+远远不及的。

灵活、易扩展的动画机制!.Net Framework 3.0类库提供了强大的基类,只需继承就可以实 现自定义程序使用绘制。接口设计非常直观,完全面向对象的对象模型。使用对象描述语 言XAML。使用开发工具的可视化编辑。

您可以使用任何一种.Net编程语言(C#,VB NET等开发语言)进行开发。XAML主要针对 界面的可视化控件描述,成生进会分析成.cs或.vb文件,并最后将编译为CLR中间运行语

言。

组成结构

Windows Presentation Foundation 由两个主要部分组成:引擎和编程框架

  1. Windows Presentation Foundation引擎。Windows Presentation Foundation 引擎统一 了开发人员和设计人员体验文档、媒体和 UI 的方式,为基于浏览器的体验、基于窗体的应 用程序、图形、视频、音频和文档提供了一个单一的运行时库。Windows Presentation Foundation 使得应用程序不仅能够充分利用现代计算机中现有的图形硬件的全部功能,而 且能够利用硬件将来的进步。例如,Windows Presentation Foundation 的基于矢量的呈现 引擎使应用程序可以灵活地利用高 DPI监视器,而无需开发人员或用户进行额外的工作。同 样,当 Windows Presentation Foundation 检测到支持硬件加速的视频卡时,它将利用硬件 加速功能。

  2. Windows Presentation Foundation 框架。Windows Presentation Foundation 框架为媒 体、用户界面设计和文档提供的解决方案远远超过开发人员现在所拥有的。Windows Presentation Foundation 的设计考虑了可扩展性,使开发人员可以完全在 Windows Presentation Foundation引擎的基础上创建自己的控件,也可以通过对现有 Windows Presentation Foundation 控件进行再分类来创建自己的控件。Windows Presentation Foundation 框架的核心是用于形状、文档、图像、视频、动画、三维以及用于放置控件和内容的面板的一系列控件。这些“自有控件”为开发下一代用户体验提供了构造块。
    Microsoft 在引入 Windows Presentation Foundation 的同时,还引入了 XAML,这是一种 公开表示 Windows应用程序用户界面的标记语言,可使开发人员和设计人员用来构建和重 用 UI 的工具更加丰富。对于 Web 开发人员,XAML 提供了熟悉的 UI 说明模式。XAML 还 使 UI 设计从基础代码中分离出来,从而使开发人员和设计人员之间的合作更加紧密。

创建我的第一个WPF项目

新建WPF应用程序

这里使用的IDE是 Visual Studio 2015。打开IDE,左上角 文件 --> 新建 --> 项目,会打开新建项目窗口,选择C#语言后在模板列表中选择WPF应用程序并起一个合适的项目名称最后点击确定。

image.png

项目新建后就这德行

image.png

App.config应用程序的配置文件

App.xaml设置应用程序的起始入口与资源

MainWindow.xaml WPF应用程序界面与XMAL设计文件,该文件展开可以看见MainWindow.xaml.cs这是这个界面的后台文件。

App.xaml文件

<Application x:Class="HelloWPF.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:HelloWPF"
  StartupUri="MainWindow.xaml">
  <Application.Resources>
  </Application.Resources>
</Application>

Application标签的StartupUri属性描述了整个项目的起始页面。

<Application.Resources>在这里定义的资源文件,无论在哪个页面,都可以使用。相当于Vue/Cli项目的main文件导入的style

搭建一个简单的、可交互的页面

在XAML中,Window标签描述的是当前文件是一个窗口设计文件,UserControl标签描述的是当前文件是一个组件设计文件。

<Window x:Class="HelloWPF.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:HelloWPF"
  mc:Ignorable="d"
  Title="MainWindow" Height="350" Width="525">
  <Grid>
  </Grid>
</Window>

创建一个文本,写一些简单的描述。

<Window.Resources>
  <Style TargetType="TextBlock">
    <Setter Property="FontSize" Value="14"></Setter>
  </Style>
</Window.Resources>
<Grid>
  <TextBlock Text="点击这个按钮,会出现不可思议的神奇事情!" Width="300" Height="30"></TextBlock>
</Grid>

当前窗口是这样的

image.png

然后让我们引入一个外部的一个组件。右键 解决方案 --> 属性 --> 添加 --> 新建项目

选择 c#语言 --> window --> 经典桌面

找到:WPF用户控件

起一个适合的名字点确定

image.png

打开UserControl1「UserControl1.xaml」,可以看到当前文件使用UserControl标签描述,表明这厮是一个组件

添加一个按钮

<Button Width="200" Height="30">我是按钮,你点不到我!</Button>

现在这个组件这副德行

image.png

给这个组件添加一个点击事件吧!在XAML编辑区右键点击查看代码或者打开这个组件的.cs文件添加一个方法ShowMessage_Click

public void ShowMessage_Click(object sender, RoutedEventArgs e)
{
  MessageBox.Show("啊呀!被点到了呢!");
}

回到XAML编辑页面,像HTML一样给Button控件添加Click事件

<Button Width="200" Height="30" Click="ShowMessage_Click">我是按钮,你点不到我!</Button>

回到HelloWPF这个窗口

image.png

右键引用 --> 添加引用 --> 项目 -- > 解决方案

选择刚刚创建的项目,也就是 MyFirstComponent ,选中后确定。

引入这个项目中的组件,在Window标签中使用:

xmlns:com="clr-namespace:MyFirstComponent;assembly=MyFirstComponent"

<Window x:Class="HelloWPF.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:com="clr-namespace:MyFirstComponent;assembly=MyFirstComponent"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:HelloWPF"
  mc:Ignorable="d"
  Title="MainWindow" Height="350" Width="525">

使用MyFirstComponent中的组件,输入:<com:会自动提示

<com:UserControl1 Margin="0,29,0,-29.333"></com:UserControl1>

启动项目点击~~嗯!!perfect

image.png

如何像css一样使用clas或者id来使用样式支援

Style标签中使用x:Key属性来指定一个类似css中的ID

<Window.Resources>
  <Style x:Key="TB" TargetType="TextBlock">
    <Setter Property="FontSize" Value="14"></Setter>
    </Style>
    </Window.Resources>

在需要的控件中使用这个样式:Style="{DynamicResource TB}"

<TextBlock Style="{DynamicResource TB}" Text="点击这个按钮,会发生不可思议的神奇事情!" Width="300" Height="30"></TextBlock>

窗体

Window、UserControl(用户控件,布局的时候像窗体那样布局就可以 了)、Page把窗体以网页形式展现。而一个XAML页面里只能有一个顶级元素。 而顶级元素里面只能有一个子元素。因此要有布局控件。

窗体属性

窗口外观

WPF默认窗口框架的外观,主要取决于Icon、Title、WindowStyle、ResizeMode等属性。

  • Icon 指定窗口的图标
    image.png

  • Title 指定窗口的标题

    • 这个就不测试了,用脚趾头都能想到
  • WindowStyle 指定窗口样式,有4个取值

    1. Node

      • 无边框,当ResizeMode属性为NoResize时,仅剩下窗口核心
        image.png
    2. SingleBorderWindow

      • 单边框「这是一个默认值」
    3. ThreeDBorderWindow

      • 3D边框
        这个值好像在Windows10体现出来,据说Windows7可以,我也不知道长什么样。

        image.png

    4. ToolWindow

      • 工具箱窗口
        image.png
  • ResizeMode 是指定窗口的大小调节

    1. NoResize
      不可调节大小,同时没有最大最小化按钮
      image.png

    2. CanMinimize

      不可调节大小,但可以最小化「最大化按钮不可用」
      image.png

    3. CanResize
      可调节大小「默认」

    4. CanResizeWithGrip
      可根据网络调节「在窗口的右下角显示可调节网格」
      image.png

窗口的位置

  • WindowStartupLocation 指定窗口初始位置

    1. Manual
      手动指定窗口位置,表示可以通过设置其Top、Left属性值来决定窗口的初始位置
      image.png

    2. centerScreen
      使窗口处在屏幕中央

    3. CenterOwner
      在父窗体中央


      给定父窗体一个x:Name属性x:Name="far",用于初始化子窗口时指定父窗口,x表示的是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"该属性名直接指向的是当前窗口实例。

      子窗口设置 WindowStartupLocation="CenterOwner"属性,表示如果存在父窗口的话,该窗口出现的位置在父窗口中间。

      给一个按钮,当点击这个按钮时打开子窗口,所以需要给按钮绑定一个点击事件。

      设置一个按钮:

      <Button Width="200" Height="30" Click="Button_Click" Content="呼叫儿子"></Button>
      

      实现点击响应事件

      private void Button_Click(object sender, RoutedEventArgs e) {}
      

      点击事件实现以下代码:

      打开子窗体的话需要将对应的子窗体实例化

      MainWindow child = new MainWindow();
      

      此时需要给子窗口指定父窗口

      far就是当前父窗口,因为我们刚刚在xaml设置了x:Name属性,不多赘述,用多了就知道~~

      child.Owner = far;
      

      此时需要呼叫儿子,用ShowShowDialog都行

      child.Show();
      

      当当当当~儿子出来了

      image.png

  • Topmost

    调节窗口的前后顺序,属性为true时,窗口位于最前。Topmost值为true的窗口,位于Topmost值为false的窗口之前。Topmost值都为true的窗口,获得焦点的窗口位于前。

窗口的大小

  • Width、Height,分别表示窗口的宽度和高度,称为**“尺寸属性”**。MaxWidthMinWidthMaxHeightMinHeight,分别表示窗口最大宽度、最小宽度、最大高度、最小高度。可以通过得到和更改这些属性值,来获取和改变窗口的大小和长宽范围。

    页面设置宽高

    <Window 
      ...
      Height="150" Width="400" MaxHeight="300" MaxWidth="600" MinHeight="100" MinWidth="300">
              ...
    </Window>
    
    

    后台获取宽高

    public GetActualValue()
    {
      InitializeComponent();
      String height = Height.ToString(),
        width = Width.ToString(),
        maxHeight = MaxHeight.ToString(),
        minHeigth = MinHeight.ToString(),
        maxWidth = MaxWidth.ToString(),
        minWidth = MinWidth.ToString();
      MessageBox.Show($"Height: {height},Width: {width}\n MaxHeight: {maxHeight}, MinHeight: {minHeigth} \n MaxWidth: {maxWidth},MinWidth: {minWidth}");
    }
    

    image.png

    当然也可以动态赋值

    public GetActualValue()
    {
      InitializeComponent();
      Height = 150;
      Width = 400;
      MaxHeight = 300;
      MaxWidth = 600;
      MinHeight = 100;
      MinWidth = 300;
      String height = Height.ToString(),
        width = Width.ToString(),
        maxHeight = MaxHeight.ToString(),
        minHeigth = MinHeight.ToString(),
        maxWidth = MaxWidth.ToString(),
        minWidth = MinWidth.ToString();
      MessageBox.Show($"Height: {height},Width: {width}\n MaxHeight: {maxHeight}, MinHeight: {minHeigth} \n MaxWidth: {maxWidth},MinWidth: {minWidth}");
    }
    

    image.png

  • ActualWidth、ActualHeight,分别表示窗口的实际宽度和实际高度,称为**“实际尺寸属性”**。实际尺寸是根据当前窗口大小、最小化时窗口大小和最大化时窗口大小来计算得到的,其值是只读的,也就是说,不能通过改变ActualWidthActualHeight的值来改变窗口大小

  • SizeToContent表示窗口大小由内容决定

    1. Manual
      手动调整「默认值」

    2. Width

      窗体宽度由内容决定
      image.png

      image.png

  1. Height
    窗体高度由内容决定
    image.png

    image.png

  2. WidthAndHeight
    窗体大小由内容决定

如果内容尺寸超过了窗口的最大或最小范围,还是以最大/最小范围为主。如果手动指定了窗口的Width、Height属性,那么SizeToContent将被忽略。

窗口的可见性和状态

  • Visibility,窗口可见性,有4个枚举值
  1. Visible
    可见的
  2. Hidden
    隐藏的
  3. Collapsed
    折叠

虽然CollapsedHidden效果一样,但二者区别在于,Hidden仅仅将元素设为不可见的,但是元素在画面上依然占有空间。而Collapsed在不可视的基础上,能将元素在画面上的占位符清除,元素彻底不影响画面。

  • Show、Hide
    显示窗口和隐藏窗口的两个方法。如果窗口的ShowInTaskbar属性值为trueHide不但隐藏窗口本身,同时隐藏其在任务栏上的图标。如果为false,窗口打开的时候不在任务栏上显示。

  • WindowState 窗口属性状态,有3个枚举值

    1. Normal
      正常
    2. Maximized
      窗口在启动时最大化
    3. Minimized
      窗口在启动时最小化
  • RestoreBounds 获取窗口在最小化或最大化之前的大小和位置,有四个枚举值,Top、Left、Width、Height。

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      MessageBox.Show(RestoreBounds.ToString());
    }
    

    image.png

    只有窗口在Normal状态下移动或调整时,RestoreBounds的值才会改变。于是可以在窗口关闭时将RestoreBounds属性值保存到配置文件,下一次启动程序窗口时,读取上一次保存的窗口大小、位置来初始化窗口。

    例如

    例子来自于微软官方

    关闭时记录位置,所以为window注册一个Closing事件

    启动

    private string fileName = "setting.txt";
    public GetActualValue()
    {
      InitializeComponent();
      initWindow();
    }
    
    private void initWindow()
    {
      IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
      try
      {
        using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Open, storage))
        {
          using (StreamReader fileReult = new StreamReader(stream))
          {
            Rect restoreBounds = Rect.Parse(fileReult.ReadLine());
            Height = restoreBounds.Height;
            Width = restoreBounds.Width;
            Top = restoreBounds.Top;
            Left = restoreBounds.Left;
          }
        }
      }
      catch (FileNotFoundException ffe)
      {
        MessageBox.Show("欢迎使用牛逼哄哄系统!");
      }
    }
    
    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
      writerStoreFile();
    }
    
    private void writerStoreFile()
    {
      IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
      try
      {
        using(IsolatedStorageFileStream steam = new IsolatedStorageFileStream(fileName, FileMode.Create, storage))
        {
          using(StreamWriter writer = new StreamWriter(steam))
          {
            writer.WriteLine(RestoreBounds);
          }
        }
      }
      catch (Exception)
      {
    
        throw;
      }
    }
    

    如果找不到配置文件,那么可能被删除了,也可能是第一次使用,提示一个欢迎界面

    image.png

    第一次启动位置是在这里的

    image.png

将窗口随便调整大小和拖动窗口后关闭窗口后写入配置文件,此时我们查看刚刚写入的配置文件内容:

995,206.5,270,414

启动窗口发现窗口位置大小都是我们上次关闭时的位置和大小

image.png

还原矩形是窗口在最小化或最大化之前占用的区域,在应用程序关闭之前,可以使用RestoreBounds来保存窗口的最后出现的大小和位置,并且在下次应用程序启动时检索这些值,以将窗口还原到用户离开它的方式。

如果在显示窗口之前或在窗口关闭之后RestoreBounds查询,则返回Empty

窗口的生命周期

  • Initialized

    当前窗口的FrameworkElement底层初始化时触发,即InitializedComponent方法调用时触发

  • LocationChanged
    窗口被移动时触发

  • 窗口被激活时触发

  • Deactivated
    窗口处于非激活时「即其它窗口处于激活时」触发。

  • Loaded
    显示窗口之前触发

  • ContentRendered
    当内容显示的时候触发。

  • Closing
    尝试关闭窗口时触发,可以将参数CancelEventArgs的Cancel的属性设置为true,取消关闭操作。

  • Closed

    窗口在关闭后触发改事件,无法取消

  • Unloaded
    当关闭窗口并且从可视化树移除后触发


控件

控件是我们的门面,控件有很多,但是如果仔细去分析,也是有规律可循的,根据其作用,我们可以把控件分类,日常工作中我们打交道最多的控件有6类

分类

  • 布局控件
    是可以容纳多个控件或嵌套其它布局的控件,用于在UI上组织和排列控件。Grid、StackPanel、DockPanel等控件都属于此类,他们拥有的共同的父类为Panel。

  • 内容控件
    只能容纳一个控件或者布局控件作为它的内容。Window、Button等控件属于此类,因为只能容纳一个控件作为其内容,所以经常借助布局控件来规划其内容。它们的共同父类是ContentControl

  • 带标题内容控件
    相当于一个内容控件,但是可以加一个标题「Header」,标题部分亦可容纳一个控件或布局,GroupBox。TabItem等是这类控件的典型代表。它们的共同父类是HeaderedContentControl

  • 条目控件
    可以显示一列数据,一般情况下这列数据的类型是相同的。此类控件包括ListBox、ComboBox等。它们的共同基类是ItemControl。此类控件在显示集合类型数据方面功能比较强大。

  • 带标题条目控件

    相当于一个条目控件加上一个标题显示区。TreeViewItem、MenuItem都属于此类控件。这类控件往往用于显示层级关系数据,节点显示在其Header区域,子级节点则显示在其条目控件区域。此类控件的共同基类是HeaderdeItemsControl。

  • 特殊内容控件
    比如TextBox容纳的是字符串,TextBlock可以容纳可自由控制格式的文本、Image容纳图片类型数据等。这类控件相对比较独立,但也比较常用。

6类控件的派生关系图

image.png

WPF的内容模型

根据是否可以装载内容、能够装载什么内容,WPF的UI元素可以分为如表所示的这些类型

| 名称|注解|
|:-:-:|:-:-:|
|ContentControl |单一内容控件|
|HeaderedContentControl |带标题的单一内容控件|
|ItemsControl |以条目集合为内容的控件|
|HeaderedItemsControl |带标题的以条目集合为内容的控件|
|Decorator |控件装饰元素|
|Panel |面板类元素|
|Adorner |文字点缀元素|
|FlowText |流式输入框|
|TextBox |文本输入框|
|TextBlock |静态文字|
|Shape |图形元素|

你可以把控件想象成一个容器,容器里装的东西就是它的内容。控件的内容可以直接是数据,也可以是控件。当控件的内容还是控件的时候就形成了控件的嵌套。我们把被嵌套的控件称为子级控件,这种控件嵌套在UI布局时尤为常见。因为允许控件嵌套,所以WPF的UI会形成一个树形结构。如果不考虑控件内部的组成结构,只观察由控件组成的,那么这棵树为逻辑树(LoicalTree)。WPF控件往往是由更基本的控件构成的,即控件本身就是一棵树,如果连控件本身的树也考虑在内,则这棵比逻辑树更**“繁茂”的树称为可视元素树(Visual Tree)**

控件是内存中的对象,控件的内容也是内存中的对象。控件通过自己的某个属性引用作为其内容的对象,这个属性称为内容属性(Content Property)。内容属性是个统称,具体到每中控件上,内容属性都有自己确切的名字,有的直接叫Content,有的叫Child。有些控件的内容可以是集合,其内容属性有叫ItemsChildren

控件的内容属性与XAML标签的内容存在一定的对应关系。

所谓“于理”,就是说我们严格按照语法来行事。控件不是有内容属性吗?那在XAML里我们就应该能够使用Attribute=Value或者属性标签的形式来为内容赋值。比如想把字符串"OK"作为内容赋值给Button,下面两种写法都是正确的。

<Button Content="OK" />
<Button>
  <Button.Content>
    OK
  </Button.Content>
</Button>

所谓“于情”,是指如果说得通就不必要按照冗长的语法来写。控件对应到XAML文档里就是标签,控件的内容就应该是标签的内容、子级控件就应该是标签的子级元素「简称标签元素」。标签的内容是加在起始标签和夹在结束标签间的代码,因此,上面的代码也可以写成这样

<Button>
  OK
</Button>

有些控件内容是一个集合,例如StackPanel的内容属性是Children、ListBox的内容属性是Items。为这类控件添加内容时一样可以省略内容属性的标签。以StackPanel为例,当一个StackPanel添加三个TextBox和一个Button时,完整的语法应该是这样

<StackPanel>
  <StackPanel.Children>
    <TextBlock Margin="5">1</TextBlock>
    <TextBlock Margin="5">2</TextBlock>
    <TextBlock Margin="5">3</TextBlock>
    <Button Content="你点不到我!" Margin="5"></Button>
  </StackPanel.Children>
</StackPanel>

image.png

简写后的代码

<StackPanel Grid.ColumnSpan="2" Margin="0,0,0,0.5">
  <TextBlock Margin="5">1</TextBlock>
  <TextBlock Margin="5">2</TextBlock>
  <TextBlock Margin="5">3</TextBlock>
  <Button Content="你点不到我!" Margin="5"></Button>
</StackPanel>

image.png

各类内容模型详解

我们把符合某类内容模型的UI元素称为一个族,每个族用它们共同基类来命名。

ContentControl族

  • 派生自ContentControl类
  • 它们都是控件(Control)
  • 内容属性的名称为Content
  • 只能由单一元素充当其内容

怎样理解**“只能单一元素充当其内容”**?

Button控件属于这一族,所以,下面两个Button的代码都是正确的,第一个Button的内容是一个文本,第二个Button的内容是一张图片。

<StackPanel Background="Gray">
  <Button>
    <TextBlock>Hello World</TextBlock>
  </Button>
  <Button>
    <Image Source="/Images/1000866.jpeg" />
  </Button>
</StackPanel>

image.png

如果你想让Button的内容既包含文字又包含图片是不允许的,Button只能接受一个元素作为它的Content。

<StackPanel Background="Gray">
  <Button>
    <TextBlock>Hello World</TextBlock>
    <Image Source="/Images/1000866.jpeg" />
  </Button>
</StackPanel>

如果真的需要一个带图标的Button,控件的内容也可以是控件,我们只需要先用一个可以包含多个元素的布局控件把图片和文字包装起来,再把这个布局控件作为Button的内容就好。

<StackPanel Background="Gray">
  <Button>
    <StackPanel>
      <TextBlock>Hello World</TextBlock>
      <Image Source="/Images/1000866.jpeg" Height="200" />
      <Image Source="/Images/2000025.jpeg" Height="200"/>
    </StackPanel>
  </Button>
</StackPanel>

image.png

ContentControl族包含的控件

  • Button
  • ButtonBase
  • CheckBox
  • ComboBoxItem
  • ContentControl
  • Frame
  • GridViewColumnheader
  • GroupItem
  • Label
  • ListBoxItem
  • NavigationWindow
  • RadioButton
  • RepearButton
  • ScrollViewer
  • StatusBarItem
  • ToggleButton
  • ToolTip
  • UserControl
  • Window

HeaderedContentControl族

  • 它们都是派生自HeaderedContentControl类,HeaderContentControlContentControl类的派生类。
  • 它们都是控件用于显示带标题的数据。
  • 除了用于显示主体内容之外,控件还具有一个显示标题(Header)的区域。
  • 内容属性为Content和Header。
  • 无论是Content还是Header都只是容纳一个元素作为其内容。

HeaderedContentControl族包含的控件如表所示。

  • Expander
  • GroupBox
  • HeaderedContentControl
  • tabItem

一个以图表为Header,文字为主体内容的GroupBox。

<GroupBox FontSize="30">
 <GroupBox.Header>
   <Image Source="./Images/WechatIMG1.jpeg" Width="20" Height="20"></Image>
 </GroupBox.Header>
 <TextBlock>
   妈妈快看,看这个傻子又在写BUG了!
 </TextBlock>
</GroupBox>

image.png

TextBlock的TextWrapping属性有三个值

  1. WrapWithOverflow
    根据宽度流式显示文本内容

  2. NoWrap

    不允许换行

  3. Wrap

    内容换行

ItemsControl族

  • 均派生自ItemsControl类。
  • 它们都是控件,用于显示列表化的数据
  • 内容属性为Items或ItemsSource
  • 每种ItemsControl都对应有自己的条目容器(Item Container)

控件列表

  • Menu
  • MenuBase
  • ContextMenu
  • ComboBox
  • ItemsControl
  • ListBox
  • ListView
  • TabControl
  • TreeView
  • Select
  • StatusBar

WPF的ListBox在显示功能上比WinForm或者ASP .NET的ListBox要强大很多。传统的ListBox只能将条目以字符串的形式显示,而WPF的ListBox除了可以显示中规中矩的字符串条目还能够显示更多的元素,如CheckBox、Button、RadioButton、TextBox等,这样一来,我们就能制作出更加丰富的UI。

<ListBox>
  <Button x:Name="CallChild">呼叫儿子</Button>
  <Button x:Name="Victor">HiaHiaHia</Button>
  <Image Source="./Images/2000025.jpeg" Width="100" ></Image>
</ListBox>

image.png

表面上看上去ListBox直接包含了一些Button和Image,实际上并非这样,为Victor这个按钮添加点击事件,看看它的父容器是什么。

private void Victor_Click(object sender, RoutedEventArgs e)
{
  Button btn1 = sender as Button;
  DependencyObject parent = VisualTreeHelper.GetParent(btn1);
  DependencyObject parent1 = VisualTreeHelper.GetParent(parent);
  DependencyObject parent2 = VisualTreeHelper.GetParent(parent1);
  Console.WriteLine(parent2.GetType().ToString());
}

image.png

由于WPF的UI是树形结构,VisualTreeHelper类就是帮助我们在这棵由可视化元素构成的树上进行导航的辅助类。我们沿着被单机的Button一层层向上找,找到第三层发现他是一个ListBoxItem。ListBoxItem是ListBox对应的Item Container,无论将什么数据放到ListBox,它都会以这种方式自动包装。

<ListBox>
  <ListBoxItem>
    ...
  </ListBoxItem>
</ListBox>

LIstBox数据动态绑定数据

如果一个程序需要绑定一个ListBox,那么假设程序需要绑定Employee类型的数据

class Employee
{
  public int id { get; set; }
  public string name { get; set; }
  public int age { get; set; }
}

需要将数据添加到List集合中,那么就得需要一个Employee集合

List<Employee> list = new List<Employee>()
{
  new Employee () { id=1, name="张三", age=18 },
  new Employee () { id=1, name="李四", age=19 },
  new Employee () { id=1, name="王五", age=20 },
};

在程序的界面上有一个NamelistBoxEmployee的ListBox,需要将数据绑定到这个集合当中,当然,ListBox绑定的时候要求你这个集合是空的

<ListBox x:Name="listBoxEmployee"></ListBox>

ListBox需要绑定三个值,一个是显示值,一个是选中的实际值

listBoxEmployee.DisplayMemberPath = "name";
listBoxEmployee.SelectedValue = "id";

除了这些,ListBox还要求你指定数据源,即数据绑定来源

 listBoxEmployee.ItemsSource = list;

ok大功告成了,我现在需要的是在程序加载的时候执行,那么我loaded事件触发时使用这个代码就可以了,但是我认为,使用和实现(执行)是两个不同的概念,所以,我将它们分成不同的方法

将实现的 代码放到一个bindList方法中

private void bindList()
{
  List<Employee> list = new List<Employee>()
  {
    new Employee () { id=1, name="张三", age=18 },
    new Employee () { id=1, name="李四", age=19 },
    new Employee () { id=1, name="王五", age=20 },
  };

  listBoxEmployee.DisplayMemberPath = "name";
  listBoxEmployee.SelectedValue = "id";
  listBoxEmployee.ItemsSource = list;
}

程序加载时只需要去掉用去使用就可以

private void Window_Loaded(object sender, RoutedEventArgs e)
{
  bindList();
}

image.png

HeaderedItemsControl族

除了具有ItemsControl的特性外,还具显示标题的能力

  • 均派生自HeaderedItemsControl类
  • 它们都是控件,用于显示列表化的数据,同时可以显示一个标题
  • 内容属性为Items、ItemsSource和Header。

控件

  1. MenuItem
  2. TreeViewItem
  3. ToolBar

Decorator族

Decorator的元素是在UI上起装饰效果的,如果可以使用Border元素为一些组织在一起的内容加个边框。如果需要组织在一起的内容能够自由缩放,则可使用ViewBox元素。

  • 均派生自Decorator类
  • 起到UI装饰的作用
  • 内容属性为Child
  • 只能由单一元素充当内容

控件

  1. ButtonChrome
  2. Border
  3. AdornerDecorator
  4. ClassBorderDecorator
  5. InkPresenter
  6. ListBoxChrome
  7. BulletDecorator
  8. SystemDropShadowChrome
  9. Viewbox

TextBlock和TextBox

这两个控件最主要是显示文本。TextBlock只能显示文本不能编辑,所以被称为静态文本。TextBox则允许用户编辑其中的内容,TextBlock虽然不能剪辑内容,但可以使用丰富的印刷级的格式控制标记显示专业的排版效果。

TextBox不需要太多格式显示,所以它的内容是简单的字符串,内容属性为Text。

TextBlock由于需要操纵格式,所以内容属性是Inlines(印刷中的“行”),同时,TextBlock也保留一个名为Text的属性,当简单的显示一个字符串时,可以使用这个属性。

Shape族

友好的用户界面离不开各种图形的搭配。Shape族元素(它们只是简单的视觉元素,不是控件)就是专门用来在UI上绘制图形的一类元素。这类元素没有自己的内容,我们可以使用Fill属性为它们设置填充效果,还可以使用Stroke属性为它们设置边线效果。

  • 均派生自Shape类
  • 用于2D图形绘制
  • 无内容属性
  • 使用Fill属性设置填充,使用Stroke属性设置边线

Panel族

Panel用于所有的UI布局

  • 派生自抽象类
  • 主要功能是控制UI布局
  • 内容属性为Children
  • 内容可以是多个元素,Panel元素将控制它们的布局

对比ItemsControl和Panel元素,虽然内容都可以是多个元素,但ItemsControl强调以列表的形式来展现数据,而Panel则前调对包含的元素进行布局,所以ItemsControl的内容属性是Items和ItemsSource而Panel的内容属性名为Children。

控件

  • Canvas
  • DockPanel
  • Grid
  • TabPanel
  • ToolBarOverflowPanel
  • StackPanel
  • ToolBarPanel
  • UniformGrid
  • VirtualizingPanel
  • VirtualizingStackPanel
  • WrapPanel

基本控件的简介

默认可以看到的控件

  1. Border 放到其他控件内部,给其他控件画边框,其他容器必须支持双标签。

  2. Button 按钮,按钮用图片应该设置内容为Image,而不能设置BackGroud为Image,如果只

    设置背景,按钮鼠标经过样式还在

    <Button>
      <Image Source="./Images/1000866.jpeg" />
    </Button>
    
  3. Calendar 日历。

  4. Canvas 画布控件,用来画图的。也可以用来做容器用。子控件根据left/right和

    top/bottom来获取相对与canvas边界的定位。

    <Canvas>
      <Button Canvas.Top="80" Canvas.Left="200" Content="略略略,你点不到我" />
    </Canvas>
    
  5. Checkbox 复选框。

  6. Combobox 下拉列表框。

  7. ContentControl 内容控件。button、checkbox等的基类。一般控件都是现实Text中的文

    本,这个控件显示Content属性中的文本。

  8. DataGrid 显示表格数据。

  9. DataPicker 日期选择控件,带日历。

  10. DockPanel 停靠布局容器。子控件就像一个个船,定义DockPanel.Dock属性表示向上/

    下/左/右靠过去。

  11. Ellipse 实心椭圆

  12. Expander 下拉框

  13. Frame

  14. Grid 网格布局控件,内部分两部分

    <Grid.RowDefinitions>和<Grid.ColumnDefinitions>定义行列,行列的宽高有两种表示方

    法,按尺寸和比例。按比例,表示方式是加 数字+*,计算方式是,例如,三个列

    的尺寸比是1:1:2。尺寸和比例可以混用,会先将尺寸扣除,剩下的按比例显示。

    控件部分需要指定所在行列,可以设置跨行(Grid.RowSpan属性)和跨列

    (Grid.ColumnSpan属性)。

  15. GridSplitter 分割线

  16. GroupBox 具有标题的容器盒子

  17. Image 图片控件 source属性设置图片路径

  18. Label 文本标签 不支持换行 Content属性内为显示的文本

  19. ListBox 列表选择组件,可以横向也可以竖向,能获取选中值。可使用Separator控件做

    分隔符。

  20. ListView 列表视图
    分视图显示方式( <ListView.View>,里面放ViewBase类型的控件,如GridView控件,只能放一个)和数据源( <ListView.ItemsSource>)两个部分。如果只是显示数据的,单用GridView就行了,所以这个常用与一个数据源,存在多个View的情况。提供右键菜单。

  21. MediaElement 播放视频音频,默认是界面上什么都没有,需要再代码中调用Play()方

    法才会播放。

  22. Menu 菜单栏,一般放在顶部

  23. PasswordBox 密码输入框。输入内容显示为*******

  24. ProgressBar 进度条。value属性表示进度,范围0­100

  25. RadioButton 单选按钮

  26. Rectangle 实心矩形

  27. RichTextBox 富文本输入框

  28. ScrollBar 滚动条,一般都直接再外面套一个ScrollViewer

  29. ScrollViewer 带滚动条的容器。

  30. Separtor 竖向分隔线,可以用于ToolBar中分隔按钮组。

  31. Slider 滑动条,当用户关注相对大小,而不是具体的数字时使用。常用语音量控制等。

  32. StackPanel 堆叠容器。子控件每个都占一行或一列。Orientation属性设置横向Horizontal和竖向Vertical

  33. StatusBar 状态栏,一般放在底部,显示各种状态信息

  34. TabControl tab选项卡和对应容器

  35. TextBlock 文本块 Text属性为显示内容,TextWrapping="Wrap"表示换行显示文本。

  36. TextBox 文本框

  37. ToolBar 工具栏,单独用时独占一行。

  38. TolBarPanel 继承自StackPanel,工具栏容器

  39. ToolBarTray 工具栏集合,当有多个工具栏时使用。内部的ToolBar不再独占一行。

  40. TreeView 树形视图。

    <TreeView>
      <TreeViewItem Header="常用WPF控件" IsExpanded="True">
        <TreeViewItem Header="指针" /> 
        <TreeViewItem Header="Border" /> 
        <TreeViewItem Header="Button" /> 
        <TreeViewItem Header="CheckBox" /> 
        <TreeViewItem Header="ComboBox" /> 
        <TreeViewItem Header="Grid" /> 
        <TreeViewItem Header="Image" /> 
        <TreeViewItem Header="Label" /> 
      </TreeViewItem>
    </TreeView>
    

    image.png

  41. Viewbox 主要为子控件提供拉伸,缩放等功能。

  42. WebBrowser 内置Web浏览器(IE内核,不装IE不能用)

  43. WindowsFormsHost 这个里面支持WinForm控件。

  44. WrapPanel 包裹块装元素的容器。子控件是一块一块的,从左往右排列,如果超过右

    边框,则换行;超过下边界,则隐藏。

默认不能看到的控件

  1. 文档

    DocumentViewerBase 提供用于显示固定内容或流动内容(分别由 System.Windows.Documents.FixedDocument 或System.Windows.Documents.FlowDocument 表示)的查看器的基类。DocumentPageView 表示已分页 System.Windows.Documents.DocumentPage 的视区。FlowDocumentPageViewer 表示一个用于在固定查看模式下查看流内容的控件,该模式一次显示一页内容。FlowDocument 用高级文档功能(如分页和列)承载流内容和设置流内容格式。 定义一个文档,文档样式

布局

XAML常用的五个布局元素

  1. Grid
    • 网格,可以自定义行和列并通过行列的数量、行高列宽来调整布局。
  2. DockPanel
    • 泊靠式面板,内部元素可以选择泊靠的方向「上下左右」
  3. StackPanel
    • 栈式面板,可以将包含的元素在水平或垂直方向排成一条线,当移除一个元素后,后面的元素会自动向前填充空缺
  4. WrapPanel
    • 自动折行面板,内部元素在排满一行后能够自动折行
  5. Canvas
    • 画布,内部元素可以使用以像素为单位的绝对坐标进行定位。

Grid网格

它的子控件被放在一个一个实现定义好的小格子中,整齐分布,Grid和其它Panel比较起来,功能最多也最复杂。

要使用Grid,首先要RowDefinitionsColumnDefinitions属性中添加一定数量的RowDefinitionsColumnDefinitions元素,从而定义行和列数。

而放置在Grid面板中的控件元素都必须显示采用附加属性语法定义其放置所在的行和列,它们都是以0为基准的整型值,如果没有显示设置任何行或列,Grid的组成并非简单的添加属性标记来区分行列,这也使得用户在实际应用中可以具体到某一单元格中,所以布局起来就更精细了。

特点

  • 可以定义任意数量的行和列,非常灵活。
  • 行的高度和列的宽度可以使用绝对值、相对比例或自动调整的方式进行精确设定,并可以设置最大值和最小值。
  • 内部元素可以设置自己所在的行和列,还可以设置自己纵向跨几行,横向跨几列。
  • 可以设置Children元素的对齐方向。

Grid的适用场景

  • UI布局的大框架设计
  • 大量UI元素需要成行或者成列对齐的情况
  • UI尺寸改变的时候,元素需要保留的宽度和高度比例。

Grid常用属性

  • Grid.Column
    • 读取或设定指定FrameworkElement的附加属性Grid.Column的值
  • Grid.ClumnSpan
    • 读取或者定指定FrameworkElement的附加属性Grid.ColumnSpan的值
  • Grid.Row
    • 读取或设定指定FrameworkElement的附加属性Grid.Row的值
  • Grid.RowSpan
    • 读取或设定指定FrameworkElement的附加属性Grid.RowSpan的值

单位

只定义行和列的个数还远远不够,我们还需要设置行的高度和列的宽度才能够形成有意义的布局,这就引出两个问题

  • 宽度和高度的单位是什么
  • 宽度和高度可以取什么样的值

计算机图形设计的标准单位是像素(Pixel),所以Grid的宽度和高度单位就是像素。除了可以使用像素作为单位外,Grid还接受英寸(Inch)、厘米(Centimeter)和点(Point)作为单位

英文名中文名简写换算
Pixel像素px(默认单位,可省略)图形基本单位
Inch英寸in1inch = 96pixel
Centimeter厘米cm1cm =(96 / 2.54)pixel
Pointpt1pt = (96 / 72)pixel
<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="30"/>
    <RowDefinition Height="0.5in"/>
    <RowDefinition Height="1cm"/>
    <RowDefinition Height="30pt"/>
  </Grid.RowDefinitions>
</Grid>

image.png

上面代码有几点值得注意

  • 属性值为double类型。
  • 因为像素是默认单位,所以px可以省略
  • 其它单位也会被转换成像素并显示在Grid的边缘处

实际工作中使用什么单位要看程序具体功能,如果UI只用显示在计算机屏幕上,那么像素单位最为合适,如果程序设计打印输出,则公制单位选择厘米、英制单 位使用英寸比较合适。

对与Grid的行高和列宽,我们可以设置三类值

  • 绝对值
    double数值加单位后缀
  • 比例值
    double数值后加一个星号(“ ***** ”)
  • 自动值
    字符串Auto

前面的例子使用的就是绝对值,绝对值的特点是一经设定就不会改变,所以又称固定值。当控件的宽度和高度不需要改变或者使用空行、空列作为控件间隔时,绝对值是不二之选。

比例值是在 double 类型数据后加一个星号(“ ***** ”)。解析器会把所有的数值加起来作为分母、把每个比例值的数值作为分子,在用这个分数乘以未被占用空间的像素数,得到的结果就是分配给这个比例值的最终像素数。比如一个总高度为 150px 的 Grid ,它包含 5 行,其中两行采用绝对值 25px ,其它三行分别是 2*、 1*、2*,使用上面的计算方法,这三行分配的像素数应该是 40px、20px 和 40px

Grid 使用

使用5行5列来布局

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="1*"/>
    <ColumnDefinition Width="1*"/>
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="1*" />
    <ColumnDefinition Width="1*"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="1*" />
    <RowDefinition Height="4*" />
    <RowDefinition Height="1*" />
    <RowDefinition Height="1*" />
    <RowDefinition Height="1*" />
  </Grid.RowDefinitions>
</Grid>

image.png

添加一个带图标的标题,并使用Grid.RowSpan跨行和Grid.ColumnSpan跨列让这个GroupBox铺满

<GroupBox Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Grid.ColumnSpan="5">
  <GroupBox.Header>
    <StackPanel Orientation="Horizontal" >
      <Image Source="./Images/WechatIMG1.jpeg" Width="20" Height="20"/>
      <TextBlock Text="牛逼哄哄系统"/>
    </StackPanel>
  </GroupBox.Header>
</GroupBox>

image.png

添加一张图片到指定位置

<Image Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="3"  Source="./Images/2000025.jpeg"/>

image.png

添加一个按钮

<Button Grid.Row="3" Grid.Column="2" Click="Button_Click" Content="Join to NiuBiHongHong System" />

image.png

搞定

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="1*"/>
    <ColumnDefinition Width="1*"/>
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="1*" />
    <ColumnDefinition Width="1*"/>
  </Grid.ColumnDefinitions> 
  <Grid.RowDefinitions>
    <RowDefinition Height="1*" />
    <RowDefinition Height="4*" />
    <RowDefinition Height="1*" />
    <RowDefinition Height="1*" />
    <RowDefinition Height="1*" />
  </Grid.RowDefinitions>
  <GroupBox Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Grid.ColumnSpan="5">
    <GroupBox.Header>
      <StackPanel Orientation="Horizontal" >
        <Image Source="./Images/WechatIMG1.jpeg" Width="20" Height="20"/>
        <TextBlock Text="牛逼哄哄系统"/>
      </StackPanel>
    </GroupBox.Header>
  </GroupBox>
  <Image Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="3"  Source="./Images/2000025.jpeg"/>
  <Button Grid.Row="3" Grid.Column="2" Click="Button_Click" Content="Join to NiuBiHongHong System" />
</Grid>

image.png

DockPanel 泊靠式面板

DockPanel定义一个区域,在此区域中,你可以使子元素通过描点的形式排列,这些对象位于Children属性中。

DockPanel会对每个子元素进行排序,并将根据指定的边进行停靠,多个停靠在同侧的元素则按顺序排序。

在DockPanel中,指定停靠边的控件,会根据定义的顺序占领边角,所有控件绝不会交叠。

默认情况下,后添加的元素只能使用剩余控件,无论对DockPanel的最后一个元素设置任何停靠值,该子元素都将始终填满剩余的空间。如果不希望最后一个元素填充剩余区域,可以将DockPanel属性LastChildFill设置为false,还必须为最后一个子元素显示指定停靠方向。

使用场合

  • 填充整个剩余空间
  • 最后元素不填充剩余空间

使用

DockPanel停靠容器,专门负责自适应窗口的布局。

<DockPanel>
  <Button Content="我是第一个button" />
  <Button Content="我是第二个button" />
  <Button Content="我是最后一个button" />
</DockPanel>

image.png

DockPanel元素有先后顺序,最后一个元素默认填满剩余空间,可以使用DockPanel的附加属性来改变泊靠方向。

<DockPanel>
  <Button Content="我是第一个button" Height="50px" DockPanel.Dock="Top"/>
  <Button Content="我是第二个button" />
  <Button Content="我是第三个元素" DockPanel.Dock="Bottom" />
  <Button Content="我是第四个元素" DockPanel.Dock="Right"/>
  <Button Content="我是最后一个button" />
</DockPanel>

image.png

  • 第一个元素向顶部泊靠时由于没有元素在顶部,所以占满整个顶部的剩余空间

  • 没有元素使用DockPanel.Dock指定泊靠方向,所以第二个元素默认从左到右排序

  • 第三个元素向底部泊靠,第二个元素将高度占满,所以第三个元素的宽度受到影响

  • 第四个元素向右泊靠,高度部分高度被第一和第三个元素占据

  • 最后一个元素占满剩余空间

如果需要最后一个元素不占满剩余空间,那么需要将DockPanelLastChildFill改为False

  <DockPanel LastChildFill="False">...</DockPanel>

image.png

StackPanel 栈式面板

StackPanel就是将控件按照行或列来顺序排序,但不会换行。

通过设置面板的Orientation属性设置了两种排列方式

  • 横排(Horizontal 默认的)
  • 竖排 (Vertical)

默认情况下垂直排列时,每个元素都与面板一样高;水平排列时,每个元素都与面板一样宽。如果包含的元素超过了面板空间,它只会截断多出的内容

元素的Margin属性用于使元素之间产生一定得间隔,当元素空间大于其内容的空间时,剩余空间将由HorizontalAlignmentVerticalAlignment属性来决定如何分配。

使用场合:

  • 同类元素需要紧凑排列(如制作菜单和列表)。

  • 移除其中的元素后能够自动补缺的布局或者动画。

StackPanel 的三个属性

属性名称数据类型可取值描述
OrientationOrientation枚举Horizontal
Vertical
决定内部元素是横向积累还是纵向累积
HorizontalAlignmentHorizontalAlignment枚举Left
Center
Right
Stretch
决定内部元素水平方向上的对齐方式
VerticalAlignmentVerticalAlignment枚举Top
Center
Bottom
Stretch
决定内部元素竖直方向上的对齐方式

使用

Orientation="Horizontal" 内容横向排列

<StackPanel>
  <GroupBox>
    <GroupBox.Header>StackPanel Orientation="Horizontal"</GroupBox.Header>
    <StackPanel Orientation="Horizontal">
      <Button>第一个选项</Button>
      <Button>第二个选项</Button>
      <Button>第三个选项</Button>
      <Button>第四个选项</Button>
    </StackPanel>
  </GroupBox>
</StackPanel>

image.png

Orientation="Vertical" 内容纵向排列

<StackPanel>
  <GroupBox Margin="0 50 0 0">
    <GroupBox.Header>StackPanel Orientation="Vertical"</GroupBox.Header>
    <StackPanel Orientation="Vertical">
      <Button>第一个选项</Button>
      <Button>第二个选项</Button>
      <Button>第三个选项</Button>
      <Button>第四个选项</Button>
    </StackPanel>
  </GroupBox>
</StackPanel>

image.png

WrapPanel:自动折行面板(环绕面板)

WrapPanel布局面板将各个控件从左至右按照行或列的顺序罗列,当长度或高度不够时就会自动调整进行换行,后续排序按照从上至下或从右至左的顺序进行。

Orientation——根据内容自动换行。当Orientation属性的值设置为 Horizontal:元素是从左向右排列的,然后自上至下自动换行。当Orientation属性的值设置为Vertical:元素是从上向下排列的,然后从左至右自动换行

ItemHeight——所有子元素都一致的高度。每个子元素填充高度的方式取决于它的VerticalAlignment属性、Height属性等。任何比ItemHeight高的元素都将被截断

ItemWidth——所有子元素都一致的宽度。每个子元素填充高度的方式取决于它的VerticalAlignment属性、Width属性等。任何比ItemWidth高的元素都将被截断

  • Orientation

    1. Horizontal

      <WrapPanel Orientation="Horizontal">
        <Button Margin="5 10 0 0" >Hello</Button>
        ...
      </WrapPanel>
      

      窗体宽度较小的时候
      image.png

      窗体拉大后

      image.png

    2. Vertical

      <WrapPanel Orientation="Vertical">
        <Button Margin="5 10 0 0" >Hello</Button>
        ...
      </WrapPanel>
      

      当窗体高度较小时

      image.png

      将窗体高度拉高后

      image.png

    3. 使用HorizontalAlignment属性可以设置内容的停靠方向

      例如HorizontalAlignment="Right"

      image.png

Canvas 画布面板

画布,用于完全控制每个元素的精确位置。他是布局控件中最为简单的一种,直接将元素放到指定位置,主要来布置图面。

使用Canvas,必须指定一个子元素的位置(相对于画布),否则所有元素都将出现在画布的左上角。调整位置用Left、Right、Top和Bottom四个附加属性。

如果Canvas是窗口主元素(即最外层的布局面板是Canvas),用户改变窗口大小时,Canvas也会随之变化,子元素的位置也会随之移动,以保证相对于Canvas的位置属性不变。

Canvas允许子元素的部分或全部超过其边界,默认不会裁剪子元素,同时可以使用负坐标,即溢出的内容会显示在Canvas外面,这是因为默认 ClipToBounds=”False”,因此画布不需要指定大小。如果想复制画布内容,将ClipToBounds设为true即可。

  1. 子元素不超出边界
    image.png

    <Canvas>
      <Button>HiaHiaHia按钮</Button>
      <Button Canvas.Right="0">HiaHiaHia按钮</Button> 
    </Canvas>
    
  2. 子元素超出边界
    image.png

    <Canvas>
      <Button>HiaHiaHia按钮</Button>
      <Button Canvas.Right="-30">HiaHiaHia按钮</Button> 
    </Canvas>
    

    在XAML设计界面,超出的部分不会进行裁剪,如图所示:

    image.png

    如果将ClipToBounds属性设置为true,在设计界面将会对子元素超出的部分进行裁剪

    image.png

    <Canvas ClipToBounds="True">
      <Button>HiaHiaHia按钮</Button>
      <Button Canvas.Right="-30">HiaHiaHia按钮</Button> 
    </Canvas>
    

    Canvas 内的子控件不能使用两个以上的 Canvas 附加属性,如果有同时设置 Canvas.Left 和 Canvas.Right 属性,那么后者将会被忽略。


属性

属性设置

属性

属性是对XAML元素特征进行描述的方法,属性不允许在XAML中重复设置多次,允许在托管代码中改变元素的属性值

设置方式

  • 使用属性语法
  • 使用属性元素语法
  • 使用内容元素语法
  • 使用集合语法

使用属性语法

每个属性对应一个属性值,属性值类型必须与属性匹配,一个标记中可以设置对象的多个属性,只有实例化对象才可以设置实例属性,格式如下

<objectName propertyName='propertyValue' propertyName1='propertyValue1' />

<objectName propertyName='propertyValue' propertyName1='propertyValue1'></objectName>å

栗子🌰

<Canvas Background="Red" Width="150" Height="150"></Canvas>

<Canvas Background="Red" Width="150" Height="150" />

image.png

使用属性元素语法

某些属性可以使用属性元素语法来设置

<object>
  <object.property>
    ...
  </object.property>
</object>

栗子🌰

<Ellipse Width="150" Height="150">
  <Ellipse.Fill>
    <SolidColorBrush Color="Red"></SolidColorBrush>
  </Ellipse.Fill>
</Ellipse>

image.png

使用内容元素语法

某些元素的属性支持内容元素语法,允许忽略元素的名称,实例对象会根据XAML元素中的第一个标记值来设置属性,对于大量的格式化文本,使用内容元素语法更加灵活,属性标记之间可以插入大量的文本内容

例子🌰

<TextBlock>
  某些元素的属性支持内容元素语法,
  允许忽略元素的名称,
  实例对象会根据XAML元素中的第一个标记值来设置属性,
  对于大量的格式化文本,使用内容元素语法更加灵活,
  属性标记之间可以插入大量的文本内容
</TextBlock>

使用集合语法

元素支持一个属性元素的集合,才使用集合语法进行设置属性,使用托管代码的Add方法来增加更多的集合元素,本质是面向对象的集合中添加属性项

例子🌰

方式一
<Rectangle Width="200" Height="150" >
  <Rectangle.Fill>
    <!--线性梯度画刷-->
    <LinearGradientBrush>
      <!--属性集合语法-->
      <GradientStopCollection>
        <GradientStop Offset="0.0" Color="Red" />
        <GradientStop Offset="1.1" Color="Black" />
      </GradientStopCollection>
    </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>
方式二
<Rectangle Width="200" Height="150" >
  <Rectangle.Fill>
    <!--线性梯度画刷-->
    <LinearGradientBrush>
      <!-- 省略 GradientStopCollection 隐式的属性设置方法 -->
      <LinearGradientBrush.GradientStops>
        <!--属性集合语法-->
        <GradientStopCollection>
          <GradientStop Offset="0.0" Color="Red" />
          <GradientStop Offset="1.1" Color="Black" />
        </GradientStopCollection>
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>

image.png

基本属性、附加属性和依赖属性

附加属性

  • 附加属性作用于支持附加属性的元素
  • 附加属性是由支持附加属性的父元素产生作用,支持附加属性的元素会继承所在的父元素的属性
  • 附加属性的格式:AttachedPropertyProvider.PropertyName

栗子🌰

<Canvas>
  <Ellipse Canvas.Left="50" Canvas.Top="50" Width="200" Height="200" Fill="Red" />
</Canvas>

image.png

依赖属性

  • 英文名:Dependency Properties
  • 依赖属性和CRL属性类似,提供一个实例级私有字段的访问封装,通过GetValue和SetValue访问器实现属性的读写操作
  • 最重要的一个特点是属性值依赖于一个或者多个数据源,提供这些数据源的方式也可以不同
  • 由于依赖属性多数据源的缘故,故称之为依赖属性

什么是依赖属性

依赖属性就是一种自己可以没有值,并且可以通过Binding绑定从其他数据源获取值。依赖属性可支持WPF中的样式设置、数据绑定、继承、动画及默认值。

image.png

  • 希望可在样式中设置属性。
  • 希望属性支持数据绑定
  • 希望可使用动态资源引用设置属性。
  • 希望从元素树中的父元素自动继承属性值。
  • 希望属性可进行动画处理。
  • 希望属性系统在属性系统、环境或用户执行的操作或者读取并使用样式更改了属性以前的值时报告。
  • 希望使用已建立的、WPF 进程也使用的元数据约定,例如报告更改属性值时是否要求布局系统重新编写元素的可视化对象。

依赖属性的特点

无论什么时候,只要依赖属性的值发生变化,WPF就会自动根据属性的元数据触发一系列的动作,这些动作可以重新呈现UI元素,也可以更新当前布局,刷新数据绑定等等,这种变更的通知最有趣的特点之一就是属性触发器,它可以在属性值改变的时候,执行一系列自定义的动作,而不需要更改任何其它的代码来实现。通过下面的示例来演示属性变更通知:

栗子🌰

当鼠标移动到Button按钮上面时,文字的前景色变为红色,离开时变为默认颜色黑色,采用传统方式和依赖属性两种方式实现

使用传统方式实现:XAML代码
<Button MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave" Height="40" Width="200" Content="点我鸭!"></Button>

后台代码实现

// 鼠标移入时触发
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
  Button btn = sender as Button;
  btn.Foreground = Brushes.Red;
}

// 鼠标移出时触发
private void Button_MouseLeave(object sender, MouseEventArgs e)
{
  Button btn = sender as Button;
  btn.Foreground = Brushes.Black;
}
使用依赖属性实现
<Button  Height="40" Width="200" Content="点我鸭!">
  <Button.Style>
    <Style TargetType="Button">
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <!--字体颜色变成红色-->
          <Setter Property="Foreground" Value="Red"/>
          <!--鼠标变成手型的光标-->
          <Setter Property="Cursor" Value="Hand" />
        </Trigger>
      </Style.Triggers>
    </Style>
  </Button.Style>
</Button>

使用上面两种方式都可以实现 Button 按钮的前景色改变

image.png


标记拓展(Markup Extensions)

  1. 实际项目中为XAML控件属性复制经常遇到
    • 设计时属性处于未知状态
    • 运行时才能获取到
  2. 轻松实现XAML页面属性赋值,资源引用,类型转换等操作

常用拓展标记

image.png

Binding(XAML载入时,将数据绑定到XAML对象)

<StackPanel>
  <Button Background="Red" Height="30" x:Name="refButton">啊哈哈哈,我要绑定你</Button>
  <Button Height="30" Background="{Binding ElementName=refButton, Path=Background}">嘤嘤嘤~我被绑定了</Button>
</StackPanel>

ElementName 用于绑定到XAML界面设计中添加的其它控件对象 refButton ,Path 路径的是需要绑定的具体属性

image.png

StaticResorce (引用数据字典中定义的静态资源)

<Window.Resources>
  <Style TargetType="Ellipse" x:Key="fill-e">
    <Setter Property="Fill">
      <Setter.Value>
        <LinearGradientBrush EndPoint="0, 0" StartPoint="1, 1">
          <GradientStop Offset="0" Color="Red" />
          <GradientStop Offset="1" Color="Black" />
        </LinearGradientBrush>
      </Setter.Value>
    </Setter>
  </Style>
</Window.Resources>
<StackPanel>
  <Ellipse Style="{StaticResource fill-e}" Width="200" Height="200" ></Ellipse>
</StackPanel>

Style="{StaticResource fill-e}" 使用静态资源

LinearGradientBrush线性渐变

image.png

TemplateBinding (XAML页面中对象模版绑定调用)

<Window.Resources>
  <Style x:Key="btnStyle" TargetType="Button">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Grid Height="30" Width="Auto">
            <Grid.Background>
              <RadialGradientBrush>
                <GradientStop Offset="0" Color="#900" />
                <GradientStop Offset="1" Color="Black" />
              </RadialGradientBrush>
            </Grid.Background>
            <Grid.ColumnDefinitions>
              <ColumnDefinition />
              <ColumnDefinition Width="3*" />
            </Grid.ColumnDefinitions>
            <Image Source="./Images/截屏2020-06-29 21.23.01.png" />
            <TextBlock Foreground="White" x:Name="text" Grid.Column="1" Text="{TemplateBinding Content}" VerticalAlignment="Center"></TextBlock>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</Window.Resources>
<Grid>
  <Button Content="HiaHiaHia 按钮" Style="{StaticResource btnStyle}"></Button>
</Grid>

image.png

RelativeSource (对特定数据源引用)

RelativeSource有两种应用模式

self模式

  1. 目标对象将作为源对象绑定到自身
  2. 可以实现同一对象不同元素之间的绑定操作
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="RelativeSource:Self测试" ToolTipService.ToolTip="{Binding Text,RelativeSource={RelativeSource Self}}"/>

TemplatedParent模式

  1. 仅在ControlTemplate (控件模板)或者DataTemplate(数据模板)下有效
  2. 不同的模板,将返回不同类型的绑定结果
<Window.Resources>
  <Style x:Key="btnStyle" TargetType="Button" >
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Grid Height="30" Width="200">
            <TextBlock x:Name="text" Text="{Binding Path=Content,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" VerticalAlignment="Center"/>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</Window.Resources>
<Grid>
  <Button Name="RelativeSource测试TemplatedParent模式" Style="{StaticResource btnStyle}">
    HiaHiaHia按钮
  </Button>
</Grid>

数据绑定

  1. OneWay

    Source影响着Target,但是Target却影响不到Source。

  2. OneWayToSource

    Target影响Source,而Source却影响不到Target。

  3. TwoWay

    Source与Target相互影响。

  4. OneTime

    在OneWay的基础上延伸了一个OneTime,仅绑定一次。

栗子🌰

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="3*" />
    <ColumnDefinition Width="3*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="3*" />
    <RowDefinition Height="2*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
    <RowDefinition Height="3*" />
  </Grid.RowDefinitions>
  <ScrollBar Grid.Row="1"  Grid.Column="1" Grid.ColumnSpan="4" Name="scrollBar" Orientation="Horizontal" Canvas.Left="107" Canvas.Top="35" Minimum="1" Maximum="100" SmallChange="1"/>
  <Label Content="OneWay" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" />
  <TextBox 
           Grid.Row="3"
           Grid.Column="2"
           Grid.ColumnSpan="3"
           Height="25"
           VerticalAlignment="Center"
           Text="{Binding ElementName=scrollBar, Path=Value, Mode=OneWay}"
           />

  <Label 
         Content="OneWayToSource" 
         Grid.Row="4" 
         Grid.Column="1" 
         HorizontalAlignment="Left" 
         VerticalAlignment="Center" 
         />
  <TextBox 
           Grid.Row="4" 
           Grid.Column="2" 
           Grid.ColumnSpan="3" 
           Height="25" 
           VerticalAlignment="Center"
           Text="{Binding ElementName=scrollBar, Path=Value, Mode=OneWayToSource}"
           />

  <Label 
         Content="TwoWay" 
         Grid.Row="5" 
         Grid.Column="1" 
         HorizontalAlignment="Left"
         VerticalAlignment="Center" 
         />
  <TextBox 
           Grid.Row="5"
           Grid.Column="2"
           Grid.ColumnSpan="3"
           Height="25"
           VerticalAlignment="Center"
           Text="{Binding ElementName=scrollBar, Path=Value, Mode=TwoWay}"
           />

  <Label 
         Content="OneTime"
         Grid.Row="6" 
         Grid.Column="1" 
         HorizontalAlignment="Left"
         VerticalAlignment="Center" 
         />
  <TextBox  
           Grid.Row="6" 
           Grid.Column="2"
           Grid.ColumnSpan="3" 
           Height="25"
           VerticalAlignment="Center" 
           Text="{Binding ElementName=scrollBar, Path=Value, Mode=OneTime}"
           />
</Grid>

image.png


事件

  • Windows消息机制中重要概念之一,最常见的人机交互手段之一
  • XAML帮助应用管理用户输入,执行不同的行为
  • 引入增强型事件处理系统­Routed Event(路由事件)
  • 事件常常被用于控制更改通知操作

事件基础语法

<ObjectName EventName="EventHandle">

<Button Click="Button_Click"></Button>
private void Button_Click(object sender, RoutedEventArgs e) {...}

事件系统在WPF中被升级进化称为路由事件(Routed Event),并在其基础上衍生出命令传递机制。这些机制很大程度上减少了对程序员的束缚,让程序的设计和实现更加灵活,模块之间的耦合度也进一步降度。

路由事件处理方式

image.png

什么是路由事件

WPF中的事件为路由事件,所谓路由事件,MSDN定义如下:

功能定义

路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件

实现定义

路由事件是一个CLR事件,可以由RoutedEvent类的实例提供支持并由Windows Presentation Foundation 事件系统来处理

<Border Height="50" Width="250" BorderBrush="Gray" BorderThickness="2" CornerRadius="5">
  <StackPanel Background="LightGray" Orientation="Horizontal" MouseUp="StackPanel_MouseUp">
    <TextBlock Name="YesTB" Width="50" MouseUp="YesTB_MouseUp" Background="Blue" Foreground="#fff">
      驾~~~
    </TextBlock>
  </StackPanel>
</Border>
private void StackPanel_MouseUp(object sender, MouseButtonEventArgs e)
{
  YesTB.Text = "StackPanel";
}
private void YesTB_MouseUp(object sender, MouseButtonEventArgs e)
{
  YesTB.Text = "YesTB";
}

image.png

中断事件路由

所有的路由事件都共享一个公共的事件数据基类 RoutedEventArgs。 RoutedEventArgs 定义了一个采用布尔值的 Handled 属性。 Handled 属性的目的在于,允许路由中的任何事件处理程序通过将 Handled 的值设置为 true 来将路由事件标记为“已处理”。「在JavaScript中叫做阻止事件冒泡」

private void StackPanel_MouseUp(object sender, MouseButtonEventArgs e)
{
  YesTB.Text = "StackPanel";
}

private void YesTB_MouseUp(object sender, MouseButtonEventArgs e)
{
  YesTB.Text = "YesTB";
  e.Handled = true;
}

image.png

什么是冒泡事件和预览事件(隧道事件)

路由事件实际上分两类:冒泡事件预览事件(隧道事件)

冒泡事件是WPF路由事件中最为常见,它表示事件**从源元素扩散(传播)到可视树,直到它被处理或到达根元素。**这样你就可以针对源元素的上方层级对象处理事件。例如,你可以嵌入的Grid元素附加一个Button.Click处理程序,而不是直接将其附加到按钮本身。气泡事件有指示其操作的名称(例如 MouseDown)

隧道事件采用另一种方式,从根元素开始,向下遍历元素树,直到被处理或到达事件的源元素。这样上游元素就可以在事件到达源元素之前先行截取并进行处理。根据命名惯例,隧道事件带有前缀 Preview(例如PreviewMouseDown)。

区别

冒泡事件:在YesTB上点击,先显示“YesTB”再显示“StackPanel”。

预览事件(隧道事件)事件:在YesTB上点击,首先弹出“StackPanel”,再弹出“YesTB”。

示例

在 Windows Presentation Foundation (WPF) 中,元素以元素树结构形式排列。 父元素可以参与处理最初由元素树中的子元素引发的事件。 这都是因为事件路由。

路由事件通常遵循以下两个路由策略之一:浮升隧道。 此示例重点介绍浮升事件,并使用ButtonBase.Click事件可显示路由的工作原理。

下面的示例创建两个Button控制,并使用XAML特性语法将事件处理程序附加到公用父元素,它在此示例中为StackPanel。 而不是将单个事件处理程序附加每个Button子元素,该示例使用特性语法将附加到的事件处理程序StackPanel父元素。 此事件处理模式展示了如何使用事件路由技术来减少附加处理程序的元素数。 每个的所有浮升事件Button通过父元素路由。

<StackPanel Button.Click="StackPanel_Click">
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <Setter Property="Height"  Value="20"/>
      <Setter  Property="Width" Value="250"/>
      <Setter Property="HorizontalAlignment" Value="Left" />
    </Style>
  </StackPanel.Resources>
  <Button Name="Button1">Item 1</Button>
  <Button Name="Button2">Item 2</Button>
  <TextBlock Name="results"/>
</StackPanel>
private void StackPanel_Click(object sender, RoutedEventArgs e)
{
  StringBuilder eventStr = new StringBuilder();
  FrameworkElement fe = (FrameworkElement)sender;
  eventStr.Append("元素处理的事件");
  eventStr.Append(fe.Name);
  eventStr.Append("\n");

  FrameworkElement fe2 = (FrameworkElement)e.Source;
  eventStr.Append("事件起源于类型的源元素 ");
  eventStr.Append(e.Source.GetType().ToString());
  eventStr.Append(" with Name ");
  eventStr.Append(fe2.Name);
  eventStr.Append("\n");

  eventStr.Append("事件使用路由策略 ");
  eventStr.Append(e.RoutedEvent.RoutingStrategy);
  eventStr.Append("\n");

  results.Text = eventStr.ToString();
}

image.png


资源

  • 与传统WEB应用中CSS样式表类似

  • 目的为了实现对象的重复使用

  • 有助于XAML代码重用,有助于应用维护的一致性

  • 定义资源的语法格式:

    <根元素对象.Resources>

    <资源定义>

    </根元素对象.Resources>

<Grid>
  <Grid.Resources>
    <LinearGradientBrush x:Key="bgBrush" StartPoint="0,0" EndPoint="1,1" >
      <GradientStop Color="Red" Offset="0" />
      <GradientStop Color="Orange" Offset="0.2"/>
      <GradientStop Color="Yellow" Offset="0.4"/>
      <GradientStop Color="Green" Offset="0.6"/>
      <GradientStop Color="Blue" Offset="0.8"/>
      <GradientStop Color="Violet" Offset="1"/>
    </LinearGradientBrush>
  </Grid.Resources>
  <Button Background="{StaticResource bgBrush}"></Button>
</Grid>

image.png

资源词典(ResourceDictionary)

资源词典分类

  • WPF应用程序中,XAML资源分为StaticResource(静态资源) 和DynamicResource(动态资源)
  • Windows8应用中,XAML资源仅支持StaticResource(静态资源)
  • 资源应用域不同,XAML资源可分为FrameworkElement.Resources和Application.Resources
    1. FrameworkElement.Resources是将资源对象应用于同一个对象数的不同对象上,称之为页面资源,通常被定义在XAML页面根元素上。
    2. Application.Resources是贯穿整个应用级别的资源,通常被定义在App.xaml页面

FrameworkElement.Resources(页面资源词典)

<Grid>
  <Grid.Resources>
    <ResourceDictionary>
      <LinearGradientBrush x:Key="bgBrush" StartPoint="0,0" EndPoint="1,1" >
        <GradientStop Color="Red" Offset="0" />
        <GradientStop Color="Orange" Offset="0.2"/>
        <GradientStop Color="Yellow" Offset="0.4"/>
        <GradientStop Color="Green" Offset="0.6"/>
        <GradientStop Color="Blue" Offset="0.8"/>
        <GradientStop Color="Violet" Offset="1"/>
      </LinearGradientBrush>
      <LinearGradientBrush x:Key="bgBrush2" StartPoint="0,0" EndPoint="1,1" >
        <GradientStop Color="Red" Offset="0" />
        <GradientStop Color="Orange" Offset="0.2"/>
        <GradientStop Color="Yellow" Offset="0.4"/>
        <GradientStop Color="Green" Offset="0.6"/>
        <GradientStop Color="Blue" Offset="0.8"/>
        <GradientStop Color="Violet" Offset="1"/>
      </LinearGradientBrush>
    </ResourceDictionary>
  </Grid.Resources>
  <StackPanel Orientation="Horizontal">
    <Button Background="{StaticResource bgBrush}" Width="200"></Button>
    <Button Background="{StaticResource bgBrush}" Width="200"></Button>
  </StackPanel>
</Grid>

image.png

Application.Resources(贯穿整个应用级别的资源)

Style也是资源的一种,从某种意义上来说,它很类似于我们给普通HTML中的元素建立CSS。

样式是最常见的一种资源,而且它总是被定义在Resource Dictionary中,为了来重用。

定义资源词典

应用程序鼠标右键 --> 添加 --> 新建项 -->

image.png

找到资源词典点击添加
image.png

示例
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:HelloWPF2">
  <!--资源一-->
  <LinearGradientBrush x:Key="bgBrush" StartPoint="0,0" EndPoint="1,1" >
    <GradientStop Color="Red" Offset="0" />
    <GradientStop Color="Orange" Offset="0.2"/>
    <GradientStop Color="Yellow" Offset="0.4"/>
    <GradientStop Color="Green" Offset="0.6"/>
    <GradientStop Color="Blue" Offset="0.8"/>
    <GradientStop Color="Violet" Offset="1"/>
  </LinearGradientBrush>
  <!--资源二-->
  <LinearGradientBrush x:Key="bgBrush2" StartPoint="0,0" EndPoint="1,1" >
    <GradientStop Color="Red" Offset="0" />
    <GradientStop Color="Orange" Offset="0.2"/>
    <GradientStop Color="Yellow" Offset="0.4"/>
    <GradientStop Color="Green" Offset="0.6"/>
    <GradientStop Color="Blue" Offset="0.8"/>
    <GradientStop Color="Violet" Offset="1"/>
  </LinearGradientBrush>
</ResourceDictionary>
合并资源词典属性

所有的资源项在最终都会被整合到Resource Dictionary中的,也就是说无论是FrameworkElement的Resources,还是Window的Resources,还是Application的Resources,还是特定的ResourceDictionary中定义的resources在整个应用编译执行的时候实际上他们都在一起的作为可遍历集合共同存在于一个相对会话空间内的。

Resource的key是可以被允许有相同的,这样在遍历不同相对地址的Resource Dictionary时会根据StaticResource或者DynamicResource的lookup behavior来确定哪个有效。通常为了维护和灵活性的考虑,我们通常会将Resource Dictionary文件分成好几个,但在某些场合下我们只需要用其中某些资源,那么我么可以将资源从几个独立的文件中提取并合并.

<Application x:Class="HelloWPF2.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:HelloWPF2"
             StartupUri="Window2.xaml">
  <Application.Resources>
    <ResourceDictionary Source="Dictionary1.xaml" />
  </Application.Resources>
</Application>

image.png


Style 样式

Style 中的Setter

Setter,设置器。什么的设置器呢?属性值的。我们给属性赋值的时候一般都采用“属性名=属性值”的形式。Setter类的Property属性用来指明你想为目标的那个属性赋值;Setter类的Value属性则是你提供的属性值。下面的例子中在Window的资源词典中放置一个针对TextBlock的Style,Style中使用若干Setter来设定TextBlock的一些属性,这样程序中的TextBlock就会具有统一的风格,除非你使用显示地清空Style。XAML代码如下:

<Style TargetType="TextBlock"> 
  <Setter Property="FontSize" Value="24"/>
  <Setter Property="TextDecorations" Value="Underline"/>
  <Setter Property="FontStyle" Value="Italic"/>
</Style>

Style中的Trigger

Trigger,触发器,即当某些条件满足时会触发一个行为(比如某些值的变化或动画的发生等)。触发器比较像事件。事件一般是由用户操作触发的,而触发器除了有事件触发型的EventTrigger外还有数据变化触发型的Trigger/DataTrigger及多条件触发型的MultiTrigger/MultiDataTrigger等。

基本Trigger

Trigger类是最基本的触发器。类似于Setter,Trigger也有Property和Value这两个属性,Property是Trigger关注的属性名称,Value是触发条件。Trigger类还有一个Setter属性,此属性值是一组Setter,一旦触发条件被满足,这组Setter的“属性—值”就会被应用,触发条件不在满足后,各属性值会被还原。

下面这个例子中包含一个针对CheckBox的Style,当CheckBox的IsChecked属性为true的

时候字体大小为20,false的时候字体大小为 1

<Window.Resources>
  <Style TargetType="CheckBox">
    <Style.Triggers>
      <Trigger Property="IsChecked" Value="True">
        <Trigger.Setters>
          <Setter Property="FontSize" Value="20" />
        </Trigger.Setters>
      </Trigger>
      <Trigger Property="IsChecked" Value="False">
        <Trigger.Setters>
          <Setter Property="FontSize" Value="1" />
        </Trigger.Setters>
      </Trigger>
    </Style.Triggers>
  </Style>
</Window.Resources>
<Grid>
  <CheckBox Content="哈哈啊哈哈,我是BUG,你找不到我,找到也解决不了我!!!" />
</Grid>

MultiTrigger

因为必须多个条件同时成立时才会被触发。MultiTrigger比Trigger多了一个Conditions属性,需要同时成立的条件就存储在这个集合中。

要求同时满足CheckBox被选中且Content为“哈哈”时才会被触发。

<Window.Resources>
  <Style TargetType="CheckBox">
    <Style.Triggers>
      <MultiTrigger>
        <MultiTrigger.Conditions>
          <Condition Property="IsChecked" Value="true" />
          <Condition Property="Content" Value="哈哈"/>
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
          <Setter Property="FontSize" Value="200" />
        </MultiTrigger.Setters>
      </MultiTrigger>
    </Style.Triggers>
  </Style>
</Window.Resources>
<Grid>
  <StackPanel>
    <CheckBox Content="哈哈" />
    <CheckBox Content="呵呵" />
  </StackPanel>
</Grid>

image.png

由数据触发的DataTrigger

程序中经常会遇到基于数据执行某些判断情况,遇到这种情况时我们可以考虑使用DataTrigger。DataTrigger对象的Binding属性会把数据源源不断送过来,一旦送来的值与Value属性一致,DataTrigger即被触发。

<Window.Resources>
  <local:L2BConverter  x:Key="cvtr" />
  <Style TargetType="TextBox" x:Key="is">
    <Style.Triggers>
      <DataTrigger
                   Binding="{Binding RelativeSource={x:Static RelativeSource.Self},
                            Path=Text.Length, Converter={StaticResource cvtr}}"
                   Value="false"
                   >
        <Setter Property="BorderBrush" Value="Red" />
      </DataTrigger>
    </Style.Triggers>
  </Style>
</Window.Resources>
<Grid>
  <StackPanel>
    <TextBox Style="{StaticResource is}"/>
    <TextBox />
    <TextBox Style="{StaticResource is}"/>
  </StackPanel>
</Grid>

为了将控件自己作为数据源,我们使用了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将控件自己作为数据的来源”,这是错误的,因为不明确指出Source时Binding会把控件的DataContext属性作为数据源而非把控件自身当作数据源。Binding的 Path被设置为Text.Length,即我们关注的是字符串的长度。长度是一个具体的数值,如何基于这个长度做判断呢?这就用到了Converter

class L2BConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    int length = (int)value;
    return length > 6;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw new NotImplementedException();
  }
}

image.png

多数据条件触发的W

有时我们会遇到要求多个数据条件同时满足时才能触发变化的需求,此时可以考虑使用 MultiDataTrigger。比如有这样一个需求:用户界面上使用ListBox显示了一列Student数据,当Student对象同时满足ID为2、Name为Tom的时候,条目的高亮显示。

<Window.Resources>
  <Style TargetType="ListBoxItem" >
    <!--重写模板-->
    <Setter Property="ContentTemplate" >
      <Setter.Value>
        <!--设置数据模板-->
        <DataTemplate>
          <!-- 对Stack Panel中的元素垂直排序 -->
          <StackPanel Orientation="Horizontal" >
            <!-- 绑定对应数据 -->
            <TextBlock Text="{Binding ID}" Width="60" />
            <TextBlock Text="{Binding Name}" Width="60" />
            <TextBlock Text="{Binding Age}" Width="60" />
          </StackPanel>
        </DataTemplate>
      </Setter.Value>
    </Setter>
    <Style.Triggers>
      <!-- 定义多条件 触发器MultiDataTrigger -->
      <MultiDataTrigger>
        <!--定义条件-->
        <MultiDataTrigger.Conditions>
          <Condition Binding="{Binding Path=ID}" Value="2" />
          <Condition Binding="{Binding Path=Name}" Value="Tom" />
        </MultiDataTrigger.Conditions>
        <!--当满足条件是触发-->
        <MultiDataTrigger.Setters >
          <Setter Property="Background" Value="#dcdcdc" />
        </MultiDataTrigger.Setters>
      </MultiDataTrigger>
    </Style.Triggers>
  </Style>
</Window.Resources>
<Grid>
  <ListBox x:Name="listBoxStudent" />

</Grid>

设置ListBox数据

public void getData()
{
  List<Student> students = new List<Student>
  {
    new Student() { ID = 1, Name = "Tim", Age="16" }, 
    new Student() { ID = 2, Name = "Tom", Age="18" },
    new Student() { ID = 3, Name = "Yue", Age="20" },
    new Student() { ID = 3, Name = "Kenny", Age="20" }, 
    new Student() { ID = 3, Name = "Yue", Age="22" },
  };
  listBoxStudent.ItemsSource = students;
}

class Student
{
  public string Name { get; set; }
  public string Age { get; set; }
  public int ID { get; set; }
}

image.png

由事件触发的EventTrigger

EventTrigger是触发器中最特殊的一个。首先,它不是由属性值或数据的变化来触发而由事件来触发;其次,被触发后它并非应用一组Setter,而是执行一段动画。因此,UI层的动画效果往往与 EventTrigger事件触发,另一个由MouseLeave事件触发。

<Window.Resources>
  <Style TargetType="Button">
    <Style.Triggers>
      <EventTrigger RoutedEvent="MouseEnter">
        <BeginStoryboard>
          <Storyboard>
            <DoubleAnimation To="150" Duration="0:0:0.5" Storyboard.TargetProperty="Width"/>
            <DoubleAnimation To="150" Duration="0:0:0.5" Storyboard.TargetProperty="Height" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
      <EventTrigger RoutedEvent="MouseLeave">
        <BeginStoryboard>
          <Storyboard>
            <DoubleAnimation To="50" Duration="0:0:0.5" Storyboard.TargetProperty="Width" />
            <DoubleAnimation To="50" Duration="0:0:0.5" Storyboard.TargetProperty="Height" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Style.Triggers>
  </Style>
</Window.Resources>
<Grid>
  <Button Width="50" Height="50" Content="HaHa按钮" Click="Button_Click"></Button>
</Grid>

image.png

# 学习  C#  WPF 
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×