WPF 中的 loaded 事件和 Initialized 事件

在 WPF 中, 控件有 Loaded 和 Initialized 两种事件. 初始化和加载控件几乎同时发生, 因此这两个事件也几乎同时触发. 但是他们之间有微妙且重要的区别. 这些区别很容易让人误解. 这里介绍我们设计这些事件的背景. (不仅适用于 Control 类, 同样在通用类如 FrameworkElement 和 FrameworkContentElement 类也适用.)

下面是个小故事:

  • Initialized 事件只说: 这个元素已经被构建出来,并且它的属性值都被设置好了,所以通常都是子元素先于父元素触发这个事件.当一个元素的 Initialized 事件被触发, 通常它的子树都已经初始化完成, 但是父元素还未初始化. 这个事件通常是在子树的 Xaml 被加载进来后触发的. 这个事件与 IsInitialized 属性相互绑定.
  • Loaded 事件说: 这个元素不仅被构造并初始化完成,布局也运行完毕,数据也绑上来了,它现在连到了渲染面上(rendering surface),秒秒钟就要被渲染的节奏.到这个时候,就可以通过 Loaded 事件从根元素开始画出整棵树. 这个事件与 IsLoaded 属性绑定.

如果你不确定该用哪个事件, 而且也不想继续读下去, 那就用 Loaded 事件好了, 通常它都是对的.

然后, 就是整个故事了.

Initialized 事件

这个事件在所有子元素都被设置完成时触发. 具体来说, FrameworkElement/FrameworkContentElement 实现了 ISupportInitialize 接口, 当该接口的 EndInit  方法调用时, IsInitialized 值被设置为 true. 事件就被触发了.

ISupportInitialize 在 WPF 之前就存在了. 有这个接口, 你就可以在设置 control 的某个属性时,提前告知它你要开始执行一个批处理,之后再告诉它你已经做完了.这样实现了这个接口的对象就可以推迟它的属性值修改事件的处理直到 EndInit 被调用. 在 WPF 中, 不只是 element 用这个接口来触发 Initialized 事件, 其他对象如 DataSourceProvider 也实现这个接口.

槽点是, 到底什么时候调用 EndInit 方法? 起点在 Xaml 加载器.(如果你懂 Baml 的话, 这个方法 Baml 加载器也会调用.)  Xaml 加载器在构造对象时就调用 BeginInit. (也就是看见了起始标签), 然后在结束标签那里调用 EndInit 方法. 例子如下:

<Button Width="100">
  Hello
</Button>

...创建一个 Button 对象, 调用 BeginInit, 设置宽度属性, 设置内容属性, 调用 EndInit 方法.

如果用代码来构建元素, 你也可以自己调用 BeginInit/EndInit 方法. 有个问题就是, 不用 Xaml 构造, 也不自己调用 EndInit 方法, 那初始化事件还能触发吗? 答案必须是 yes. 所以我们提供了一些别的方式来设置 IsInitialized 值, 触发事件.

  • 当一个未初始化的元素被加到可视化树中时, 初始化事件被触发. 这个方法对于所有的非根元素都有效. 至于根元素, 所有的根元素都是从 PresentationSource 中来的, 所以你懂的...
  • 当一个未初始化元素被加入到 PresentationSource 中的时候, 初始化事件会被近似的触发.

从 Initialized 事件的定义中, 可以看出, 这个事件必定是由下向上触发的, 也就是说父元素不应该被初始化直到子元素被初始化完成. 所以通常情况下都是子元素先于父元素被初始化. 不过这一点无法保证, 因为任何人都有可能调用 ISupportInitialize. 从 Xaml 中加载元素的话, 这点倒是可以保证.

另一个槽点是, 元素要这个事件干嘛用? 元素无法获取别处定义的 styles 直到初始化事件触发. 例如 Button1 会从这个 style 中获取一个蓝色的背景. 但是在初始化事件之前, 这个背景是null.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" >
  <Page.Resources>
    <Style TargetType="Button">
      <Setter Property="Background" Value="Blue" />
    </Style>
  </Page.Resources>

  <Button Name="Button1">Clack</Button>
</Page>

Loaded 事件

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
时间: 2024-12-25 18:56:30

WPF 中的 loaded 事件和 Initialized 事件的相关文章

MVVM设计模式和在WPF中的实现(四) 事件绑定

系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中的实现(三)命令绑定 MVVM模式解析和在WPF中的实现(四)事件绑定 MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信 MVVM模式解析和在WPF中的实现(六)用依赖注入的方式配置ViewModel并注册消息 0x00 为什么要事件绑定 这个问题其实是很好理解的,因为事件是丰富多样的,单纯的命令绑定远不能覆盖所有的事件.例

WPF 中如何屏蔽多点触控事件?

由于项目中还没有更好的多点触控思路,所以需要将多点触控暂时关闭: 关闭多点触控的代码只有一行: ? private void image_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { e.Mode = ManipulationModes.None;  } 留个记录,以备日后用; 但是,ManipulationModes.None并不是乱用的,详情参照MSDN: http://msdn.microsof

WPF: WPF 中的 Triggers 和 VisualStateManager

在之前写的这篇文章 WPF: 只读依赖属性的介绍与实践 中,我们介绍了在 WPF 自定义控件中如何添加只读依赖属性,并且使其结合属性触发器 (Trigger) 来实现对控件样式的改变.事实上,关于触发器,在 WPF 中除了属性触发器,还有事件触发器 (EventTrigger) 和数据触发器 (DataTrigger).此外,为了控制控件外观的切换,除了可以使用触发器外,我们还可以使用 VisualStates 和 VisualStateManager 来完成. 本文接下来会分别简单地介绍 Tr

WPF中的事件列表 .

以下是WPF中的常见事件汇总表(按字母排序),翻译不见得准确,但希望对你有用. 事件 描述 Annotation.AnchorChanged 新增.移除或修改 Anchor 元素时发生. Annotation.AuthorChanged 新增.移除或修改 Author 元素时发生. Annotation.CargoChanged 新增.移除或修改 Cargo 元素时发生. AnnotationStore.AnchorChanged 存放区中任何注释上的 Anchor 元素变化时发生. Annot

正确处理WPF中Slider值改变事件的方式

最近在用WPF数据绑定重写一下播放器项目时遇到的关于Slider的问题,在窗体透明度调节和播放进度调节上用了Slider控件.调节窗体透明度我是 这么想的:将窗体的Opacity属性的值与Slider的值绑定不就可以了,Opacity="{Binding ElementName=Slider,Path=Value,Mode=OneWay}",这样根本不用处理Slider的值改变事件 (ValueChanged).不过我要做保存设置的功能,因此肯定要记录Slider的值咯,于是处理一下V

WPF快速指导10:WPF中的事件及冒泡事件和隧道事件(预览事件)的区别

本文摘要: 1:什么是路由事件: 2:中断事件路由: 3:自定义路由事件: 4:为什么需要自定义路由事件: 5:什么是冒泡事件和预览事件(隧道事件): 1:什么是路由事件 WPF中的事件为路由事件,所谓路由事件,MSDN定义如下: 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件. 实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (W

WPF中C#代码触发鼠标点击事件

1.如下代码; 1 <Button x:Name="btnTest" Click="btnTest_Click"> 2 <Button.Triggers> 3 <EventTrigger RoutedEvent="Button.Click"> 4 <BeginStoryboard> 5 <!--要执行的动画代码--> 6 </BeginStoryboard> 7 </E

WPF中的Visual Tree和Logical Tree与路由事件

1.Visual Tree和Logical TreeLogical Tree:逻辑树,WPF中用户界面有一个对象树构建而成,这棵树叫做逻辑树,元素的声明分层结构形成了所谓的逻辑树!!Visual Tree:可视树(也叫视觉树),可视树是对逻辑树的扩展,可视树将逻辑树的节点打散,分放到核心棵树组件中,它表述了一些详细的可视化实现,而不是把每个元素当做一个”黑盒“.我们以一个简单的程序来观察下逻辑树与可视树: <Window x:Class="WpfApplication28.MainWind

WPF中路由事件的传播

路由事件(RoutedEvent)是WPF中新增的事件,使用起来与传统的事件差别不大, 但传播方式是完全不同的. 路由事件的传播方式 通过RoutingStrategy来定义传播的方式 public enum RoutingStrategy { Tunnel = 0, //隧道,由顶层元素向内传播,事件一般以Preview开头 Bubble = 1, //冒泡,与隧道相反,向外传播 Direct = 2, //直接,与传统的事件相似 } WPF中的路由事件用的最多的就是Tunnel和Bubble