深入浅出WPF(2)——解剖最简单的GUI程序

简介:
深入浅出 WPF 2 —— 解剖最简单的 GUI 程序
小序:
从这篇文章开始,我们进行 XAML 语言的上手学习。说实话, XAML 这种语言一点也不难。如果把 XML 看成是 父类 ,那么 XAML 就是 XML 的一个派生类了,所以 XML 的概念在 XAML 中是通用的。 What ?你不了解 XML ?没关系,反正我们是从头开始!
正文:
你还能想起学 C/C++ 的时候写的第一个程序吗?呵呵,一定是 “Hello World” 吧!今天我们来解析一个 “Hello WPF” 。准备好了吗? Let's go
准备知识
使用 VS2008 新建一个 WPF Application ,你立刻就会得到一个看上去是 的窗体。窗体这个东西,在 Windows Form 程序里叫 “Form” ,在 WPF 里叫 Window ”—— 喔, Win32 API 里也叫 Window !是的,你说对了, WPF 在某种程度上是向 Win32 API 返璞归真
为什么说它 看上去 是空的呢?实际上,这个 Window 的内部有一个叫 <Grid> 元素( Element ,只是这个元素是看不见的,它就像信纸上的 暗格 一样。
针对 XAML 文件,是可以进行 所见即所得 的可视化设计的。你在 XAML 代码上做的修改,只要是合乎语法的,那么在设计器里就会立刻反映出来(有时候需要刷新一下)。如果你发现设计器里显示不出来了,那一定是 XAML 语句出了问题,最好想办法修正它。不然的话,在设计器里都看不到效果、只能运行起来看,这还算什么可视化编程呢?要 XAML 还有什么意义呢?
在我们正式剖析代码之前,让我们牢记两件事:
1.      这个世界是一个 组合 的世界 —— 汽车是由一个车身和四个轮子组合成的;飞机是由机翅和机身组合成的。这些组成部分,我们称之为 元素( Element
2.      XAML 文件里,每写一个元素就相当于定义了一个元素所对应的 .NET Framework 类的实例。
有必要强调一点 :如果一个实体是由一些(同类或者不同类的)子对象组合成的,我们就称这个实体为 父元素 、称这些子对象为 子元素 ,因为父元素包含着子元素,所以常把父元素称为 包含元素 、把子元素称为 被包含元素 或父元素的 内容 ”—— 我们需要注意,被包含元素并不等同于包含元素的属性( property ),被包含元素只是包含元素的一个部分    初听这句话,肯定是一头雾水, OK ,让我举个两个例子。比如有一个班级,这个班由 56 个学生、 1 个老师、 60 张桌子、 70 把椅子组成,那么这些学生、老师、桌子和椅子,只是这个班级的一些 组成部分 ;而这个班级的人数、班级隶属的年级、班级的编号是这个班级的属性。再比如我有一个 Window ,这个 Window 里有 1 Grid ,这个 Grid 里又包含着 3 TextBox 2 Button ,那么这 1 Grid 就是这个 Window 的子元素, 3 TextBox 2 Button 又是 Grid 的子元素;而 Window Name Icon 、尺寸乃至 Resources 都是这个 Window 的属性。
你可能会问,这个道理这么简单,有什么好强调的呀?
原因是这样的:对于 C# 的类而言,属性( property )肯定是一个对象(比如 Window Name 属性,它就是一个 String 类型的对象),这个对象也是类实例的一个组成部分;而在对这个类进行扩展的时候(对这个类进行派生),我们新添加进来的元素(比如 3 TextBox 2 Button )也是类实例的组成部分。 OK ,大家看到了,从现实世界抽象到编程世界来之后,它们的区别就不那么鲜明了。为了再让它们的区别 鲜明 起来,请大家记住两句话:
  • 属性对象(元素)是父元素所固有的,子元素则可由设计人员来进行增减
  • 属性对象(元素)是隶属于父类的(从父类继承而来),子元素是在设计派生类时新添加进来的
之所以在剖析代码之前讲述这些东西,是因为 XAML 是一种 XML 语言,它的语法完全是元素嵌套组合式的,而属性和子元素也都是类实例的组合体,如果不先分清楚,读代码的时候一定会感觉混乱。
  在了解了这些内容之后,我们就可以放心地读代码了。
剖析代码
  请新建一个名为 HelloWPF WPF Application 项目。在 XAML 语言编辑器里,你会看到和下面一样的代码。
   < Window  x:Class ="HelloWPF.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" >
    
< Grid >
        
    
</ Grid >
</ Window >
 
  让我们一个词一个词地分析这段代码。
就像我们遇到复杂长句时先要分析它的主干一样 —— 让我们暂时抛开花花绿绿的代码,看看这段程序的主干是什么。喔 ~~~ 这段程序的主干是如此的简单!就是一个 < Window > 元素里包含着一个   < Grid > 元素。
   < Window >
    
< Grid >
        
    
</ Grid >
</ Window >
 
  一个句子的主干找出来之后,句子的大意也就明白了。我们已经得到这段程序的主干了,那这段程序说的是什么呢?前面我叮嘱大家一定要记住两件事件。其中一件就是 见到元素就相当于创建实例 。我想你一定会说:这段程序就是在定义一个 Window 类的实例,这个实例的一个组成部分是一个 Grid 类的实例。
呵呵,对于这个答案,既可以说它是对的,也可以说它是错的,为什么呢?
请注意, Window 元素的一个 attribute x : Class ="HelloWPF.Window1" ,这个 Class 就是在告诉我们 嘿!本 XAML 文件实际上是这个类的 UI 部分哦! 。本例中,类名就是等号后面的 “HelloWPF.Window1” ,也就是说,是 HelloWPF 名称空间中的 Window1 这个类。在项目浏览器中找到与这个 XAML 文件配套的 C# 文件( XAML 文件名 .cs ),果然能找到这个类。
   public   partial   class  Window1 : Window
    {
        
public  Window1()
        {
            InitializeComponent();
        }
    }
 
从这个角度来看,上面的答案就是错的了 —— 因为这段代码是在定义一个 Window1 的实例会是什么样子。
那为什么又可以说它的对的呢?显然, Window1 Window 类的派生类,根据多态的原理,你说 Window1“ 是一个 ”Window 并没有错 —— 就像你说 鸭子是一只鸟 一样正确。在派生过程中,我们使用 <Grid> 标签为它添加了一个 Grid 类型的 UI 成员 —— 派生吗,一定是要做些扩展的。
看到这儿,我想你已经猜到了, XAML 文件就是用来定义 Window1 这个类的 UI 部分(一旦这个类创建了实例,那这个实例的 UI 将与 XAML 代码的描述相一致)。微软通过 XAML 语言把 UI 设计完全暴露给了我们,让设计师可以像设计网页一样来设计桌面程序的界面。至于这个类的逻辑部分,还是用传统的 C# 语言来实现。这样,设计人员和开发人员就能各司其职、协同工作了。
一个类能够 掰成两半 来写,这要归功于 partial 这个关键字,使用这个关键字,可以把一个类的代码分散在多处来实现。可问题又来了 ——XAML 代码怎么和 C# 代码 对接 啊?呵呵,这个还真不用咱们操心,微软的 XAML 解析器本着 进村悄悄地,开枪地不要 原则,在背后把这件事完成了。因为 XAML 代码中没有逻辑,所以,解析 XAML 的大部分工作就是按照元素标签的描述把对象创建出来 —— 比如,解析器见到有 <Grid> 标签出现,就会生成与 C# 代码 new Grid() 等价的代码。
喘口气儿 ……
让我们继续。
XAML 名称空间
如果你问一个初学 XAML 的人(碰巧他还没有 XML 编程经验):最让他迷惑的是什么?我想他会告诉是: 就是那个 x 老实讲,我就是他们中的一员,初学的时候我也很 痛恨 那个 x 。一会儿是 “:x” ,一会儿是 “x:”…… 这个 x 到底是什么呢?
其实非常简单 —— 这个 x 是一个名称空间、一个使用 XML 语法声明的名称空间 。只是 XML 语言声明名称空间的时候语法比较怪而已。下面,让我一一为你解释。
首先,如果你使用 C# ,那么你对这几句代码一定不陌生:


这是对 .NET Framework 类库中名称空间的引用。一旦引用了这些名称空间,在使用这些名称空间中的类时就不并在再类名前加上长长的前缀了。
请大家考虑这样一种情况:有两个很长的名称空间,我需要使用它们中的类,但不巧的是这两个名称空间里的类又有很多是重名的 …… 怎么办呢?呵呵,我们可以使用名称空间的别名来解决这个问题:
using Software = Microsoft.Google.Adobe.RedHat.CA;
using Hardware = IBM.Sun.RedHat.Dell.Lenovo.HP.Oracle;
这样,即解决了输入字符过多的问题,又解决了类名冲突的问题:
Software.Company c1 = new Software.Company();
Hardware.Company c2 
= new Hardware.Company();
 
XAML 名称空间跟 C# 的名称空间别名类似,但不完全一样。先让我们看那个 x x 其实就是一个简写的名称空间啦! xmlns 就是 XML Namespace 的简写,意思是要声明一个名称空间。
这句话的意思就是:声明一个名为 x 的名称空间( xmlns 与名称空间的名字间用冒号隔开)。后面为什么要跟一个 网址 呢?呵呵,我们都被骗了 —— 那根本不是一个网址,不信你用 IE 试试。其实,它就是一个普通的字符串,你尽可以把它当成 “Microsoft.WinFX.XAML” 来理解。但值得注意的一点是:这个字符串不只代表着一个名称空间,而是代表了一组名称空间,这组名称空间合称 “XAML 语言空间 ”—— 因此,它的名字是 x 。换句话说,这个 x 相当于一下子引用了好几个名称空间进来,这几个名称空间在 .NET Framework 里都能查到,包含这些名称空间里的类都是与 XAML 语言的语法、特性、功能有关的。
XAML 中,想使用某个名称空间里的类就要使用 名称空间 + 冒号 + 类名 的格式,所以:
x : Class 的意思是使用 x 名称空间里名为 Class 的类。类似地,以后我们还会看到 x:Static x:Type x:XData 等等,这都是在使用 x 这个名称空间里的类。
与声明 x 名称空间类似,这儿还有一句:
xmlns =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] "
这回的 网址 与前面的不一样,最后一个词是 “presentation” ,顾名思义,这回引用进来的一组名称空间一定是与显示相关的。说对了!比如 System.Window.Control 这个 .NET Framework 的名称空间就包含在里面,这个名称空间里几乎包含了所有 WPF UI 元素( WPF 里,我们称控件为 UI 元素 )。  
你可能会问:这不是在声明名称空间吗!名字哪儿去了?
问的非常好!当 xmlns 后面没有跟随名称空间的名字时,就相当于省去了名称空间的名字,当使用这个名称空间中的类时就无需再加前缀(根本没前缀可加,怎么加?)。换句话说,当一个类名前面没有前缀时, 默认 就是此名称空间里的类。因此,它称为 默认名称空间 。这个用法跟 using  System 差不多。 BTW :默认名称空间只能有一个。
大家可以动手试试这样做,把 xmlns =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] " 改成 xmlns:n =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] ",    这时候程序就编译不过去了。当你把后面的 <Grid> 元素改成 <n:Grid> </n:Grid> 后,就又可以通过编译了。
  最后, Title ="Window1"  Height ="300"  Width ="300" 的意思是设置 Window1 类(也可以说是 Window 类)的几个实例属性。这种语法称为 使用标签的 attribute 设置对象的 property” ,碰巧, attribute property 这两个词都被译为了 属性 ,所以这句话就没法翻译了。除了使用 attribute 设置对象 property 的语法外, XAML 还支持使用子元素方式设置元素属性的语法。下面这段代码与原代码是等价的:
<Window x:Class="HelloWPF.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">
    
<Window.Title>Window1</Window.Title>
    
<Window.Height>300</Window.Height>
    
<Window.Width>300</Window.Width>
    
<Grid>
        
    
</Grid>
</Window> 
大家注意啦! <Window.Title> <Window.Height> <Window.Width> 叫做 属性元素 ,表示它虽然是一个子元素,但它是父元素的一个属性;而 <Grid> 则是一个普通元素,而非 <Window> 元素的属性 —— 它们虽然都是 <Window> 的组成元素,但不是一个圈子里的(请跳转到上面,看看准备知识)。总有初学者问我: 反正 Title 也是 Window 的一个组成部分,能不能写成 < Title > Window1 </ Title > 啊? 幽默点讲, XAML 解析器没那么聪明;地道的说法是,从物理上讲,并没有 <Title> 这个 UI 元素;从 XAML 语法上讲,这样会造成语义上的含混、远不及 <Window.Title> 来得清晰。
啰嗦一句:当对象的 property 用一个简单的 string 就能描述清楚时,完全没必要使用子元素式语法小题大作。当对象的属性是一个复杂的对象时(你想用 attribute 式语法都办不到),再使用子元素式语法。
到此,一个最简单的 WPF 程序(的 XAML 部分)就算分析完了。本文成于仓促,之间有不少不严谨的地方,我会慢慢修改。大家有什么好的建议,请在文后盖楼。
~~~ 看来今天是 Hello 不了 WPF 鸟,以后再说吧 ~~~
 









本文转自 水之真谛 51CTO博客,原文链接:http://blog.51cto.com/liutiemeng/95263,如需转载请自行联系原作者
目录
相关文章
|
3月前
|
C#
WPF/C#:程序关闭的三种模式
WPF/C#:程序关闭的三种模式
60 0
|
5月前
|
C#
WPF/C#:程序关闭的三种模式
WPF/C#:程序关闭的三种模式
76 3
|
C#
WPF防止程序多次运行
WPF防止程序多次运行
220 0
WPF界面无法正常显示(资源引用,如转换器),但程序正常运行
WPF界面无法正常显示(资源引用,如转换器),但程序正常运行
WPF界面无法正常显示(资源引用,如转换器),但程序正常运行
WPF从外部文件或者程序集加载样式或其他静态资源
WPF从外部文件或者程序集加载样式或其他静态资源
WPF从外部文件或者程序集加载样式或其他静态资源
|
Java C# 程序员
WPF程序中的弱事件模式
原文:WPF程序中的弱事件模式 在C#中,得益于强大的GC机制,使得我们开发程序变得非常简单,很多时候我们只需要管使用,而并不需要关心什么时候释放资源。但是,GC有的时并不是按照我们所期望的方式工作。 例如,我想实现一个在窗口的标题栏中实时显示当前的时间,一个比较常规的做法如下:     var...
1144 0
|
C#
WPF整理-跨程序集访问资源
原文:WPF整理-跨程序集访问资源 “Sometimes binary resources are defined in one assembly (typically a class library), but areneeded in another assembly (another class library or an executable).
972 0
|
C#
WPF 启动唯一程序(项目,exe,实例)
原文:WPF 启动唯一程序(项目,exe,实例) 描述:用户多次快速开启WPF程序的时候  只运行起来 一个 程序(exe) 其他多开的 进程 自动关闭掉 App.xaml.cs文件   1 protected override void OnStartup(Startu...
1276 0
|
C# Windows
WPF:如何为程序添加splashScreen?
原文:WPF:如何为程序添加splashScreen? 大家是否还记得在Windows Forms程序中如何实现splashScreen吗?我们一般都会使用Microsoft.VisualBasic.dll中提供的那个WindowsFormsApplicationBase类型,它有一个所谓的splashscreen属性,可以指定为一个窗体的。
893 0
|
C# API
在WPF程序中将控件所呈现的内容保存成图像
原文:在WPF程序中将控件所呈现的内容保存成图像 有的时候,我们需要将控件所呈现的内容保存成图像保存下来,例如:InkCanvas的手写墨迹,WebBrowser中的网页等。可能有人会说,这个不就是截图嘛,找到控件的坐标和大小,调用截图API不就可以了嘛。
1129 0