none
Child container in WPF User Control

    Question

  • I've created a UserControl that I want to be able to work as a container for other elements, i.e. I want to be able to add childs to it. For example if I just want a UserControl that is a specially styled border and a heading, so I want to add content into it. That's what I've created, an advanced rectangle with some properties that make it work as a sort of panel.

    How do I accomplish this?

    Regards, Simeon
    Sunday, May 11, 2008 1:01 AM

All replies

  • You may just want to add your panel to the custom control and expose its Children property via your user control.  You could also expose a collection of your own, like a UIElementCollection or maybe even an ObservableCollection.

     

    Additionally, you may want to add teh ContentPropertyAttribute to your user control.

     

    -Jer

    • Proposed as answer by nilz Tuesday, October 06, 2009 2:51 AM
    Sunday, May 11, 2008 3:58 AM
  • You may also want to look at possibly styling existing Container controls than creating a new user control.

     

    You can also get details about host of custom panels already written by people here - 

    http://rrelyea.spaces.live.com/blog/cns!167AD7A5AB58D5FE!1887.entry

     

    Sunday, May 11, 2008 4:38 AM
  •  Jeremiah Morrill wrote:

    You may just want to add your panel to the custom control and expose its Children property via your user control.


    Sorry, but I'm having troubles understanding which part of your words are WPF objects and which are not... may be related to me being a Swede.


    I have no "custom control", I have created a UserControl and placed a Grid in it which I named to ChildHost, because I want to be able to add content into it when I've placed some instances of this UserControl onto some Windows throughout my application.


    Could you provide a code sample in XAML on how to "expose its Children property via my UserControl" ? I have found no property named Children... not in any of my objects in my UserControl.


    The suggestion of creating a custom collection seems pretty smart, I'll do that if this doesn't work (or I don't manage to understand!)

    Sunday, May 11, 2008 12:20 PM
  • Now I've spent a lot of time (two full-time workdays) trying to get this trivial (make that extremely trivial) sh*t to work. It just doesn't happen. After finding examples of this in ASP.NET and WinForms I'm getting really confused. Right now, I've managed (I think) to get most of it right, but there are things that doesn't work. These are the problems I've had or have:
    • Blend doesn't recognize the container and cannot insert objects into it
    • Visual Studio could for a while, but I managed to break that...
    • Now in Visual Studio (2008) I get some strange instance error
    • Before I managed to crash Visual Studio
    Please help me get this simple panel to work! I'm starting to go insane.

    Download project: http://www.mediafire.com/?j3hur4gbnhx

    Replicate manually: Create a WPF application, name it WpfApplication2 to get the namespaces right. Then add this XAML to your Window1.xaml

    Code Snippet for Window1.xaml

    <Window
        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/2006"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:WpfApplication2="clr-namespace:WpfApplication2"
        x:Class="WpfApplication2.Window1"
        x:Name="Window"
        Title="WpfApplication2"
        Width="456" Height="381" BorderBrush="{x:Null}" ResizeMode="CanMinimize" BorderThickness="0">

        <Grid x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="False" Width="Auto" Height="Auto">
            <WpfApplication2:OptionAreaPanelWrapper Margin="50.867,25,115,0" VerticalAlignment="Top" Height="129"/>
        </Grid>
    </Window>



    Then add a class to the project, name it OptionAreaPanelWrapper.cs and add:


    Code Snippet for OptionAreaPanelWrapper.cs

    using System.Windows.Controls;
    using System.Windows;

    namespace WpfApplication2
    {
        public class OptionAreaPanelWrapper : Grid
        {
            protected UserControlOptionArea OptionArea { get; set; }
            private UIElementCollection _oaCollection { get; set; }

            public OptionAreaPanelWrapper()
            {
                _oaCollection = new UIElementCollection(this.Parent as UIElement, this as FrameworkElement);

                OptionArea = new UserControlOptionArea();
                OptionArea.VerticalAlignment = VerticalAlignment.Stretch;
                OptionArea.HorizontalAlignment = HorizontalAlignment.Stretch;

                if (!_oaCollection.Contains(OptionArea))
                    _oaCollection.Add(OptionArea);
            }

            protected new UIElementCollection Children
            {
                get
                {
                    return _oaCollection;
                }
                set
                {
                    _oaCollection = value;

                    if (!_oaCollection.Contains(OptionArea))
                        _oaCollection.Add(OptionArea);
                }
            }

            protected override Size ArrangeOverride(Size arrangeSize)
            {
                OptionArea.Height = this.Height;
                OptionArea.Width = this.Width;

                return base.ArrangeOverride(arrangeSize);
            }
        }
    }



    Add a UserControl to you application and name it UserControlOptionArea.xaml and add following code:


    Code Snippet for UserControlOptionArea.xaml

    <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/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
        x:Class="WpfApplication2.UserControlOptionArea"
        x:Name="UserControlOA"
        Width="271.036" Height="113" MinHeight="40">

        <UserControl.Resources>
            <Storyboard x:Key="OnMouseEnter1">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                    <SplineColorKeyFrame KeyTime="00:00:00.3000000" Value="#FF484848"/>
                </ColorAnimationUsingKeyFrames>
            </Storyboard>
            <Storyboard x:Key="OnMouseLeave1">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                    <SplineColorKeyFrame KeyTime="00:00:00.2000000" Value="#FF3C3C3C"/>
                </ColorAnimationUsingKeyFrames>
            </Storyboard>
        </UserControl.Resources>
        <UserControl.Triggers>
            <EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="rectangle">
                <BeginStoryboard Storyboard="{StaticResource OnMouseEnter1}"/>
            </EventTrigger>
            <EventTrigger RoutedEvent="Mouse.MouseLeave" SourceName="rectangle">
                <BeginStoryboard x:Name="OnMouseLeave1_BeginStoryboard" Storyboard="{StaticResource OnMouseLeave1}"/>
            </EventTrigger>
        </UserControl.Triggers>

        <DockPanel x:Name="LayoutRoot" Width="Auto" Height="Auto" LastChildFill="True">
            <Grid Width="Auto" Height="Auto" x:Name="GridLayoutRoot">
                <Rectangle Opacity="1" Fill="#FF3C3C3C" StrokeThickness="5" RadiusX="7.5" RadiusY="7.5" x:Name="rectangle" IsHitTestVisible="True">
                    <Rectangle.Stroke>
                        <RadialGradientBrush MappingMode="RelativeToBoundingBox">
                            <GradientStop Color="#3F000000" Offset="1"/>
                            <GradientStop Color="#00FFFFFF" Offset="0"/>
                        </RadialGradientBrush>
                    </Rectangle.Stroke>
                    <Rectangle.BitmapEffect>
                        <BlurBitmapEffect Radius="1" KernelType="Gaussian"/>
                    </Rectangle.BitmapEffect>
                </Rectangle>
                <StackPanel IsHitTestVisible="False" Margin="8,8,8,0" VerticalAlignment="Top">
                    <TextBlock ScrollViewer.VerticalScrollBarVisibility="Disabled" FontFamily="Segoe UI" FontSize="12" FontStyle="Normal" FontWeight="Normal" Foreground="#FFFFFFFF" Padding="2,0,2,0" TextAlignment="Center" TextWrapping="Wrap" x:Name="TextBlockHeader" Text="Header"><TextBlock.BitmapEffect>
                            <OuterGlowBitmapEffect GlowColor="#FF000000" GlowSize="3"/>
                        </TextBlock.BitmapEffect></TextBlock>
                    <Path RenderTransformOrigin="0.496,0.492" Fill="{x:Null}" Stretch="Fill" StrokeThickness="1" Height="1" Data="M35.666667,42.666667 L159.70295,42.666667" Width="Auto" x:Name="Line">
                        <Path.Stroke>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#00FFFFFF" Offset="1"/>
                                <GradientStop Color="#25FFFFFF" Offset="0"/>
                            </LinearGradientBrush>
                        </Path.Stroke>
                        <Path.BitmapEffect>
                            <OuterGlowBitmapEffect GlowColor="#FFFFFFFF" GlowSize="2"/>
                        </Path.BitmapEffect>
                    </Path>
                </StackPanel>
                <Grid Margin="8,28,8,8" x:Name="ChildHost"/>
            </Grid>
        </DockPanel>
    </UserControl>




    Lastly, paste this into UserControlOptionArea.xaml.cs


    Code Snippet

    using System.ComponentModel;
    using System.Windows.Markup;
    using System.Windows.Controls;
    using System.Windows;
    using WpfApplication2;

    namespace WpfApplication2
    {
        [DesignTimeVisibleAttribute()]
        [ContentProperty("ChildHostGrid")]
        public partial class UserControlOptionArea
        {
            protected Grid _panel = new Grid();

            public UserControlOptionArea()
            {
                this.InitializeComponent();
                _panel = new Grid();
                _panel.Margin = new Thickness(8, 28, 8, 8);
                GridLayoutRoot.Children.Add(_panel);
            }

            [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
            protected Grid ChildHostGrid
            {
                get { return _panel; }
                set { _panel = value; }
            }

            public string Text
            {
                get
                {
                    return TextBlockHeader.Text;
                }
                set
                {
                    TextBlockHeader.Text = value;
                }
            }
        }
    }



    The download link for all this as a project zip is provided here: http://www.mediafire.com/?j3hur4gbnhx
    (same as at the top)
    Monday, May 12, 2008 2:16 PM
  • I'm having the same type of trouble with a trivial control. I first created a rather difficult usercontrol converting a piece from an exisiting Windows Form project and had the control show up completely blank in my Windows Form I host it in. So I decided to try to start simple... extremely simple. I made a UserControl and simply drag/dropped a Label into it and set its content to Testing123. I made a Windows Form which opens on button click from the main screen. I drag/drop my Testing123 control into the form after having the whole project build perfectly. Visual Studio throws an error "Error setting value 'WPFControlLibrary.TestScreen' to property 'Child'. Details: Could not load type WPFControlLibrary.TestScreen' from assembly XYZ.
    Is there a hotfix or something we're missing in Visual Studio 2008 that I'm not aware of for basic operation of WPF UserControls in .Net 3.5.1SP1 windows forms?
    Its not that I don't hear you, your just insignificant
    Wednesday, June 03, 2009 5:13 AM
  • Hi Simeon,

    Like Jeremiah said I think you might be able to achieve this by exposing the Children as a public property. I have modified your code as follows :

    Code Snippet for  UserControlOptionArea.xaml

        [ContentProperty("Children")]
        public partial class UserControlOptionArea
        {
    
    
            public ObservableCollection<UIElement> Children { get; private set; }
            protected Grid _panel = new Grid();
    
            public UserControlOptionArea()
            {
                this.InitializeComponent();
    
                Children = new ObservableCollection<UIElement>();
    
                Children.CollectionChanged += 
                     new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Children_CollectionChanged);
    
            }
    
            void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                switch (e.Action)
                {
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                        foreach (UIElement elem in e.NewItems)
                        {
                            ChildHost.Children.Add(elem);
                        }
    
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
    
                        foreach (UIElement elem in e.OldItems)
                        {
                            ChildHost.Children.Remove(elem);
                        }
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                        break;
                    default:
                        break;
                }
            }
    
            public string Text
            {
                get
                {
                    return TextBlockHeader.Text;
                }
                set
                {
                    TextBlockHeader.Text = value;
                }
            }
    and in Window1.xaml you can use it like this:

    <WpfApplication2:UserControlOptionArea x:Name="myControl">
                
           <Button>Some Button!</Button>
               
    </WpfApplication2:UserControlOptionArea>

    I'm not sure whether this is the most elegant method through. Also I don't use Expression Blend so not sure whether this method will work in there; however, it works perfectly fine in VS 2008. Hope this helps.

    Cheers,

    Nilu
    Tuesday, October 06, 2009 2:48 AM