WPF Window 的几个重要事件的引发顺序
首先执行构造器,并且在默认无参构造器中调用 InitializeComponent() 方法;
引发 Initialized ;
(如果是在实例化之后,立即调用 Show() ) 引发 IsVisibleChanged;
(如果是在实例化之后,立即调用 Show() ) 引发 Activated;
引发 Loaded ;
注意:
在XAML中设置ComboBox.SelectedIndex 或 ComboBox.SelectedItem。当Items为空时,不会引发异常;当Items首次被赋值为非空集合时,SelectedIndex 会生效(初始化未完成或完成后的行为都相同)。但是如果在 ContentRendered 引发以后(包括引发时)对ComboBox设置选定项,如果赋值的新选定项与刚赋值Items集合时自动选中的项目相同,则不会引发 SelectionChanged 事件。
正确的实践应当是:XAML中不设置当前选定项(项或序号都不要设置);在引发 ContentRendered 时,修改 ItemsControl.SelectedItem 或 ItemsControl.SelectedIndex 属性。
但是,在 Window.ContentRendered 引发之前(包括引发时),SelectionChanged事件不会生效(即使在XAML中已经订阅了该事件)
并且,每当窗体从“非前台”切换到“前台”时,都会引发 Activated 事件。
首先简要说明一下:
Window 有几个重要的事件:
他们之间的调用顺序是:
Window.Initialized
Window.Activatied
Window.Loaded
Window.ContentRendered
Window.DeActivatied
Window.Closing
Window.Closed
Window.UnLoad
注意 :Window.Activatied可能会与Window.DeActivatied 切换多次(窗体切换的时候发生)
有一篇来自 MSDN 博客的文章:
https://blogs.msdn.microsoft.com/mikehillberg/2006/09/19/the-loaded-event-and-the-initialized-event/
The Loaded event and the Initialized event
In WPF, controls have both a Loaded even and an Initialized event. Initializing and loading a control tend to happen at about the same time, and consequently these events fire at roughly the same time. But they have slightly – though important – different meanings, and the differences can be a source of confusion. So here’s some background on how we designed these events. (And this doesn’t just apply to the Control class in particular, but to the FrameworkElement and FrameworkContentElement classes in general.)
Here’s the short story:
- The Initialized event says just that an element has been created and its properties have all been set, and as a consequence this usually fires on children before their parent. So when Initialized is raised on an element, its whole sub-tree is likely initialized, but its parent is not. The Initialized event is typically fired when the Xaml for a sub-tree is loaded. This event corresponds to the IsInitialized property.
- The Loaded event says that the tree is not only built and initialized, but layout has run on it, data has been bound, it's connected to a rendering surface (window), and you're on the verge of being rendered. When we reach that point, we canvas the tree by broadcasting the Loaded event, starting at the root. This event corresponds to the IsLoaded property.
If you’re not sure which event to use, and you don’t want to read any more, use the Loaded event; it’s more often the right choice.
And here’s the whole story …
Initialized Event
The Initialized event typically fires when the properties of an element have all been set. Specifically, FrameworkElement/FrameworkContentElement implement ISupportInitialize, and when the EndInit method of that interface is called, the IsInitialized property is set to true, and the Initialized event is fired.
ISupportInitialize has existed since before WPF, and exists so that when you’re about to set several properties on a control, you can tell it ahead of time that you’re about to set a batch (BeginInit), and tell it afterwards that you’re done (EndInit). An object that implements ISupportInitialize is allowed to defer property change handling until after the EndInit is called. So in WPF, elements use this to trigger the Initialized event and IsInitialized property. Other objects in WPF implement ISupportInitialize too, such as DataSourceProvider.
The interesting thing is seeing when EndInit gets called. The primary place it’s called is by the Xaml loader (if you’re familiar with Baml, it’s called by the Baml loader too.) The Xaml loader calls BeginInit right after it creates an object (i.e. after the start tag), and calls EndInit right after finishing with the Xml tree rooted at that tag (i.e. after the end tag). For example, in this Xaml:
<Button Width="100">
Hello
</Button>
… the Button will be created, BeginInit will be called, the Width property will be set, the Content property will be set (to “Hello”), and then EndInit will be called.
If you’re creating elements from code, you can call BeginInit/EndInit too, just like the Xaml loader does. That begs a question, though – if you don’t create elements using Xaml, and you don’t call ISupportInitialize, does the Initialized event ever fire? We knew that the answer to that had to be yes, so we added support to raise it and set IsInitialized on a couple of other cues:
- When an un-Initialized element is added to a Visual tree, the Initialized event is raised.
That works well for all elements except the root element. But the root element is ultimately connected to a PresentationSource to get rendered. So … - When an un-Initialized element is connected to a PresentationSource (a window), the Initialized event is similarly raised.
By the nature of the definition of Initialized, it typically fires “bottom-up”. That is, a parent shouldn’t be considered Initialized until the child is Initialized, therefore Initialized typically fires on all children elements before their parent. There’s no guarantee of this, though, because anyone can call ISupportInitialize. But when you’re loading from Xaml, it’s pretty much assured.
There's one other interesting thing about what elements do with the Initialized event; elements don’t get their implicit styles until it is raised. For example, the Button1 element below gets a blue Background property from the style, but before the button fires Initialized, its Background is 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 Event
The Loaded event fires when an element is not only initialized, but it is about to be rendered. The motivation for the Loaded event is the typical scenario where you want to do some initialization in your application at load-time.
The Initialized event is nice for this purpose too, because it means that an element has been created and its properties have been set. But it misses some things. For example, you may want to know the ActualWidth of an element, but when Initialized is fired, the ActualWidth value hasn’t been calculated yet. Or you may want to look at the value of a data-bound property, but that hasn’t been calculated yet either.
To deal with this, we initially implemented the Loaded event so that it would fire just after the window was rendered, but before any input was processed. We figured that if it was ready enough for input, it was ready enough for load-time initialization. But then we started to trigger animations off of the Loaded event, and saw the problem; for a split second you’d see the content render without the animation, then you’d see the animation start. You might not always notice it, but it was especially noticeable when you run the app remotely.
So we moved Loaded so that it now fires after layout and data binding have had a chance to run, but just before the first render. (And note that if you do anything in your Loaded event handler that invalidates layout, it might be necessary to re-run it before rendering.)
Since a whole element tree goes Loaded at the same time, the event is essentially broadcast throughout the tree. This broadcast starts at the root, and as a consequence the Loaded event fires on a parent before its children. So whereas the Initialized event usually goes “bottom-up”, firing on children before the parent, the Loaded event goes “top-down”, firing on a parent before its children.
The Property is the Chicken, the Event is the Egg
One last interesting question for both of these events is – do you set the property and raise the event, or raise the event and set the property? Conceptually they both happen at the same time, but in reality it has to be one before the other.
There might be some exceptions, but in general across WPF, when there's a property and a corresponding property-changed event for it, the property is set before the event is fired. For example, on a ListBox, the SelectedItem property has been updated by the time the SelectionChanged event is raised. So Initialized/IsInitialized and Loaded/IsLoaded follow this same pattern.
The unique thing about the Loaded event and the IsLoaded property, is that the IsLoaded property is updated throughout the element tree before the Loaded event is fired on any of the elements. That is, all the elements in a tree get their IsLoaded property set to true, and then all the elements in the tree get their Loaded event raised.
So putting these together, looking at the above example of a Button inside a Page, if you load it from Xaml the sequence you should see is:
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
CSDN有一篇翻译自这篇博文的中文文章:
https://blog.csdn.net/changtianshuiyue/article/details/43201409
在 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 事件在元素即将要被渲染时触发. 设计这个事件是考虑你可能需要在程序加载期间做一些初始化操作. --------------------- 本文来自 changtianshuiyue 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/changtianshuiyue/article/details/43201409?utm_source=copy用 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 构造函数InitializeComponent()和Loaded()
写在Xaml中的控件初始化,发生在InitializeComponent中,所有控件都加载完毕后,会调用Loaded().
不要滥用Loaded()!
Loaded 通常是元素初始化序列中最后引发的事件, 它总是在 Initialized 之后引发。选择处理 Loaded 还是 Initialized 取决于您的需求。
如果不需要读取元素属性,也不需要获取任何布局信息,而只是希望重置属性,则最好执行 Initialized 事件。
如果希望元素的所有属性都可用,并且将设置可能重置布局的属性,则最好执行 Loaded 事件。
如果布局系统解释为需要新布局处理过程的所有属性都被处理程序重置,则在重新进入时应小心。(如果不确定哪些属性在更改时需要新的布局处理过程,可能需要检查属性的 FrameworkPropertyMetadata 值。)
有关 FrameworkElement 的对象事件序列以及一些相关应用程序和元素类的更多信息,请参见对象生存期事件。
直接路由事件不遵循路由,而是仅在引发它们的元素中处理。 直接路由事件确实支持其他路由事件行为:它们支持可访问的处理程序集合,并且可以用作样式中的EventTrigger。
当用户启动的系统主题更改时,可能会在控件上同时引发 Loaded 和 Unloaded。 主题更改会导致控件模板以及包含的可视化树失效;反过来,这又会导致卸载和重新加载整个控件。 因此,不能假定仅在通过导航到某页而首次加载该页时才发生Loaded。
PS:在TabItem中添加UserControl,每次TabControlSelectionChanged函数之后都会调用一次UC的Loaded函数!