在继续总结后面的内容之前,我们先来看一下前面漏掉的一个知识点,那就是分部类,下面我们来看一下什么是分部类,以及为什么要用分部类。
一,分部类:
我们还是拿之前的例子来看,代码如下。
XAML代码:
<UserControl x:Class="SilverlightApplication1.MainPage"
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"
d:DesignHeight="300" d:DesignWidth="400">
<!--http://schemas.microsoft.com/winfx/2006/xaml/presentation:核心Silverlight命名空间
http://schemas.microsoft.com/winfx/2006/xaml:是XAML命名空间
-->
<Grid x:Name="LayoutRoot" Background="White">
<Button
x:Name="button"
Width="200"
Height="25"
Click="button_Click"
>
Click me,baby,one more time!
</Button>
</Grid>
</UserControl>
Code-behind代码:
public partial class MainPage : UserControl//分部类关键字partial
{
public MainPage()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
//todo:添加Click事件处理代码
MessageBox.Show("button被单击了!");
}
}
大家可能已经注意到MainPage类名前的关键字partial,没错,它在这边声明的就是分部类,从这个例子中我们能够了解到什么是分部类以及分部类的作用。
1,什么是分部类?
将一个类的定义拆分到两个或多个源文件中。在类声明前添加关键字partial.
2,分部类的作用?
一个类分布在多个独立源文件中可以让多位程序员同时对该类进行处理,在这里,实现了界面和行为的分离。
关键字partial使得编译器将XAML文件生成的类(在这里指XAML文件)与人为生成的类(在这里指Code-behind文件)结合起来形成一个完整的类。这两部分代码是成对出现并且相互依赖的。XAML文件中定义的部分MainPage类依赖于隐藏代码中的类来调用InitializeCompoent方法,并处理事件。隐藏代码中的类则依赖于XAML文件中定义的分部MainPage类来实现InitializeCompoent,从而得到了主窗体的"外观"(同时包含了相关的子控件).
二,子元素:
在讲解子元素之前,我们先来看一下完整的XAML代码:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Ellipse/>
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
</Grid>
</Window>
其中子元素的XAML代码为:
<Grid>
<Ellipse/>
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
</Grid>
不管什么时候提供嵌套内容,XAML编译器都需要父类型(在这里是Window)或者是通过ContentPropertyAttribute来注释的基类。在这个示例中,虽然Windows没有这个属性,但是它的ContentControl基类有这个属性,如下。
处理子内容:
[ContentProperty("content"),...]
public class ContentControl:Control
{
...
public Object Content{get{...} set{...}}
}
这个属性将会为XAML编译器提供包含子内容的属性名。因此,在这个示例中,编译器将会为一个将要创建并且指派到Content属性上的Grid对象进行排列,如下是相应的代码:
窗口内容:
MainWindow myWindow=new MainWindow(); //创建MainWindow类的对象
Grid g=new Grid(); //创建Grid类对象
myWindow.Content=g;
Content属性是属性Object类型,这就意味着Window元素只支持单个子元素。诸如面板之类的可以包含多个子元素的元素只需指定一个带有集合类型的元素作为内容属性,如:
处理多个子元素:
[ContentProperty("Children"),...]
public class Panel:FrameworkElement
{
...
public UIElementCollection Children{get{...}}
}
由于Panel是Grid的一个基类,因此这就告诉了我们XAML编译器是如何在这个示例中将子元素添加到Grid的,说明代码如下。
为Grid添加内容:
Grid g=new Grid(); //创建Grid类对象
myWindow.Content=g;
Ellipse e=new Ellipse();//创建Ellipse类对象
TextBlock t=new TextBlock();//创建TextBlock 类对象
...
g.Children.Add(e);
g.Children.Add(t);
TextBlock有着更深层的嵌套内容(一些文本和第二个TextBlock)。这个特定的示例变得有点复杂了,因为在这个情况下,XAML编译器将会为子元素自动生成封装对象。如果在文本元素里加入普通的文本,比如TextBlock或Paragraph,它就会自动被封装进一个Run对象中。同样地,子元素FrameworkElement将会被自动地封装进InlineUIContainer元素里。下面的代码对这些进行了解释。
隐式的Run和InlineUIContainer:
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
显式的Run和InlineUIContainer:
<TextBlock>
<Run Text="Name"/>
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</TextBlock>
以上代码之所以能够运行是因为一对自定义属性。TextBlock通过ContentPropertyAttribute进行了注释,这表示其子元素内容应该加入到其内嵌的属性中。而这个属性属于InlineCollection类型,并且通过一对ContentWrapperAttribute自定义属性进行了注释。
ContentWrapperAttribute自定义属性:
[ContentWrapper(typeof(Run)),ContentWrapper(typeof(InlineUIContainer)),WhitespaceSignificantCollection]
public class InlineCollection:TextElementCollection<Inline>,...
通过以上代码我们就可以发现上例等价于以下代码:
等价的TextBlock代码:
TextBlock t=new TextBlock();
t.Inlines.Add(new Run("Name:"));
TextBlock t2=new TextBlock();
t.Inlines.Add(new InlineUIContainer(t2));
t2.SetBinding(new Binding("Name"));
三,属性和类型转换器:
在上面的例子中我们设置了Window元素的Title属性。这个属性属于String类型。纯文本属性本身就与XML兼容,因为XML是一种基于文本的格式。不过其它属性是怎么样的呢?请看下面代码:
<!--非字符串属性-->
<Rectangle Width="100"
Height="20"
Stroke="Black"
Fill="#80FF40EE"/>
以上设置的属性中没有一个是字符串类型,其中Width和Height两者都是Double型,而Stroke和Fill两者都需要一个Brush。为了支持不同的属性类型,XAML依赖于.NET的TypeConverter(类型转换器)系统。一个TypeConverter可以在不同类型的值之间产生映射,最常见的是在String和原始类型之间。
Width和Height属性是通过LengthConverter类型来进行转换。其中BrushConverter类是为其它两个属性所用,因为尽管它们没有TypeConverterAttribute,但它们属于Brush类型,而Brush类型拥有TypeConverterAttribute,这表示应该使用到BrushConverter.以下代码说明了属性如何在每种情况下进行应用。
public class FrameworkElement:UIElement,...
{
...
[TypeConverter(typeof(LengthConverter))...]
public double Width{...}
[TypeConverter(typeof(LengthConverter))...]
public double Height{...}
}
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush:Animatable,...
由于Stroke设置了一个标准的已命名颜色,因此这将促使相关的SolidColorBrush从Brushes类那里取得。
上面示例等同于以下代码:
Rectangle r = new Rectangle();//创建Rectangle类对象
r.Width = 100.0;//设置属性值(中间经过了LengthConverter转换器转换)
r.Height = 20.0;
r.Stroke = Brushes.Black;
r.Fill = new SolidColorBrush(Color.FromArgb(0x80,0xff,0x40,0xEE));
当然,如果自义定组件允许带有非标准类型的属性通过XAML进行设置的话,它们就会提供自身的类型转换器。
四,属性元素语法:
尽管使用类型转换器系统一般可以通过XAML中的属性来指定属性值,但是它也有一定的限制,比如BrushConverter就不能提供一种渐变填充的方式,对于这些情况,就可以使用XAML所支持的属性元素语法(property element syntax)。
通过属性元素语法,你可以使用嵌套的元素来设置属性,而不直接使用属性。这个嵌套元素的名字形式可以是Parent.PropertyName,其中Parent是作为进行属性设置的元素名称,而PropertyName则是属性名。以下这种缩进式的语法将元素作为属性值进行标记而不是子内容。
属性元素语法:
<Button>
<Button.Background>
<SolidColorBrush Color="#FF4444FF"/>
</Button.Background>
Click me
</Button>
使用属性元素语法来设置一个Button元素的Background属性,它等价于以下代码:
Button btn = new Button();//创建Button类对象
SolidColorBrush brush = new SolidColorBrush();//创建SolidColorBrush类对象
brush.Color = Color.FromArgb(0xFF,0x44,0x44,0xFF);
btn.Background = brush;
btn.Content = "Click me";
这个示例的效果与属性Background="#FF4444FF"一样,也就是说如果将数字颜色指定成一个画刷的话,BrushConverter就会将其转换为一个SolidColorBrush。尽管这种属性元素版本的代码有点冗余,不过它清楚地展示了这里所创建的画刷类型。
尽管这种更详细的语法有助于揭示后台类型转换器的工作原理,但这并不是使用属性元素语法的理由,其主要原因是因为我们需要在属性里嵌套一些更加复杂的定义。如下示例。
嵌套的属性元素:
<Button VerticalAlignment="Center" HorizontalAlignment="Center">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="#800"/>
<GradientStop Offset="0.35" Color="Red"/>
<GradientStop Offset="1" Color="#500"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
Click me
</Button>
XAML效果为:
上例中的等价代码为:
Button b = new Button();//创建Button类对象
b.VerticalAlignment = VerticalAlignment.Center;//设置属性
b.HorizontalAlignment = HorizontalAlignment.Center;
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(0,0);
brush.EndPoint = new Point(0,1);
GradientStop gs = new GradientStop();
gs.Offset = 0;
gs.Color = Color.FromRgb(0x80, 0, 0);
brush.GradientStops.Add(gs);
gs = new GradientStop();
gs.Offset = 0.35;
gs.Color = Colors.Red;
brush.GradientStops.Add(gs);
gs = new GradientStop();
gs.Offset = 1;
gs.Color = Color.FromRgb(0x50,0,0);
brush.GradientStops.Add(gs);
b.Background = brush;
b.Content = "Click me";
五,附加属性:
XAML不仅允许设置普通的.NET属性,而且还支持附加属性。一个附加属性指其属性是由一个不同的类定义的,而不是由进行应用的元素来定义。示例如下。
附加属性:
<Button Grid.Row="1" Name="myButton"/>
其中Grid.Row="1"就是附加属性,附加属性的语法简单易懂,通常总是DefineType.PropertyName的形式,其中DefineType是定义属性的类型名称,而PropertyName则是属性名。XAML会将其解释为对静态DefineType.SetPropertyName方法的调用,并且会传入目标对象和值。以下代码解释了这个过程。
通过代码设置一个附加属性
Grid.SetRow(myButton,1);
以上就是今天总结的内容,下一篇将会总结扩展标记以及其它一些主题,欢迎大家指正,因为以前没有学习过XAML,这次是看了书后总结的,如果大家有一些好的关于XAML方面的资料的话希望可以分享一下。
今天是大年初二,在这里祝大家新年快乐,龙年大吉。