<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>tabcontrol on 寒流の编程笔记</title><link>https://blog.coldwind.top/tags/tabcontrol/</link><description>Recent content in tabcontrol on 寒流の编程笔记</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Mon, 15 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.coldwind.top/tags/tabcontrol/index.xml" rel="self" type="application/rss+xml"/><item><title>WPF TabControl 控件的子元素的加载事件该如何对待</title><link>https://blog.coldwind.top/posts/tabcontrol-children-loaded-events/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/tabcontrol-children-loaded-events/</guid><description>&lt;p&gt;&lt;code&gt;TabControl&lt;/code&gt; 是 WPF 中一个相当常用的控件。当我们需要设计一个多页面的界面时，借助 &lt;code&gt;TabControl&lt;/code&gt; 可以很方便地实现页面的切换。然而，&lt;code&gt;TabControl&lt;/code&gt; 的一些行为可能不太符合我们的预期，尤其是关于子元素的加载与卸载的时机。具体是怎么一回事呢？&lt;/p&gt;
&lt;h2 id="tabitem-的原理"&gt;&lt;a href="#tabitem-%e7%9a%84%e5%8e%9f%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;TabItem 的原理
&lt;/h2&gt;&lt;p&gt;在探讨后面的内容之前，我们先简单分析一下 &lt;code&gt;TabControl&lt;/code&gt; 及 &lt;code&gt;TabItem&lt;/code&gt; 的底层原理，或者说在切换 Tab 后，相应的 &lt;code&gt;Content&lt;/code&gt; 是如何呈现出来的。&lt;/p&gt;
&lt;p&gt;从实现上来看，&lt;code&gt;TabControl&lt;/code&gt; 主要可以拆成两部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;上方用于切换的页签头，也就是每个 &lt;code&gt;TabItem&lt;/code&gt; 的 &lt;code&gt;Header&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;下方用于承载当前页内容的内容展示区域&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中，页签头通常会全部生成出来，因为用户需要看到所有可以切换的标签；但内容展示区域在同一时刻通常只会显示当前选中的那一项。微软文档在 &lt;code&gt;TabControl&lt;/code&gt; 的默认模板里明确给出了一个名为 &lt;code&gt;PART_SelectedContentHost&lt;/code&gt; 的部件，它的类型是 &lt;code&gt;ContentPresenter&lt;/code&gt;，并且通过 &lt;code&gt;ContentSource=&amp;quot;SelectedContent&amp;quot;&lt;/code&gt; 来展示当前选中项的内容。当选项卡切换时，这个属性会更新为当前活动 &lt;code&gt;TabItem&lt;/code&gt; 的 &lt;code&gt;Content&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以当切换 Tab 后，并不是 &lt;code&gt;TabItem&lt;/code&gt; 本身被加载到内容展示区域，而是它的 &lt;code&gt;Content&lt;/code&gt; 被加载。&lt;code&gt;TabItem&lt;/code&gt; 本身仍然是内容的拥有者，只是负责实际呈现的是内容区里的 &lt;code&gt;ContentPresenter&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;另外，如果应用了 MVVM 模式，那么通常还会跟视图模型打交道。如果 &lt;code&gt;Content&lt;/code&gt; 没有单独显式设置 &lt;code&gt;DataContext&lt;/code&gt;，它会继承 &lt;code&gt;TabItem&lt;/code&gt; 的 &lt;code&gt;DataContext&lt;/code&gt;。也就是说，切换时进入内容区的是 &lt;code&gt;Content&lt;/code&gt; 这棵界面树，而它绑定时所使用的数据上下文，默认仍然往往来自对应的 &lt;code&gt;TabItem&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="tabitem-的-loaded-事件"&gt;&lt;a href="#tabitem-%e7%9a%84-loaded-%e4%ba%8b%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;TabItem 的 Loaded 事件
&lt;/h2&gt;&lt;p&gt;然后我们来看一看 &lt;code&gt;TabItem&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 是什么时候触发的。稍加尝试后，我们会轻易地发现，&lt;code&gt;TabItem&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 事件只会在第一次被加载到界面树时触发一次。不仅如此，它们的加载顺序还有一些规律可循：它们会按照顺序加载，并且第一个被呈现的 Tab 的 &lt;code&gt;Loaded&lt;/code&gt; 事件会在其他 &lt;code&gt;TabItem&lt;/code&gt; 之后触发。&lt;/p&gt;
&lt;p&gt;例如有 A、B、C 三个 &lt;code&gt;TabItem&lt;/code&gt;，A 是默认选中的，那么它们的 &lt;code&gt;Loaded&lt;/code&gt; 事件触发顺序是 B、C、A。如果此时将 B 设为默认选中，那么顺序就是 A、C、B。&lt;/p&gt;
&lt;p&gt;不过这通常没有什么好关注的，因为 &lt;code&gt;TabItem&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 事件只会在第一次加载时触发一次，之后即使切换 Tab，也不会再触发。而且我们基本上也不会在它的 &lt;code&gt;Loaded&lt;/code&gt; 事件里做什么事情。&lt;/p&gt;
&lt;h2 id="content-的-loaded-事件"&gt;&lt;a href="#content-%e7%9a%84-loaded-%e4%ba%8b%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;Content 的 Loaded 事件
&lt;/h2&gt;&lt;p&gt;当涉及到 &lt;code&gt;TabItem.Content&lt;/code&gt; 时，问题就变得复杂了一些。首先我们尝试直接将 &lt;code&gt;Content&lt;/code&gt; 声明在 &lt;code&gt;TabItem&lt;/code&gt; 中，类似：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;TabControl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TabItem&lt;/span&gt; &lt;span class="na"&gt;Header=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;StackPanel&lt;/span&gt; &lt;span class="na"&gt;Loaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Loaded&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Unloaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Unloaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content A&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Loaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Loaded&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Unloaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Unloaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/StackPanel&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/TabItem&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TabItem&lt;/span&gt; &lt;span class="na"&gt;Header=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;StackPanel&lt;/span&gt; &lt;span class="na"&gt;Loaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Loaded&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Unloaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Unloaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content B&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Loaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Loaded&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Unloaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Unloaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/StackPanel&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/TabItem&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TabItem&lt;/span&gt; &lt;span class="na"&gt;Header=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;C&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;StackPanel&lt;/span&gt; &lt;span class="na"&gt;Loaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Loaded&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Unloaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Unloaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content C&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Loaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Loaded&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Unloaded=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content_Unloaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/StackPanel&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/TabItem&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/TabControl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此时我们会观察到，&lt;code&gt;TabItem&lt;/code&gt; 与 &lt;code&gt;Content&lt;/code&gt; 是交替加载的，而 &lt;code&gt;Content&lt;/code&gt; 又会先加载默认显示的页面的，然后再按顺序加载其他的。这就会产生一个很有意思的现象。还是以 A、B、C 三个页面举例子。我们如果默认显示 A，那么各控件的 Loaded 顺序将会是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A 的 Content&lt;/li&gt;
&lt;li&gt;B 的 TabItem&lt;/li&gt;
&lt;li&gt;B 的 Content&lt;/li&gt;
&lt;li&gt;C 的 TabItem&lt;/li&gt;
&lt;li&gt;C 的 Content&lt;/li&gt;
&lt;li&gt;A 的 TabItem&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;是不是感觉被绕晕了？其实只要把 &lt;code&gt;Content&lt;/code&gt; 和 &lt;code&gt;TabItem&lt;/code&gt; 分开看，就会看出规律：它们各自符合前面提到的规律，然后交替进行。感兴趣的话，也可以想象一下如果将 B 设为默认选中，那么顺序会是怎么样，然后可以写个小程序来验证一下。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;答案&lt;/summary&gt;
&lt;ol&gt;
&lt;li&gt;B 的 Content&lt;/li&gt;
&lt;li&gt;A 的 TabItem&lt;/li&gt;
&lt;li&gt;A 的 Content&lt;/li&gt;
&lt;li&gt;C 的 TabItem&lt;/li&gt;
&lt;li&gt;C 的 Content&lt;/li&gt;
&lt;li&gt;B 的 TabItem&lt;/li&gt;
&lt;/ol&gt;
&lt;/details&gt;
&lt;p&gt;其实这个顺序并不是很重要（而且看起来就像是脑筋急转弯），真正重要的是：所有 &lt;code&gt;Content&lt;/code&gt; 都会在刚一开始就被触发，即便它们并没有被显示出来。这恐怕并不符合我们的直觉，更不是我们期望的效果。我们通常期望的效果是，页面只有在被切换到时才会加载，而不是在一开始就全部加载。&lt;/p&gt;
&lt;h2 id="content-的-unloaded-事件"&gt;&lt;a href="#content-%e7%9a%84-unloaded-%e4%ba%8b%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;Content 的 Unloaded 事件
&lt;/h2&gt;&lt;p&gt;幸运的是，&lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Unloaded&lt;/code&gt; 事件是符合我们预期的。它会在切换到其他 Tab 时触发，并且只会触发当前显示的页面的 &lt;code&gt;Unloaded&lt;/code&gt; 事件。所以当切换页面时，比如从 A 切换到 B，我们会观察到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A 的 &lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Unloaded&lt;/code&gt; 事件触发&lt;/li&gt;
&lt;li&gt;B 的 &lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 事件触发&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以关于这一部分，就没有太多可以赘述的内容了。&lt;/p&gt;
&lt;h2 id="如何解决-loaded-事件被多次触发的问题"&gt;&lt;a href="#%e5%a6%82%e4%bd%95%e8%a7%a3%e5%86%b3-loaded-%e4%ba%8b%e4%bb%b6%e8%a2%ab%e5%a4%9a%e6%ac%a1%e8%a7%a6%e5%8f%91%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;如何解决 Loaded 事件被多次触发的问题
&lt;/h2&gt;&lt;p&gt;现在我们很容易发现一个问题：&lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 事件在一开始就会被触发，并且在切换 Tab 时也会再次触发，这就导致了一个问题：如果我们在 &lt;code&gt;Loaded&lt;/code&gt; 事件里做一些初始化操作，那么这些操作可能会被执行多次，甚至在页面还没有显示出来时就已经执行了，这显然不是我们想要的效果。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，我们通常的做法是引入一个局部变量，用来标记当前页面是否已经被加载过。这样在 &lt;code&gt;Loaded&lt;/code&gt; 事件里，我们就可以先检查这个标记，如果已经加载过，就不再执行初始化操作。例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_isContentALoaded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;ContentLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RoutedEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isContentALoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_isContentALoaded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果是在视图模型中实现初始化的逻辑，也可以用相同的方式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TabViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_isInitialized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Loaded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isInitialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_isInitialized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果借助行为库，通常我们会这样实现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;TabControl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TabItem&lt;/span&gt; &lt;span class="na"&gt;Header=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;A&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;DataContext=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{Binding TabViewModel}&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;Grid&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:Interaction.Triggers&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:EventTrigger&lt;/span&gt; &lt;span class="na"&gt;EventName=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Loaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:InvokeCommandAction&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{Binding LoadedCommand}&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/i:EventTrigger&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/i:Interaction.Triggers&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content A&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/Grid&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/TabItem&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/TabControl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="如何解决-loaded-事件在一开始就被触发的问题"&gt;&lt;a href="#%e5%a6%82%e4%bd%95%e8%a7%a3%e5%86%b3-loaded-%e4%ba%8b%e4%bb%b6%e5%9c%a8%e4%b8%80%e5%bc%80%e5%a7%8b%e5%b0%b1%e8%a2%ab%e8%a7%a6%e5%8f%91%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;如何解决 Loaded 事件在一开始就被触发的问题
&lt;/h2&gt;&lt;p&gt;现在我们来讨论另一个问题：&lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 事件在一开始就会被触发，这可能会导致一些不必要的初始化操作在页面还没有显示出来时就已经执行了。那么这个问题又该如何解决呢？&lt;/p&gt;
&lt;p&gt;首先有一个方式，就是我们额外添加一个判断，即 Tab 只有在被选中，或者可见时，才会执行初始化操作。这样就可以避免在页面还没有显示出来时就执行初始化操作了。以判断是否可见为例，除了可以在 &lt;code&gt;Loaded&lt;/code&gt; 事件里判断 &lt;code&gt;IsVisible&lt;/code&gt; 属性外，还可以借助行为库的条件行为来实现。具体的实现方式如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;i:EventTrigger&lt;/span&gt; &lt;span class="na"&gt;EventName=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Loaded&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:Interaction.Behaviors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:ConditionBehavior&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:ConditionalExpression&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:ComparisonCondition&lt;/span&gt; &lt;span class="na"&gt;LeftOperand=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IsVisible}&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;RightOperand=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;True&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/i:ConditionalExpression&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/i:ConditionBehavior&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/i:Interaction.Behaviors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;i:InvokeCommandAction&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{Binding LoadedCommand}&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/i:EventTrigger&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个看起来很复杂的实现，其实就是在 &lt;code&gt;Loaded&lt;/code&gt; 事件触发时，额外添加了一个条件。如果条件不满足，则不执行后面的 &lt;code&gt;Action&lt;/code&gt;。这样就可以确保只有在页面可见时，才会执行初始化操作。&lt;/p&gt;
&lt;p&gt;除此之外还有一种不太能想到的方式，就是借助 &lt;code&gt;TabItem.ContentTemplate&lt;/code&gt; 来实现，也就是不借助 &lt;code&gt;Content&lt;/code&gt; 属性。形如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;TabControl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TabItem&lt;/span&gt; &lt;span class="na"&gt;Header=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TabItem.ContentTemplate&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;DataTemplate&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content A&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/DataTemplate&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/TabItem.ContentTemplate&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/TabItem&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/TabControl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;通过这样的方式，&lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; 事件就不会在一开始就被触发了，而是只有在切换到该 Tab 时才会触发。这一方式的原理是，模板只是告诉控件如何生成界面元素，而并没有直接生成界面元素。只有在切换到该 Tab 时，控件才会根据模板生成界面元素，从而触发 &lt;code&gt;Loaded&lt;/code&gt; 事件。&lt;/p&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;TabControl&lt;/code&gt; 在切换页面时，真正进入和离开内容区的通常不是 &lt;code&gt;TabItem&lt;/code&gt; 本身，而是当前选中项的 &lt;code&gt;Content&lt;/code&gt;。也正因为如此，&lt;code&gt;TabItem&lt;/code&gt; 自身的 &lt;code&gt;Loaded&lt;/code&gt; 往往只在初次进入界面树时触发一次，而 &lt;code&gt;Content&lt;/code&gt; 的 &lt;code&gt;Loaded&lt;/code&gt; / &lt;code&gt;Unloaded&lt;/code&gt; 则会随着切换被重复触发；如果是直接把内容内联写在 &lt;code&gt;TabItem&lt;/code&gt; 里，甚至还可能在初始阶段就先触发。&lt;/p&gt;
&lt;p&gt;所以在实际开发中，最好不要把 &lt;code&gt;Loaded&lt;/code&gt; 直接等同于“用户第一次看到这个页面”。更稳妥的做法通常是：要么给初始化逻辑加上幂等保护，避免重复执行；要么结合选中状态、可见性，或者改用 &lt;code&gt;ContentTemplate&lt;/code&gt;，让初始化发生在真正合适的时机。&lt;/p&gt;</description></item></channel></rss>