dparsons
dparsons

Reputation: 2866

Modify ToolboxItem during drag and drop

I am working with the DesignSurface class and I have run into a frustrating problem that I can't seem to figure out. I am dragging items from my Toolbox and dropping them onto my form without a problem but I want to modify the properties of these items as they are placed onto the DesignSurface. So, for example, if I drag a label onto the DesignSurface I may change the value of the BackColor of the control. I know that ToolboxItem exposes CreateComponents() which takes an optional parameter of properties but this is called by the lower level API and I don't see where or how to override it so I can supply my own values. Does anyone have any ideas? Thanks!

Upvotes: 1

Views: 618

Answers (2)

dparsons
dparsons

Reputation: 2866

While I feel that this is a bit "hacky" I don't necessarily know that it is wrong (and if it is technically "wrong" please leave me some feedback on best practices!) and it solved the problem at hand.

What I ended up doing was extending ToolboxItem based on an MSDN article that I read.

public class SurfaceToolboxItem : ToolboxItem
{
    private delegate void SetTextHandler(Control c, string text);
    public string ReportFieldName { get; set; }

    protected override IComponent[] CreateComponentsCore(IDesignerHost host)
    {
        var ctrl = (Label)host.CreateComponent(typeof(Label));
        var root = (Control) host.RootComponent;

        root.BeginInvoke(new SetTextHandler(SetText),
                         new object[] {ctrl, ReportFieldName});

        return new IComponent[]{ ctrl };
    }

    private void SetText(Control ctrl, string text)
    {
        ctrl.Text = text;
    }

}

Some obvious deficiencies:

  • This item will only ever create a Label control
  • This item will only ever set the text property of said label control based on the public string property.

To overcome these problems the item could be extended to act more like the base ToolboxItem (implementing a ctor accepting a Type parameter, allowing a dictionary of property name/values to be passed in to be set after the fact, etc)

Additionally this broke my drag and drop functionality from my Toolbox to the Design Surface and it was particularly frustrating because it wasn't overly apparent why. The problem was in my DeserializeToolboxItem method of my Toolbox Service implementation.

Originally it looked like this:

ToolboxItem IToolboxService.DeserializeToolboxItem(object serializedObject)
{
   return (ToolboxItem) ((DataObject) serializedObject).GetData(typeof (ToolboxItem));
}

The problem was that the cast to ToolboxItem was returning null since the object was of type SurfaceToolboxItem so I modified the method to look like this:

ToolboxItem IToolboxService.DeserializeToolboxItem(object serializedObject)
{
   var data = (ToolboxItem) ((DataObject) serializedObject).GetData(typeof (ToolboxItem));

   return data ?? (ToolboxItem)((DataObject)serializedObject).GetData(typeof(SurfaceToolboxItem));
 }

Thus supporting both my extension as well as default ToolboxItem's.

Upvotes: 1

King King
King King

Reputation: 63327

This is not an answer, I just want to show what I have found with your problem. I think you should have your own way to access to your own Toolbox, and this Toolbox implements the interface IToolboxService, this interface has a method called GetSelectedToolboxItem(IDesignerHost). And I think you have to implement that method to specify how you get the selected item, I think your Toolbox should have some control which holds the list of toolbox items, it can be a ListBox...

The second important thing is the IDesignerHost, as I understand this may be what you're interested in. It should have some relation with your DesignSurface. It has some events for you to interest, like this:

IDesignerHost host = {some method to get it};
host.TransactionClosed += (s,e) => {
  ICollection selected = ((ISelectionService)host.GetService(typeof(ISelectionService))).GetSelectedComponents();                
  IComponent[] comps = new IComponent[selected.Count];
  selected.CopyTo(comps, 0);
  string displayName = YourToolBox.GetSelectedToolboxItem(host).DisplayName;
  ((Control)comps[0]).Text = displayName;
};

I've tested in a project which implements a very complex IDesignerHost, it has a method for me to create the host to test. When dragging-n-dropping a control on the DesignSurface, it's still not affected by the code in TransactionClosed, I have to do something like Resizing, Moving... the control and after that TransactionClosed event is raised and it shows the DisplayName OK. I don't find any other event better, the most suitable event should be ComponentAdded or ComponentAdding but neither of them works.

I have one more question, the sample project I tried is a CustomFormsDesigner which runs as other normal Windows Forms application, it shows a Toolbox, a DesignSurface and a Properties Grid. I do everything after running it. But what about your case? I mean when you drag-n-drop your control onto your DesignSurface, you are still at design time in the Visual Studio window? or in your own application (as a Designer) window?

I have to say that, this problem is so complex to me. Thanks to you, I know about this kind of application.

Upvotes: 1

Related Questions