Trying to create a custom panel but getting a: The object already has a child and cannot add 'Button' compiler exception

StackOverflow https://stackoverflow.com/questions/17953454

Question

I am pretty new to wpf.

I am trying to make a custom panel with some fancy design borders. I got everything working but only if I have only 1 control in the custom panel. If I try to add more than one then I get the following exception:

The object 'BordersPanel' already has a child and cannot add 'Button'. 'BordersPanel' can accept only one child.

I spent a lot of time trying to find a solution on the net, but so far nothing. So I am turning to the community for help.

The panel style:

    <Style
    TargetType="{x:Type local:BordersPanel}">
    <Setter
        Property="Template">
        <Setter.Value>
            <ControlTemplate
                TargetType="{x:Type local:BordersPanel}">
                <Grid Margin="0,0,0,0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="10.8" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="10.8" />
                    </Grid.RowDefinitions>
                    <local:Border
                        x:Name="topBorder"
                        Grid.Row="0"
                        Margin="0"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="top"
                        BlocksSize="{TemplateBinding BlocksSize}"
                        Width="Auto"
                        Height="10.8"/>

                    <ScrollViewer
                        Grid.Row="1"
                        HorizontalScrollBarVisibility="Auto"
                        VerticalScrollBarVisibility="Auto">
                        <ContentPresenter
                            Cursor="{TemplateBinding Cursor}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            Margin="{TemplateBinding Padding}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"/>
                    </ScrollViewer>
                    <local:Border
                        x:Name="bottomBorder"
                        Grid.Row="2"
                        Margin="0"
                        VerticalAlignment="Bottom"
                        HorizontalAlignment="Stretch"
                        BlocksSize="{TemplateBinding BottomBlocksSize}"
                        Width="Auto"
                        Height="10.8"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The C# code behind:

    class BordersPanel : ContentControl
{
    public static readonly DependencyProperty BlocksSizeProperty =
        DependencyProperty.Register("BlocksSize", typeof(string), typeof(BordersPanel));

    public static readonly DependencyProperty BottomBlocksSizeProperty =
        DependencyProperty.Register("BottomBlocksSize", typeof(string), typeof(BordersPanel));

    private Border topBorder;

    private Border bottomBorder;

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);

        if (e.Property == BordersPanel.BlocksSizeProperty)
        {
            BottomBlocksSize = BlocksSize + ";T";
        }
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        topBorder = GetTemplateChild("topBorder") as Border;
        bottomBorder = GetTemplateChild("bottomBorder") as Border;
    }

    static BordersPanel()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(BordersPanel),
            new FrameworkPropertyMetadata(typeof(BordersPanel)));
    }

    public BordersPanel()
    {
        SizeChanged += new SizeChangedEventHandler(Border_SizeChanged);
    }

    public string BlocksSize
    {
        get
        {
            return (string)GetValue(BlocksSizeProperty);
        }
        set
        {
            SetValue(BlocksSizeProperty, value);
        }
    }

    protected string BottomBlocksSize
    {
        get
        {
            return (string)GetValue(BottomBlocksSizeProperty);
        }
        set
        {
            SetValue(BottomBlocksSizeProperty, value);
        }
    }

    private void Border_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        double available = Width;
        if (Double.IsNaN(available) == true)
        {
            available = ActualWidth;
        }

        if (topBorder != null)
        {
            topBorder.Width = available;
        }
        if (bottomBorder != null)
        {
            bottomBorder.Width = available;
        }
    }
}

And this is how I use it:

    <local:BordersPanel
        BlocksSize="LL50;ML50;RU"
        Height="208" Canvas.Left="213" Canvas.Top="56.82" Width="428.5">
        <local:BinarySquares Margin="0,0,10,15" HorizontalAlignment="Right" VerticalAlignment="Bottom" d:LayoutOverrides="Width, Height"/>
        <Button Content="Button" HorizontalAlignment="Stretch" Height="300"/>
    </local:BordersPanel>

I am pretty sure that I am missing something pretty obvious, but that's the thing with obvious things, the more you look at them, the more they elude you...

By they way, if you think there are better ways to do what I did (with the binding, panel,...), comments are welcomed. :)

To be very clear: Before I was using:

    <local:Border
        Margin="0"
        VerticalAlignment="top"
        BlocksSize="LL50;ML200;RU"
        Width="Auto" Height="10.8" HorizontalAlignment="Stretch" d:LayoutOverrides="Width"/>
    <local:BinarySquares Margin="0,0,10,15" HorizontalAlignment="Right" VerticalAlignment="Bottom" d:LayoutOverrides="Width, Height"/>
    <Grid  Margin="8,14.8,8,24">
        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <Canvas x:Name="pnl_Content5" Margin="0,0,0,0">
                <Button Content="Button" HorizontalAlignment="Stretch" Canvas.Left="77" Canvas.Top="44"/>
            </Canvas>
        </ScrollViewer>
    </Grid>
    <local:Border
        Margin="0"
        VerticalAlignment="Bottom"
        BlocksSize="LL50;ML200;RU;T"
        Width="Auto" Height="10.8" HorizontalAlignment="Stretch"/>

I am using this code over and over each time I use a panel.

Obviously what I wrote in the previous code block is shorter and easier to maintain. (At least in my opinion) That is why I made this custom panel with the border to avoid duplicating code.

Était-ce utile?

La solution

Well I found a solution that is working. It's not exactly what I wanted, but it's close enough that it will do. I simply put a grid declaration inside the BordersPanel instance. I would have preferred to not have to do this, but I am guessing that that way it actually allows me to use any type of layout inside the panel.

            <local:BordersPanel
                BlocksSize="LL50;ML50;RU"
                Margin="0">
                <Grid>
                    <local:BinarySquares Margin="0,0,10,15" HorizontalAlignment="Right" VerticalAlignment="Bottom" d:LayoutOverrides="Width, Height"/>
                    <Button Content="Button" HorizontalAlignment="Stretch" Height="300"/>
                </Grid>
            </local:BordersPanel>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top