Results 1 to 12 of 12

Thread: [RESOLVED] Making a Component "one per Form"

  1. #1

    Thread Starter
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Resolved [RESOLVED] Making a Component "one per Form"

    I'm making a custom Toolbox Component which I want to restrict to one instance per form. It's a non-visual component like a Timer, not a control. I am able to enforce the restriction at design time by checking if the Container.Components list already contains a component of the same type.

    But I want to do something similar when someone adds an instance of the component in code. It's easy in the case of a Control because you can check in Form.Controls. Unfortunately the Controls list doesn't contain non-visual components.

    Does anyone know a way of getting a list of a form's non-visual components? They are obviously present in the form's designer.vb file, but I don't know how to access that in code. Or can someone suggest another way to restrict the component to one per form?

    Note that I don't want to place special requirements on the form itself. For example, I considered using the Form's Tag property to set a flag if it already contains an instance of the component. But that precludes using the Tag for some other purpose.

    BB
    Last edited by boops boops; Jul 8th, 2010 at 02:33 AM. Reason: always a typo!

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Making a Component "one per Form"

    Quote Originally Posted by boops boops View Post
    But I want to do something similar when someone adds an instance of the component in code.
    What does that actually mean? If I do this:
    vb.net Code:
    1. Dim obj As New SomeComponent
    I'm not "adding" an instance to anything. I'm simply creating an instance. That code might be in a form or in some other class, so your restriction is not really meaningful. It's really only if you do actually add the component to the form's collection of components, which noone is ever going to do at run time.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  3. #3

    Thread Starter
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Making a Component "one per Form"

    Quote Originally Posted by jmcilhinney View Post
    What does that actually mean? If I do this:
    vb.net Code:
    1. Dim obj As New SomeComponent
    I'm not "adding" an instance to anything. I'm simply creating an instance. That code might be in a form or in some other class, so your restriction is not really meaningful. It's really only if you do actually add the component to the form's collection of components, which noone is ever going to do at run time.
    Sorry that I didn't make that clear. My component has a close relation with the form that hosts it. Among other things it sets various form properties to default values. The compile-time constructor therefore requires a Form as an argument, e.g.
    Code:
    Dim obj As New SomeComponent(Me)
    I'm trying to deal with the situation where the programmer has dragged a SomeComponent onto a Form in the Designer, and then declares one or more SomeComponents for that form in code. I could just say "don't do it" but I would prefer to throw an appropriate exception.

    BB
    Last edited by boops boops; Jul 8th, 2010 at 04:08 AM.

  4. #4
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Making a Component "one per Form"

    How can your Component class require a parameter in its constructor? Then you wouldn't be able to drop it on a form in the first place. Unless it also has a parameterless constructor, in which case: what stops the user from using that one?

    Anyway, I guess the solution is pretty simple: get a list of all components on the form, and check if it contains one of the same type as your component. The problem of course is how to get that list...

    You can get to it via the Site.Container.Components property of the Form. There is a problem though: the Site property is often Nothing, and I don't think there's any events that you can use to react when it gets its value.

    But you can override the Site property of the Component, and when it changes you can get the parent form of your component. From that, you can get to the components.

    Something like this works for me, during Design-time:
    vb.net Code:
    1. Imports System.ComponentModel
    2. Imports System.ComponentModel.Design
    3.  
    4. Public Class TestComponent
    5.     Inherits Component
    6.  
    7.     Private _ParentForm As Form
    8.     Public Property ParentForm() As Form
    9.         Get
    10.             Return _ParentForm
    11.         End Get
    12.         Set(ByVal value As Form)
    13.             _ParentForm = value
    14.  
    15.             ' ParentForm changed, check for other components here
    16.             Me.CheckComponents()
    17.         End Set
    18.     End Property
    19.  
    20.     Private Sub CheckComponents()
    21.         If Me.ParentForm IsNot Nothing Then
    22.             If Me.ParentForm.Site IsNot Nothing AndAlso Me.ParentForm.Site.Container IsNot Nothing Then
    23.                 For Each c As Component In Me.ParentForm.Site.Container.Components
    24.                     If TypeOf c Is TestComponent AndAlso c IsNot Me Then
    25.                         Throw New Exception("Duplicates!")
    26.                     End If
    27.                 Next
    28.             End If
    29.         End If
    30.     End Sub
    31.  
    32.     'Override the Site property to know when it changes so you can get the parent form
    33.     Public Overrides Property Site() As System.ComponentModel.ISite
    34.         Get
    35.             Return MyBase.Site
    36.         End Get
    37.         Set(ByVal value As System.ComponentModel.ISite)
    38.             MyBase.Site = value
    39.  
    40.             If value Is Nothing Then Return
    41.  
    42.             Dim host = TryCast(value.GetService(GetType(IDesignerHost)), IDesignerHost)
    43.             If host IsNot Nothing Then
    44.                 Dim componentHost = host.RootComponent
    45.                 If componentHost IsNot Nothing Then
    46.                     Me.ParentForm = TryCast(componentHost, Form)
    47.                 End If
    48.             End If
    49.         End Set
    50.     End Property
    51. End Class
    It does not allow me to add two instances of TestComponent to a form. It throws an exception and consequently does not create the component.


    I don't know how to handle your other method of creating the component (via code). I tried running the same code in the constructor (the one that takes a Form parameter), but the Site property of the Form is then Nothing and you cannot get to the components. No idea why...

  5. #5

    Thread Starter
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Making a Component "one per Form"

    Hi Nick,

    The component does indeed have a parameterless constructor for design time. It is empty so if someone uses it in code it simply has no action. I have no problem with that.

    I am already doing something very similar to what you have suggested for enforcing "one per form" at design time. Of course I take advantage of your earlier help with getting the parent form through the Site property. Instead of throwing an exception, I quietly copy the properties of the existing component and then dispose it. It works almost perfectly: the icon in the Components tray blinks ever so slightly.

    If neither John nor you know of a way of getting a list of the form's non-visible components at run time, then I'm prepared to believe one does not exist. I do have other options. I could upgrade the component to do something useful with multiples, although I don't think it would be worth the effort. I could disallow instantiation in code by deleting the constructor with a parameter. Or I could tolerate multiple instances of the component and disclaim responsibility for the consequences (which will probably be mainly cosmetic).

    thanks, BB

  6. #6

  7. #7
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Making a Component "one per Form"

    Couldn't your component keep a Shared list of forms it's been added to and then throw an exception if the same form is added twice?
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  8. #8
    PowerPoster
    Join Date
    Mar 2002
    Location
    UK
    Posts
    4,780

    Re: Making a Component "one per Form"


  9. #9
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Making a Component "one per Form"

    Quote Originally Posted by Grimfort View Post
    Looks promising for design-time, but the problem is really during run-time. I'm sure you can still create a new instance of the component in code using this approach.

    Quote Originally Posted by jmcilhinney View Post
    Couldn't your component keep a Shared list of forms it's been added to and then throw an exception if the same form is added twice?
    Good idea. That may be the best solution yet, in combination with Grimfort's link.

  10. #10

    Thread Starter
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Making a Component "one per Form"

    Using a Shared list is one of the first things I tried. It is certainly possible to build such a list at design time (although I found Container.Components easier to use for the purpose of eliminating an existing component). Unfortunately, it turns out that the list is empty at run time. I guess the designer does not serialize Shared properties.

    I don't have much experience with serialization, but perhaps it is possible to enforce serialization of the Shared List(Of Form) in some way.

    BB

  11. #11

    Thread Starter
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Making a Component "one per Form"

    I've found something useful about getting a list of components at runtime:Enumerating non-ui components from the given form instance on DotNetMonster. I made this little function based on one of the postings:
    vb.net Code:
    1. Imports System.ComponentModel
    2. Imports System.Reflection
    3. ' ....
    4.    Public Function ComponentList(ByVal frm As Form) As ComponentCollection
    5.         Dim fieldInfo As FieldInfo = frm.GetType.GetField("components", BindingFlags.NonPublic Or BindingFlags.Instance)
    6.         If fieldInfo IsNot Nothing Then
    7.             Dim container As IContainer = TryCast(fieldInfo.GetValue(frm), IContainer)
    8.             If container IsNot Nothing Then Return container.Components
    9.         End If
    10.         Return Nothing
    11.     End Function
    It works -- a bit. The limitations are:
    1. It only returns components created in the Designer. (That's not a problem for my present purposes, since I can prevent multiple instantiations at run time by setting a Shared Boolean flag.)
    2. It includes some components but not others. Timer, ImageList and SerialPort are included, but BackgroundWorker and HelpProvider are not. Nor is my custom component.

    The above thread includes a more complex method which may turn out to return a more complete collection. I'll report my findings on that when I have had time to try it out, if no one else does first.

    BB

  12. #12

    Thread Starter
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Making a Component "one per Form"

    The "full" version used an Enumerator class to access the components field, but the same basic method for extracting components. Obviously it suffered from the same defects. It seems that the form's components field, besides being hard to get at, only contains certain components.

    Time to think of another way. Components created at design time are all declared in the form's designer.vb file. Since designer.vb is a Partial Class of the form class, a given form's declared objects will include both design time and code instances. So I looked into Reflection for a way of getting the fields of a class (Form1) and came up with this:
    vb.net Code:
    1. Public Function InstancesOf(SomeComponent As Component) As Integer
    2.     Dim count as integer = 0
    3.       Dim fieldInfos() As FieldInfo = Form1.GetType.GetFields _
    4.       (BindingFlags.NonPublic Or BindingFlags.Instance)
    5.        For Each f As FieldInfo In fieldInfos
    6.             If f.FieldType Is GetType(SomeComponent)) Then count+=1
    7.        Next
    8.     Return count
    9. End Function
    This successfully returns the number of declared instances of my custom component, whether created in the designer or in code. So I can use it to throw an exception as required. Thread resolved.

    GetFields can also be used to make a list of all the components on a form. I'll post a function for that to the CodeBank.

    BB

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width