22

I'm designing a simple expander control.

I've derived from UserControl, drawn inner controls, built, run; all ok.

Since an inner Control is a Panel, I'd like to use it as container at design time. Indeed I've used the attributes:

[Designer(typeof(ExpanderControlDesigner))]
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))] 

Great I say. But it isn't...

The result is that I can use it as container at design time but:

  • The added controls go back the inner controls already embedded in the user control
  • Even if I push to top a control added at design time, at runtime it is back again on controls embedded to the user control
  • I cannot restrict the container area at design time into a Panel area

What am I missing? Here is the code for completeness... why this snippet of code is not working?

[Designer(typeof(ExpanderControlDesigner))]
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))] 
public partial class ExpanderControl : UserControl
{
    public ExpanderControl()
    {
        InitializeComponent();
....

[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
internal class ExpanderControlDesigner : ControlDesigner
{
    private ExpanderControl MyControl;

    public override void Initialize(IComponent component)
    {
        base.Initialize(component);

        MyControl = (ExpanderControl)component;

        // Hook up events
        ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService));
        IComponentChangeService c = (IComponentChangeService)GetService(typeof(IComponentChangeService));

        s.SelectionChanged += new EventHandler(OnSelectionChanged);
        c.ComponentRemoving += new ComponentEventHandler(OnComponentRemoving);
    }

    private void OnSelectionChanged(object sender, System.EventArgs e)
    {

    }

    private void OnComponentRemoving(object sender, ComponentEventArgs e)
    {

    }

    protected override void Dispose(bool disposing)
    {
        ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService));
        IComponentChangeService c = (IComponentChangeService)GetService(typeof(IComponentChangeService));

        // Unhook events
        s.SelectionChanged -= new EventHandler(OnSelectionChanged);
        c.ComponentRemoving -= new ComponentEventHandler(OnComponentRemoving);

        base.Dispose(disposing);
    }

    public override System.ComponentModel.Design.DesignerVerbCollection Verbs
    {
        get
        {
            DesignerVerbCollection v = new DesignerVerbCollection();

            v.Add(new DesignerVerb("&asd", new EventHandler(null)));

            return v;
        }
    }
}

I've found many resources (Interaction, designed, limited area), but nothing was usefull for being operative...

Actually there is a trick, since System.Windows.Forms classes can be designed (as usual) and have a correct behavior at runtime (TabControl, for example).

Community
  • 1
  • 1
Luca
  • 11,646
  • 11
  • 70
  • 125

3 Answers3

21

ParentControlDesigner doesn't know what you want do. It only knows you want your UserControl to be a container.

What you need to do is implement your own designer which enables design mode on the panel:

using System.ComponentModel;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MyCtrlLib
{
    // specify my custom designer
    [Designer(typeof(MyCtrlLib.UserControlDesigner))]
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        // define a property called "DropZone"
        [Category("Appearance")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
        public Panel DropZone
        {
            get { return panel1; }
        }
    }

    // my designer
    public class UserControlDesigner  : ParentControlDesigner
    {
        public override void Initialize(System.ComponentModel.IComponent component)
        {
            base.Initialize(component);

            if (this.Control is UserControl1)
            {
                this.EnableDesignMode(
                    ((UserControl1)this.Control).DropZone, "DropZone");
            }
        }
    }
}

I've learned this from Henry Minute on CodeProject. See the link for some improvements on the technique.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
  • 1
    It works! But actually it isn't has the following defects: you can move/size the "WorkingArea" (which differences has the "DropZone"?) but at runtime it isn't reflected; the dopped controls at design time cannot be aligned on container bounds. – Luca Apr 23 '10 at 05:55
  • 1
    I'm having those same 2 problems and wondering how that can be fixed – Telanor Apr 24 '10 at 23:09
  • Off the top of my head I'm not sure how to handle those issues. Did you look at the link? He might have solved those with his enhanced version. http://www.codeproject.com/KB/miscctrl/NestedControlDesigner.aspx – Igby Largeman Apr 25 '10 at 02:15
  • Yes. I've red carefully the article, but the behavior it's the same. Apart from designer integration, it seems that the settings of the control are not stored in the source, since at runtime is has all default values. – Luca May 03 '10 at 21:16
  • 1
    I know this is old stuff, but I found that if you make your DropZone docked the control can no longer be moved or resized from the designer. If you need the control in a very specific place, you could consider using two panels. An outer panel which is carefully positioned how you want and an inner panel which is dock filled and acts as the drop zone. – David Peterson May 24 '16 at 22:38
  • 1
    My above comment fixes the issue, but for this particular use case, it also disables the auto-scroll capability. This is indeed a problem. Hopefully, my comment will still help get someone in the right direction. I will continue poking at it myself and post again if I solve it. – David Peterson May 24 '16 at 22:42
3

In addition to the answer above. It is mentioned in the comments, that the user is able to drag the WorkingArea. My fix for that is to include the WorkingArea panel in another panel, setting it to Dock.Fill. To disallow the user to change it back, I have created a class ContentPanel that overrides and hides the Dock property:

class ContentPanel : Panel
{
    [Browsable(false)]
    public override DockStyle Dock
    {
        get { return base.Dock; }
        set { base.Dock = DockStyle.Fill; }
    }
}

For me, this makes it sufficiently safe. We are only using the control internally, so we mainly want to prevent developers from accidently dragging things around. There are certainly ways to mess it up anyway.

philip
  • 321
  • 3
  • 19
3

To prevent the working area from being moved/resized in the designer you have to create a class for that working area that hides the Location, Height, Width, Size properties from the designer:

public class WorkingArea : Panel
{
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public new Point Location
    {
        get
        {
            return base.Location;
        }
        set
        {
            base.Location = value;
        }
    }
...
}   
Jens Meinecke
  • 2,904
  • 17
  • 20