23 程序集资源
WPF中的程序集资源基本概念是为项目添加文件,从而VS可将其嵌入到编译过的
应用程序的exe或dll文件中
添加资源
(注意:资源文件的属性中的生成操作(build action)要设置为Resource)
内容文件
资源文件有时是不方便的:
希望改变文件,但不想重新编译程序
资源文件非常大
资源文件时可选的
资源文件是声音文件
将未编译的文件专门标记为内容文件:
方法:将文件的生成操作属性换成内容,将复制到输出目录设置为总是复制
24 将元素绑定到一起
数据绑定的最简单情形是,源对象是WPF元素而且源属性是依赖项属性,当在源对象中
改变依赖项属性的值时,会立即更新目标对象中的绑定属性。
绑定表达式
当使用数据绑定是,不必使用源对象做任何改动,只需要配置源对象使其属性具有正确的值的范围。
绑定表达式例子:
FontSize="{Binding ElementName=slider1,Path=Value,Mode=TwoWay}"
数据绑定表达式使用XAML标记扩展(因此具有花括号),因为正在创建System.Windows.Data.Binding类的一个实例,所以绑定表达式以单词Binding开头。尽管
有很多种方法配置Binding对象,但在本例中只需要设置两个属性:ElementName属性和Path属性
绑定错误
绑定模式
数据绑定的一个特性是目标会被自动更新,而不考虑源的修改方式。
可采用一种方式强制在两个方向上传递数值:从源到目标以及从目标到源。
技巧是设置Binding对象的Mode属性。
当设置Bingding.Mode属性时,WPF允许使用5个System.Windows.Data.BindingMode枚举值中任何一个。
使用代码创建绑定
基于标记的绑定比通过代码创建的绑定更常见
创建绑定:
Binding binding=new Binding();
binding.Source=slider;
binding.Path=new PropertyPath("value");
binding.Mode =BindingMode.TwoWay;
lblSampleText.SetBinding(TextBlock.FontSize,binding);
删除绑定:
BindingOperations.ClearAllBindings(lblSampleText);
多绑定
Q:一个属性如何绑定到两个元素
A::利用Mode(TwoWay)
绑定更新
当使用OneWay或者TwoWay绑定时,改变或的值会立即从源传播到目标。
然而,反向的变化传递----从目标到源----未必会立即放生。他们的行为由Binding.UpdateSourceTrigger属性控制。
例子:
(代码:WpfApp8_1)
25 绑定到非元素对象
在数据驱动的应用程序中,更常见的情况是创建从不可见对象中提取数据的绑定表达式。
唯一的要求是希望显示的信息必须存储在公有属性中。WPF数据绑定基础结构不能获取私有信息或公有字段。
当绑定到非元素对象时,需要放弃Binding ElementName属性,并使用以下属性中的一个:Sourse; RelativeSource;DataContext
Sourse属性
Source属性的唯一问题似乎为了研究绑定,需要有数据对象。
可从资源中提取数据对象,可通过编写代码生成数据对象,也可在数据提供程序的帮助下获取数据对象。
最简单的选择时将Source只想一些已经准备好了 的静态对象:
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=Source}"><TextBlock>
另一种选择时绑定到先前作为资源创建的对象。例如,下面的标记创建只想Calibri字体的FontFamiy对象:
<Window.Resources>
<FontFamily x:Key="CustomFont">Calibri</FontFamily>
</Window.Resources>
绑定:
<TextBlock Text="{Binding Source={StaticResource CustomFont},Path=Source}"></TextBlock>
RelativeSource属性
通过RelativeSource属性可根据相对于目标对象的关系指向源对象。例如,可使用RelativeSource属性将元素绑定到自身或其父元素
为了设置Binding.RelativeSource属性,需要使用RelativeSource对象。
一种选择时使用属性设置语法而不是使用Binding标记扩展。
下面是创建一个Binding对象。
<TextBlock>
<TextBlock.Text>
<Binding Path="Title">
<Binding.RelativeSource>
<relativeSource Mode="FindAncestor" AncestorType="{x:Type Window}"/>
</Binding.RelativeSource>
</Binding >
</TextBlock.Text>
</TextBlock>
DataContext 属性
当大量元素绑定到同一个对象,可为每个元素使用Sources属性,但会使标记变得非常长。
对于这种情况使用FrameworkElement.DataContext属性一次性定义绑定源会更清晰。
如为包含所有TextBlock元素的StackPannel面板设置DataContext属性是合理的
可使用和设置Binding.Source属性相同的方法设置元素的DataContext属性。
<StackPanel DataContext="{x:Static SystemFonts.IconFontFamily}">
现可通过省略源信息来精简绑定表达式
<TextBlock Margin="5" Text="{Binding Path=Source}"></TextBlock>
当在绑定表达式中省略源信息时,WPF会检查元素的DataContext属性。如果属性为null,WPF会继续向上在元素树中查找第一个不为null的数据上下文。
26 资源基础
WPF资源
WPF资源系统是一种保管一系列有用对象的简单方法,从而使你可以更容易地重用这些对象。WPF允许在代码中以及在标记中的各个位置定义资源。
资源集合
每个元素都有Resources属性,该属性存储了一个资源字典集合(它是ResourcesDictionary 类的
实例)。资源集合可以包含任意类型的对象,并根据字符串编写索引。
尽管每个元素都提供了Resources属性,但通常在窗口级别定义资源,这是因为每个元素
都可以访问各自资源集合中的资源,也可以访问所有父元素的资源集合中的资源。
下面在窗口的资源中定义图像画刷:
<Window.Resources>
<ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3">
</ImageBrush>
</Window.Resources>
重要的是第一个特性,即Key特性。
为了使用XAML标记中的资源,需要一种引用资源的方法。这是通过标记扩展完成的。
下面是使用画刷资源的按钮:
<Button Background="{StaticResource TileBrush}"
Margin="5" Padding="5" FontWeight="Bold" FontSize="14">
A Tile Button
</Button>
可使用动态资源执行相同的操作:
<Button Background="{DynamicResource TileBrush}"
资源层次
为了找到期望的资源,WPF在元素树中进行递归搜索。
当使用静态资源是,必须总是在引用资源之前在标记中定义资源。
只要不在同一集合中多次使用相同的资源名,就可以重用资源名称。
静态资源和动态资源
区别在于静态资源只从资源集合中获取对象一次。根据对象类型(以及对象使用方式),
对象的任何变化都可能被立即注意到。然而,动态资源在每次需要第项时都会重新从资源集合中查找对象。这意味着可在同一键下放置一个全新对象,
而且动态资源会应用该变化。
实例:
this.Resource["TileBrush"]=new SoildColorBrush(Colors.LightBlue);
动态资源会应用该变化,而静态资源不知道它的画刷已在Resource集合中被其他内容替换,它仍继续使用原来的ImageBrush.
通常不需要使用动态资源,除非:
资源具有以来于系统设置的属性
准备通过编程方式替换资源对象
通过代码访问
可通过名称从资源集合中提取资源。为此,需要使用正确元素的资源集合。对于标记没有这一限制。
示例:
private void cmdChange_Click(object sender ,RoutedEventArgs e)
{
Button cmd =(Button)sender;
ImageBrush brush=(ImageBrush)sender.FindResource("TileRrush")
}
应用程序资源
窗口不是查找资源的最后一站。如果在控件或其容器中(直到包含窗口或者页面)找不到指定的资源,WPF会继续检查为应用程序定义的资源集合。在Visual Studio中,这些资源时在App.xmal文件的标记中定义的资源。
系统资源
动态资源主要用于辅助应用程序对系统环境设置的变化做出响应,为此需要三个类 SystemColors,SystemFonts,SystemParameters,这些类
都位于System.Windows名称空间中。
示例:
label.Foreground=new SoildBrush(SystemColors.WindowTextColor);
例子:
(代码WpfApp10_1)
27 资源字典
资源字典出现的初衷就在于可以实现多个项目之间的共享资源,资源字典只是一个简单
的XAML文档,该文档除了存储希望使用的资源之外,不做任何其它的事情。
创建资源字典
创建资源字典的过程比较简单,只是将需要使用的资源全都包含在一个xaml文件之中即
可。如下面的例子(文件名test.xaml,与后面的app.xaml文件中的内容相对应):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<LinearGradientBrush x:Key="FadeBrush">
<GradientStop Color="Red" Offset="0"/>
<GradientStop Color="Gray" Offset="1"/>
</LinearGradientBrush>
</ResourceDictionary>
说明:在创建资源的时候要确保资源文件的编译选项为page,这样就能够保证XAML
资源文件最终能够编译为baml文件。但是如果设置为Resource也是一个不错的选择,
这样它能够嵌入到程序集中,但是不被编译,当然其解析的速度回稍微慢一点。
使用资源字典
**集成资源:**
要是用资源字典,首先要将资源字典集成到应用程序的某些资源集合中。一般的做
法都是在app.xaml文件中进行集成。代码如下:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Test.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
说明:上面的标记通过明确地创建一个ResourceDictionary对象进行工作,资源集合总
是ResourceDictionary对象。但是这只是需要明确指定细节从而可以设定ResourceDictionary.MergedDictionaries属性的一种情况。
如果没有这个步骤ResourceDictionary.MergedDictionaries属性将为空。
ResourceDictionary.MergedDictionaries属性是一个ResourceDictionary对象的集合,可以使用这个集合提供自己需要使用的资源的集合。也就是说如果需要某个资源,只需要将与该资源相关的xaml文件
。添加到这个属性中即可。如上面添加test.xaml一样。
**使用资源:**
集成之后就可以在当前的工程中使用这些资源了。使用方法如下:
<Window x:Class="HelloWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="340" Width="406" WindowStartupLocation="CenterScreen"
Icon="SC.ico" >
<Grid Height="304" Width="374">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Margin="121,30,107,230" Grid.Row="2" Click="Button_Click" Background="{StaticResource FadeBrush}">
</Button>
</Grid>
</Window>
使用资源的方法比较简单只需要使用StaticResource 关键字去添加即可。
使用资源字典的主要原因有两个:
提供皮肤功能。
存储需要被本地话的内容(错误消息字符串等,实现软编码)
在程序集之间共享资源
28 样式基础
样式基础
样式(Style)是组织和重用格式化选项的重要工具。不是使用重复的标记填充XAML,以便设置外边距、内边距、颜色以及字体等细节,而是创建一系列封装所有这些细节的样式,然后再需要之处通过属性来应用样式。
样式是可应用于元素的属性值集合。使用资源的最常见原因之一就是保存样式。
使按钮具有统一格式的实现方式一:资源
<Window.Resources>
<FontFamily x:Key="ButtonFontFamily">Times New Roman</FontFamily>
<sys:Double x:Key="ButtonFontSize">18</sys:Double>
<FontWeight x:Key="ButtonFontWeight">Bold</FontWeight>
</Window.Resources>
<Grid Name="gird1" ShowGridLines="True">
<Button FontFamily="{StaticResource ButtonFontFamily}" FontSize="{StaticResource ButtonFontSize}" FontWeight="{StaticResource ButtonFontWeight}">
</Button>
</Grid>
这个示例可以正常工作,它将字体细节(所谓的magic number)移出的标记。但也存在两个问题。
除了资源的名称相似之外,没有明确指定三个资源是相关的。这使维护应用程序变得复杂。
需要使用资源的标记非常繁琐。还没有原来不使用资源时简明。
样式为解决这个问题提供了非常好的解决方案。可独立的用户封装所有希望设置的属性的样式。
<Window.Resources>
<Style x:Key="BigFontButtonStyle">
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<Grid Name="gird1" ShowGridLines="True">
<Button Style="{StaticResource BigFontButtonStyle}">
</Button>
</Grid>
通过样式系统不仅可以创建多组明显相关的属性设置,而且使得应用程序这些设置更加容易,从而精简了标记。
创建样式对象
设置属性
每个Style对象都封装了一个Setter对象的集合。每个Setter对象设置元素的单个属性。在某些情况下不能使用简单的特性字符设置属性值。可使用嵌套的元素代替如:
<Setter Property="Control.Background">
<Setter.Value>
<ImageBrush TileMode="Tile" ViewboxUnits="Absolute" Viewport="0 0 32 32" ImageSource="" Opacity="0.3"></ImageBrush>
</Setter.Value>
</Setter>
创建只应用按钮的样式:
<Style x:Key="BigFontButtonStyle" TargetType="Button">
<Setter Property="FontFamily" Value="Times New Roman"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
关联事件处理程序
<Window.Resources>
<Style x:Key="MouseOverHighlightStyle">
<EventSetter Event="TextBlock.MouseEnter" Handler="element_MouseEnter"></EventSetter>
<EventSetter Event="TextBlock.MouseLeave" Handler="element_MouseLeave"></EventSetter>
</Style>
</Window.Resources>
<Grid Name="gird1" ShowGridLines="True">
<TextBlock Style="{StaticResource MouseOverHighlightStyle}"></TextBlock>
</Grid>
多层样式
每个WPF元素一次只能使用一个样式对象,这像是一种限制,但由于属性值的继承和样式继承特性,这种限制实际是不存在的。
例如,希望为一组控件使用相同的字体,又不想为每个控件应用相同的样式。对于这种情况,可将他们放置到面板中,并设置容器的样式。
只要设置的属性具有属性值继承特性,这些值就会传递到子元素。使用这种模型的属性包括IsEnabled、IsVisible、Foreground以及所有字体属性。
另外一些情况,可能希望在另一个样式的基础上建立样式,可用BasedOn特性。
<Window.Resources>
<Style x:Key="BigFontButtonStyle">
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style>
<Style x:Key="NewBigButtonBigFontStyle" BasedOn="{StaticResource BigFontButtonStyle}">
<Setter Property="Control.Foreground" Value="Red"/>
<Setter Property="Control.Background" Value="DarkBlue"/>
</Style>
</Window.Resources>
<Grid Name="gird1" ShowGridLines="True">
<TextBlock Style="{StaticResource NewBigButtonBigFontStyle}">test</TextBlock>
</Grid>
通过类型自动应用样式
上面都是具有名称的样式,还有一种为特定类型元素自动应用的样式
下面的例子中第二个按钮显示替换了样式,将style设置为null,有效的删除了样式。
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<Grid Name="gird1" ShowGridLines="True">
<Button>one</Button>
<Button Style="{x:Null}">two</Button>
<Button>three</Button>
</Grid>
示例:
(代码:WpfApp11_1)
29 触发器
使用触发器可自动完成简单的样式的改变,不需要使用代码,也可以完成不少工作。
触发器通过Style.Trigger集合链接到样式。
每个样式可以有任意多个触发器。每个触发器都是System.Windows.TriggerBase的实例。
简单触发器
可为任何依赖项属性关联简单触发器。例如,可通过相应Control类的IsFocused、IsMouseOver以及IsPressed属性的变化,创建鼠标悬停效果和焦点效果。
每个触发器都制定了正在监视的属性以及正在等待的属性值。当属性值出现时,将应用Trigger.Setters集合里的设置器。
<Window.Resources>
<Style x:Key="BigFontButton">
<Style.Setters>
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="Control.IsFocused" Value="True">
<Setter Property="Control.Foreground" Value="DarkRed"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
触发器的优点是不需要为翻转他们而编写任何代码。
如果希望几个条件都满足时才激活触发器,可使用MultiTrigger
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="Control.IsFocused" Value="true"/>
<Condition Property="Control.IsMouseOver" Value="true"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Control.Foreground" Value="DarkRed"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
事件触发器
普通触发器等待属性发生变化,而事件触发器等待特定事件被引发。事件触发器要求用户提供一系列修改控件的动作。
这些动作常用于动画。如监听MouseEnter事件,然后动态改变按钮的FontSize属性从而形成动画效果,在0.2秒的时间内容字体放大到22个单位。
与属性触发器不同如果希望元素返回原始状态,需要反转事件触发器。如上面例子,需要编写MouseLeav事件的触发器。不需要指明字体的大小,默认会恢复成第一次动画之前的字体大小。
<Window.Resources>
<Style x:Key="BigFontButton">
<Style.Setters>
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style.Setters>
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:3" Storyboard.TargetProperty="FontSize"
To="50"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:3" Storyboard.TargetProperty="FontSize"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Name="gird1" ShowGridLines="True">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource BigFontButton}">onesfsfsfsfsdfsdf</Button>
</Grid>
示例:
(代码:WpfApp11_2)
30 行为
行为
行为旨在封装一些UI功能,从而可以不用编写代码就能够将其应用到元素上。
获取行为支持
添加对System.Windows.Interactivity.dll的引用
创建行为
然后创建一个继承自Behavior基类的类。在此类中覆盖OnAttached()和OnDetaching()方法,
在这两个方法中可以通过AssociatedObject属性访问放置行为的元素。在OnAttached()方法中关联事件处理程序,在OnDetaching()中移除事件处理程序。
以下是创建一个可以为任意元素提供使用鼠标在Canvas面板上拖动元素的行为类 DragInCanvasBehavior:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace CustomBehaviorsLibrary
{
public class DragInCanvasBehavior : Behavior<UIElement>
{
private Canvas canvas;
protected override void OnAttached()
{
base.OnAttached();
// Hook up event handlers.
this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
// Detach event handlers.
this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
}
// Keep track of when the element is being dragged.
private bool isDragging = false;
// When the element is clicked, record the exact position
// where the click is made.
private Point mouseOffset;
private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Find the canvas.
if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas;
// Dragging mode begins.
isDragging = true;
// Get the position of the click relative to the element
// (so the top-left corner of the element is (0,0).
mouseOffset = e.GetPosition(AssociatedObject);
// Capture the mouse. This way you'll keep receiveing
// the MouseMove event even if the user jerks the mouse
// off the element.
AssociatedObject.CaptureMouse();
}
private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
// Get the position of the element relative to the Canvas.
Point point = e.GetPosition(canvas);
// Move the element.
AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
}
}
private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
AssociatedObject.ReleaseMouseCapture();
isDragging = false;
}
}
}
}
DragInCanvasBehavior
使用行为
为测试行为,创建一个新的WPF应用程序项目。然后添加对定义DragInCanvasBehavior类的类库以及System.Windows.Interactivity.dll程序集得应用。接下来在XML中映射这两个标记名称。假定存储DragInCanvasBehavior类的类库名为CustomBehaviorsLibrary,所需的标记如下所示:
<Window x:Class="BehaviorTest.DragInCanvasTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:custom="clr-namespace:CustomBehaviorsLibrary;assembly=CustomBehaviorsLibrary"
Title="DragInCanvasTest" Height="300" Width="300">
为使用该行为,只需要使用Interaction.Behaviors附加属性在Canvas面板中添加任意元素。下面的标记创建一个具有三个图形的Canvas面板。
两个Ellipse元素使用了DragInCanvasBehavior,并能在Canvas面板中拖动。Rectangle元素没有使用DraginCanvasBehavior,因此无法移动。
<Canvas>
<Rectangle Canvas.Left="10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60"></Rectangle>
<Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
<Ellipse Canvas.Left="80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
</Canvas>