Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Named Controls inside content property of UserControl always appear null at run time

I have created a user control that I reuse for different purposes. I have defined a Dependency Property that holds a UIElement, which I present as the content of one of the areas in the user control.

I noticed that when I use this control. and give names to the elements inside the content property, they always appear as null at run time.

MyContainer.xaml:

<UserControl x:Class="BSSApp.UI.Tests.MyContainer" x:Name="userControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    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">

    <Border x:Name="LayoutRoot" Background="Green" CornerRadius="20" BorderBrush="#005500" BorderThickness="10">
        <ContentPresenter Content="{Binding ElementName=userControl, Path=MyContent}" Margin="20"/>

    </Border>
</UserControl>

MyContainer.xaml.cs

namespace BSSApp.UI.Tests
{
    public partial class MyContainer : UserControl
    {
        public static readonly DependencyProperty MyContentProperty =
                    DependencyProperty.Register("MyContent",
                                                typeof(UIElement),
                                                typeof(MyContainer),
                                                new PropertyMetadata(new Grid()));

        public UIElement MyContent
        {
            get
            {
                return (UIElement)GetValue(MyContentProperty);
            }
            set
            {
                SetValue(MyContentProperty, value);
            }
        }


        public MyContainer()
        {
            InitializeComponent();
        }
    }
}

And the using class: UserControlContentBug.xaml:

<UserControl x:Class="BSSApp.UI.Tests.UserControlContentBug"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:t="clr-namespace:BSSApp.UI.Tests"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <t:MyContainer>
            <t:MyContainer.MyContent>
                <Button Name="btnWithName" Click="btnWithName_Click">Click Me</Button>
            </t:MyContainer.MyContent>
        </t:MyContainer>
    </Grid>
</UserControl>

and the code behind: UserControlContentBug.xaml.cs

 namespace BSSApp.UI.Tests
 {
     public partial class UserControlContentBug : UserControl
     {
         public UserControlContentBug()
         {
             InitializeComponent();
         }

         private void btnWithName_Click(object sender, RoutedEventArgs e)
         {
             // this throws an exception
             btnWithName.Tag=2;
         }
     }
  }

So there is a declaration of a button, named "btnWithName". the variable is indeed declared in the code behind, but it holds null... It only happens when the button is declared inside a property of another user control.

Does anybody know how to solve this?

Thanks

like image 406
Kobi Hari Avatar asked Apr 14 '11 23:04

Kobi Hari


1 Answers

I just ran into this problem myself. The generated partial class (e.g., obj\UserControlContentBug.xaml.g.cs) which defines the code-accessible named elements in your XAML uses FrameworkElement.FindName() to make those assignments.

According to MSDN:

A run-time API such as FindName is working against an object tree. These objects are loaded into the content area and the CLR runtime engine of the overall Silverlight plug-in. When part of the object tree is created from templates or run-time loaded XAML, a XAML namescope is typically not contiguous with the entirety of that object tree. The result is that there might be a named object in the object tree that a given FindName call cannot find.

Since MyContainer establishes its own namescope boundary as a UserControl, calling FindName() from UserControlContentBug will not explore that part of the object tree (where btnWithName is defined).

There are probably a few different ways to work around this, but the simplest one that works for me is to:

  1. Give MyContainer a name (x:Name="myContainer").

  2. Remove the name from the Button (btnWithName) so that the superfluous, always-null variable of the same name isn't reserved in the generated C# code.

  3. Create a reference to the Button in the codebehind.

Like so:

namespace BSSApp.UI.Tests
{
    public partial class UserControlContentBug : UserControl
    {
        private Button btnWithName;

        public UserControlContentBug()
        {
            Loaded += UserControlContentBugLoaded;
            InitializeComponent();
        }

        private void UserControlContentBugLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            btnWithName = myContainer.MyContent as Button;
        }

        private void btnWithName_Click(object sender, RoutedEventArgs e)
        {
            // this throws an exception, not so much
            btnWithName.Tag=2;
        }
    }
}

Hope this helps.

like image 84
Lee Fastenau Avatar answered Oct 23 '22 21:10

Lee Fastenau