The TabControl in Silverlight has some interesting behaviors. One of those behaviors is that since Silverlight does not have a Logical Tree and only has a Visual Tree, controls that are inside a tab that is not visible will not appear in the visual tree. If this sounds foreign to you, don’t worry.
To see this in action, here is a simple Silverlight application:
1 | <span style="color:#606060" id="lnum1"> 1:</span> <span style="color:#0000ff"><</span><span style="color:#800000">UserControl</span> <span style="color:#ff0000">x:Class</span><span style="color:#0000ff">="SilverlightApplication2.MainPage"</span> |
1 | <span style="color:#606060" id="lnum2"> 2:</span> <span style="color:#ff0000">xmlns</span><span style="color:#0000ff">="http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span> |
1 | <span style="color:#606060" id="lnum3"> 3:</span> <span style="color:#ff0000">xmlns:x</span><span style="color:#0000ff">="http://schemas.microsoft.com/winfx/2006/xaml"</span> |
1 | <span style="color:#606060" id="lnum4"> 4:</span> <span style="color:#ff0000">xmlns:d</span><span style="color:#0000ff">="http://schemas.microsoft.com/expression/blend/2008"</span> |
1 | <span style="color:#606060" id="lnum5"> 5:</span> <span style="color:#ff0000">xmlns:mc</span><span style="color:#0000ff">="http://schemas.openxmlformats.org/markup-compatibility/2006"</span> |
1 | <span style="color:#606060" id="lnum6"> 6:</span> <span style="color:#ff0000">xmlns:sdk</span><span style="color:#0000ff">="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"</span> |
1 | <span style="color:#606060" id="lnum7"> 7:</span> <span style="color:#ff0000">xmlns:tk</span><span style="color:#0000ff">="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum8"> 8:</span> |
1 | <span style="color:#606060" id="lnum9"> 9:</span> <span style="color:#0000ff"><</span><span style="color:#800000">Grid</span> <span style="color:#ff0000">x:Name</span><span style="color:#0000ff">="LayoutRoot"</span> <span style="color:#ff0000">Background</span><span style="color:#0000ff">="White"</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum10"> 10:</span> <span style="color:#0000ff"><</span><span style="color:#800000">StackPanel</span> <span style="color:#ff0000">Orientation</span><span style="color:#0000ff">="Vertical"</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum11"> 11:</span> <span style="color:#0000ff"><</span><span style="color:#800000">sdk:TabControl</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum12"> 12:</span> <span style="color:#0000ff"><</span><span style="color:#800000">sdk:TabItem</span> <span style="color:#ff0000">Header</span><span style="color:#0000ff">="Tab 1"</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum13"> 13:</span> <span style="color:#0000ff"><</span><span style="color:#800000">TextBlock</span> <span style="color:#ff0000">x:Name</span><span style="color:#0000ff">="Control1"</span> <span style="color:#ff0000">Text</span><span style="color:#0000ff">="This is control 1"</span><span style="color:#0000ff">></</span><span style="color:#800000">TextBlock</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum14"> 14:</span> <span style="color:#0000ff"></</span><span style="color:#800000">sdk:TabItem</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum15"> 15:</span> <span style="color:#0000ff"><</span><span style="color:#800000">sdk:TabItem</span> <span style="color:#ff0000">Header</span><span style="color:#0000ff">="Tab 2"</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum16"> 16:</span> <span style="color:#0000ff"><</span><span style="color:#800000">TextBlock</span> <span style="color:#ff0000">x:Name</span><span style="color:#0000ff">="Control2"</span> <span style="color:#ff0000">Text</span><span style="color:#0000ff">="This is control 2"</span><span style="color:#0000ff">></</span><span style="color:#800000">TextBlock</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum17"> 17:</span> <span style="color:#0000ff"></</span><span style="color:#800000">sdk:TabItem</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum18"> 18:</span> <span style="color:#0000ff"></</span><span style="color:#800000">sdk:TabControl</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum19"> 19:</span> |
1 | <span style="color:#606060" id="lnum20"> 20:</span> <span style="color:#0000ff"><</span><span style="color:#800000">Button</span> <span style="color:#ff0000">Content</span><span style="color:#0000ff">="Find Control 1"</span> <span style="color:#ff0000">Click</span><span style="color:#0000ff">="FindControl1_Click"</span><span style="color:#0000ff">></</span><span style="color:#800000">Button</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum21"> 21:</span> <span style="color:#0000ff"><</span><span style="color:#800000">Button</span> <span style="color:#ff0000">Content</span><span style="color:#0000ff">="Find Control 2"</span> <span style="color:#ff0000">Click</span><span style="color:#0000ff">="FindControl2_Click"</span><span style="color:#0000ff">></</span><span style="color:#800000">Button</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum22"> 22:</span> <span style="color:#0000ff"></</span><span style="color:#800000">StackPanel</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum23"> 23:</span> <span style="color:#0000ff"></</span><span style="color:#800000">Grid</span><span style="color:#0000ff">></span> |
1 | <span style="color:#606060" id="lnum24"> 24:</span> <span style="color:#0000ff"></</span><span style="color:#800000">UserControl</span><span style="color:#0000ff">></span> |
This application has a single TabControl with two tabs. Each tab has a TextBlock inside it. This is the application as it’s running:
When either button is clicked, the code will walk the visual tree. The entire codebehind for this code follows:
1 | <span style="color:#606060" id="lnum1"> 1:</span> <span style="color:#0000ff">using</span> System.Text; |
1 | <span style="color:#606060" id="lnum2"> 2:</span> <span style="color:#0000ff">using</span> System.Windows; |
1 | <span style="color:#606060" id="lnum3"> 3:</span> <span style="color:#0000ff">using</span> System.Windows.Controls; |
1 | <span style="color:#606060" id="lnum4"> 4:</span> <span style="color:#0000ff">using</span> System.Windows.Media; |
1 | <span style="color:#606060" id="lnum5"> 5:</span> |
1 | <span style="color:#606060" id="lnum6"> 6:</span> <span style="color:#0000ff">namespace</span> SilverlightApplication2 { |
1 | <span style="color:#606060" id="lnum7"> 7:</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">partial</span> <span style="color:#0000ff">class</span> MainPage : UserControl { |
1 | <span style="color:#606060" id="lnum8"> 8:</span> <span style="color:#0000ff">public</span> MainPage() { |
1 | <span style="color:#606060" id="lnum9"> 9:</span> InitializeComponent(); |
1 | <span style="color:#606060" id="lnum10"> 10:</span> } |
1 | <span style="color:#606060" id="lnum11"> 11:</span> |
1 | <span style="color:#606060" id="lnum12"> 12:</span> <span style="color:#0000ff">private</span> <span style="color:#0000ff">void</span> FindControl1_Click( <span style="color:#0000ff">object</span> sender, RoutedEventArgs e ) { |
1 | <span style="color:#606060" id="lnum13"> 13:</span> StringBuilder path = <span style="color:#0000ff">new</span> StringBuilder(); |
1 | <span style="color:#606060" id="lnum14"> 14:</span> GetParentPath( <span style="color:#0000ff">this</span>.Control1, path ); |
1 | <span style="color:#606060" id="lnum15"> 15:</span> MessageBox.Show( path.ToString() ); |
1 | <span style="color:#606060" id="lnum16"> 16:</span> } |
1 | <span style="color:#606060" id="lnum17"> 17:</span> |
1 | <span style="color:#606060" id="lnum18"> 18:</span> <span style="color:#0000ff">private</span> <span style="color:#0000ff">void</span> FindControl2_Click( <span style="color:#0000ff">object</span> sender, RoutedEventArgs e ) { |
1 | <span style="color:#606060" id="lnum19"> 19:</span> StringBuilder path = <span style="color:#0000ff">new</span> StringBuilder(); |
1 | <span style="color:#606060" id="lnum20"> 20:</span> GetParentPath( <span style="color:#0000ff">this</span>.Control2, path ); |
1 | <span style="color:#606060" id="lnum21"> 21:</span> MessageBox.Show( path.ToString() ); |
1 | <span style="color:#606060" id="lnum22"> 22:</span> } |
1 | <span style="color:#606060" id="lnum23"> 23:</span> |
1 | <span style="color:#606060" id="lnum24"> 24:</span> <span style="color:#0000ff">private</span> <span style="color:#0000ff">void</span> GetParentPath( DependencyObject target, StringBuilder path ) { |
1 | <span style="color:#606060" id="lnum25"> 25:</span> <span style="color:#0000ff">if</span>( target == <span style="color:#0000ff">null</span> ) <span style="color:#0000ff">return</span>; |
1 | <span style="color:#606060" id="lnum26"> 26:</span> |
1 | <span style="color:#606060" id="lnum27"> 27:</span> <span style="color:#0000ff">if</span>( path.Length != 0 ) |
1 | <span style="color:#606060" id="lnum28"> 28:</span> path.Insert( 0, <span style="color:#006080">" > "</span> ); |
1 | <span style="color:#606060" id="lnum29"> 29:</span> path.Insert( 0, target.GetType().Name ); |
1 | <span style="color:#606060" id="lnum30"> 30:</span> |
1 | <span style="color:#606060" id="lnum31"> 31:</span> GetParentPath( VisualTreeHelper.GetParent( target ), path ); |
1 | <span style="color:#606060" id="lnum32"> 32:</span> } |
1 | <span style="color:#606060" id="lnum33"> 33:</span> } |
1 | <span style="color:#606060" id="lnum34"> 34:</span> } |
Each button click calls a recursive function which walks the visual tree until it gets to a root parent element, then displays the path using a MessageBox. Simple enough, what happens when you have “Tab 1” active and click the first button?
Great! Exactly as expected. What happens when you have “Tab 2” active and click the second button?
Also exactly as expected. Now that “Tab 2” is selected, what happens if you click the first button?
Now that’s strange, isn’t it? The TextBlock is inside the TabItem, which is inside the TabControl, and so on, but Silverlight is saying the TextBlock doesn’t have any parent elements.
Why? Because anything inside a TabItem that is not the active TabItem are not in the visual tree.
What kind of side effects does this have? Well, one side effect is that every time the tab is activated the TextBlock Loaded event is raised. To see this in action, we’ll attach an event handler to the Load event:
1 | <span style="color:#606060" id="lnum1"> 1:</span> <span style="color:#0000ff"><</span><span style="color:#800000">TextBlock</span> <span style="color:#ff0000">x:Name</span><span style="color:#0000ff">="Control1"</span> <span style="color:#ff0000">Text</span><span style="color:#0000ff">="This is control 1"</span> <span style="color:#ff0000">Loaded</span><span style="color:#0000ff">="Control1_Loaded"</span><span style="color:#0000ff">></</span><span style="color:#800000">TextBlock</span><span style="color:#0000ff">></span> |
And in codebehind, we’ll update the Text of the TextBlock every time the Loaded event is raised.
1 | <span style="color:#606060" id="lnum1"> 1:</span> <span style="color:#0000ff">private</span> <span style="color:#0000ff">void</span> Control1_Loaded( <span style="color:#0000ff">object</span> sender, System.Windows.RoutedEventArgs e ) { |
1 | <span style="color:#606060" id="lnum2"> 2:</span> <span style="color:#0000ff">this</span>.Control1.Text = <span style="color:#006080">"Loaded at "</span> + DateTime.Now.TimeOfDay.ToString(); |
1 | <span style="color:#606060" id="lnum3"> 3:</span> } |
Once the application opens, we can see the time in the TextBlock loaded:
And if we switch to Tab 2, then back to Tab 1:
The Loaded event fired again, as evidenced by the text of the TextBlock changing.
What does this all mean then? If a control has some sort of behavior it runs on load, assuming that it will only be loaded a single time, theoretically it could have an unwanted consequence.
See part 2 for an example of just such an unwanted consequence.