一个 MainWindow.xaml 文件和一个 MainWindow.g.i.cs 文件相对应。
如上图所示窗口类文件被定义为partial的就是因为类 MainWindow 在 MainWindow .g.i.cs文件中还有定义,如下
在 MainWindow.g.i.cs 文件中 MainWindow 的完整声明如下:
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector
注意到: MainWindow 类除了继承了System.Windows.Window类(这个在 MainWindow.xaml.cs文件中已有声明)还实现了接口:System.Windows.Markup.IComponentConnector
在MSDN中查看该接口的说明:
(显然,这个中文的翻译是机器翻译的。)IComponentConnector接口的功能是:解析 MainWindow.xaml 文件然后将该文件中所有标签中具有Name或x:Name属性的标签声明与该标签对应的对象,例如在MainWindow.xaml中下面的标签
在 MainWindow.g.i.cs 文件中生成了如下的语句
这也就解释了为什么我们可以直接在 MainWindow.xaml.cs文件中的类MainWindow可以直接使用由x:Name或Name来标示的对象------因为他们都在 MainWindow.g.i.cs 文件中进行了声明。但是光有这些声明是不能够使用这些对象的,因为他们没有被初始化!这一就是为什么在 MainWindow.g.i.cs 文件中定义的 MainWindow 类还要实现接口 IComponentConnector 的原因-----初始化上面那些组件。初始化的过程有两步:第一步:在内存中构建这些对象的实例(对应接口IComponentConnector中的InitializeComponent方法);第二步:将第一步中内存中的对象实例赋给上面那些声明的对象引用,这样这些引用才能够使用(对应接口IComponentConnector中的Connect方法)。
IComponentConnector 接口有两个方法:
下面为InitializeComponent方法的一个实现
public void InitializeComponent()
{
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/WpfApplication1;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
}
显然这个方法的作用就是:① 加载 MainWindow.xmal 文件;② 解析该XMAL文件,并在内存中构造该xaml文件中出现的命名对象。(参考msdn中对Application.LoadComponoent方法的说明:加载位于指定uniform resource identifier (URI) 处的 XAML 文件,并将其转换为由该 XAML 文件的根元素指定的对象的实例。)
下面为Connect方法的实现:
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target)
{
switch (connectionId)
{
case 1:
this.mainWindow = ((WpfApplication1.MainWindow)(target));
#line 4 "..\..\..\MainWindow.xaml"
this.mainWindow.Loaded += new System.Windows.RoutedEventHandler(this.mainWindow_Loaded);
return;
case 2:
this.label1 = ((System.Windows.Controls.Label)(target));
return;
case 3:
this.label2 = ((System.Windows.Controls.Label)(target));
return;
case 4:
this.textBoxName = ((System.Windows.Controls.TextBox)(target));
return;
}
this._contentLoaded = true;
}
由这个实现可知,此时MainWindow.g.i.cs文件中声明的那些对象引用就可以正确使用了。
在考虑这几个函数的调用过程。
1.在MainWindow.xaml.cs文件中MainWindow类的构造函数调用了MainWindow.g.i.cs中的InitializeComponent方法
2. 在看InitializeComponent方法的实现,最后调用了LoadComponent方法,可以猜想InitializeComponent方法一定调用了Connect方法来实现应用对象的初始化。(如果这个方法没有调用Connect方法,那么上面那些对象就无法被实例化,就不能在MainWindow类中使用了)
下面通过扒源码来证明上面的猜想:
(1).将上面的项目生成的PE文件WpfApplication1.exe进行反编译,
(2). 找到InItializeComponent方法的实现
(3). 进入Application.LoadComponent方法的实现
结合LoadComponent方法签名,可知该方法将this对象传给了XamlReader.LoadBaml方法
(4). 进入XamlReader.LoadBaml方法,该方法的签名如下:
如上图this对象又被传给了WpfXamlLoader.LoadBaml方法
(5). 跟进WpfXamlLoader.LoadBaml方法
注意到this对象被传入到方法Load中
(6). 跟进Load方法
该方法将this对象(这个方法中的名称为rootObject)转换成IStyleConnector对象styleConnector,并将该对象传入TransformNodes方法
(7). 跟进TransformNodes方法
通过七次方法的调运 We find it!
总结:InitializeComponent方法确实调用了Connect方法。
同时我们也知道了WPF中的一个窗口是由3个文件组成的*.xaml,*.xaml.cs,*.g.i.cs
由上面的分析可知,只有MainWindow的构造函数中的InitializeComponent方法被调用完毕之后才可以使用MainWindow.xaml中的那些由标签“创建”的对象,如果在之前调用,会出现什么情况呢?
如上图,在InitializeComponent方法之前使用了 label1 对象,给该对象设置宽度。注意下面,编译成功了!但是这个程序是无法执行的,因为label1是null,所以this.label1.Width = 100这个赋值操作会出现异常。