欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

WPF: A Beginner's Guide: Part 2 of n

程序员文章站 2023-12-30 19:33:22
...

原文来自CodeProject。文章比较长,翻译的不一定很对,见谅。

What To Do In Xaml

通俗来讲,WPF可以被看做类似于Model View Controller(MVC),Model与Controller组合在一起,但XAML背后的代码能够使界面与代码逻辑相区分,有点像ASP.Net;有了XAML,使这不同于传统的MVC,那就是界面是被分开定义的;

What To Do In Code

在界面之后,代码(C#|VB.Net)必须实现事件处理和用户逻辑来驱动界面;界面最好是傻瓜式的,如果界面中有问题的话,最好XMAL能使用自带的异常处理来完成;我们的代码需要实现:

*)所有的事件处理(没得选择,必须由代码实现)

*)任何循环,数学计算,调用中间的非UI类的方法

*)任何验证部分

*)任何异常处理(由于XAML声明的特性,它不存在与XAML中)

*)任何递归操作

基本来讲,UI(XAML)只是呈现数据,任何其他的东西都有代码来完成。

How To Reference Classes/Assemblies From XAML

在C#,我们可以这样实现引用:

using System.Windows.Controls

XAML中于此类似,如果我们想在当前命名空间中使用控件,我们也需要这种方式声明:在XAML文件最上面指定引用;很奇怪吧,XAML中类可以被直接实例化,只要我们引用了正确的命名空间;下面给出4个实例:

Assembly Ref1:Using  a Local (Same NameSpace)Control in a Window

这是最简单的一种,由于我们引用的类在同一个命名空间,可以直接包括在xmlns;如下命名空间WPF_Tour_Beginners_Part_2中使用用户控件UserControl2:

WPF: A Beginner's Guide: Part 2 of n

可以使用下面的声明:

 xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

我们可以这样使用UserControl,如

<local:UserControl2/>

至于为什么在xmlns中使用local,文中解释:你可以使用任何你熟悉的名字,但local已在很多书中出现过,而且,它也表示同一个命名空间;

Assembly Ref 2: Using a Local (Same nameSpace)Class in a Window

类似于上面所说的UserControl;不同的是UserControl在WPF中是一个虚拟对象(visual object,继承自Visual或Visual3D),意味着可以当做UI的一部分来使用;这也意味着,只要不是继承自Visual或Visual3D,就不属于UI对象类型,不能适用于UI Markup,但可以被看做资源;我们现在还没有谈到资源(Resources),后面会有补充,这里只是知道我们可以实例化一个类,然后在Resources部分,或XMAL中的资源文件中就可以引用它;

还是上面同一个实例,命名空间中包括类LocaClass,可以使用下面的声明:

xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

可以这样使用:

<local:LocalClass x:Key="localClass1" AnIntProp="5"/>

上面语句中我们实例化了一个对象localClass1,并给属性AnIntProp赋值5;注意到,为了在XAML中构建一个类,我们需要声明一个无参的构造函数(如同这一句);在C#代码中,我们可以直接使用对象localClass1了。

Assembly Ref 3: Using an External (Different namespace/Assembly) Control in a Window

这有点像UserControl,不同的是,我们的user Contorl在不同的命名空间(SeperateWPFUserControl_Dll):

WPF: A Beginner's Guide: Part 2 of n

那我们如何在XAML中引用这个User Contorl呢?

如前面所见,就是在xmlns中添加Assembly的名字,也就是全名:

xmlns:SeperateWPFUserControlDll="clr-namespace:SeperateWPFUserControl_Dll;assembly=SeperateWPFUserControl_Dll"

接下来就是对User Contorl1的使用:

<SeperateWPFUserControlDll:UserControl1/>

命名规则可以随意,不过,建议还是大家熟知的比较好,如以SeperateWPFUserControlDll模块名来命名是很不错的选择。

Assembly Ref 4: Using an External (Different Namespace/Assembly) Control in a Window

有点像local class,不同的是,我们将调用来自.net的库,但基本原则一样;

如果我们想在XAML中使用以下类:

*) System.Collections.Hashtable (mscorlib.dll)

*) System.Int32 (mscorlib.dll)

如下,可以分别取个有意义的名字:collections,sys

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

接下来就是使用:

<collections:Hashtable x:Key="ht1">
      <sys:Int32 x:Key="key1">1</sys:Int32>
      <sys:Int32 x:Key="key2">2</sys:Int32>
</collections:Hashtable>
Markup Extentions

标记扩展能在运行时根据字符串产生合适的对象,使XAML使用起来更加灵活;

任何时候当一个属性值包括在{}之内,XAML编译器把它看做一个扩展标记值,而不是一个字符串;如:

<Rectangle  Fill="{x:Null}" Stroke="Black" StrokeThickness="2" Height="20"/>

编译器看到{}包括的字符串(设置矩形的背景色为空),会把它看做扩展标记,这些是属于属性元素语法(property-element sybtax); 我们可以这样声明一个扩展标记

<Binding RelativeSource="{RelativeSource modeEnumValue}" .../>

或者

<object mode="{Binding RelativeSource={RelativeSource modeEnumValue} ...}" .../>

现在,使用属性元素语法,我们就可以吧mode属性看做树中的元素,类似于它自己的子元素,如:

<Binding>
  <Binding.RelativeSource>
    <RelativeSource Mode="modeEnumValue"/>
  </Binding.RelativeSource>
</Binding>

来一个稍微复杂点的实例:

<Binding>
  <Binding.RelativeSource>
    <RelativeSource
      Mode="FindAncestor"
      AncestorType="{x:Type typeName}"
      AncestorLevel="2"
    />
  </Binding.RelativeSource>
</Binding>

在XAML中,我们可以做很多事情;注意到,所有的对象都是类,所以我们这样声明是没有问题的;这种属性元素语法可以适合所有情况,下面是另外一个属性被用作一个元素:

<Button Width="auto" Content="Button">
    <Button.Background>
        <SolidColorBrush Color="Blue"/>   
    </Button.Background>
</Button>
System.Windows.Markup.MarkupExtension是基类,所有的扩展类都继承于此,如System.Windows.markup.ArrayExtension等;

在WPF编程中应用最广的是支持资源引用类(StaticResource,DynamicResource)和支持数据绑定类(Binding);注意后面没有'Extension'字样;这是由编译器暗指的,如同{}指代扩展标记,‘Extension’后缀是不需要的;

  • StaticResource:为XAML属性提供一个值,来取代资源中已定义的值;
  • DynamicResource:为XAML属性提供一个值,用于运行时来指代对一个资源的引用;
  • Binding:给属性提供绑定,每个数据对应一个元素;
  • RelativeSource:为Binding提供原始信息,方便用户在运行元素树上导航几种可能的相关关系;

其它几种扩展标记,典型的以前缀x标识,WPF使用同样的基类,如

  • x:Type为命名的类型提供类型对象,通常用于模式或模板;
  • x:Static从值类型的代码块(本身并不是值属性类型,但可从类型中估计出来)中提供静态值;
  • x:Null指定null作为一个值;
  • x:Array提供以XAML语法创建通用的数组,适用于基本元素和控件模式的组合没有提供的情况下;

注:我们也可以创建自己的扩展标记,可以参考实例WPF Control State Persistency,作者创建了一个可以保存WPF元素的状态到文件的扩展标记。

What Are Resources, And How Can They help Me

资源(Resources)是可以被重复使用的对象,可能被声明在以下几个不同的地方:

  • 文件app.xaml中(或应用文件被创建的地方),这意味着这些资源是全局可见的;
  • 在当前窗体中的Resouce属性中,意味着是窗体级别的可视范围,窗体中所有UI元素都可以使用这些资源;
  • 在FrameworkElement或FrameContentElement中的Resources属性;
  • 单独的XAML资源文件;

我们可以认为资源是一个单独的对象,是以字典的形式存储在资源对象中,也由此,每个资源有一个独一无二的key;

当我们在标记语言中定义资源时,我们通常这样指定关键字:x:key;通常,关键字是个字符串,然而,我们也可以使用合适的扩展标记语言来指定其他的对象类型;非字符串关键字的资源常用于类型,部件资源,数据类型等;在后面的文章中我们会看到更多。

每个Framework级别(FrameworkElement或FrameContentElement)的元素有一个Resources属性,提供包含资源的属性,如同一个资源字典,我们可以在任何元素上定义资源;然而,常用的是定义资源在根元素上,也就是窗体级别。

一旦定义了一个资源,我们就可以在元素上*使用;下面的代码中在窗体级别上定义了一个新的资源SolidColorBrush,后面在窗体的两个按钮上使用它来指定背景色。

<Window x:Class="WPF_Tour_Beginners_Part_2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:SeperateWPFUserControlDll=
      "clr-namespace:SeperateWPFUserControl_Dll;
       assembly=SeperateWPFUserControl_Dll"
    xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="
    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    Title="Window1" Height="300" Width="600"
    WindowStartupLocation="CenterScreen">
    <Window.Resources>
        <SolidColorBrush x:Key="windowLevelResourceBlueBrush" 
                     Color="Blue"/>   
    </Window.Resources>
    
    <StackPanel x:Name="sp1" Orientation="Vertical">
        <!-- Declare 2 Buttons that use the Window.windowLevelResourceBlueBrush resource -->
        <Button Width="auto" 
          Content="1st Button : I use the resourceBrushBlue Window Resource" 
          Background="{StaticResource windowLevelResourceBlueBrush}"/>
        <Button Width="auto" 
          Content="2nd Button : I use the resourceBrushBlue Window Resource" 
          Background="{StaticResource windowLevelResourceBlueBrush}"/>
    </StackPanel>
</Window>

这个简单实例表明:两个按钮对象有一个蓝色的SolidColorBrush用语它们的背景色;(在这里我们没有涉及到当鼠标在按钮上,按钮显示的颜色,这只是鼠标不在按钮上时的颜色;在后面的模式/模板中会设计到这些)。

我们注意到我们在使用语句StaticResourceExtension(实际可以去掉后缀Extension),但StaticResourceExtension究竟为我们作了啥?事实上,我们有两种资源可以使用;

也就是静态资源和动态资源,分别用StaticResourceExtension和DynamicResourceExtension来表示;

根据MSDN Resources (WPF)部分,指明了何时使用它们:

Static Resources

适用于以下情况:

  • 资源第一引用后不再改变它;
  • 针对大量的资源,无需在程序运行时再次调用它;
  • 对属性值的设定不是一个DependancyObject或Freezable;
  • 创建的资源字典将会被编译成dll,以作为应用程序的一部分,或被多个应用程序共享;
  • 为客户控件创建一个主题,定义的资源将会用于主题之内,所需的资源的动态引用时无需查找;
  • 使用资源来设置大量依赖属性值得情况,当在加载阶段给依赖属性值提供一个值,依赖属性无需检查可以直接返回上次的有效值;这种技术对改进程序运行效果有帮助;

Dynamic Resources

适用于以下情况:

  • 资源定位依赖于运行条件,也就是直到运行时才知道引用啥;
  • 为客户控件创建或引用主题式样;
  • 在程序的生命周期内有意向改变ResourceDictionary;
  • 有一个比较复杂的资源结构,还有一定的依赖关系,这时需要一个前向的引用;静态资源引用并不支持前向引用的;
  • 引用的资源主要来自于编译或工作环境状态,当页面加载时并不会立即使用到;当页面加载时静态资源引用通常会直接从XAML加载,而动态资源引用只有在实际使用中才会被加载;
  • 创建一种式样,它的设定值来自于受影响的主体或其它设定值;
  • 在逻辑树上使用的资源会被别的类继承;
Your Resource Options

以上就是有关资源的介绍,接下来就是如何定义这些不同的资源,如

1) 应用级别的资源(全局)

2)窗体级别的资源

3)FrameworkElement级别的资源

4)松散的XAML资源

Application Level Resource

为了创建一个全局的资源,只需在App.xaml中添加一个Resources目录,如:

<Application x:Class="WPF_Tour_Beginners_Part_2.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  StartupUri="Window1.xaml">
    <Application.Resources>
        <SolidColorBrush x:Key="appLevelResourceGreenBrush" Color="Green"/>
    </Application.Resources>
</Application>

这意味着,在当前应用中任何对象都可以使用这个资源,如Window1.xaml中的Button:

<!-- Declare 1 Button that use the Application level appLevelResourceGreenBrush resource -->
<Button Width="auto" 
  Content="1st Button : I use the appLevelResourceGreenBrush Application level Resource" 
  Background="{StaticResource appLevelResourceGreenBrush}"/>

Window Level Resource

<Window x:Class="WPF_Tour_Beginners_Part_2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:SeperateWPFUserControlDll=
      "clr-namespace:SeperateWPFUserControl_Dll;assembly=SeperateWPFUserControl_Dll"
    xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="
    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Title="Window1" Height="300" Width="600"
    WindowStartupLocation="CenterScreen">
   
    <Window.Resources>
        <SolidColorBrush x:Key="windowLevelResourceBlueBrush" Color="Blue"/>   
    </Window.Resources>
    
    <StackPanel x:Name="sp1" Orientation="Vertical">
        <!-- Declare 2 Buttons that use the Window.windowLevelResourceBlueBrush resource -->
        <Button Width="auto" 
           Content="1st Button : I use the resourceBrushBlue Window Resource" 
           Background="{StaticResource windowLevelResourceBlueBrush}"/>
        <Button Width="auto" 
           Content="2nd Button : I use the resourceBrushBlue Window Resource" 
           Background="{StaticResource windowLevelResourceBlueBrush}"/>
    </StackPanel>
</Window>

上例中使用windowLevelResourceBlueBrush于两个按钮对象上。

Framework Element Level Resource

就像上面讲过的,每个FrameworkElement或FrameWorkContentElement都有一个Resource属性,意味着可以创建一个局部资源:

<Window x:Class="WPF_Tour_Beginners_Part_2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:SeperateWPFUserControlDll=
      "clr-namespace:SeperateWPFUserControl_Dll;
       assembly=SeperateWPFUserControl_Dll"
    xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="
    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Title="Window1" Height="300" Width="600"
    WindowStartupLocation="CenterScreen">
    
    .....
    .....
    
    <StackPanel x:Name="sp1" Orientation="Vertical">
        <!-- I have added this ust to show you that you 
             can add classes to XAML. The ones i've used 
             can't be part of the actual UI, as they 
             aren't UI controls (dont inherit from Visual). 
             So I've put them into a resource Dictionary.
             -->
        <StackPanel.Resources>
            <SolidColorBrush x:Key="parentLevelResourceOrangeBrush" 
                  Color="Orange"/>
        </StackPanel.Resources>

          ......
          ......

        <!-- Declare 1 Button that use the parent (the StackPanel) 
             level parentLevelResourceOrangeBrush resource -->
        <Button Width="auto" 
          Content="Button : I use the parentLevelResourceOrangeBrush 
                   parent (the StackPanel) level Resource" 
          Background="{StaticResource parentLevelResourceOrangeBrush}"/>

    </StackPanel>
</Window>

任何在FrameworkElement中的对象或其子对象都可以使用这个资源,除此之外的情况将会导致一个异常抛出。

Separate Loose XAML Resourses

WPF支持类似组合字典形式的资源特征,可以让我们在编译的XAML应用之外定义资源;这样资源就可以被跨应用共享,便于局部独立。

注意到ResourceDictionary元素没有x:key属性,这通常是资源组合必备的;但Resourceictionary中的MergedDictionaries组合是个特例;每个Resourceictionary中的MergedDictionaries组合指定了一个Source属性,Source值必须是个统一资源定位符(Uniform Resource Identifier,URI)来指定将要组合的资源文件的位置;这个URI的目的地必须是另外一个XAML文件,同时,指定ResourceDictionary作为它的根元素。

下面解释如何定义松散的XAML文件:

<ResourceDictionary 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="seperateResourceFilePinkBrush" 
                     Color="Pink"/>
</ResourceDictionary>

这样足够了,唯一需要确认的是在Visual Studio中的'Build Action'对应的Resource正确。

如何使用?考虑在在按钮中使用这个松散的XAML文件,如:

<!-- Declare 1 Button that uses a seperate loose XAML level Resource, 
     namely the seperateResourceFilePinkBrush resource -->
<Button Width="auto" 
        Content="Button : Uses a seperate loose XAML level Resource, 
                 namely the seperateResourceFilePinkBrush resource">
    <Button.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="SeperateResourceDictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Button.Resources>
    <Button.Background>
        <StaticResourceExtension ResourceKey="seperateResourceFilePinkBrush"/>
    </Button.Background>
</Button>

上例中,按钮声明了以MergedDictionaries,我们就能使用这个XAML文件中所有的资源,条件是局限于Button局部。


相关标签: WPF

上一篇:

下一篇: