WPF简介
WPF(Windows Presentation Foundation)是运行在.Net FrameWork 3.0框架下,基于DirectX,并融合了User GDI、GDI+并采用类Html排版布局的一种新的表现层框架技术。WPF利用愈加强大的显卡处理器使得软件具有更好的用户体验,成为下一代WinForm应用程序开发基础。是下一代显示系统,用于生成能带给用户震撼视觉体验的Windows 客户端应用程序。使用 WPF,您可以创建广泛的独立应用程序以及浏览器承载的应用程序,为兼容WinForm和WebForm提供了可能。
1.1构建WPF应用程序
//MyApp.cs
using System;
using System.Windows;//the root WPF NameSpace
namespace MyFirstWpfApp
{
class MyApp
{
[STAThead]
static void main()
{
MessageBox.Show("Hell,WPF");
}
}
}
STAThead属性是在当COM在应用程序的主线程上初始化时,保证它的初始化工作与单线程用户界面工作相兼容,是WPF应用程序所必需的。
此时的程序仍不能运行,原因是没有引用WPF的三个主要的程序集(WindowsBase、PresentationCore和PresentationFramk),位于(local):\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\下。在这里要指明的是,虽然代码中仍然沿用System.Windows命名空间,然而此时调用的程序集已经不是System.Windows.Forms.dll
1.2XAML(eXtensible Application Markup Language )
与2.0框架对比相同的是,3.0同样将界面代码与行为代码分离开来;不同的是在2.0框架下,一个窗体文件分为A.CS代码文件、A.Designer.CS 界面布局显示文件和A。resx的资源文件,而在3.0WPF应用程序下,则分为A.xaml界面显示文件和A.xaml.cs的行为代码文件。值得提醒的是,在WPF中找不到程序运行的起点Main函数,取而代之的是App.xaml的默认入口,在App.xaml中定义了程序启动的初始窗体方法StartupUri="Window1.xaml"。这样看来App.xaml有点像Program.cs
了解一下工程文件(.csproj)的结构
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WpfApplication1</RootNamespace>
<AssemblyName>WpfApplication1</AssemblyName>
<TargetFrameworkVersion>v3.0</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Window1.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Window1.xaml.cs">
<DependentUpon>Window1.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<AppDesigner Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
在工程文件中ApplicationDefinition以及.NET3.0中特有的Microsoft.CSharp.targets文件为程序生成一个入口点,该入口点自动创建Application类。DependentUpon元素用于将隐藏代码的文件和XAML文件关联起来。顺带提一下:我最近2.0下的一个工程项目出现了cs文件和代码文件脱离的情况,我就是在工程文件中修改此元素回复在VS2005中的关系显示的,但奇怪的是,为什么这两个文件在脱离了这个元素的情况下仍然正常使用,不解!待高人相助,指点一二。继续,最后在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727目录下的Microsoft.CSharp.targets文件里的指令可以将XAML文件编译到相应的partial关键字的类定义中。
XAML文件
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Button Name="button1" Width="200" Height="25" Click="button1_Click">Click Me</Button>
</Window>
这里就不用过多解释了,估计都看得懂,有点类似于asp.net
相关联代码文件
namespace WpfApplication1
{
/// <summary>
/// Window1.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
}
}
}
1.3内容模型和XAML元素语法
WPF的表示层的最基本元素不在是以往的Control,而是Model(个人认为存在的东西,以后讨论),在WPF中的元素可以有自己的行为和表示,最重要的是元素具有容器的性质,也就是一个控件可以包含另一个控件,这样以来原先的Button就不是只能包含字符串属性了。
一个简单的Button例子
<Window >
<Button Width="100" Heigth="100">Hi</Button>
</Window>
其中Hi字符串是Button显示的属性,因此也可以替换成<Button Width="100" Height="100" Content="Hi">
WPF是基于XML的,因此将字符串Hi当成Button的属性是没问题的,但将Button控件当成容器来包含其他控件的时候,这么些就不那么合适了,这时应该用到属性元素语法,如下
<Window >
<Button Width="100" Height="100">
<Button.Content>
<Image Source="Tom.png">
</Button.Content>
</Button>
</Window>
WPF定义了每个元素只能包含一个元素,因此在Button中想要直接加入图片和TextBlock就不那么可行了,这就有必要讨论一下可行性方案----布局
1.4布局和XAML附加属性语法
面板是一个负责拍了它所包含对象的控件,WPF提供的面板如下,以适应开发人员的布局需求。
画布(Canvas):通过对象的位置和大小来排列,当画布大小改变时不自动进行重排
停靠面板(DockPanel):根据容器内每个对象的停靠边来排列对象,最后一个对象将填充整个面板
网格(Grid):按照开发人员自定义的行和列来排序,是最为灵活的一种面板。
栈面板(StackPanel):按照从左到右或从上到下的顺序排列对象
均布网格(UniformGrid):根据显示需要按相等的行列数在网格中排列对象
环绕面板(WrapPanel):按照水平的行排列,当放不下对象的时候自动换行
试想一下,如果我们想在Button中添加多个元素,为什么不添加个具有多个元素的面板呢?
<Button Width="100" Height="100">
<Grid>
<!--定义2行-->
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="Tom.Png"/>
<TextBlock Grid.Row="1" HorizontalAlignment="Center">Tom</TextBlock>
</Grid>
</Button>
在定义Grid面板包含的Image和TextBlock元素中,用到了Grid.Row属性,这就称为“附加属性”。附加属性是用来在一个指定的元素中设置在另一个元素中定义的属性。
1.5数据绑定
书中用一个例子来定义什么是数据绑定。实现的功能是在2个TetBox控件输入Name和Nick属性,在ListBox控件中显示
书中定义了一个实现标准INotifyPropertyChanged接口的NickName类,包含Name和Nick属性,和实现NickName类型的集合类NickNames
Pulic class NickNames:ObservableCollection<NickName>{}
<!--Window.xaml-->
<Window>
<DockPanel Name="dockPanel">
<TextBlock DockPanel.Dock="Top">
<TextBlock VerticalAlignMent="Center">Name:</TextBlock>
<TextBox Text="{Binding Path=Name}" />
<TextBlock VerticalAlignMent="Center">Nick:</TextBlock>
<TextBox Text="{Binding Path=Nick}" />
</TextBlock>
<Button DockPanel.Dock="Bottom" Name="addButton">Add</Button>
<ListBox ItemSource="{Binding}" IsSynchronizeWithCurrentItem="True" />
</DockPanel>
</Window>
在两个TextBox控件分别指定数据绑定为数据源的Name和Nick属性,其中Text=“{Binding Path=Nick}"这个扩展标记也可以替换为
<TextBox.Text>
<Binding Path="Nick">
</TextBox.Text>
隐藏代码文件
//Window1.xaml.cs
namespace DataBindMemo
{
public partial class Window1:Window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.names=new Nicknames();
dockPanel.DataContent=names;
}
}
}
TextBox和ListBox是如何确定他们的Binding是同一个数据源呢,原因是在于ockPanel的DataContent的设定。在WPF中,控件如果没有指定其DataContent,那么该控件的数据源为其容器的数据源,TextBox和ListBox都没有设置DataContent所以其数据元就是它们共同所在的dockPanel设置的DataConent数据源.
在xaml中ListBox绑定的数据源并没有再进一步操作,这件导致其确实绑定了数据源,但显示的结果却不是其属性值,而是对象的ToString()结果,因此这里就必须在对ListBox设置数据模板
<ListBox
ItemsSource="{Binding}"
IsSynchronizeWithCurrentItem="True"
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBox Text="{Binding Path=Name}"/>
<TextBox Text="{Binding Path=Nick}"/>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
1.6资源
看一下新的数据绑定方式--资源
<!--Window1.xaml-->
<Window xmlns:local="clr-namespace:DataBindingDemo">
<Window.Resources>
<local:Nicknames x:key="names">
<local:Nickname Name="Don" Nick="Naked" />
<local:Nickname Name="Martin" Nick="Gudge" />
<local:Nickname Name="Tim" Nick="Stinky" />
</local:Nicknames>
</Window.Resources>
<DockPanel DataContent="{StaticResource names}">
<TextBlock VerticalAlignment="Center">Names:</TextBlock>
</DoucPanel>
</Window>
public paretial class Window1 :window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.addButton.Click+=addButton_Click;
//get names from resource
this.names=(Nicknames)this.FindResource("names");
//no need to make data available for binding here
//dockPanel.DataContext=this.names;
}
void addButton_Click(object sender , RoutedEventArgs e)
{
this.names.add(New Nickname());
}
}
在上面的例子中,使用xaml的命名空间映射语法将DataBindingDemo命名空间映射成local,这样local:Nicknames所指向就是DataBindingDemo工程下的类,其次使用属性元素语法创建了一个包含3个Nickname对象的Nicknames集合names,最后设定DockPanel对象的数据源为静态类型的资源names.在后台隐藏代码中则只需要查找已经存在的资源即可。顺带讨论一下,像names这样在工程内部定义的类等统称为内部资源,所谓的外部资源自然是可以文件图片等。
1.7风格与空间模板
风格就是一组属性或值的集合,应用到一个或多个元素中,使得这些元素有相同的属性。在我看来风格就类似于css,风格也相当于一种资源
<Window>
<Window.Resource>
<Style x:key="myStyle" TargetType="{x:Type TextBolck}">
<Sender Property="VerticalAlignment" value="Center" />
<Sender Property="Margin" value="2" />
<Sender Property="FontWeight" value="Bold" />
<Sender Property="FontStyle" value="Italic" />
</Style>
</Window.Resource>
<DockPanel>
<TextBlock>
<TextBlock Style="{StaticResource myStyle}">Name:</TextBlock>
<TextBox Text="{Binding Path=Name}" />
<TextBlock Style="{StaticResource myStyle}">Nick:</TextBlock>
<TextBox Text="{Binding Path=Nick}" />
</TextBlock>
</DockPanel>
</Window>
控件模板也可以用来改变控件的外观,也可以改变控件的外形,而Style则只能改变控件的属性。
<Button>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Width="128" Height="32" Fill="Yellow" Stroke="Black"/>
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
1.8图形
<Button>
<Button.LayoutTransform>
<ScaleTransform ScaleX="3" ScaleY="3" />
</Button.LayoutTransform>
<StackPanel Orientation="Horizontal">
<Canvas Width="20" Height="18" VerticalAlignment="Center">
<Ellipse Canvas.Left="1" Canvas.Top="1" Width="16" Height="16" Fill="Yellow" Stroke="Black" />
<Ellipse Canvas.Left="4.5" Canvas.Top="5" Width="2.5" Height="3" Fill="Black" />
<Ellipse Canvas.Left="11" Canvas.Top="5" Width="2.5" Height="3" Fill="Black" />
<Path Data="M 5,10 A 3,3 0 0 0 13,10" Stroke="Blue" />
</Canvas>
<TextBlock VerticalAlignment="Center">Click!</TextBlock>
</StackPanel>
</Button>
ScaleTransform用于将按钮扩大3被,接下来在Path指定的路径上画圆,组成图形