Click here to Skip to main content
15,888,325 members
Articles / Programming Languages / Visual Basic
Tip/Trick

Accessing the Controls on a Form at Design Time

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
29 Oct 2013CPOL3 min read 33.4K   450   7   16
How to discover all the controls on a form at design time for a property editor

Introduction

The point of the tip is about gaining access to the design time form from within a property editor (UITypeEditor) in Visual Studio using Visual Basic.

Notes on the download

  1. Don't try running the app, it doesn't do anything
  2. The point is that within the IDE, with the form designer open (Simple Demo -> ExampleForm), you can select the component called ExampleComponent1 and then in the property editor, the TargetControls property is a collection and clicking on the ellipsis reveals the UITypeEditor
  3. You can note that the controls that are selected (or not) are persisted between IDE sessions which means that when you build your app, the collection will be visible at run time

I was creating a Visual Studio property editor (UITypeEditor) for a .NET component that holds a subset of the controls on the form to which it is attached at design time. Initially, I had great difficulty working out how to do that but as usual, once you discover the answer, it turns out to be pretty easy.

The image below shows a demo application using the custom UITypeEditor. There's a custom component with a property, called TargetControls, which uses that custom UITypeEditor. The actual property editor itself is the form bottom left (red border) and as you can see, it contains a list of the controls on the form shown at the top. The image is from within Visual Studio, not in debugging mode, so that's the form designer you can see at the top.

The Control Collection Property Editor showing a CheckedListBox with all the Controls on a Form in the Design window

Background

It's pretty easy to create a Collection(Of Control) at run-time, where the Controls in the Collection are the Controls on the Form. It's notably more difficult to do this at design time - and that's what I needed to do, so I created a UITypeEditor that can populate a Component or Control property with one or more of the Controls on a form at design time. I used the Creating Property Editors in DesignTime for VS.Net Easily (UITypeEditor Helper) article by S.Serpooshan to provide the base class.

Here, I will reproduce only the part of the code for retrieving the list of Controls on the form since the full class will (hopefully) be the subject of an article rather than a tip.

Using the Code

Within a UITypeEditor, there is a method called EditValue, whose job is to display the custom control (a dropdown or a modal form) and update the property dependent on how the user interacts with the control. I've removed all the surrounding code that loads and shows the custom control and the code that manages updating the property in order to focus the tip on the interesting bit. All the other stuff you can get from S. Serpooshan's article.

myControl is a reference to the custom property editor control, which in this case is a form with a CheckedListBox (the name of which is AvailableControls).

The first For Each loop adds items to that CheckedListBox having accessed a reference to the design time form through the context parameter of the EditValue function. Working out that this parameter gives you a way to reference the design time form was the key, it's pretty easy from there.

The second For Each loop simply determines which items in the CheckedListBox to check before displaying it, i.e., those that are held in the current value of the property before the user gets a chance to edit it.

VB.NET
Public Overrides Function EditValue(ByVal context As ITypeDescriptorContext, _
ByVal provider As IServiceProvider, ByVal value As Object) As Object
    ...
    ...
    'Load the AvailableControls checked list with all form controls visible through the Designer
    For Each c As Component In context.Container.Components
        'Cycle through the components owned by the form in the designer
        If TypeOf c Is Control Then
            'if the component is a control then add it to the CheckedListBox
            myControl.AvailableControls.Items.Add(c)
        End If
    Next
    If value IsNot Nothing Then
        'If the property currently has anything in its collection 
        'then check those items in the checked list box
        'create a temporary Collection that holds the current controls held by the property
        Dim tCollection As Collection(Of Control) = CType(value, Collection(Of Control))
        Dim found As Integer
        For Each c As Control In tCollection
            'cycle through the current controls held by the property
            found = myControl.AvailableControls.Items.IndexOf(c)
            If Not found = -1 Then
                'if the control has been found in the CheckedListBox 
                'then set its checked state to true
                myControl.AvailableControls.SetItemChecked(found, True)
            End If
        Next
    End If
    ...
    ...
End Function

To use the code, you would create a custom component with a public property of type Collection(Of Control). You'd then set its EditorAttribute to the class name of the UITypeEditor.

VB.NET
Public Class ControlCollectionUITypeEditor
    Inherits UITypeEditor
    ...
    ...
End Class

Public Class ExampleComponent
    Inherits Component
    Private _TargetControls As New Collection(Of Control)
    'EditorAttribute tells the Property Grid to use this editor to change the property
    'DesignerSerializationVisibility stops the compiler trying to 
    'serialize a Collection object (since it can't be serialized)
    <EditorAttribute(GetType(ControlCollectionUITypeEditor), _
    GetType(System.Drawing.Design.UITypeEditor))>
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
    Public Property TargetControls As Collection(Of Control)
        Get
            Return _TargetControls
        End Get
        Set(ByVal value As Collection(Of Control))
            'Validity checking, event raising etc. deliberately left out for simplicity
            _TargetControls = value
        End Set
    End Property
End Class

Points of Interest

I went around the houses on this with a couple of folks at the MSDN Visual Basic General forum, the main thrust of which is 'you can't do it'.

You can if you try hard enough!

History

  • Version 1: Original tip
  • Version 2: Added download per answer to question 29 October 2013

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
France France
A hobbyist begin-again-er!

Spent a few years longer ago than I care to remember with BBC Basic, a couple of years with Delphi about 10-15 years ago with a smattering af MS Access applications along the way. Dropped out of it completely except for the occasional Excel macro.

Finally picked up the baton again with VB.Net in VS2010 and now VS 2012and have been playing around quite a bit with a few odds and sodds, learning much as I go - and long may it continue.

I don't work even close to the IT industry and probably never will, but I do enjoy it as a hobby.

Comments and Discussions

 
QuestionUsing the ShowDialog method of IWindowsFormsEditorService Pin
_Plutonix29-Oct-13 6:52
_Plutonix29-Oct-13 6:52 
AnswerRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger29-Oct-13 8:31
M-Badger29-Oct-13 8:31 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
_Plutonix29-Oct-13 12:43
_Plutonix29-Oct-13 12:43 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger29-Oct-13 12:50
M-Badger29-Oct-13 12:50 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
_Plutonix30-Oct-13 7:13
_Plutonix30-Oct-13 7:13 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger30-Oct-13 9:27
M-Badger30-Oct-13 9:27 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
_Plutonix30-Oct-13 15:31
_Plutonix30-Oct-13 15:31 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger30-Oct-13 20:56
M-Badger30-Oct-13 20:56 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger31-Oct-13 8:39
M-Badger31-Oct-13 8:39 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
_Plutonix3-Nov-13 6:24
_Plutonix3-Nov-13 6:24 
for 1 and 2, all I did was add 2 vars to the UICtlEditor class, then inherit it and set the vars. it allows them to change project-to-project without creating a method or property:
VB
<System.Security.Permissions. ...
Public Class PDWControlCollectionUIEditor
    Inherits ControlCollectionUIEditor

    ' "local" instance creator to set behavior flags.

    Public Sub New()
        MyBase.bExcludeForm = True
        MyBase.bExcludeSelf = True

    End Sub
End Class


As to your question relating to the quote, this has entirely to do when the calling class needs to do something with the returned list of selected controls. Unlike a component, a container type control will want to use or "adopt" the selected controls.

In my case, I add the selected ones to MyBase.Controls - which is where the problem lies for special controls like a TabPage. You cant add a TabPage to anything but a TabControl, but you also cannot completely trap for it in a simple Try/Catch. MyBase.Controls.Add(ctl) does throw an error you can Catch, BUT the control has also already been removed from its TabControl parent leaving it orphaned. It entirely has to do with my fringe use-case for the UIEditor (most container controls can just have children dropped on them!), but since it LOOKS like the UIEd is crashing upon OK_Click, the comment related to perhaps explaining how/why.

Based on that and upon further reflection, I think the way it is - designed for Components (e.g. ErrorProvider or ToolTip) is fine. I might add the ExcludeForm option but that is it. In LoadValues:

VB
' any idea why GetType doesnt work in the UIEditor?
If TypeOf context.Instance Is Control Then
        ' instead of:
        'thisCtl = context.Instance
        Throw New InvalidOperationException("This designer intended for use by Components", _
        New Exception("Use ControlsCollectionEditorEx for container controls"))

       ' TODO: Decide if it is valid to allow DGV Columns in the list
       ' (probably not).

End If


ControlsCollectionEditorEx could then be a new editor which inherits the existing one (more likely clones it) which handles all these goofy fringe issues (I'd write it, if you want). MOST container controls wont need it, so using it would be a very, very fringe use case.

The main thing I would do in it, is add a mechanism to specify excluded Types (ie a List(of Type)) which starts out with TabPage in it. Devs could add other control type they need to exclude. In this case, a ContainersOnly option might be worthwhile. [Note: in my mind it is a List(Of Type) but a List(Of Control) would be easier for most VB devs to populate and the UIEd code can do the Type compares.] In addition or alternatively, a similar onlyList(Of Type) to only show panels for instance might offer maximum flexibility.

I started to add the List(of Type) to handle TabPage, but the underlying control I was using it with broke, then something in a larger more important project grabbed the spotlight. I also want to take a stab at using a panel as the design time canvas so I wont need any of this.

3.1 - no need. It already lists container children in the list. In fact, I was toying with the idea of adding a ContainersOnly flag, so I could adopt them (eg panels) and retain the Locations of all the child ctls.

3.2 - that whole thing with designer serialization confuses me. My control thing is not saving/serializing TargetControls even though it is set to .Content. Thats part of the reason I move them to MyBase.Contols.

4. The code I posted upstream does exclude non-Controls (ie Components like DataGridViewColumns).

Finally, just curious, did you try using a 'ControlCollection' instead of 'Collection (Of Controls)' for TargetControls property? I tried to change it so it would match `myBase.Controls` but it wouldnt work. No matter, just curious.

HTH
-Mike
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger12-Dec-13 11:36
M-Badger12-Dec-13 11:36 
GeneralRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
_Plutonix13-Dec-13 4:18
_Plutonix13-Dec-13 4:18 
AnswerRe: Using the ShowDialog method of IWindowsFormsEditorService Pin
M-Badger29-Oct-13 11:34
M-Badger29-Oct-13 11:34 
GeneralMy vote of 5 Pin
hoernchenmeister4-Sep-12 22:14
hoernchenmeister4-Sep-12 22:14 
Questionmy vote of 5 Pin
beleshi31-Jul-12 1:31
beleshi31-Jul-12 1:31 
GeneralRe: my vote of 5 Pin
M-Badger31-Jul-12 4:04
M-Badger31-Jul-12 4:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.