views:

113

answers:

1

I am trying to create a UserControl that will hopefully be able to expose multiple content properties. However, I am ridden with failure!

The idea would be to create this great user control (we'll call it MultiContent) that exposes two content properties so that I could do the following:

    <local:MultiContent>
        <local:MultiContent.ListContent>
            <ListBox x:Name="lstListOfStuff" Width="50" Height="50" />                
        </local:MultiContent.ListContent>
        <local:MultiContent.ItemContent>
            <TextBox x:Name="txtItemName" Width="50" />
        </local:MultiContent.ItemContent>
    </local:MultiContent>

This would be very useful, now I can change ListContent and ItemContent depending on the situation, with common functionality factored out into the MultiContent user control.

However, the way I currently have this implemented, I cannot access the UI elements inside of these content properties of the MultiContent control. For instance, lstListOfStuff and txtItemName are both null when I try to access them:

public MainPage() {
    InitializeComponent();
    this.txtItemName.Text = "Item 1"; // <-- txtItemName is null, so this throws an exception
}

Here is how I have implemented the MultiContent user control:

XAML: MultiContent.xaml

<UserControl x:Class="Example.MultiContent"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <ContentControl x:Name="pnlList" Grid.Column="0" />
        <ContentControl x:Name="pnlItem" Grid.Column="1" />
    </Grid>
</UserControl>

Code Behind: MultiContent.xaml.cs

// Namespaces Removed
namespace Example
{
    public partial class MultiContent : UserControl
    {
        public UIElement ListContent
        {
            get { return (UIElement)GetValue(ListContentProperty); }
            set 
            {
                this.pnlList.Content = value;
                SetValue(ListContentProperty, value); 
            }
        }
        public static readonly DependencyProperty ListContentProperty =
            DependencyProperty.Register("ListContent", typeof(UIElement), typeof(MultiContent), new PropertyMetadata(null));

        public UIElement ItemContent
        {
            get { return (UIElement)GetValue(ItemContentProperty); }
            set 
            {
                this.pnlItem.Content = value;
                SetValue(ItemContentProperty, value); 
            }
        }
        public static readonly DependencyProperty ItemContentProperty =
            DependencyProperty.Register("ItemContent", typeof(UIElement), typeof(MultiContent), new PropertyMetadata(null));


        public MultiContent()
        {
            InitializeComponent();
        }
    }
}

I am probably implementing this completely wrong. Does anyone have any idea how I could get this to work properly? How can I access these UI elements by name from the parent control? Any suggestions on how to do this better? Thanks!

+2  A: 

You can definitely achieve your goal but you need to take a different approach.

In your solution you're trying to have a dependency property for of a UIElement - and since it never gets set and default value is null that's why you get a NullReference exception. You could probably go ahead by changing the default value from null to new TextBox or something like that but even if it did work it still would feel like a hack.

In Silverlight you have to implement Dpendency Properties yourself. However you've not implemented them as they should be - I tend to use this dependency property generator to do so.

One of the great things about DPs is that they support change notification. So with that in mind all you have to do to get your sample working is define to DPs: ItemContent and ListContent with the same type as Content (object) and when the framework notifies you that either of them has been changed, simply update your textboxes! So here is the code to do this:

MultiContent.xaml:

   <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <ContentControl x:Name="pnlList" Grid.Column="0" />
        <ContentControl x:Name="pnlItem" Grid.Column="1" />
    </Grid>    

MultiContent.xaml.cs:

namespace MultiContent
{
    public partial class MultiContent : UserControl
    {
        #region ListContent

        /// <summary>
        /// ListContent Dependency Property
        /// </summary>
        public object ListContent
        {
            get { return (object)GetValue(ListContentProperty); }
            set { SetValue(ListContentProperty, value); }
        }
        /// <summary>
        /// Identifies the ListContent Dependency Property.
        /// </summary>
        public static readonly DependencyProperty ListContentProperty =
            DependencyProperty.Register("ListContent", typeof(object),
            typeof(MultiContent), new PropertyMetadata(null, OnListContentPropertyChanged));

        private static void OnListContentPropertyChanged
          (object sender, DependencyPropertyChangedEventArgs e)
        {
            MultiContent m = sender as MultiContent;
            m.OnPropertyChanged("ListContent");
        }

        #endregion

        #region ItemContent

        /// <summary>
        /// ItemContent Dependency Property
        /// </summary>
        public object ItemContent
        {
            get { return (object)GetValue(ItemContentProperty); }
            set { SetValue(ItemContentProperty, value); }
        }
        /// <summary>
        /// Identifies the ItemContent Dependency Property.
        /// </summary>
        public static readonly DependencyProperty ItemContentProperty =
            DependencyProperty.Register("ItemContent", typeof(object),
            typeof(MultiContent), new PropertyMetadata(null, OnItemContentPropertyChanged));

        private static void OnItemContentPropertyChanged
          (object sender, DependencyPropertyChangedEventArgs e)
        {
            MultiContent m = sender as MultiContent;
            m.OnPropertyChanged("ItemContent");
        }

        #endregion

        /// <summary>
        ///  Event called when any chart property changes
        ///  Note that this property is not used in the example but is good to have if you plan to extend the class!
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        ///  Called to invoke the property changed event
        /// </summary>
        /// <param name="propertyName">The property that has changed</param>
        protected void OnPropertyChanged(string propertyName)
        {
            if (propertyName == "ListContent")
            {
                // The ListContent property has been changed, let's update the control!
                this.pnlList.Content = this.ListContent;
            }
            if (propertyName == "ItemContent")
            {
                // The ListContent property has been changed, let's update the control!
                this.pnlItem.Content = this.ItemContent;
            }
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public MultiContent()
        {
            InitializeComponent();
        }
    }
}

MainPage.xaml:

    <Grid x:Name="LayoutRoot" Background="White">
        <local:MultiContent>
            <local:MultiContent.ListContent>
                <ListBox x:Name="lstListOfStuff" Width="50" Height="50" />
            </local:MultiContent.ListContent>
            <local:MultiContent.ItemContent>
                <TextBox x:Name="txtItemName" Width="50" />
            </local:MultiContent.ItemContent>
        </local:MultiContent>
    </Grid>

This should do the trick!

Gergely Orosz