views:

191

answers:

3

I am writing a Base UserControl, that will be inherited by a bunch of other UserControls. I need to enforce a certain design for all these descendant controls (e.g. a couple of buttons must be on the top along with a label or two).

The rest of the descendant UserControl area is free to have whatever on it.

Initially, I thought that I could just plop a Panel onto the Base UserControl, set the Dock=Fill and the designer of the descendant control would be forced to add all the UI into this said panel. Then, I could resize the panel to my content.

But that is not the case - when you drop a control (say a GridView) onto the descendant UserControl, it adds it to the .Controls collection of the descendant user control, not the Panel I added.

Is there a way to force a certain layout from the Base user control?

+4  A: 

The short answer is "Yes"... However to get this behavior delves into the ugly world of writing your own designers which you have to associate with the each control which will need to inherit the special placement into the content panels..

The following link should be a good starting point as it gives an overview of what needs to be done as well as provides sample code.

http://support.microsoft.com/?id=813808

Be warned, however, this is not a trivial task. And there is a lot of code (30+ files) to sift through in order to understand how to implement a designer. It's been about 2 years since I have tried this.

On the other hand, have you considered changing your design to have a single parent control which has the labels and buttons, and which populates the content area with the appropriate child control(s)? Perhaps even having your child controls for the content area implement a specific interface to guarantee a contract so that the parent can interact with them without having to know a specific class name?

Jason D
Thanks for the answer. The piece you added in italics was basically I was gonna go with unless something simple was found to address my problem. I ended going with the single parent control approach.
AngryHacker
I'm always happy to help.
Jason D
+3  A: 

AngryHacker -

I went off thinking this was to be very easy, and found myself embarking on an interesting journey into WinForm design-time land. This is definitely not as easy as it was in VB6 :-) .

Anyhow, after a bit of research, I found a number of references to a method called EnableDesignMode(). However, this is not directly accessible from a WinForm component. This has to be called from a class that subclasses ParentControlDesigner, which is injected into the UserControl via a Designer attribute. In this project, I have called the subclass ButtonBarDesigner, and have overridden the Initialize() method, which allows me to tell the designer that the component ButtonBar has a child component fillPanel which can be accessed via the public property "FillPanel".

At this point, it seemed to be working. I managed to drop a control onto the ButtonBar and it was appearing in the fill panel. However, if you saved the form, and then reloaded it, it turned out that the control was instantiated, but not placed in the ButtonBar control. It seems that there was one sneaky last bit which the documentation for EnableDesignMode() conveniently leaves out. You need to have the DesignerSerializationVisibility attribute on the FillPanel property. Adding this attribute makes this work.

You need a reference to System.Design in your project for the design time stuff to work.

The following code is for the putative base class, ButtonBar:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Threading;

namespace ForceUserControl
{
    [Designer(typeof(ButtonBarDesigner))]
    public partial class ButtonBar : UserControl
    {
     public ButtonBar()
     {
      InitializeComponent();
     }

     /// <summary>
     /// Returns inner panel.
     /// </summary>
     /// <remarks>Should allow persistence.</remarks>
     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
     public Panel FillPanel
     {
      get { return fillPanel; }
     }

    }

    private class ButtonBarDesigner : ParentControlDesigner
    {
     public override void Initialize(IComponent component)
     {
  base.Initialize(component);

      Panel fillPanel = ((ButtonBar)component).FillPanel;

      // The name should be the same as the public property used to return the inner panel control.
      base.EnableDesignMode(fillPanel, "FillPanel");
     }
    }
}
Mark Bertenshaw
A: 

The short answer is "Yes"... However to get this behavior delves into the ugly world of writing your own designers which you have to associate with the each control which will need to inherit the special placement into the content panels..

The following link should be a good starting point as it gives an overview of what needs to be done as well as provides sample code.

olo