I have the following code in a Windows 8.1 Store App. This code runs perfectly fine on Windows 10 but crashes on Windows 8.1. The second named control in MainPage.xaml.cs is null on Win 8.1 but not on Windows 10. It's not a timing issue as the named control still won't be populated in any subsequent event handler following the page load. What on earth is going on here?
To summarize, I have a ContentControl with a ContentPresenter defined in its Template. That ContentControl is then instantiated on a page, with a named child control (using "x:Name") as its Content. On Windows 10, that named control exists in code-behind. On Windows 8.1 it is null
MyUserControl1.xaml
<ContentControl
x:Class="App1.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<ContentControl.Template>
<ControlTemplate>
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</ContentControl.Template>
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TextBlock1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
FontSize="50"
TextWrapping="Wrap" />
<local:MyUserControl1 Grid.Column="1">
<TextBlock x:Name="TextBlock2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
FontSize="50"
TextWrapping="Wrap" />
</local:MyUserControl1>
</Grid>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace App1
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
TextBlock1.Text = "This works";
TextBlock2.Text = "This does not work because TextBlock2 is null";
}
}
}
Of course you cannot reference this. Your
TextBlock2is explicitly being set BY YOU to be the content of another control fundamentally out of scope. After rendering is complete, yourTextBlock2is no longer a child of yourMainPagebut instead a child of theControlTemplatein yourUserControl. Windows 10 is behaving EXACTLY how it should, and it appears you have discovered a bug in the Windows 8 rendering engine, if it worked.One
There are a few workarounds. The first is the textbook approach of adding a property to your
UserControlthat adds access to this control. Because you are allowing the content to be dynamic, the operation inside that property (or method) would also need to be dynamic. Something likeGetControl<TextBlock>("TextBlock1")which could hunt for you.Two
The second thing you could do is simply hunt for the control through the child hierarchy of the
UserControlfrom the page itself. The logic would be the same as number one (above) but it would execute inside the page and not be part of yourUserControllogic. You already do this sort of thing when you need to find theScrollViewerin aListViewor maybe the innerBorderin aButtonfor some reason.Three
And here's a third, perhaps simplest way to do it. Handle the
Loadedevent ofTextBlock1in yourMainPageand set a field to the value of thesenderin the handler method. You can even cast it toTextBlockso everything is typed. This gives you simple access, but has the potential downside of timing. If you try to access the field value before it is set, you might find it is null. But, in most cases, this works the easiest.So, that's three ways to handle it. I think it is very important that you recognize that this is EXPECTED behavior since a XAML element can have only one parent and you are setting that parent through the
Contentproperty of yourContentPresenter. That being said, it might be expected behavior, but it is not obviously intuitive.Best of luck.