NET includes a number of different UITypeEditors such the
StringCollectionEditor used to edit
Listbox initial contents or any property defined as a
Collection(of String). There is also a nice
ControlsCollectionEditor which allows the user to add new controls to your Component. This article will show you how to implement a UITypeEditor which allow you to select from controls already on the form.
It has always been more than a little mysterious how to get a list of existing form controls for a
UITypeEditor (which many refer to in shorthand as a UIDesigner). Mike-MadBadger worked out the hard part for this in his article 'Accessing the Controls on a Form at Design Time'. It is an excellent tip dealing in some detail the basics of a
UITypeEditor works. I will not be re-hashing those details here as it is a short, well written article.
As he points out, he drew heavily from a 2007 article by Saeed Serpooshan. Saeed's article is more in depth but an excellent UIDesigner primer. Mike took Saeed's work showing how to implement a
UITypeEditor, made it a set of generic procedures in an abstract (
MustInherit in VB) class and then replaced the standard drop down editor with a very nice Dialog Form.
The two pieces make an excellent starting point for a highly reusable
ControlsCollectionEditor, which is what I will present here.
First, this version is intended for use only by Components. For instance, you are writing an
ToolTip) which inserts a new property onto various controls. It is not intended for use by Container Controls, for instance if your project inherits from Panel, in this case you should be able to just drag/drop the desired controls.
One major issue is that that certain common controls seem to make their way into the form's controls collection even though they are components. The UIDesigner actually has access to the components on the form which seems confusing. Since the UIDesigner is attached to a property defined as
Collection(Of Control) why are there Components in it? Well, NET
doesn't actually pass the backing field or Collection to the Designer Code. Mike got the form's Components through some properties associated with the instance passed to the Designer. As such, all that is available is a list of your container's components (
This is great, but trying to add Components to a
Collection(Of Control) will end badly. One thing I immediately encountered was the
DataGridViewColumn. Since it will only work with/attach to a
DataGridView it is unlikely anyone actually wants to work with it in this context, and second, it is actually a component, not a control.
Another possibility, is the
TabPage. It is possible that someone might be developing a component to work with these, but they are also a specialty control which can only be added to a
TabControl. And there are things like the
TableLayoutPanel - since this is invisible it seems highly likely that no one will ever want it in their list of controls. So there are a great many more cases where we would want to exclude different types of controls from our
In the course of developing an UnDoManager component, I set out to again use the Mike-Saeed
ControlsCollectionEditor to allow the developer to select the controls to be managed. Since not all controls interact with the user (like
GroupBox), they are not supported. A final issue is that the parent form is included in the list. That might be fine in some cases, but not mine. So a means to filter controls was needed. As this was the 3rd time I would rework the class, I decided to fix it for good.
So among other things, I added a selection or filtering mechanism to their work so the developer can either select the control types allowed or exclude certain types.
Mike reworked Saaed's class to act as an abstract/
MustInherit class, then built his
ControlsCollectionUIEditor on it. It works great as a generic demo, but as shown, in the real world things are often more complex. So, I did the same thing as Mike: I made some small changes to his class and made it
MustInherit and will show you how to use it as a Base Class. This will require very little code to implement.
No Forms Allowed
First, a flag was added to ExcludeForm(s) from the control list. Since Forms inherit from Control, they look like just another control and can make it onto the list of controls. The exclusion mechanism (described next) could be used for forms as easily as a
TableLayoutPanel, but I also used a
ExcludeForm flag as a convenience for those who just need to exclude the form. Keep in mind that this is for working with a component you are developing, so you should know in detail what it can and cannot deal with.
An Exclusive Club
There was an inherent need to exclude certain things like the
DataGridViewColumn (a component, not a control), the
TableLayoutPanel (invisible) and maybe the
TabPage (can only be added to a
TabControl). So, an exclusion mechanism was clearly needed.
The standard NET
ControlsCollection editor has an inclusion mechanism and that's what I needed for the UnDoManager: a way to only include the types of controls my tool was designed to work with. For maximum flexibility, I implemented it both ways: An include List and an exclude List. This way you use either one depending on whether you need to let in, or keep out just a few types.
Just before Mike's dialog form displays, Saaed's base class will call
LoadValues. Here is how the filters are applied:
Dim bAdd As Boolean = True
Dim thisCtl As Control = Nothing
For Each obj As Object In context.Container.Components
bAdd = True
If TypeOf obj Is Control Then
thisCtl = CType(obj, Control)
If ExcludeForm Then
bAdd = Not (TypeOf thisCtl Is Form)
If (typeIncludeOnly IsNot Nothing) AndAlso (typeIncludeOnly.Count > 0) Then
If typeIncludeOnly.Contains(thisCtl.GetType) = False Then
bAdd = False
If (typeExclude IsNot Nothing) AndAlso (typeExclude.Count > 0) Then
If typeExclude.Contains(thisCtl.GetType) Then
bAdd = False
If bAdd Then
These various settings I've added are just
Protected Friend variables. The lists are
already instanced, so there is nothing for you to do except add your Types to them. Code such as:
If (typeExclude IsNot Nothing) Then
tries to watch for someone who thinks it is a good idea to set it to Nothing just in case there is something in it (there are no defaults). Otherwise, I left
Try/Catch blocks out of it because this is a design time tool only, and if you are using it wrong it seems best to let the Exceptions through so you know. Catching them in a design time tool such as this actually makes them harder to find (there was one in Saaed's code which was very hard to find).
Other Small Changes
One of the things I liked best about Mike's work was learning how to use a modal dialog form instead of the default dropdowns. However, for maximum flexibility, I implemented a second class to use the dropdown method. These are small and terribly ugly with .NET appending the full type name and other "useful" information to the Control Name. That said, there are cases where that method might be more appealing.
The result is that there are now
ControlCollectionDropDownUIEditor for invoking the dropdown CheckboxList and
ControlCollectionDialogUIEditor for the Dialog Form version. There are other classes, but they are
MustInherit base classes.
Most other changes were to the structure, such as making it into a DLL to prevent loosing files or accidentally changing something critical. To use the DLL form, add a reference and import the namespace. If you do choose the file method, the dialog form was merged into the
ControlCollectionEditor.vb file so there is one less file to keep track of.
What it Looks Like
Selecting the ellipsis for the controls property displays the Dialog Form. In this case, we exclude
GroupBox from the eligible list.
The .NET default DropDown version follows the same rules.
Using the Code
Using the included demo, here is what is required to implement the
ControlCollectionUIEditor. (the demo doesnt DO anything but provide a host for an ExampleComponent to use in VS). You will very likely need to Clean and Build the project since VS needs a compiled version of the project to implement the ExampleComponent in the project.
1. Decorate the property which will be implementing a
Collection(Of control) with the EditorAttribute:
Private _TargetControls As New Collection(Of Control)
Public Property TargetControls() As Collection(Of Control)
- Note that
Collection(Of Control) is from
System.Collections.ObjectModel, not the
- The designer name is one you create for your Class property: in this case,
- This has nothing to do with the UIEditor, but you will also need these procedures for your Component to work correctly:
Public Sub ResetTargetControls()
_TgtControls = Nothing
Public Function ShouldSerializeTargetControls() As Boolean
Return (_TgtControls IsNot Nothing)
Note how your property name is embedded into the procedure names.
2. Write the UIEditor
Fully 99% of the work is already done and should be in the DLL, you just need to provide a local class. This class can be in the same file as your main project
(your property must be decorated as above):
System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Public Class ExampleFormControlCollectionUIEditor
Public Sub New()
MyBase.ExcludeForm = True
Note: It is hard to imagine a case using both the inclusion and exclusion list. Use whichever one fits your use case best. Both are shown above to illustrate the names and usage. Notes:
- the class name (
ExampleFormControlCollectionUIEditor) is exactly the same one used in the property
typeExcludeOnly are a
- There are NO DEFAULT entries for either one. I debated adding
TableLayoutPanel and maybe
TabPage as default exclusions, but decided that was bad since you cant easily see what is in there. Also, having used this 4 times now, it seems including only certain types is by far the more common use case.
- If you leave the
typeIncludeOnly empty (
Count == 0) all the controls on the form will show in the list. When Types are added this sort of works like the
CanExtend function in an ExtenderProvider.
- As noted, the form itself can be excluded either using the ExcludeForm flag or by adding Form to the typeExcludeList. The flag is kind of nice if that is all you need to modify as to controls.
- If you do not need to tweak any settings you will just need to call
To test this, you must clean and build the Demo. Then in design mode, open the form, select the component in the form tray, then in the Properties window, select 'TargetContols'. Thats it. Saeed's work takes over to implement the .NET collection editor using Mike's Dialog Form.
The demo has an example component and BOTH the Dialog and DropDown versions coded. To test the DropDown version:
- Change the EditorAttribute on your property to
Clean and Build so VS can compile and use the other editor in the Properties window
Class, Member Reference
ControlCollectionDialogUIEditor MustInherit Class
Allows the developer to select existing Form Controls using a modal dialog. Uses a
CheckedListBox so multiple controls can be selected.
ControlCollectionDropDownUIEditor MustInherit Class
Provides a DropDown
CheckedListBox to allow the developer to select multiple Form Controls.
A list of
System.Types (controls). Only controls of the Types in the list will show on the UIEditor.
A list of
System.Types (controls). All controls of these Types will be excluded from the Dialog or DropDown UIEditor.
Flag indicating whether to include this component's parent form in the list. Default is
Applies only to the
ControlCollectionDropDownUIEditor. Sets the width of the drop down. The minimum is 280, default value is 400.
Developing tools which will run in the developer's VS designer can be confusing: you are after all using Visual Studio to write something which will run in VS at design time. It is not something many of us do everyday and there are not a lot of clear references on the subject.
Saeed's article on this is informative and can be very helpful in understanding some of the arcane aspects of UI Designers in general. Mike's article is equally valuable, abstracting Saaed's work into a set of base tools and adding a dialog form to the toolset.
The object of the code provided here was to use these previous efforts to provide the means to implement a flexible and controls collection editor powerful enough to handle a variety of situations.
- 2013-11-25 Initial article and ver 1.02 of the sample code.