4
votes

Situation:

I have a "wrapper panel" UserControl like this (namespaces and visual details removed for brevity):

<UserControl ...>
<Grid x:Name="LayoutRoot" Background="White">
    <ContentPresenter x:Name="integratedPanelContent" Margin="5" /> 
</Grid>
</UserControl>

Then in the Code-behind I have registered a dependency property

public FrameworkElement PanelContent
{
    get { return (FrameworkElement)GetValue(PanelContentProperty); }
    set { SetValue(PanelContentProperty, value); }
}
public static readonly DependencyProperty PanelContentProperty =
    DependencyProperty.Register("PanelContent", typeof(FrameworkElement), typeof(MyWrapperPanel),
      new PropertyMetadata(null, OnPanelContentChanged));

private static void OnPanelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ((MyWrapperPanel)d).OnSetContentChanged(e);
}

protected virtual void OnSetContentChanged(DependencyPropertyChangedEventArgs e)
{
    if (PanelContent != null)
        integratedPanelContent.Content = PanelContent;
}

Now I can wrap any content into my control:

<my:MyWrapperPanel x:Name="myWrap">
    <my:MyWrapperPanel.PanelContent>
        <TextBlock x:Name="tbxNothing" Text="Nothing" />
    </my:MyWrapperPanel.PanelContent>
</my:MyWrapperPanel>

Description of the problem:

Whenever I try to use the reference tbxNothing in codebehind, the system throws NullReferenceException because tbxNothing, although as a reference exists, does not point to the TextBlock defined in XAML, but is null.

Possible (but inconvenient) workaround:

There is a workaround where I remove x:Name from the TextBlock, and then I explicitely define private TextBlock called tbxNothing. Then in the OnNavigatedTo event handler I assign the value the following way:

tbxNothing = myWrap.PanelContent as TextBlock;

This works but is not a right way to do it, because if a content is a stackpanel that contains wanted controls, I'd have to traverse the tree to find what I need, which is extremely inconvenient.

Question:

Why is the textblock no longer visible when wrapped in a User control (the way described), and how to get it by its x:Name in code-behind?

1

1 Answers

5
votes

The problem is your panel content is falling between two stools. On the one hand the content with the name "tbxNothing" is create in the namescope of the main page. However its not added to the object tree at that point. On the other hand the MyWrapperPanel being a UserControl has its own namescope and its into the object tree below this that the item with then name "tbxNothing" is added. FindName on the main page won't find anything inside the MyWrapperPanel because it has its own namescope and FindName on the MyWrapperPanel won't find "tbxNothing" because it doesn't exist in its namescope (being actually created in the main page).

The answer is don't use a UserControl as a basis for MyWrapperPanel. Instead create a Silverlight Template Control. Modify the base class it inherits from to ContentControl and tweak its default template to include a ContentPresenter. Should look something like this:-

public class MyWrapperPanel : ContentControl
{
    public MyWrapperPanel ()
    {
        this.DefaultStyleKey = typeof(MyWrapperPanel );
    }
}

then in themes/generic.xaml the style can look like this:-

<Style TargetType="local:MyWrapperPanel">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MyWrapperPanel">
                <Grid>
                    <ContentPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Your main page xaml would look like:-

<my:MyWrapperPanel x:Name="myWrap">
    <TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel>

Note that deriving from ContentControl gives you a Content property which the ContentPresenter auto-magically wires to.