locked
How do I launch the collection editor associated to a property? RRS feed

  • Question

  • Hi!  I am refining a databound ListView control I created by adding a designer to it.  I am currently in the process of adding smart tags.  I want to keep the original ones, so I added, among other items, a DesignerActionMethodItem object called "Edit columns...".  I did it the standard way, by overriding ActionLists from the designer, and returning an instance of my own ActionList-derived action list.  This action list of mine have the method below.  It doesn't show any errors, but it doesn't show the collection editor.

    Does anybody know what I am doing wrong?

            public void EditColumns()
            {
                PropertyInfo pi = _lv.GetType().GetProperty("Columns");
                object[] atts = pi.GetCustomAttributes(typeof(EditorAttribute), true);
                if (atts.Length > 0)
                {
                    EditorAttribute editorAtt = atts[0] as EditorAttribute;
                    Type editorType = System.Type.GetType(editorAtt.EditorTypeName);
                    ConstructorInfo ctor = editorType.GetConstructor(new Type[] { typeof(Type) });
                    UITypeEditor editor = ctor.Invoke(new object[] { 
                        typeof(ListView.ColumnHeaderCollection) }) as UITypeEditor;
                    editor.EditValue(null, this.Component.Site, _lv.Columns);
                }
            }
    


    Thank you.
    MCP
    • Moved by jialge_msftMicrosoft employee Thursday, September 3, 2009 9:34 AM move to winform designer forum as requested by OP (From:.NET Base Class Library)
    Monday, August 31, 2009 1:11 PM

Answers

  • And it is done!!

    It has to be Microsoft's fault, this one.  I'll explain.

    First of all, yes, I needed to provide the IWindowsFormsEditorService myself.  I haven't implemented the DropDownControl() and CloseDropDown() methods yet, but at this point is of lesser importance, I think.  Anyway, that is point 1.

    Second:  IUIService and AmbientProperties services are required.

    How Did I Find Out?
    I decided to implement IServiceProvider in my DesignerActionList so I could monitor the requested service types.  Initially, only the IWindowsFormsEditorService service was being requested, so I was puzzled.  Just a single call to GetService() requesting IWindowsFormsEditorService.  That was it.  I was sure I was going to see more service types!  So much for my theory, I thought.

    I then debugged the call to UITypeEditor.EditValue() when called from a property grid.  It had context, service provider, and value.  I noticed that both context and service provider were of the same class (PropertyGridEntry or something like that), and most likely they were the exact same object.  So I said to myself:  "Myself, how come you didn't think of that?  Instead you did a separate class to provide a context.  Do it like this so you don't have that many classes lying around!".  So I did.  My DesignerActionList class now implemented IServiceProvider, IWindowsFormsEditorService, and now ITypeDescriptorContext.  This changed my call to UITypeEditor.EditValue():

    //Before... context was of type EditorContext, a class of my own that implemented ITypeDescriptorContext
    editor.EditValue(context, this, _lv.Columns);
    
    //Now... ITypeDescriptorContext is implemented by the DesignerActionList class.
    editor.EditValue(this, this, _lv.Columns);
    

    To my surprise, at this point I started receiving requests for the IUIService and AmbientProperties services!!

    Where is Microsoft's fault here?  They are using the object passed as context as a service provider, instead of using the service provider parameter (second one).  Sure, they did this because the PropertyGrid control uses the same object to provide both context and services, but my ignorance of this was leading me to use two different objects.

    So, the final solution is:

    1. Make a class of yours implement IServiceProvider, IWindowsFormsEditorService, and ITypeDescriptorContext.  I used the same DesignerActionList class.
    2. Pass the instance of the above class to the call of EditValue() as both context and service provider.
    The IUIService and AmbientProperties services can be provided by the designer host.  Relay calls to any unknown service to the designer host:

            object IServiceProvider.GetService(Type serviceType)
            {
                if (serviceType.Equals(typeof(IWindowsFormsEditorService)))
                {
                    return this;
                }
                return GetService(serviceType);
            }

    Many thanks to everyone that chipped in!  Very much appreciated.  Really.
    MCP
    • Marked as answer by webJose Wednesday, September 30, 2009 7:13 PM
    Wednesday, September 30, 2009 7:13 PM

All replies

  • Use TypeDescriptor.GetEditor to get the editor associated with an object or its type.  However I'm not sure about the rest of your code and whether it'll work the way you want.  I'd have to play around with it.  The DGV (for example) doesn't bother going back through the editor interface but rather just directly creates and manages the underlying UI.

    Michael Taylor - 8/31/09
    http://p3net.mvps.org
    Monday, August 31, 2009 1:41 PM
  • Hi Michael:

    Thanks for the post.  I tried using TypeDescriptor.GetEditor(_lv.Columns, typeof(UITypeEditor)).  It shows no errors, but no user interface as well.

    Could you elaborate on how the DGV does its "Edit columns..." DesignerActionMethodItem?  It could be that I just have to do it the same way. :(
    MCP
    Monday, August 31, 2009 1:49 PM
  • DGV just creates the underlying column editor directly and foregoes the editor interface altogether.  This works if you have direct access to the underlying designer but if you are working from a derived class then it probably won't as the designers are rarely public.

    You can debug your designer by debugging VS directly.  It makes things a little easier.  The easiest way I've found to do that is debug your control library (or wherever your control is) but set the debug program to be VS.  Then start debugging.  When VS starts up create a new project and drop your control onto it.  You can set BPs in your designer and watch them get triggered by VS.

    I'm a little curious as to why you need to invoke the original actions.  One of the things you're designer will probably do is override the action list.  If you call the base class first then you'll get back the list of actions defined for the LV.  You can then either return these directly or return your own.  Either way the column editor action will be in the list.  Provided you return the original action you shouldn't have to do anything yourself.

    Michael Taylor - 8/31/09
    http://p3net.mvps.org
    Monday, August 31, 2009 2:07 PM
  • Hi.

    I don't have access to the form directly because my column editor inherits from CollectionEditor.  I don't have access to the underlying form used by CollectionEditor.  :(

    And to clarify things, I am not returning the base ActionList object for the original ListView.  I just want my brand new ActionList class to provide the same options the original ListView provides except "Edit items...".  So, I want my own action items, plus copies (or clones if you will) of the action items shown by a regular ListView control.

    Now, back to the issue, I just can't conceive that Microsoft would have made it impossible for developers to create such a smart tag.  I think there must be a way.  Maybe I'm using the wrong IServiceProvider?  What IServiceProvider does the PropertyGrid use?  Because the Visual Studio PropertyGrid has no problems invoking my editor.

    UPDATE:  I actually tested the PropertyGrid control in the same form and ran the sample.  My collection editor works fine from that PropertyGrid.  I think the money is in the IServiceProvider used, or maybe in calling the other overload of EditValue, the one with 3 parameters.
    MCP
    Monday, August 31, 2009 4:21 PM
  • Editors are designed to run inside the PG.  Trying to run them outside PG might not work.  For the smart tags the associated action generally doesn't rely on the editor at all.  For example an editor that wants to display itself as a dropdown can't be displayed outside a parent window.  Only editors that display modal dialogs would work.  Designers are designed to be self-sustaining.  Inheritance from the base controls is not generally supported.  This is the battle you're going to face.  In this particular case you want to share this functionality.  Unfortunately without derivation (or even with) there isn't going to be any easy way.  However you might be able to hack around it if you need to.

    Your only real option would be to use reflection to load the underlying column editor dialog because it isn't visible.  This of course makes you dependent upon the implementation.  Unfortunately the alternative approach of replicating the action list of ListView won't work because you can't clone the existing action items and hook your component in.

    Another alternative that seemed reasonable was using the designer's verbs but unfortunately it doesn't appear to work in this case.  Nevertheless I've come up with a hack that seems to work.  You'll want to be careful using it and make sure you test it though.

    public void InvokeTest ( )
    {
       ListView lv = new ListView();
       ControlDesigner designer = TypeDescriptor.CreateDesigner(lv, typeof(IDesigner)) as ControlDesigner;
       if (designer != null)
       {
          ControlDesigner newDesigner = Activator.CreateInstance(designer.GetType(), true) as ControlDesigner;
          if (newDesigner != null)
          {
             newDesigner.Initialize(Component);
    
             foreach (DesignerActionList list in newDesigner.ActionLists)
             {
                foreach (DesignerActionItem item in list.GetSortedActionItems())
                {
                   if (String.Compare(item.DisplayName, "Edit Items", true) == 0)
                   {
                      DesignerActionMethodItem meth = item as DesignerActionMethodItem;
                      if (meth != null)
                         meth.Invoke();
                   };
                };
             };
          };
       };
    }
    The code assumes that your component derives from ListView.  To avoid reflection and potential hard-coding of values it gets the standard listview designer by creating a new listview instance and then fetching the designer for it.  You would think that you could use your own component but the designer impl is smarter than that so creating a new instance is easiest.  Once you have the underlying type you can create a new designer instance.  Given the new designer instance you can then associate it with your component.  Finally you can locate the action you want and invoke it.

    Note that there is plenty of room for optimizations here.  Firstly you need only find the designer type and action once.  Subsequent requests can reuse the same designer and action or, at the very least, recreate the designer instance.  It's a hack but it seems to work.

    Michael Taylor - 8/31/09
    http://p3net.mvps.org

    Monday, August 31, 2009 5:31 PM
  • Michael:

    Your proposed solution is ingenious indeed, and it probably is the closest to a solution so far.  I must say, however, that it doesn't work for me in this particular instance.  It turns out that I want "Edit columns...", but I don't use the usual column headers.  Instead, I use a derived class in order to add a new property:  DataField.  Cool.  But the stock Columns editor was not showing DataField.  What did I have to do?  Hide the original Columns collection with the new keyword and declare my own, so I could set my own collection editor derived from CollectionEditor.

    So your solution works for 100% stock properties only.  Any "pimped" properties seem to be out of the question. :(

    I'll dig some more later on, but it is a grim panorama so far.
    MCP
    Monday, August 31, 2009 7:01 PM
  • Moderator(s):

    May I ask that you move this thread to the Windows Forms Designer forum?  Maybe it is the best place for this question.

    Many thanks.
    MCP
    Tuesday, September 1, 2009 7:23 PM
  • Hello MCP

    I will help you move the thread to the Windows Forms Designer forum.

    Regards,
    Jialiang Ge
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Thursday, September 3, 2009 9:34 AM
  • UPDATE:

    I can now get the form to show, although it is not a good solution.

    After playing a bit and reading, I read something that hinted that the IWindowsFormsEditorService was not provided by the Visual Studio designer, but instead it was provided by the PropertyGrid control itself.  Although some of my later findings might say no-no to this last part, it was true that Visual Studio designer was not providing the IWindowsFormsEditorService service.

    So in my latest attempt I did two things:

    1.  I created a context class that implements ITypeDescriptorContext so I can pass a context object to UITypeEditor.EditValue(), and
    2.  I made my DesignerActionList class implement IWindowsFormsEditorService.

    The first one was simple enough, and the second one too, but I had to add the service to the designer first using the designer host service's AddService() method.

    My implementation of IWindowsFormsEditorService was simple too:  I just did:

    DialogResult ShowDialog(Form dialog)
    {
        return dialog.ShowModal();
    }
    At this point, the columns editor showed up! :-)  But now new problems:

    Have you noticed a property called DisplayIndex on top of the PropertyGrid whenever you edit the Columns collection of a ListView control?  Well, that property accurately shows the display index if I edit the Columns collection from the PropertyGrid provided by Visual Studio in the main designer window, but if I invoke the collection editor through my DesignerActionList class, that property shows -1 instead of a correct display index.

    Furthermore, the PropertyGrid used to edit the properties of the column headers attempts to use my IWindowsFormsEditorService service instead of using what it would have used if invoked from the main PropertyGrid, and because I did not implement DropDownControl() and CloseDropDown(), I cannot edit certain properties, most notably my own DataField property.

    So, I think I'm being cornered here to just one solution:  Find a way to acquire the original IWindowsFormsEditorService service that the main PropertyGrid uses when invoking type editors.

    Any ideas?? Anyone??  I'm all ears. :-)
    MCP
    Monday, September 28, 2009 11:11 PM
  • The PG does implement this interface as does any host that wants to house a UITypeEditor.  This interface provides just a copy of methods needed to host a custom editor inside a parent (in this case the PG).

    If you're going to host the editors directly then you'll have to implement this interface.  It is true that any editor that is hosted from within any editor that you are hosting will use your implementation as well.  Implementing DropDownControl should be pretty straightforward as all you really need to do is give it a combo box like control to work with.  Whether you have to implement the combo logic or not I don't recall.  CloseDropDown just closes the dropdown if it were open.  Given all you've implemented this shouldn't be that hard :}

    FYI if you want the PG's implementation of the interface then you can always create an instance of the PG and then get the interface for it.  However the PG's implementation is probably going to make assumptions about how it hosts things so I don't think it'll work correctly.  Haven't tried it though.

    Michael Taylor - 9/28/09
    http://p3net.mvps.org
    Tuesday, September 29, 2009 1:14 AM
  • And it is done!!

    It has to be Microsoft's fault, this one.  I'll explain.

    First of all, yes, I needed to provide the IWindowsFormsEditorService myself.  I haven't implemented the DropDownControl() and CloseDropDown() methods yet, but at this point is of lesser importance, I think.  Anyway, that is point 1.

    Second:  IUIService and AmbientProperties services are required.

    How Did I Find Out?
    I decided to implement IServiceProvider in my DesignerActionList so I could monitor the requested service types.  Initially, only the IWindowsFormsEditorService service was being requested, so I was puzzled.  Just a single call to GetService() requesting IWindowsFormsEditorService.  That was it.  I was sure I was going to see more service types!  So much for my theory, I thought.

    I then debugged the call to UITypeEditor.EditValue() when called from a property grid.  It had context, service provider, and value.  I noticed that both context and service provider were of the same class (PropertyGridEntry or something like that), and most likely they were the exact same object.  So I said to myself:  "Myself, how come you didn't think of that?  Instead you did a separate class to provide a context.  Do it like this so you don't have that many classes lying around!".  So I did.  My DesignerActionList class now implemented IServiceProvider, IWindowsFormsEditorService, and now ITypeDescriptorContext.  This changed my call to UITypeEditor.EditValue():

    //Before... context was of type EditorContext, a class of my own that implemented ITypeDescriptorContext
    editor.EditValue(context, this, _lv.Columns);
    
    //Now... ITypeDescriptorContext is implemented by the DesignerActionList class.
    editor.EditValue(this, this, _lv.Columns);
    

    To my surprise, at this point I started receiving requests for the IUIService and AmbientProperties services!!

    Where is Microsoft's fault here?  They are using the object passed as context as a service provider, instead of using the service provider parameter (second one).  Sure, they did this because the PropertyGrid control uses the same object to provide both context and services, but my ignorance of this was leading me to use two different objects.

    So, the final solution is:

    1. Make a class of yours implement IServiceProvider, IWindowsFormsEditorService, and ITypeDescriptorContext.  I used the same DesignerActionList class.
    2. Pass the instance of the above class to the call of EditValue() as both context and service provider.
    The IUIService and AmbientProperties services can be provided by the designer host.  Relay calls to any unknown service to the designer host:

            object IServiceProvider.GetService(Type serviceType)
            {
                if (serviceType.Equals(typeof(IWindowsFormsEditorService)))
                {
                    return this;
                }
                return GetService(serviceType);
            }

    Many thanks to everyone that chipped in!  Very much appreciated.  Really.
    MCP
    • Marked as answer by webJose Wednesday, September 30, 2009 7:13 PM
    Wednesday, September 30, 2009 7:13 PM