在 WPF 中, 控件有 Loaded 和 Initialized 两种事件. 初始化和加载控件几乎同时发生, 因此这两个事件也几乎同时触发. 但是他们之间有微妙且重要的区别.。具体区别如下
概念介绍
Initialized 事件只说: 这个元素已经被构建出来,并且它的属性值都被设置好了。所以通常都是子元素先于父元素触发这个事件。当一个元素的 Initialized 事件被触发, 通常它的子树都已经初始化完成, 但是父元素还未初始化(此处父元素是指xaml的根节点本身)。这个事件通常是在子树的 Xaml 被加载进来后触发的. 这个事件与 IsInitialized 属性相互绑定。
Loaded 事件说: 这个元素不仅被构造并初始化完成,布局也运行完毕,数据也绑上来了,它现在连到了渲染面上(rendering surface),秒秒钟就要被渲染的节奏。到这个时候,就可以通过 Loaded 事件从根元素开始画出整棵树。 这个事件与 IsLoaded 属性绑定.。
Loaded 通常是元素初始化序列中最后引发的事件, 它总是在 Initialized 之后引发。选择处理 Loaded 还是 Initialized 取决于您的需求。
如果不需要读取元素属性,也不需要获取任何布局信息,而只是希望重置属性,则最好执行 Initialized 事件。
如果希望元素的所有属性都可用,并且将设置可能重置布局的属性,则最好执行 Loaded 事件。
如果布局系统解释为需要新布局处理过程的所有属性都被处理程序重置,则在重新进入时应小心。(如果不确定哪些属性在更改时需要新的布局处理过程,可能需要检查属性的 FrameworkPropertyMetadata 值。)
Initialized 事件
这个事件在所有子元素都被设置完成时触发. 具体来说, FrameworkElement/FrameworkContentElement 实现了 ISupportInitialize 接口, 当该接口的 EndInit 方法调用时, IsInitialized 值被设置为 true. 事件就被触发了.。
ISupportInitialize 在 WPF 之前就存在了. 有这个接口, 你就可以在设置 control 的某个属性时,提前告知它你要开始执行一个批处理,之后再告诉它你已经做完了.这样实现了这个接口的对象就可以推迟它的属性值修改事件的处理直到 EndInit 被调用. 在 WPF 中, 不只是 element 用这个接口来触发 Initialized 事件, 其他对象如 DataSourceProvider 也实现这个接口.
那么到底什么时候调用 EndInit 方法? 起点在 Xaml 加载器.(如果你懂 Baml 的话, 这个方法 Baml 加载器也会调用.) Xaml 加载器在构造对象时就调用 BeginInit. (也就是看见了起始标签), 然后在结束标签那里调用 EndInit 方法. 例子如下:
. 创建一个 Button 对象, 调用 BeginInit, 设置宽度属性, 设置内容属性, 调用 EndInit 方法.。
从 Initialized 事件的定义中, 可以看出, 这个事件必定是由下向上触发的, 也就是说父元素不应该被初始化直到子元素被初始化完成. 所以通常情况下都是子元素先于父元素被初始化. 不过这一点无法保证, 因为任何人都有可能调用 ISupportInitialize. 如果从 Xaml 中加载元素的话, 就没有任何问题。
如上代码所示, 元素要这个事件干嘛用? 元素无法获取别处定义的 styles 直到初始化事件触发. 例如 Button1 会从这个 style 中获取一个蓝色的背景. 但是在初始化事件之前, 这个背景是null.
Loaded 事件
Loaded事件是自顶向下以广播方式触发,从根节点,由父向子触发。而Initialized 事件是自底向上触发,先子后父。
Loaded 事件在元素即将要被渲染时触发. 设计这个事件是考虑你可能需要在程序加载期间做一些初始化操作.
用 Initialized 事件也可以满足这个要求, 因为这个事件意味着元素已经被构建出来, 而且它的元素值也被设置过. 但这个事件还是少了点东西. 举个例子, 你可能需要知道一个元素的 AcutualWidth 属性值, 但是初始化事件触发时, 实际宽度还没有计算出来(是因为具体加载事件顺序不同,导致对应值不会在初始化计算). 或者你想要看数据绑定的值, 这个值一样也还没有设定.
所以, 我们提供了 Loaded 事件. 它可以在窗口渲染完成, 但是还没有执行任何交互时触发. 我们原本以为控件在可以接受输入的时候做加载是初始化操作就够了. 但是当我们开始在加载事件中触发动画时, 我们发现了一个问题. 有那么一小会, 你会发现元素内容在渲染时没有动画效果, 过后你才会看到动画效果. 你可能没有发现这个问题, 但是这个问题在远程运行程序时会很明显.
所以我们移动了这个事件, 保证在这个事件之前数据绑定和布局有充足的事件执行,同时保证在第一次渲染前触发.(注意如果你要在加载事件中做任何使布局失效的操作, 那一定要记得在渲染前重新运行下布局. )
因为整棵元素数在同一时间走到 Loaded 事件,这个事件会在整棵树内广播. 广播从根元素开始, 所以加载事件是从父元素到子元素.
属性是鸡, 事件是蛋.
在 WPF 中, 如果有一个属性以及一个和该属性相关的事件, 通常都是修改该属性值来触发该事件. 例如对于 ListBox, 总是修改 SelectedItem 属性值, 触发了SelectionChanged 事件, Loaded 和 Initialized 事件也遵循这个模式.
对于 Loaded 事件有点特殊, 在任何元素的 Loaded 事件触发前, IsLoade 属性在整棵元素树中被设置. 也就是说, 元素树内的所有元素的 IsLoaded 值被设置为 true 之后, 所有元素的 Loaded 事件才被触发.
现在回过头来看上面 Page 中 的 Button 的例子,从 Xaml 文件中加载这个page, 你应当会看到以下的执行顺序.
Button.IsInitialized goes true
Button.Initialized event is raised
Page.IsInitialized goes true
Page.Initialized event is raised
Page IsLoaded goes to true
Button IsLoaded goes to true
Page.Loaded is raised
Button.Loaded is raised
WPF事件执行顺序
BeginInit 初始化开始
EndInit 初始化结束
OnInitialized 触发初始化时间
MeasureOverride 计算内部控件所需空间
ArrangeOverride 排列内部控件
GetLayoutClip 计算控件实际显示大小,如果元素大小超过可用的显示空间,将自动进行剪切
OnRender 渲染窗口
OnRenderSizeChanged 更新窗口的ActualHeight和ActualWidth
MeasureOverride
ArrangeOverride
Loaded 控件加载
OnContentRendered 内部渲染