WPF窗体研究(一)
呵呵,好久没有发过文章了,今天补一篇。最近做WPF,虽然过程中很艰辛,但是自己很快乐,毕竟自己写代码,虽然很乱,但是觉得自己在进步,就不错了,希望所有的程序员都能找到自己的路,不是碌碌无为的为了赚钱。好了,开始写自己关于WPF窗体类的想法了。
WPF(Windows Presentation Foundation)窗体表现基础,是Winform的一个发展。跟Winform一样,也是用来编写桌面程序。所以窗口是少不了的。下面是MSDN里WPF的窗口
知识点:
1、一个窗口可以分为工作区和非工作区
2、要知道的是非工作区是由WPF实现的,在上面的图中包括:
边框、标题栏、图标、最大化最小化还原按钮、关闭按钮、系统菜单(包括最大化最小化还原移动和关闭按钮)
3、 工作区如上图也就是非工作区内部的区域。工作区用来添加应用程序特定的内容。
在WPF中使用Windows类进行封装,可以通过该类进行以下操作
①显示窗口。
②配置窗口的大小、位置和外观。
③承载应用程序特定的内容。
④管理窗口的生存期。
典型的窗口实现既包括外观也包括行为,外观就是用户所看到的窗口的样子,而行为则是用户与窗口交互时的运行方式。在WPF中可以使用代码和XAML标记语言来实现窗口的外观和行为。
不管是Web还是Winform,页面都要和代码隐藏文件进行关联,协同工作。而WPF中也是一样的,为了XAML和代码隐藏文件进行关联,必须实现以下的条件:
(1)在标记中,Window 元素必须包含 x:Class 属性。生成应用程序时,标记文件中存在的 x:Class 将使 Microsoft Build Engine (MSBuild) 生成一个从 Window 派生的 partial 类,并且该类的名称是由 x:Class 属性指定的。这要求添加 XAML 架构的 XML 命名空间声明 (xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml")。生成的 partial 类实现了 InitializeComponent 方法,调用此方法可注册事件并设置在标记中实现的属性。
(2)在代码隐藏中,该类必须是由标记中的 x:Class 属性指定名称的 partial 类,并且它必须从 Window 派生。这样,代码隐藏文件就与应用程序生成时为标记文件生成的 partial 类相关联。
(3)在代码隐藏中,window 类必须实现调用 InitializeComponent 方法的构造函数。InitializeComponent 是由标记文件的生成的 partial 类实现的,用来注册事件和设置在标记中定义的属性。
这里我主要说的是窗口的生命周期,就像我们研究WebForm一样要先研究它的生命周期一样。
打开窗口
要打开一个窗口,那么必须要创建这个窗口的一个实例。我们新建一个WPF应用程序的时候系统给我们默认添加了下面的构造。在APP.xaml中
<Application x:Class="WinformExanm.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Window1.xaml"> <Application.Resources> </Application.Resources> </Application> |
这里的StartupUri就是我们要启动的窗口。这样就创建了一个实例,或者是在xaml中不声明StartupUri而是在代码隐藏文件中添加Startup事件。如下:
public partial class App : Application { public App() { this.Startup += new StartupEventHandler(Application_Startup); } private void Application_Startup(object sender, StartupEventArgs e) { Window1 mywindow = new Window1(); mywindow.Show(); } } |
这样也可以达到效果,这样在应用程序启动时引发StartUp事件,然后创建Window1的实例并显示。在实例化窗口的时候,指向该窗口的引用会自动添加到Application对象管理的窗口列表(Windows,一个集合属性)中。另外在默认情况下,Application会将实例化的第一个窗口作为主应用程序窗口。
(一旦窗口在用户界面 (UI) 线程上实例化,一个 Window 引用会自动添加到 Windows;不会添加由辅助线程创建的窗口。Window 引用会在其 Closing 事件已处理之后且其 Closed事件引发之前自动移除。
默认情况下,添加到 Windows 属性的第一项成为 MainWindow。)对于Closing事件和closed事件后面讲解。
窗口所属权:
使用Show创建的窗口跟创建它的窗口没有什么隐式联系。那就是说我们可以对这两个窗口都进行交互。也就是说二者都可以进行以下操作:
1、 覆盖另一个窗口
例如:
我运行程序打开窗口二,然后窗口二覆盖窗口一,但是当我点击窗口一,那么窗口一就覆盖了窗口二。但是,如果有一个窗口的TopMost属性设置为True的话,那么就无法实现了。比如窗口二有此属性,那么就一直在最上方。
2、在不影响另一个窗口的情况下最大化最小化,当然了这是常识。
当然,如果希望某个窗口拥有某个窗口,且当关闭了某个窗口其余窗口也关闭,就可以用附加属性Owner来实现。如下代码
Window2 win2 = new Window2(); win2.Owner = this; win2.Show(); |
当建立了这种附属关系后,我们可以在Window1中通过OwnedWindows属性检查它的所有的附属窗口,而window2也可以通过owner检查它的所有者窗口
Window1代码如下
Window2 win2 = new Window2(); string strOwnerWindows="当前窗口的属性窗口有:"; win2.Owner = this; foreach (Window mywindow in this.OwnedWindows) { strOwnerWindows += mywindow.Title.ToString(); } this.ownderWindows.Content = strOwnerWindows; win2.Show(); |
Window2代码如下
string str = "当前窗口的拥有者是:"; this.label1.Content = str + this.Owner.Title.ToString(); |
关于窗口关闭、激活的原理,大家可以在msdn中查看,而且都讲的很详细
下面我主要就窗体的生成过程和生命周期做一个总结,因为MSDN上关于这块讲的比较晦涩,而且很笼统。下面是MSDN里关于生命周期中事件的触发顺序:
当我们编译运行Application的Startup事件创立一个窗口的实例的时候,会触发SourceInitiated事件,来获取窗口的HwndSource属性,也就是窗口句柄,这主要是为了可能与Win32交互要用到这个属性。当这个事件触发后,会触发Activiated事件,标明该窗口已被激活,当激活后就要实例化元素。
此时调用window的构造函数的InitializeComponent()方法,并解析拓展名为.i.cs的文件(这个文件就像是Windows的设计页面的作用是一样的,比如,我们修改页面就能实时反映到这个页面,当然修改这个文件也会实时在我们的页面显示)初始化所有元素。FrameworkElement类实现了ISupportInitialize的接口,而该接口提供了两个用于控制初始化过程的方法。分别是BeginInit和EndInit方法。当执行完EndInit后,通知元素初始化已经完成,并在该方法内触发Initialized事件,此时虽然元素的初始化完成了,但是数据的绑定,或者样式的应用还没有实现,这些工作就要在这个事件中进行。当这些完成后就要触发Loaded事件。
这里要注明一点就是Initialized事件必须在InitializeComponent()之前注册,这样才能触发,否则是调用事件默认构造。
当创建窗口的时候,每个元素分支都是从下向上的方式被初始化。这就意味着位于深层的元素在包容器之前被初始化。当引发初始化事件时,可以确保元素树种当前元素以下的子元素已经全部完成了初始化,但是,包含当前的元素可能还没有初始化,并且不能假定任何其他部分已经初始化。
在所有的元素都初始化完成后,还需要在他们的包容器中进行布局,应用样式,绑定数据源等等,这时候就是Initialized事件的天下了,当这些都完成之后,就要引发Loaded事件了,Loaded事件和Initialized事件的过程也正好是相反的,也就是说包含所有元素的窗口首先触发Loaded事件,然后才是更深层次的元素发生Loaded事件,窗口可见,元素呈现。
不知道自己说的够不够清楚,可能其中也有说错的地方,如果有的话希望能够给我留言指正。