zoukankan      html  css  js  c++  java
  • 2 XAML

    XAML 基础

    一旦你理解一些基本法则,XAML标准是十分直白的:

    • 在XAML文档中,每个元素都映射到.NET类的一个实例。元素的名字恰好匹配类的名字。例如,元素<Button就是一个Button对象。
    • 就像任何XML文档,你能在一元素内部嵌套另一个元素。如你所见,XAML使每个类可以灵活地处理这种情况。嵌套通常代表着包含关系—换句话说,如果你在一个Grid元素内部发现一个Button元素,在你的用户界面上,有一个网格,它的内部包含一个按钮。
    • 你能通过特性设置每个类的属性。但是,有些情况下,一个特性不足以处理这个工作。在这种情况下,你将使用带有一个特殊语法的嵌套标签。

    XAML名字空间

    除了提供一个类的名字,XAML解析器也需要知道.NET类位于哪个名字空间。为得出你真正希望的类,XAML解析器检查应用于元素的XML名字空间。

    xmlns特性是XML专用于声明名字空间的一个特性。

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    这个名字空间是WPF核心名字空间。包括所有的WPF类,包括用于建立界面的控件。它没有名字空间前缀,是整个文档的默认名字空间。换句话说,没有前缀的元素自动放在这个名字空间。

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    这个名字空间是XAML名字空间。它包括影响文档解释的各种实用特征。这个名字空间对应于前缀x,这意味着,你能依靠在元素名称前放上名字空间前缀来应用它。

    后台代码类

    通过在顶级元素设置Class特性,将XAML文档与后台代码类相关联:

    <Window x:Class="WindowsApplication1.Window1"

    x名字空间前缀放置Class特性到XAML名字空间。事实上,Class特性告诉XAML解析器用给定的名字生成一个新的类。那类派生自由XML元素命名的类。换句话说,这个例子创造了一个名为Window1的新类,它派生自Window类。

    Window1类在编译时自动地生成。一个Window1.xaml文件链接着一个设计器自动生成的后台代码部分类,和一个程序员手写的代码部分类。

    InitializeComponent()方法

    当你创造Window1类的一个实例时,默认构造函数调用InitializeComponent()方法。此方法由编译器自动生成。

    命名元素

    为了后台代码能处理元素,需要为元素起一个名字:

    <Grid x:Name="grid1"

    解析器为Window1类添加一个名为grid1的字段:

    private System.Windows.Controls.Grid grid1;

    现在你可以使用grid1与Grid交互:

    MessageBox.Show(String.Format("The grid is {0}x{1} units in size.",
      grid1.ActualWidth, grid1.ActualHeight));

    对于继承自FrameworkElement的类来说,Name特性前的x前缀是可选的,去掉前面的x不会改变代码的含义。

    XAML的属性和事件

    简单属性和类型转换

    XAML元素的特性值总是一个普通文本字符串,而对象的属性可以是任何.net类型。

    WPF定义了类型转换器,在普通文本字符串与.net类型之间建立了一座桥梁。

    对于类型转换器,XAML解析器依次执行如下:

    1. 解析器查找属性声明的TypeConverter特性。
    2. 解析器查找相应类型声明的TypeConverter特性。

    解析器没有找到TypeConverter,则会生成一个错误。

    复杂属性

    一般一个类的属性对应元素的一个特性,使用的是属性-特性语法(property-attribute syntax)。有时类的属性非常复杂,则使用属性-元素语法(property-element syntax)。

    用属性元素语法,你添加一个带有Parent.PropertyName形式名字的子元素。例如,Grid有一个Background属性接受一个Brush对象,如果刷子比较复杂,你需要添加一个命名为Grid.Background的子标签,如下所示:

    <Grid Name="grid1">
      <Grid.Background>
        ...   
      </Grid.Background>
      ...
    </Grid>

    使这工作的关键细节是元素名字中的点号(.)。这是属性与其它类型的嵌套内容的根本区别。

    然后,如何设置属性元素呢?答案是在嵌套元素内部,你能添加另一个标签实例化一个指定的类。例如,用一个坡度刷子设置背景。为了定义你希望的坡度,需要创造一个LinearGradientBrush对象。

    使用XAML的规则,依靠使用带有LinearGradientBrush名字的一个元素,你能创造LinearGradientBrush对象:

    <Grid Name="grid1">
      <Grid.Background>
        <LinearGradientBrush>
        </LinearGradientBrush>
      </Grid.Background>
      ...
    </Grid>

    下一步,你也需要指定坡度颜色。依靠用GradientStop对象的一个集合填充LinearGradientBrush.GradientStops属性。再一次,GradientStops属性太复杂的不能单独用一个特性值设置。代替,你需要依赖属性元素语法:

    <Grid Name="grid1">
      <Grid.Background>
        <LinearGradientBrush>
          <LinearGradientBrush.GradientStops>
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
      </Grid.Background>
      ...
    </Grid>

    最后,你能用一系列GradientStop对象填充GradientStops集合。每个GradientStop对象有一个Offset和Color属性。你能依靠使用普通的属性特性语法提供这二个值:

    <Grid Name="grid1">
      <Grid.Background>
        <LinearGradientBrush>
          <LinearGradientBrush.GradientStops>
            <GradientStop Offset="0.00" Color="Red" />
            <GradientStop Offset="0.50" Color="Indigo" />
            <GradientStop Offset="1.00" Color="Violet" />
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
      </Grid.Background>
      ...
    </Grid>

    任何XAML标签集合都能被一套执行同样任务的代码语句替换。在此之前显示标签,用坡度填充背景,等价于下列代码:

    var brush = new LinearGradientBrush();
    
    var gradientStop1 = new GradientStop();
    gradientStop1.Offset = 0;
    gradientStop1.Color = Colors.Red;
    brush.GradientStops.Add(gradientStop1);
    
    var gradientStop2 = new GradientStop();
    gradientStop2.Offset = 0.5;
    gradientStop2.Color = Colors.Indigo;
    brush.GradientStops.Add(gradientStop2);
    
    var gradientStop3 = new GradientStop();
    gradientStop3.Offset = 1;
    gradientStop3.Color = Colors.Violet;
    brush.GradientStops.Add(gradientStop3);
    
    grid1.Background = brush;

    标记扩展

    标记扩展能用在嵌套标签中或在XML特性中。当他们用在特性中时,他们总是被花括号{}括起来。例如,这里是标记扩展的用法,这允许你引用另一个类的一个静态属性:

    <Button x:Name="cmdAnswer" Foreground="{x:Static SystemColors.ActiveCaptionBrush}" />

    标记扩展的语法是 {MarkupExtensionClass Argument},在这个例子中,标记扩展是StaticExtension类。(按照惯例,当引用一个扩展类时,你能丢弃最后的单词Extension)。

    标记扩展的基类是System.Windows.Markup.MarkupExtension。它只有一个ProvideValue方法,用于获得所期望的类实例。前面的例子中,XAML解析器首先构造了一个StaticExtension类(传入字符串"SystemColors.ActiveCaptionBrush"作为构造函数的参数),然后调用类的ProvideValue()方法,获得由SystemColors.ActiveCaption.Brush静态属性所返回的对象。

    前面例子的XAML块,等价于下面的代码:

    cmdAnswer.Foreground = SystemColors.ActiveCaptionBrush;

    因为标记扩展映射到类,它们也能作为嵌套属性被使用。例如,前面例子的等价表示法:

        <Button x:Name="cmdAnswer">
            <Button.Foreground>
                <x:Static Member="SystemColors.ActiveCaptionBrush"></x:Static>
            </Button.Foreground>
        </Button>

    依赖于标记扩展的复杂性和你希望设置的属性数目,这种语法有时更简单。

    就像大多数标记扩展一样,StaticExtension需要在运行时被估值,因为只有那时你才能决定当前的系统颜色。一些标记扩展能在编译时被估值。这包括NullExtension。

    附加属性

    除了普通的属性,XAML也包含附加属性的概念—此属性可以应用于几个控件,但是被定义在另一个类中。在WPF,附加属性被频繁用于控制布局。

    当你放置一个控件到一个容器内部时,它获得附加的特征,依赖于容器的类型。(例如,如果你放置一个文本框到一个网格内部,你需要能选择它所处的网格单元格)这些附加的细节使用附加属性被设置。

    附加属性总是使用两部分名字:DefiningType.PropertyName。这个两部分命名的语法将普通属性和附加属性区分开来。

    例如,附加属性允许每个控件将自己放置在网格的行中:

    <TextBox ... Grid.Row="0">
      [Place question here.]
    </TextBox>
    <Button ... Grid.Row="1">
      Ask the Eight Ball
    </Button>
    <TextBox ... Grid.Row="2">
      [Answer will appear here.]
    </TextBox>

    附加属性不是真正的属性,它们被翻译为方法调用。XAML解析器调用形如DefiningType.SetPropertyName()的静态方法,例如,在先前XAML片段,定义类型是Grid类,属性是Row,因而解析器调用Grid.SetRow()。

    当调用SetPropertyName()时,解析器传递二参数:被修改的对象和指定的属性值。例如,当你设置文本框控件的Grid.Row属性,XAML解析器执行这代码:

    Grid.SetRow(txtQuestion, 0);

    这个代码暗示行数保存在Grid对象中,但实际上,行数保存在txtQuestion文本框对象中。

    因为所有WPF控件都是DependencyObject,并且DependencyObject被设计为依赖属性的无限集合。

    事实上,以上代码只是下面代码的快捷方式:

    txtQuestion.SetValue(Grid.RowProperty, 0);

    附加属性是WPF的一个核心成分。他们充当一个多功能的可扩展性系统。例如,依靠定义Row属性作为一个附加属性,它保证能用于任何控件。

    嵌套元素

    XAML允许每个元素决定它如何处理嵌套元素。这种相互作用被中介通过三机制之一,按这个顺序被估值:

    • 如果父元素实现IList,解析器调用IList.Add(子元素)
    • 如果父元素实现IDictionary,解析器调用IDictionary.Add(子元素)。当使用一个词典集合,你必须也设置每个项目的x:Key特性一个关键字。
    • 如果父元素被ContentProperty特性装饰,解析器使用子元素设置那属性。

    例如,LinearGradientBrush能持有GradientStop对象的一个集合:

    <LinearGradientBrush>
      <LinearGradientBrush.GradientStops>
        <GradientStop Offset="0.00" Color="Red" />
        <GradientStop Offset="0.50" Color="Indigo" />
        <GradientStop Offset="1.00" Color="Violet" />
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>

    XAML解析器知道LinearGradientBrush.GradientStops元素是一个复杂的属性因为它包含一个点号。但是,解析器处理标签内部(三GradientStop元素)有点不同。在这种情况下,它知道GradientStops属性返回一个GradientStopCollection对象,并且知道GradientStopCollection实现IList接口。如此,它假定(十分正确)每个GradientStop应该被添加到集合,依靠使用IList.Add()方法:

    GradientStop gradientStop1 = new GradientStop();
    gradientStop1.Offset = 0;
    gradientStop1.Color = Colors.Red;
    IList list = brush.GradientStops;
    list.Add(gradientStop1);

    一些属性可能支持一个以上类型的集合。在这种情况下,你需要添加一个说明集合类的标签,像这样:

    <LinearGradientBrush>
      <LinearGradientBrush.GradientStops>
        <GradientStopCollection>
          <GradientStop Offset="0.00" Color="Red" />
          <GradientStop Offset="0.50" Color="Indigo" />
          <GradientStop Offset="1.00" Color="Violet" />
        </GradientStopCollection>
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>

    注意:如果集合默认为空,你需要包括说明集合类的标签,这样创造了集合对象。如果存在一个集合的默认实例,你只需要填充它,你能忽略那部分。

    嵌套内容不总是指一个集合。例如,考虑Grid元素,它包含几个其它的控件:

    <Grid Name="grid1">
      ...
      <TextBox Name="txtQuestion" ... >
        ...
      </TextBox>
      <Button Name="cmdAnswer" ... >
        ...  
      </Button>
      <TextBox Name="txtAnswer" ... >
        ...
      </TextBox>
    </Grid>

    这些嵌套标签不是复杂属性因为他们没有包括点号。而且,Grid控件不是一个集合因为它没有实现IList或IDictionary。Grid真正支持的是ContentProperty特性,它是指可以接受任何嵌套内容的属性。从技术上,ContentProperty特性被应用于Panel类(Grid的基类),如同这样:

    [ContentPropertyAttribute("Children")] 
    public abstract class Panel

    这是指任何嵌套元素应该被用于设置Children属性。依赖于内容属性是否是一个集合属性(在这种情况下,它实现IList或IDictionary接口),XAML解析器处理它的方式有所不同。因为Panel.Children属性返回一个UIElementCollection,并且UIElementCollection实现IList,解析器使用IList.Add()方法添加嵌套内容到网格。

    换句话说,当XAML解析器遇见之前的标记,它创造每个嵌套元素的一个实例并且使用Grid.Children.Add()方法传递它到网格:

    txtQuestion = new TextBox();
    ...
    grid1.Children.Add(txtQuestion);
    cmdAnswer = new Button();
    ...
    grid1.Children.Add(cmdAnswer);
    txtAnswer = new TextBox();
    ...
    grid1.Children.Add(txtAnswer);

    接下来发生什么完全取决于控制如何实现内容属性。Grid显示它在一个看不见的行和列组成的布局中持有的所有控件。

    ContentProperty特性频繁地被使用在WPF。不仅它被用于容器控件(诸如Grid)和包含视觉项目集合的控件(诸如ListBox和TreeView),它也被用于包含单个内容的控件。例如,文本框和按钮控件能持有单个元素或文本,但是他们都使用一个内容属性处理嵌套内容,像这样:

    <TextBox Name="txtQuestion" ... >
      [Place question here.]
    </TextBox>
    <Button Name="cmdAnswer" ... >
      Ask the Eight Ball  
    </Button>
    <TextBox Name="txtAnswer" ... >
      [Answer will appear here.]
    </TextBox>

    TextBox类使用ContentProperty特性标记TextBox.Text属性。Button类使用ContentProperty特性标记Button.Content属性。XAML解析器使用提供的文本设置这些属性。

    TextBox.Text属性只有允许字符串。但是,Button.Content属性接受任何元素。

    因为Text和Content属性没有使用集合,你不能包括一个以上的内容。例如,如果你企图在一个按钮内部嵌套多个元素,XAML解析器将抛一个异常。如果你提供非文本内容(诸如一个长方形),解析器也抛出一个异常。

    注意:ContentControl允许单个嵌套的元素,ItemsControl允许一个项集,Panel是布置一组控件的容器。ContentControl、ItemsControl和Panel基类都使用ContentProperty特性。他们的ContentProperty属性分别为:Panel.Children,TextBox.Text,Button.Content。

    特殊字符和空白

    见38页。

    事件

    特性除了映射到属性之外,特性也能被用于附加事件处理器。语法是EventName="EventHandlerMethodName"。

    例如,为按钮附加点击事件处理器:

    <Button ... Click="cmdAnswer_Click">

    在许多情况下,你将在相同的元素上使用特性设置属性和附加事件处理器。WPF总是遵循相同的次序:首先它设置Name属性;然后它附加所有的事件处理器;最后它设置属性。这意味着当第一次设置属性时,就会触发相应的事件处理器。

    使用其他名字空间

    为使用一个没有定义在WPF名字空间中的类,你需要映射.NET名字空间到一个XML名字空间。

    xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"

    三个斜体的信息解释如下:

    Prefix:这是你希望使用的XML前缀,是指在你XAML标记中的名字空间。例如,XAML语言使用x前缀。

    Namespace:这是全限定的.NET名字空间名字。

    AssemblyName:类型在这个装配体中被声明,没有.dll扩展名。你的工程必须引用这个装配体。如果你希望使用你的工程装配体,留空。

    例如,这里是你将如何访问在System名字空间的基本类型,并映射他们到前缀sys:

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    这里是你将如何访问当前工程、在MyProject名字空间的你已经声明的类型,并映射他们到前缀local:

    xmlns:local="clr-namespace:MyNamespace"

    现在,为了创造一个位于某名字空间的类实例,你使用名字空间前缀:

    <local:MyObject ...></local:MyObject>

    在XAML使用的类必须有无参数的构造函数。另外,你只能使用类中公开的属性。XAML不允许你设置公开的字段或调用方法。

    如果你试图创造一个原始类型(诸如一个字符串,日期,或数字的类型),你可以提供你数据的字符串表示法作为你标签的内容。XAML解析器将随后使用类型转换器转换字符串到合适的对象。

    <sys:DateTime>10/30/2013 4:30 PM</sys:DateTime>

    DateTime类使用TypeConverter特性链接它自己到DateTimeConverter。DateTimeConverter认识这字符串是一个有效的DateTime对象并且转换它。当你使用这技术,你不能使用特性设置你对象的属性。

    装载和编译XAML

    仅代码

    纯代码WPF,常用于根据数据库记录填充窗口。动态添加或替换窗口控件。

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Markup;
    
    public class Window1 : Window
    {
        private Button button1;
    
        public Window1()
        {
            InitializeComponent();
        }
    
        private void InitializeComponent()
        {
            // Configure the form.
            this.Width = this.Height = 285;
            this.Left = this.Top = 100;
            this.Title = "Code-Only Window";
    
            // Create a container to hold a button.
            var panel = new DockPanel();
    
            // Create the button.
            button1 = new Button();
            button1.Content = "Please click me.";
            button1.Margin = new Thickness(30);
    
            // Attach the event handler.
            button1.Click += button1_Click;
    
            // Place the button in the panel.
            IAddChild container = panel;
            container.AddChild(button1);
            //alternative
            panel.Children.Add(button1);
    
            // Place the panel in the form.
            container = this;
            container.AddChild(panel);
            //alternative
            this.AddChild(panel);
            //alternative
            this.Content = panel;
    
        }
    
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button1.Content = "Thank you.";
        }
    }

    启动窗口的代码:

    public class Program : Application
    {
        [STAThread()]
        static void Main()
        {
            Program app = new Program();
            app.MainWindow = new Window1();
            app.MainWindow.ShowDialog();
        }
    }

    代码和未编译的XAML

    一个任意的文本文件如"TextFile1.txt"

    <DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
      <Button Name="button1" Margin="30">Please click me.</Button>
    </DockPanel>

    创建窗口的代码:

    using System.IO;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Markup;
    
    public class Window2 : Window
    {
        private Button button1;
    
        public Window2(string xamlFile)
        {
            // Configure the form.
            this.Width = this.Height = 285;
            this.Left = this.Top = 100;
            this.Title = "Dynamically Loaded XAML";
    
            // Get the XAML content from an external file.
            DependencyObject rootElement;
            using (FileStream fs = new FileStream(xamlFile, FileMode.Open))
            {
                rootElement = (DependencyObject)XamlReader.Load(fs);
            }
    
            // Insert the markup into this window.
            this.Content = rootElement;
    
            // Find the control with the appropriate name.
            button1 = (Button)LogicalTreeHelper.FindLogicalNode(rootElement, "button1");
    
            //alternative
            var frameworkElement = (FrameworkElement)rootElement;
            button1 = (Button)frameworkElement.FindName("button1");
    
            // Wire up the event handler.
            button1.Click += button1_Click;
        }
    
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button1.Content = "Thank you.";
        }
    }

    启动窗口:

        public class Program : Application
        {
            [STAThread()]
            static void Main()
            {
                Program app = new Program();
                System.Environment.CurrentDirectory = @"D:\Application Data\Visual Studio\Projects\WpfApplication2";
                app.MainWindow = new Window2("TextFile1.txt");
                app.MainWindow.ShowDialog();
            }
        }

    代码和编译的XAML

    当你编译一个WPF应用时,Visual Studio使用两阶段编译处理。第一阶段是编译XAML文件到BAML。例如,如果你的工程包含一个文件Window1.xaml,编译器将创造一个临时文件Window1.baml并且把它放在obj\Debug子文件夹中。与此同时,一个部分类被创造,为了你的窗口,使用你的选择的语言。例如,如果你使用C#,编译器将在obj\Debug文件夹中创造一个文件Window1.g.cs。g代表生成(generated)。

    public partial class Window1 : System.Windows.Window,
      System.Windows.Markup.IComponentConnector
    {
        // The control fields.
        internal System.Windows.Controls.TextBox txtQuestion;
    
        internal System.Windows.Controls.Button cmdAnswer;
        internal System.Windows.Controls.TextBox txtAnswer;
    
        private bool _contentLoaded;
    
        // Load the BAML.
        public void InitializeComponent()
        {
            if (_contentLoaded)
            {
                return;
            }
            _contentLoaded = true;
            System.Uri resourceLocater = new System.Uri("window1.baml",
              System.UriKind.RelativeOrAbsolute);
            System.Windows.Application.LoadComponent(this, resourceLocater);
        }
    
        // Hook up each control.
        void System.Windows.Markup.IComponentConnector.Connect(int connectionId,
          object target)
        {
            switch (connectionId)
            {
                case 1:
                    txtQuestion = ((System.Windows.Controls.TextBox)(target));
                    return;
    
                case 2:
                    cmdAnswer = ((System.Windows.Controls.Button)(target));
                    cmdAnswer.Click += new System.Windows.RoutedEventHandler(
                      cmdAnswer_Click);
                    return;
    
                case 3:
                    txtAnswer = ((System.Windows.Controls.TextBox)(target));
                    return;
            }
            this._contentLoaded = true;
        }
    }

    仅XAML

    没有创造任何代码,使用一个XAML文件,这被称为松XAML文件。松XAML文件能直接用ie浏览器打开。

    为尝试一个松XAML页面,将正常的XAML文件做如下改动:

    • 移除根元素的Class属性。
    • 移除所有绑定事件处理器的属性。
    • Window元素改为Page元素。
  • 相关阅读:
    Java基本数据类型转换
    Java中的数据类型
    Java常见关键字
    HashMap源码分析(jdk 8)
    函数参数
    存储盘在系统中对应的naa号
    Python处理文本换行符
    Python文件操作中的方法:.write()换行
    此示例示意标准输出函数print的用法
    主机端查看到的wwpn 不是以:分割解决办法
  • 原文地址:https://www.cnblogs.com/cuishengli/p/3099271.html
Copyright © 2011-2022 走看看