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) 开发者_JS百科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?
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.
精彩评论