WPF学习之深入浅出话模板
图形用户界面应用程序较之控制台界面应用程序最大的好处就是界面友好、数据显示直观。CUI程序中数据只能以文本的形式线性显示,GUI程序则允许数据以文本、列表、图形等多种形式立体显示。
用户体验在GUI程序设计中起着举足轻重的作用-----用户界面设计成什么样看上去才足够的漂亮?控件如何安排才简单易用并且少犯错误?这些都是设计师需要考虑的问题。WPF系统不但支持传统的Winfrom编程的用户界面和用户体验设计,更支持使用专门的设计工具Blend进行专业设计,同时还推出了以模板为核心的新一代设计理念。
1.1 模板的内涵
从字面上看,模板就是“具有一定规格的样板”,有了它,就可以依照它制造很多一样是实例。我们常把看起来一样的东西称为“一个模子里面刻出来的。”就是这个道理。然而,WPF中的模板的内涵远比这个深刻。
Binding和基于Binding数据驱动UI是WPF的核心部分,WPF最精彩的部分是什么呢?依我看,既不是美轮美奂的3D图形,也不是炫目多彩的动画,而是默默无闻的模板(Template)。实际上,就连2D/3D绘图也常常是为它锦上添花。
Templdate究竟有什么能力能够使得它在WPF体系中获此殊荣呢?这还要从哲学谈起,“形而上者谓之道,形而下者谓之器”,这句话出自《易经》,大意是我们能够观察到的世间万物形象之上的抽象的结果就是思维,而形象之下掩盖的就是其本质。显然,古人已经注意到“形”是连接本质和思维的枢纽,让我们把这句话引入计算机世界。
- “形而上者谓之道”指的就是基于现实世界对万物进行抽象封装,理顺它们之间的关系,这个“道”不就是面向对象思想吗!如果再把面向对象进一步提升、总结出最优的对象组合关系,“道”就上升为设计模式思想。
- “形而下者谓之气”指的是我们能够观察到的世间万物都是物质类容的本质表现形式。“本质与表现”或者说“类容与形式”是哲学范畴内的一对矛盾体。
软件之道并非本书研究的主要类容,本书研究的是WPF。WPF全称Windows Presentation Foundation,Presentation一词的意思就是外观,呈现,表现,也就是说,在WIndows GUI程序这个尺度上,WPF扮演的就是“形”的角色、是程序的外在形式,而程序的内容仍然是由数据和算法构成的业务逻辑。与WPF类似,Winform和Asp.net也都是内容的表现形式。

让我们把尺度缩小到WPF内部。这个系统与程序内容(业务逻辑)的边界是Binding,Binding把数据源源不断从程序内部送出来交由界面元素来显示,又把从界面元素搜集到的数据传回程序内部。界面元素间的沟通则依靠路由事件来完成。有时候路由事件和附加事件也会参与到数据的传输中。让我们思考一个问题:WPF作为Windows的表示方式,它究竟表示的是什么?换句话说,WPF作为一种“形式”,它表现的内容到底是什么?答案是程序的数据和算法----Binding传递的是数据,事件参数携带的也是数据;方法和委托的调用是算法,事件传递消息也是算法----数据在内存里就是一串串字符或字符。算法是一组组看不见摸不着的抽象逻辑,如何恰如其分的把它们展现给用户呢?
加入想表达一个bool类型,同时还想表达用户可以在这两个值之间自由切换这样一个算法,你会怎么做?你一定会想使用一个CheckBox控件来满足要求;再比如颜色值实际上是一串数字,用户基本上不可能只看数字就能想象出真正的颜色,而且用户也不希望只靠输入字符来表示颜色值,这时,颜色值这一“数据内容”的恰当表现形式就是一个填充着真实颜色的色块。,而用户即可以输入值又可以用取色吸管取色来设置值的“算法内容”恰当的表达方式是创建一个ColorPicker控件。相信你已经发现,控件(Control)是数据内容表现形式的双重载体。换句话说,控件即是数据的表现形式让用户可以直观的看到数据,又是算法的表现形式让用户方便的操作逻辑。
作为表现形式,每个控件都是为了实现某种用户操作算法和直观显示某种数据而生,一个控件看上去是什么样子由它的“算法内容”和“数据内容决定”,这就是内容决定形式,这里,我们引入两个概念:
- 控件的算法内容:值控件能展示哪些数据、具有哪些方法、能相应哪些操作、能激发什么事件,简而言之就是控件的功能,它们是一组相关的算法逻辑。
- 控件的数据内容:控件具体展示的数据是什么。
- ControlTemplate:是算法和内容的表现形式,一个控件怎么组织其内部结构才能让它更符合业务逻辑、让用户操作起来更舒服就是由它来控制的。它决定了控件“长成什么样子”,并让程序员有机会在控件原有的内部逻辑基础上扩展自己的逻辑。
- DataTemplate:是数据内容的展示方式,一条数据显示成什么样子,是简单的文本还是直观的图形就由它来决定了。
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
<TextBlock Text="{Binding Year}"></TextBlock>
</Grid>
<TextBlock Text="{Binding Price}" Margin="5,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>我想,尽管你还没有学习什么DataTempldate,但借助前面学习的基础一样可以看个八九不离十了。
- ContentControl的ContentTempldate属性,相当于给ContentControl的内容穿衣服。
- ItemControl的ItemTemplate,相当于给ItemControl的数据条目穿衣服。
- GridViewColumn的CellTempldate属性,相当于给GridViewColumn的数据条目穿衣服。
public class Car
{
public string AutoMark { get; set; }
public string Name { get; set; }
public string Year { get; set; }
public string TopSpeed { get; set; }
}
为了在ListBox里面显示Car类型的数据,我们需要准备一个UserControl。命名为CarListItemView。
<UserControl x:Class="WpfApplication1.CarListViewItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Margin="2">
<StackPanel Orientation="Horizontal">
<Image x:Name="igLogo" Grid.RowSpan="3" Width="64" Height="64"></Image>
<StackPanel Margin="5,10">
<TextBlock x:Name="txtBlockName" FontSize="16" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="txtBlockYear" FontSize="14"></TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
/// <summary>
/// CarListViewItem.xaml 的交互逻辑
/// </summary>
public partial class CarListViewItem : UserControl
{
public CarListViewItem()
{
InitializeComponent();
}
private Car car;
public Car Car
{
get { return car; }
set
{
car = value;
this.txtBlockName.Text = car.Name;
this.txtBlockYear.Text = car.Year;
this.igLogo.Source = new BitmapImage(new Uri(@"Resource/Image/"+car.AutoMark+".png",UriKind.Relative));
}
}
}类似的原理,我们需要为Car类型准备一个详细信息视图。UserControl名称为CarDetailView,XAML部分代码如下:
<UserControl x:Class="WpfApplication1.CarDetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
<StackPanel>
<Image x:Name="imgPhoto" Width="400" Height="250"></Image>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="Name:" FontSize="20" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="txtBlockName" FontSize="20" Margin="5,0"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="AutoMark:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="txtBlockAutoMark" Margin="5,0"></TextBlock>
<TextBlock Text="Year:" FontWeight="Bold">
</TextBlock>
<TextBlock x:Name="txtBlockYear" Margin="5,0">
</TextBlock>
<TextBlock Text="Top Speed:" FontWeight="Bold">
</TextBlock>
<TextBlock x:Name="txtTopSpeed" Margin="5,0">
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</UserControl>
后台支持数据大同小异:
/// <summary>
/// CarDetailView.xaml 的交互逻辑
/// </summary>
public partial class CarDetailView : UserControl
{
public CarDetailView()
{
InitializeComponent();
}
private Car car;
public Car Car
{
get { return car; }
set
{
car = value;
this.txtBlockName.Text = car.Name;
this.txtBlockAutoMark.Text = car.AutoMark;
this.txtBlockYear.Text = car.Year;
this.txtTopSpeed.Text = car.TopSpeed;
this.imgPhoto.Source = new BitmapImage(new Uri(@"Resource/Image/" + car.Name + ".jpg", UriKind.Relative));
}
}
}最后把它们组装到窗体上:
<Window x:Class="WpfApplication1.Window35"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window35" Height="350" Width="623" xmlns:my="clr-namespace:WpfApplication1">
<StackPanel Orientation="Horizontal" Margin="5">
<my:CarDetailView x:Name="carDetailView1" />
<ListBox x:Name="listBoxCars" Width="180" Margin="5,0" SelectionChanged="listBoxCars_SelectionChanged">
</ListBox>
</StackPanel>
</Window>
窗体的后台代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Window35.xaml 的交互逻辑
/// </summary>
public partial class Window35 : Window
{
public Window35()
{
InitializeComponent();
InitialCarList();
}
private void listBoxCars_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CarListViewItem viewItem = e.AddedItems[0] as CarListViewItem;
if(viewItem!=null)
{
carDetailView1.Car = viewItem.Car;
}
}
private void InitialCarList()
{
List<Car> infos = new List<Car>() {
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="200", Year="1990"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="250", Year="1998"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="300", Year="2002"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="350", Year="2011"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="500", Year="2020"}
};
foreach (Car item in infos)
{
CarListViewItem viewItem = new CarListViewItem();
viewItem.Car = item;
this.listBoxCars.Items.Add(viewItem);
}
}
}
public class Car
{
public string AutoMark { get; set; }
public string Name { get; set; }
public string Year { get; set; }
public string TopSpeed { get; set; }
}
}
运行并单击Item项,运行效果如下图:
- 把Converter以资源字典的形式放进资源字典里(本例使用的方法)。
- 为Converter准备一个静态属性,形成单件模式,在XAML代码里面使用{x:Static}标记扩展来访问。
//厂商名称转换为Logo路径
public class AutoMarkToLogoPathConverter:IValueConverter
{
/// <summary>
/// 正向转
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri(string.Format(@"Resource/Image/{0}.png",(string)value),UriKind.Relative));
}
/// <summary>
/// 逆向转未用到
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
//汽车名称转换为照片路径
public class NameToPhotoPathConverter:IValueConverter
{
/// <summary>
/// 正向转
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri(string.Format(@"Resource/Image/{0}.jpg", (string)value), UriKind.Relative));
}
/// <summary>
/// 逆向转未用到
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
有了这两个Converter之后我们就可以设计DataTemplate了,完整的XAML代码如下:
<Window x:Class="WpfApplication1.Window36"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1.Model"
Title="Window36" Height="350" Width="623">
<Window.Resources>
<!--Converter-->
<local:AutoMarkToLogoPathConverter x:Key="amp"/>
<local:NameToPhotoPathConverter x:Key="npp"/>
<!--DataTemplate For DatialView-->
<DataTemplate x:Key="DatialViewTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
<StackPanel>
<Image x:Name="imgPhoto" Width="400" Height="250" Source="{Binding AutoMark,Converter={StaticResource npp}}"></Image>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="Name:" FontSize="20" FontWeight="Bold"></TextBlock>
<TextBlock FontSize="20" Margin="5,0" Text="{Binding Name}"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="AutoMark:" FontWeight="Bold"></TextBlock>
<TextBlock Margin="5,0" Text="{Binding AutoMark}"></TextBlock>
<TextBlock Text="Year:" FontWeight="Bold">
</TextBlock>
<TextBlock Text="{Binding Year}" Margin="5,0">
</TextBlock>
<TextBlock Text="Top Speed:" FontWeight="Bold">
</TextBlock>
<TextBlock Text="{Binding TopSpeed}" Margin="5,0">
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
<!--Data Template For ItemView-->
<DataTemplate x:Key="ItemView">
<Grid Margin="2">
<StackPanel Orientation="Horizontal">
<Image x:Name="igLogo" Grid.RowSpan="3" Width="64" Height="64" Source="{Binding Name,Converter={StaticResource amp}}"></Image>
<StackPanel Margin="5,10">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding Year}" FontSize="14"></TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<!--窗体内容-->
<StackPanel Orientation="Horizontal">
<UserControl ContentTemplate="{StaticResource DatialViewTemplate}" Content="{Binding Path=SelectedItem,ElementName=lbInfos}"></UserControl>
<ListBox x:Name="lbInfos" ItemTemplate="{StaticResource ItemView}"></ListBox>
</StackPanel>
</Window>
代码对于初学者来说有点长但是结构非常简单。其中最重要的有两句:
/// <summary>
/// Window36.xaml 的交互逻辑
/// </summary>
public partial class Window36 : Window
{
public Window36()
{
InitializeComponent();
InitialCarList();
}
private void InitialCarList()
{
List<Car> infos = new List<Car>() {
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="200", Year="1990"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="250", Year="1998"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="300", Year="2002"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="350", Year="2011"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="500", Year="2020"}
};
this.lbInfos.ItemsSource = infos;
}
}运行程序,效果如下图:
每每提到ControlTemplate我都会想到“披着羊皮的狼”这句话-----披上羊皮之后,虽然看上去像只羊,但其行为仍然是匹狼。狼的行为指的是它能吃别的动物、对着满月嚎叫等事情,控件也有自己的行为,比如显示数据、执行方法、激发事件等。控件的行为要靠编程逻辑来实现,所以也可以把控件的行为称为控件的算法内容。举个例子,WPF中的CheckBox与其基类ToggleButton的功能几乎完全一样,但外观差别上却非常的大,这就是更换ControlTemplate的结果。经过更换ControlTemplate,我们不但可以制作披着CheckBox外衣的ToggleButton,还能制作披着温度计外衣的ProgressBar控件。
- 通过更换ControlTemplate来更换控件的外观,使之具有更优的用户体验和外观。
- 借助ControlTemplate,程序员和设计师可以并行工作,程序员可以使用WPF标准控件进行编程,等设计师的工作完成之后,只需要把新的ControlTemplate应用的程序中即可。
1.3.1 庖丁解牛看控件
<Style x:Key="RoundCornerTextBoxStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" CornerRadius="5">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>这段代码有以下几个看点:
//
// button1
//
this.button1.Location = new System.Drawing.Point(1100, 199);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "报表";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// printPreviewDialog1
//
this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0);
this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0);
this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300);
this.printPreviewDialog1.Enabled = true;
this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject("printPreviewDialog1.Icon")));
this.printPreviewDialog1.Name = "printPreviewDialog1";
this.printPreviewDialog1.Visible = false;同样的逻辑如果在XAML代码里出就变成了这样:
<Style x:Key="a">
<Setter Property="pName1" Value="value"></Setter>
<Setter Property="pName2" Value="value"></Setter>
<Setter Property="pName3">
<Setter.Value>
<!--ObjectValue-->
</Setter.Value>
</Setter>
<Setter Property="pName4">
<Setter.Value>
<!--ObjectValue-->
</Setter.Value>
</Setter>
</Style>
使用Style是,如过Value值比较简单,那么就直接用Attribute值表示,如果Value值不能用一个简单的字符串来表示那么就需要用XAML的属性对象语法。例子中,TextBox的Template是一个ControlTemplate对象,如此复杂的值只能使用属性对象语法来描述。对于Style,后面会有专门的章节来描述。
<Window
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" mc:Ignorable="d"
x:Class="WPFApplication.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="385" Height="275">
<Window.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF4D91C6" Offset="0"/>
<GradientStop Color="#FFD9DBF1" Offset="1"/>
</LinearGradientBrush>
</Window.Background>
<Grid x:Name="LayoutRoot">
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Margin="57,49,0,0" Width="255.487" Style="{DynamicResource RoundCornerTextBoxStyle}"/>
<TextBox TextWrapping="Wrap" Text="TextBox" Margin="57,106,56.513,109.163" d:LayoutOverrides="Height" Style="{DynamicResource RoundCornerTextBoxStyle}"/>
<Button Content="Button" VerticalAlignment="Bottom" Margin="149,0,145,42.163"/>
</Grid>
</Window>程序运行效果如下图:
<Grid x:Name="LayoutRoot" Margin="5">
<ListBox>
<TextBlock>Darren</TextBlock>
<TextBlock>Andy</TextBlock>
<TextBlock>Jacky</TextBlock>
<TextBlock>T-Soft</TextBlock>
</ListBox>
</Grid>
如果我们把代码改成这样:
<Grid x:Name="LayoutRoot" Margin="5">
<ListBox>
<!--ItemsPanel-->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--条目-->
<TextBlock>Darren</TextBlock>
<TextBlock>Andy</TextBlock>
<TextBlock>Jacky</TextBlock>
<TextBlock>T-Soft</TextBlock>
</ListBox>
</Grid>
条目就会包装在一个水平排列的StackPanel中,从而横向排列,如下图所示:
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
<TextBlock Text="{Binding Year}"></TextBlock>
</Grid>
<TextBlock Text="{Binding Price}" Margin="5,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
<Style BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" CornerRadius="5">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Style没有X:key标记,默认为引用到所有的x:type指定的控件上,如果不想应用则将style标记为{x:null}。运行效果如下图:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFApplication"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
x:Class="WPFApplication.Window3"
x:Name="Window"
Title="Window3"
Width="288" Height="181">
<Window.Resources>
<!--DataTemplate-->
<DataTemplate DataType="{x:Type local:Unit}">
<Grid>
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
<TextBlock Text="{Binding Year}"></TextBlock>
</Grid>
<TextBlock Text="{Binding Year}" Margin="5,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
<!--数据源-->
<c:ArrayList x:Key="ds">
<local:Unit Year="2001年" Price="100"></local:Unit>
<local:Unit Year="2002年" Price="120"></local:Unit>
<local:Unit Year="2003年" Price="140"></local:Unit>
<local:Unit Year="2004年" Price="160"></local:Unit>
<local:Unit Year="2005年" Price="180"></local:Unit>
<local:Unit Year="2006年" Price="200"></local:Unit>
</c:ArrayList>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{StaticResource ds}"></ListBox>
<ComboBox ItemsSource="{StaticResource ds}" Margin="5"></ComboBox>
</StackPanel>
</Window>
代码中的DataTemplate的目标数据类型和ListBox的条目类型都是Unit:
public class Unit
{
public string Year{get;set;}
public int Price{get;set;}
}
此时DataTemplate会自动加载到所有的Unit类型对象上,尽管我没有为ListBox和CompBox指定ItemTemplate,一样会得到下图的效果:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WPFApplication.Window4"
x:Name="Window"
Title="Window4"
Width="314" Height="210">
<Window.Resources>
<!--DataTemplate-->
<DataTemplate DataType="Unit">
<Grid>
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding XPath=@Price}"></Rectangle>
<TextBlock Text="{Binding XPath=@Year}"></TextBlock>
</Grid>
<TextBlock Text="{Binding XPath=@Year}" Margin="5,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
<!--数据源-->
<XmlDataProvider x:Key="ds" XPath="Units/Unit">
<x:XData>
<Units xmlns="">
<Unit Price="100" Year="2001"></Unit>
<Unit Price="120" Year="2002"></Unit>
<Unit Price="140" Year="2003"></Unit>
<Unit Price="160" Year="2004"></Unit>
<Unit Price="180" Year="2005"></Unit>
<Unit Price="200" Year="2006"></Unit>
</Units>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource ds}}"></ListBox>
<ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="5"></ComboBox>
</StackPanel>
</Window>
XML的优势就是可以方便的表示带有层级的数据,比如:年级----班级----小组 或 主菜单---次菜单----三级菜单。同时WPF准备了TreeView和MenuItem控件来显示层级数据。能够帮助层级控件显示层级数据的模板是HierachicalDataTemplate。下面两个实际工作中常见的例子:
<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
<Grade Name="一年级">
<Class Name="甲班">
<Group Name="A组">
</Group>
<Group Name="B组">
</Group>
<Group Name="C组">
</Group>
</Class>
<Class Name="乙班">
<Group Name="A组">
</Group>
<Group Name="B组">
</Group>
<Group Name="C组">
</Group>
</Class>
</Grade>
<Grade Name="二年级">
<Class Name="甲班">
<Group Name="A组">
</Group>
<Group Name="B组">
</Group>
<Group Name="C组">
</Group>
</Class>
<Class Name="乙班">
<Group Name="A组">
</Group>
<Group Name="B组">
</Group>
<Group Name="C组">
</Group>
</Class>
</Grade>
</Data>
程序XAML代码如下:
<Window x:Class="WPFApplication.Window6"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window6" Height="268" Width="362">
<Window.Resources>
<!--数据源-->
<XmlDataProvider x:Key="ds" Source="XMLStudent.xml" XPath="Data/Grade"></XmlDataProvider>
<!--年级模板-->
<HierarchicalDataTemplate DataType="Grade" ItemsSource="{Binding XPath=Class}">
<TextBlock Text="{Binding XPath=@Name}"></TextBlock>
</HierarchicalDataTemplate>
<!--班级模板-->
<HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath=Group}">
<RadioButton Content="{Binding XPath=@Name}" GroupName="gn"></RadioButton>
</HierarchicalDataTemplate>
<!--小组模板-->
<HierarchicalDataTemplate DataType="Group" ItemsSource="{Binding XPath=Student}">
<CheckBox Content="{Binding XPath=@Name}"></CheckBox>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Margin="5" ItemsSource="{Binding Source={StaticResource ds}}">
</TreeView>
</Grid>
</Window>
程序运行效果如下图:
<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
<Operation Name="文件" Gesture="F">
<Operation Name="新建" Gesture="N">
<Operation Name="项目" Gesture="Ctr+P"/>
<Operation Name="网站" Gesture="Ctr+W"/>
<Operation Name="文档" Gesture="Ctr+D"/>
</Operation>
<Operation Name="保存" Gesture="S"/>
<Operation Name="打印" Gesture="P"/>
<Operation Name="退出" Gesture="X"/>
</Operation>
<Operation Name="编辑" Gesture="E">
<Operation Name="剪切" Gesture="Ctr+X"/>
<Operation Name="复制" Gesture="Ctr+C"/>
<Operation Name="粘贴" Gesture="Ctr+V"/>
</Operation>
</Data>程序XAML代码如下:
<Window x:Class="WPFApplication.Window7"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window7" Height="300" Width="300">
<Window.Resources>
<!--数据源-->
<XmlDataProvider x:Key="ds" Source="MenuXML.xml" XPath="Data/Operation"></XmlDataProvider>
<!--Operation模板-->
<HierarchicalDataTemplate DataType="Operation" ItemsSource="{Binding XPath=Operation}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding XPath=@Name}" Margin="10,0"></TextBlock>
<TextBlock Text="{Binding XPath=@Gesture}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<Menu ItemsSource="{Binding Source={StaticResource ds}}"></Menu>
</StackPanel>
</Window>
运行效果如下图:
<StackPanel MenuItem.Click="StackPanel_Click">
<Menu ItemsSource="{Binding Source={StaticResource ds}}"></Menu>
</StackPanel>
事件处理代码如下:
private void StackPanel_Click(object sender, RoutedEventArgs e)
{
MenuItem item = e.OriginalSource as MenuItem;
XmlElement xe = item.Header as XmlElement;
MessageBox.Show(xe.Attributes["Name"].Value);
}
我们先来寻找由ControlTemplate生成的控件。首先设计一个ControlTemplate并把它应用在一个UserControl控件上。界面上还有一个Button,在它的Click事件处理器中我们检索ControlTemplate生成的代码。
<Window x:Class="WPFApplication.Window8"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window8" Height="300" Width="300">
<Window.Resources>
<ControlTemplate x:Key="xtTemp">
<StackPanel Background="Orange">
<TextBox Margin="6" x:Name="textbox1"></TextBox>
<TextBox Margin="6,0" x:Name="textbox2"></TextBox>
<TextBox Margin="6" x:Name="textbox3"></TextBox>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel Background="Yellow">
<UserControl x:Name="uc" Template="{StaticResource xtTemp}" Margin="5"></UserControl>
<Button Content="Find By Name" Width="120" Height="30" Click="Button_Click"></Button>
</StackPanel>
</Window>
Button的事件处理器代码如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
TextBox tb = this.uc.Template.FindName("textbox1", this.uc) as TextBox;
tb.Text = "Hello WPF";
StackPanel sp = tb.Parent as StackPanel;
(sp.Children[1] as TextBox).Text = "Hello ControlTemplate";
(sp.Children[2] as TextBox).Text = "I Can Find YOU.";
}
运行效果如下:
public class Student38
{
public int Id { get; set; }
public string Name { get; set; }
public string Skill { get; set; }
public bool HasJob { get; set; }
}
界面XAML代码如下:
<Window x:Class="WpfApplication1.Window38"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window38" Height="227" Width="269">
<Window.Resources>
<!--数据对象-->
<local:Student38 x:Key="stu" Id="1" Skill="WPF" Name="Timothy" HasJob="True"></local:Student38>
<!--DataTemplate-->
<DataTemplate x:Key="dtStu">
<Border BorderBrush="Orange" BorderThickness="2" CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding Id}" Margin="5"></TextBlock>
<TextBlock x:Name="txtBlockName" Text="{Binding Name}" Margin="5"></TextBlock>
<TextBlock Text="{Binding Skill}" Margin="5"></TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<!--主窗体布局-->
<StackPanel>
<ContentPresenter x:Name="cp" Content="{StaticResource stu}" ContentTemplate="{StaticResource dtStu}" Margin="5">
</ContentPresenter>
<Button Content="Find" Margin="5,0" Click="Button_Click">
</Button>
</StackPanel>
</Window>
Button的事件处理器代码如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
TextBlock tb = this.cp.ContentTemplate.FindName("txtBlockName", this.cp) as TextBlock;
MessageBox.Show(tb.Text);
//Student38 stu = this.cp.Content as Student38;
//MessageBox.Show(stu.Name);
}
未被注释的代码是使用DataTemplate的FindName方法获取由DataTemplate生成的控件并访问其属性,被注释的代码是直接使用其底层数据。显然,为了获取Student的某个属性,应该使用被注释的代码而不必要绕到控件上来,除非你想得到的是控件的长度,高度。与业务逻辑无关的纯UI属性。
public class Student39
{
public int Id { get; set; }
public string Name { get; set; }
public string Skill { get; set; }
public bool HasJob { get; set; }
}
准备数据集合,呈现数据的工作全部由XAML代码来完成:
<Window x:Class="WpfApplication1.Window39"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window39" Height="338" Width="446">
<Window.Resources>
<!--数据集合-->
<c:ArrayList x:Key="stuList">
<local:Student39 Id="1" Name="Timoty Liu" Skill="WPF" HasJob="True"></local:Student39>
<local:Student39 Id="2" Name="Tom Chang" Skill="BI/SQL" HasJob="True"></local:Student39>
<local:Student39 Id="3" Name="Guan Chong" Skill="Writing" HasJob="False"></local:Student39>
<local:Student39 Id="4" Name="Shanshan" Skill="C#/Java" HasJob="False"></local:Student39>
<local:Student39 Id="5" Name="Pingping Zhang" Skill="Writing" HasJob="False"></local:Student39>
<local:Student39 Id="6" Name="kenny Tian" Skill="Asp.net" HasJob="False"></local:Student39>
</c:ArrayList>
<!--DataTemplate-->
<DataTemplate x:Key="nameDT">
<TextBox x:Name="txtBoxName" Text="{Binding Name}"></TextBox>
</DataTemplate>
<DataTemplate x:Key="skillDT">
<TextBox x:Name="txtSkill" Text="{Binding Skill}"></TextBox>
</DataTemplate>
<DataTemplate x:Key="hasJobDT">
<CheckBox IsChecked="{Binding HasJob}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid Margin="5">
<ListView x:Name="lvStudent" ItemsSource="{StaticResource stuList}">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}"></GridViewColumn>
<GridViewColumn Header="姓名" CellTemplate="{StaticResource nameDT}"></GridViewColumn>
<GridViewColumn Header="技术" CellTemplate="{StaticResource skillDT}"></GridViewColumn>
<GridViewColumn Header="已工作" CellTemplate="{StaticResource hasJobDT}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
程序运行效果如下图:
<DataTemplate x:Key="nameDT">
<TextBox x:Name="txtBoxName" Text="{Binding Name}" GotFocus="txtBoxName_GotFocus"></TextBox>
</DataTemplate>因为我们是在DataTemplate里面添加了事件处理器,所以界面上任何一个由此DataTemplate生成的TextBox都会在获得焦点的时候调用txtBoxName_GotFocus这个事件处理器。txtBoxName_GotFocus的代码如下:
private void txtBoxName_GotFocus(object sender, RoutedEventArgs e)
{
TextBox tb = e.OriginalSource as TextBox; //获取事件发起的源头
ContentPresenter cp = tb.TemplatedParent as ContentPresenter;//获取模板目标
Student39 stu = cp.Content as Student39;//获取业务逻辑数据
this.lvStudent.SelectedItem = stu;//设置ListView选中项
//访问界面元素
ListViewItem lvi = this.lvStudent.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem;
CheckBox cb = this.FindVisualChild<CheckBox>(lvi);
MessageBox.Show(cb.Name);
}
private ChildType FindVisualChild<ChildType>(DependencyObject obj) where ChildType : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj,i);
if (child != null && child is ChildType)
{
return child as ChildType;
}
else
{
ChildType childOfChild = FindVisualChild<ChildType>(child);
if(childOfChild!=null)
{
return childOfChild;
}
}
}
return null;
}
}
当使用GridView作为ListView的View属性时,如果某一列使用TextBox作为CellTemplate,那么即使这列中的TextBox被鼠标单击并获得了焦点ListView也不会把此项做为自己的SelectedItem。所以,txtBoxName_GotFocus的前半部分是获得数据的源头(TextBox),然后沿UI元素树上朔到DataTemplate目标控件(ContentPresenter)并获取它的内容,它的内容一定是一个Student实例。
运行程序,并单击某个显示姓名的TextBox,效果如下图所示:
<Window x:Class="WpfApplication1.Window40"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window40" Height="310" Width="426">
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="24"></Setter>
<Setter Property="TextDecorations" Value="Underline"></Setter>
<Setter Property="FontStyle" Value="Italic"></Setter>
</Style>
</Window.Resources>
<StackPanel Margin="5">
<TextBlock Text="Hello WPF!"></TextBlock>
<TextBlock Text="This is a sample for style!"></TextBlock>
<TextBlock Text="by Time 2012-11-12!" Style="{x:Null}"></TextBlock>
</StackPanel>
</Window>
因为Style的内容属性是Setters,所以我们可以直接在<Style>标签的内容区域使用Setter。
<Window x:Class="WpfApplication1.Window41"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window41" Height="258" Width="352">
<Window.Resources>
<Style TargetType="CheckBox">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Trigger.Setters>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Foreground" Value="Orange"></Setter>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Window.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF4589D8" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
</Window.Background>
<StackPanel>
<CheckBox Content="锄禾日当午" Margin="5"></CheckBox>
<CheckBox Content="汗滴禾下土" Margin="5,0"></CheckBox>
<CheckBox Content="谁知盘中餐" Margin="5"></CheckBox>
<CheckBox Content="粒粒皆辛苦" Margin="5,0"></CheckBox>
</StackPanel>
</Window>
因为Triggers不是Style的内容属性,所以<Style.Trigger>...</Style.Trigger>这层标签不能省略,但Trigger的Setters属性是Trigger的内容属性,所以<Trigger.Setters>...</Trigger.Setters>这层标签是可以省略的。
<Style TargetType="CheckBox">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsChecked" Value="True"></Condition>
<Condition Property="Content" Value="粒粒皆辛苦"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Foreground" Value="Orange"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>运行效果如下图:
<Window x:Class="WpfApplication1.Window42"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window42" Height="184" Width="324">
<Window.Resources>
<local:L2BConverter x:Key="cbtr"></local:L2BConverter>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Text.Length,Converter={StaticResource cbtr}}" Value="false">
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Margin="5"></TextBox>
<TextBox Margin="5,0"></TextBox>
<TextBox Margin="5"></TextBox>
</StackPanel>
</Window>
这个例子中唯一需要解释的就是DataTrigger的Binding。为了将控件自身做为数据源,我们使用了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将自己作为数据的来源”,这是错误的,因为不明确指出Source的值Binding就会把控件的DataContext做为自己的数据来源。Binding的Path设置为Text.Length,即我们关注的是字符串的长度。长度是一个具体的数字,如何基于这个长度值来做判断呢?这就用到了Converter。我们创建如下Converter:
public class L2BConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int textLength = (int)value;
return textLength > 6 ? true : false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
经过Converter转换以后,长度值就会变为bool类型值。DataTrigger的value设置为false,也就是说当TextBox的文本长度小于7时DataTrigger会使用自己一组Setter把TextBox的边框设置为红色。运行效果如下图:
<Window x:Class="WpfApplication1.Window43"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window43" Height="262" Width="425">
<Window.Resources>
<Style TargetType="ListBoxItem">
<!--使用Style设置Datatemplate-->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Width="60"></TextBlock>
<TextBlock Text="{Binding Name}" Width="120"></TextBlock>
<TextBlock Text="{Binding Skill}" Width="60"></TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<!--MultiDataTrigger-->
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Id}" Value="2"></Condition>
<Condition Binding="{Binding Path=Name}" Value="Darren"></Condition>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="Orange"></Setter>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<ListBox x:Name="lbInfos" Margin="5"></ListBox>
</StackPanel>
</Window>
后台代码如下:
public Window43()
{
InitializeComponent();
InitialInfo();
}
private void InitialInfo()
{
List<Student38> infos = new List<Student38>() {
new Student38(){ Id=2, Name="Darren", Skill="WPF"},
new Student38(){ Id=1, Name="Tom", Skill="Java"},
new Student38(){ Id=3, Name="Jacky", Skill="Asp.net"},
new Student38(){ Id=2, Name="Andy", Skill="C#"},
};
this.lbInfos.ItemsSource = infos;
}
<Window x:Class="WpfApplication1.Window44"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window44" Height="258" Width="377">
<Window.Resources>
<Style TargetType="Button">
<Style.Triggers>
<!--鼠标进入-->
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation>
<DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!--鼠标离开-->
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Width="40" Height="40" Content="OK"></Button>
</Grid>
</Window>
无需任何c#代码,我们就获得了如下图所示的结果:

浙公网安备 33010602011771号