locked
VS2015 - trying to "walk" control list in a Windows Forms user control/form RRS feed

  • Question

  • I'm making an attempt to write my very first Visual Studio 2015 addin, using VSIX. I've gone through the walkthrough to create a new menu command, but I'm very quickly getting lost after that.

    In a nutshell, I'm trying to create a dialog that will come up and display the "tabbable" controls in a Windows Forms usercontrol or form, in a treeview. The user will be able to move the nodes around in the tree, and that will cause the tab order to change for those particular controls. I'm stuck on the very first part of the addin, which is how to get a list of controls in the current form/user control in the active designer. I'm thinking I need to make use of some service to get the current form, and then I can probably just enumerate ActiveForm.Controls in the usual manner in order to populate the treeview I'm creating. But I haven't been able to determine how to get the active designer form.

    Can someone please help me get started with this? I'm sure I'll probably have more questions in the future, such as how to update the source code to store the new tab order for the controls, but I'll take it one step at a time. Thank you!

    Monday, June 29, 2015 4:04 PM

Answers

  • Oops, went back and reread your post. I completely mistook tabbable to mean some sort of tab control :-(.

    Okay I get where you're going now. You're talking about tab order (aka z-order). :-). Note, you can readily identify (and change) the tab order via the View.Tab Order menu item.

    Be that as it may, what you want is to get at the IDesignerHost interface, so you can traverse the controls. Something like this:

            private IDesignerHost GetCurrentDesignerHost()
            {
                IDesignerHost designerHost = null;

                // get the IVsWindowFrame of the active document
                var vsMonSel = GetService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
                object objDocFrame = null;
                vsMonSel.GetCurrentElementValue((uint)VSConstants.VSSELELEMID.SEID_DocumentFrame, out objDocFrame);

                if (objDocFrame != null)
                {
                    IVsWindowFrame docFrame = (IVsWindowFrame)objDocFrame;
                    object objDocView = null;
                   
                    // retrieves the IUnknown of the DocView object
                    docFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out objDocView);

                    // QI for IServiceProvider
                    System.IServiceProvider sp = objDocView as System.IServiceProvider;

                    if (sp != null)
                    {
                        // QueryService to get the IDesignerHost
                        designerHost = sp.GetService(typeof(IDesignerHost)) as IDesignerHost;
                    }
                }
                return designerHost;
            }

            private void EnumWinformControls(object sender, EventArgs e)
            {
                IDesignerHost designerHost = GetCurrentDesignerHost();

                foreach (IComponent component in designerHost.Container.Components)
                {
                    System.Windows.Forms.Control ctrl = component as System.Windows.Forms.Control;
                    if (ctrl!=null)
                    {
                        string msg = string.Format("{0} : {1}", ctrl.Name, ctrl.TabIndex.ToString());
                        System.Diagnostics.Debug.WriteLine(msg);
                    }
                }
            }

    Sincerely,


    Ed Dore

    Tuesday, June 30, 2015 10:17 PM
  • Hi Eric,

    I agree the existing functionality isn't exactly convenient. Just wanted to point it out in the event you were unaware of it. I don't do a lot of winform based programming these days, and had forgotten how goofy that particular feature is.

    Once you get it working, I'd definitely encourage you to post to the VS Extension Gallery, as I suspect there are more than a few people that would be very interested in your extension here. :-)

    Sincerely,


    Ed Dore

    • Marked as answer by Caillen Thursday, July 9, 2015 2:04 AM
    Wednesday, July 1, 2015 5:02 PM

All replies

  • Hi Eric,

    >>I'm trying to create a dialog that will come up and display the "tabbable" controls in a Windows Forms usercontrol or form, in a treeview

    >>I'm stuck on the very first part of the addin, which is how to get a list of controls in the current form/user control in the active designer

    To show a Windows forms usercontrol in the VSPackage project, please follow these steps:

    1. Create a VSPackage project, in the wizard, select ToolWindow

    2. By default, the project uses WPF UserControl as the content of the tool window, you need to do a minor change to host the Windows Forms UserControl in the ToolWindow:

    HOWTO: Host a Windows Forms usercontrol in a toolwindow from a Visual Studio package.

    public class MyToolWindow : ToolWindowPane
        {
            WinformUserControl control;
    
            public MyToolWindow() :
                base(null)
            {
                this.Caption = Resources.ToolWindowTitle;
                this.BitmapResourceID = 301;
                this.BitmapIndex = 1;
                control = new WinformUserControl();
            }
    
            public override IWin32Window Window
            {
                get
                {
                    return (IWin32Window)control;
                }
            }
    
        }

    3. In the Windows Forms UserControl, you could add those standard Winform controls like TabControl, TreeView. Let's say we just want to get the button in the ToolWindow's UserControl and perform the click event. We need to find the tool window first, then use the window property to find our Winform UserControl, then find the inner controls:

    ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true);
                WinformUserControl myWinformUserControl = window.Window as WinformUserControl;
                System.Windows.Forms.Button button1 = myWinformUserControl.Controls.Find("button1", true)[0] as System.Windows.Forms.Button;
                button1.PerformClick();

    For more information, please see documentation here:

    Walkthrough: Creating a Tool Window


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, June 30, 2015 3:09 AM
  • Hi Caillen!

    While I appreciate the information you provided, I think you may have answered a question that I didn't ask. I'm not trying to create a tool window. I'm displaying a dialog (which I'm already doing successfully). What I need is:

    - The user is in a WinForms project, looking at the WinForms designer for a WinForms Form or UserControl.

    - The user clicks some *menu item*

    - I need to be able to determine what Form or UserControl the user is currently viewing in the active designer, and I then need to be able to traverse that class to get a list of the controls in it.

    I haven't been able to figure out how to accomplish the third item.

    -Eric

    Tuesday, June 30, 2015 1:37 PM
  • Hi Eric,

    Have you considered implementing a designer for that particular control type? That's typically now this sort of thing is done for winform controls.

       Custom Designers
       How to: Implement a Designer for a Control

    I haven't looked into this in years, but I do recall there is the IDesignerHost interface that you'll need to retrieve from the winform designer, but if you're building a custom control designer similar to the above links, the following topic has some sample code that shows how to retrieve various design time services you can use to do all sorts of things.

       How to: Access Design-Time Services

    Sincerely,


    Ed Dore

    Tuesday, June 30, 2015 5:44 PM
  • Ed,

    Are you saying that I need to completely re-implement the designer for the Form class and for the UserControl class? I can't believe that would be required for the simple little thing I want to do.

    I still don't think I'm explaining clearly what I need to do. I need the user to be in the *existing* designer for *any* Form or UserControl descendent in *any* WinForms project. I don't want to modify the designer in any way. I want a new menu item in the menu (which I've already done - that part was easy).

    When the user selects my new menu item, I want to get a reference to the form that the user is designing. Let me simplify the requirements of my addin a bit. Let's pretend it needs to do the following:

    1) Determine what class (Form or UserControl) the user is designing in the *built-in* (WinForms) form designer

    2) Traverse all controls in that Form/UserControl and set the TabIndex for every control to be the existing value * 10. So if the existing TabIndex is equal to 3, then my code will change it to be 30.

    That's it. Step 2 is completely contrived, but if I knew how to do the two steps listed above, then I (believe I) could implement everything else I need.

    -Eric

    Tuesday, June 30, 2015 7:06 PM
  • Oops, went back and reread your post. I completely mistook tabbable to mean some sort of tab control :-(.

    Okay I get where you're going now. You're talking about tab order (aka z-order). :-). Note, you can readily identify (and change) the tab order via the View.Tab Order menu item.

    Be that as it may, what you want is to get at the IDesignerHost interface, so you can traverse the controls. Something like this:

            private IDesignerHost GetCurrentDesignerHost()
            {
                IDesignerHost designerHost = null;

                // get the IVsWindowFrame of the active document
                var vsMonSel = GetService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
                object objDocFrame = null;
                vsMonSel.GetCurrentElementValue((uint)VSConstants.VSSELELEMID.SEID_DocumentFrame, out objDocFrame);

                if (objDocFrame != null)
                {
                    IVsWindowFrame docFrame = (IVsWindowFrame)objDocFrame;
                    object objDocView = null;
                   
                    // retrieves the IUnknown of the DocView object
                    docFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out objDocView);

                    // QI for IServiceProvider
                    System.IServiceProvider sp = objDocView as System.IServiceProvider;

                    if (sp != null)
                    {
                        // QueryService to get the IDesignerHost
                        designerHost = sp.GetService(typeof(IDesignerHost)) as IDesignerHost;
                    }
                }
                return designerHost;
            }

            private void EnumWinformControls(object sender, EventArgs e)
            {
                IDesignerHost designerHost = GetCurrentDesignerHost();

                foreach (IComponent component in designerHost.Container.Components)
                {
                    System.Windows.Forms.Control ctrl = component as System.Windows.Forms.Control;
                    if (ctrl!=null)
                    {
                        string msg = string.Format("{0} : {1}", ctrl.Name, ctrl.TabIndex.ToString());
                        System.Diagnostics.Debug.WriteLine(msg);
                    }
                }
            }

    Sincerely,


    Ed Dore

    Tuesday, June 30, 2015 10:17 PM
  • I think that's going to be the answer, Ed. It'll be tomorrow before I get time to play with it, though, so I'll mark it as the solution tomorrow, assuming it works for me :-)

    I'm painfully aware of the existing View|Tab Order menu item. That's why I want to write something that works much better for me. The existing method is adequate if you only have a few controls on a form. I (like many others, I expect) frequently have forms with numerous controls, layered several levels deep (on tab controls, group boxes, and so forth). Let's say I have 16 controls on a form, for example. Perhaps I only want to change the 15th and 16th controls. Years ago, I could Ctrl+Click on the 14th control and then just click on the 15th and 16th controls to change the tab index of the last two controls on the form. However, that hasn't worked in Visual Studio .NET in a long, long time (perhaps even never). I'm running a screen resolution of 2,560 x 1,600 and if I accidentally click just outside a control, it'll screw up the tab order and I have to start over, which causes me to use all kinds of words that I don't want to use. And on occasion, I even have a form where two controls sit on top of each other (one or the other is hidden at runtime). Trying to set the tab index of those two controls using the existing method is an exercise in futility.

    Having the control (name)s in a treeview control where I can move them up and down to specify the relative tab order, and then have the compiler update those TabIndex properties for me automatically will make my life *so* much easier in all but the most trivial cases.

    I have requested this ability in the past, and so have other developers (it was easy for me to find other requests when performing a Google search), but Microsoft's response was that their existing method is plenty easy enough. Clearly it isn't easy enough, so I finally got fed up enough that I thought it would be a good excuse for me to learn how to write a simple addin.

    -Eric

    Tuesday, June 30, 2015 10:31 PM
  • Hi Eric,

    I agree the existing functionality isn't exactly convenient. Just wanted to point it out in the event you were unaware of it. I don't do a lot of winform based programming these days, and had forgotten how goofy that particular feature is.

    Once you get it working, I'd definitely encourage you to post to the VS Extension Gallery, as I suspect there are more than a few people that would be very interested in your extension here. :-)

    Sincerely,


    Ed Dore

    • Marked as answer by Caillen Thursday, July 9, 2015 2:04 AM
    Wednesday, July 1, 2015 5:02 PM