7

I have notice something very obnoxious with VB.Net's treatment of Winform objects.

This has trashed several hours of our time. It will only get worse as we have more of our VB6 programmers that are used to doing things like this, and autoconverted code which brings the construct straight over from vb6.

Here is an acceptable way to do things:

Dim FormInstance as New FormClassName
If FormInstance.ShowDialog() = DialogResult.OK then
    TheAnswer = FormInstance.TextBox1.Text
EndIf

However, it allows this:

If FormClassName.ShowDialog() = DialogResult.OK then
    TheAnswer = FormClassName.TextBox1.Text
EndIf

Bear in mind, the properties and methods are not Shared. Turning of Application Framework doesn't matter. It seems that behind the scenes, VB instantiates a global copy of the form, and re-routes this syntax to that global reference. You can imagine the havoc this wreaks on a modern program! Often a developer will throw it in or we'll miss cleaning up some obscure code from conversion (yes, I am looking for this now, so that helps).

Any setting I can make to cause this to throw an error message, e.g., Reference to a non-shared member requires an object reference, like it ought to?

Here's the solution:

I chose to select the answer of jmoreno because he pointed out the culprit for me: My.Forms. Fixing it was as easy as putting this in a module:

Namespace My.MyProject.MyForms
End Namespace

Then you get the exact error I mentioned above. Just like you should. If you need that for legacy apps (a good thing), then don't do this! I thought Gserg might just be VB bashing (fun but not helpful) but he mentioned all this right away, and since I found the answer, there we're good again about vb not sucking unless you are just unfamiliar with it.

Note if you use the application framework you'll get an error you don't want in application.designer. The fix:

    Protected Overrides Sub OnCreateMainForm()
        ''//was: Me.MainForm = Global.WindowsApplication2.Form1
        Me.MainForm = New Form1
    End Sub

Hopefully that would be it for any bad side effects!

JMoreno's reflection, etc.

The above is so simple that I'd hate to suggest anything else, but if you are curious, here are improvements on that code to (1) add reflection to omit having to hardcode in each form you make and (2) make it automatically enforcing (with just one call to this sub at program startup). Just put this in a module:

Public Sub FixMyForms()
    For Each pi As System.Reflection.PropertyInfo In GetType(My.MyProject.MyForms).GetProperties
        Dim obj As Object = pi.GetValue(My.Forms, Nothing)
        If TypeOf obj Is Form Then
            AddHandler CType(obj, Form).Load, AddressOf Complainer
        End If
    Next
End Sub

Private Sub Complainer(ByVal sender As Object, ByVal e As System.EventArgs)
    MsgBox("WRONG!")
End Sub
Community
  • 1
  • 1
FastAl
  • 6,194
  • 2
  • 36
  • 60
  • While we're being critical, your "acceptable" code [ought](http://msdn.microsoft.com/en-us/library/c7ykbedk.aspx) to `Close` or `Dispose` the form... I'm sure you knew that. – MarkJ May 18 '11 at 20:18
  • 2
    One possibility would be to write an [FXCop custom rule](http://stackoverflow.com/questions/366095/creating-a-custom-rule-in-fxcop) to ban this. – MarkJ May 20 '11 at 08:23

2 Answers2

6

No. This confusing behaviour is by design.

It has been possible in VB to use one instance of a form without creating it, and this behaviour happily made it to VB.NET. To me, this is one of the worst design choices.
But it does make converting legacy projects easier. And it does a favour to those who never even knew a form could be explicitly instantiated.

But you are not required to use this trick, as you never were in VB6. In both languages you can use explicitly created instances, in which case the default instance will not be created. Just forget this "feature" exists and only let it back to your mind when enlightening others.

GSerg
  • 76,472
  • 17
  • 159
  • 346
  • 1
    Yeah, it's a weird one, for sure, but it's also fairly easy to see why it was done that way, way back when for the original VB. It made things a little more approachable for non-programmers. Still, I wonder about the choice to include it in vb.net too. – DarinH May 18 '11 at 18:21
  • 1
    @drventure @GSerg Way back in VB1 and VB2, this was the only way to use forms - you couldn't instantiate them. It's been maintained in VB.Net to make it easier to upgrade legacy code. Which is a good thing, unless we throw our code away every couple of years. But it should be avoided in new code. – MarkJ May 18 '11 at 20:15
  • That would be very disappointing. MS took away 99% of the VB bashers' (legitimate) complaints with OPTION STRICT/EXPLICIT ON, guess they just let this one slip. – FastAl May 19 '11 at 14:00
3

Sorta. You could create a function or override ShowDialog that checks the My.Forms collection to see if the current instance is in it and if it is thrown an exception. There's not a setting that can be used to prevent it from being used.

EDIT: adding sample code illustrating the concept (using a beep instead of an exception).

    Shared Function FormIsInMyForms(formName As String, frm As Form)
        If formName = "Form1" Then
            If frm.Equals(Form1) Then
                Return True
            Else
                Return False
            End If
        End If
        Return False

    End Function

    Public Overloads Sub ShowDialog()

        If FormIsInMyForms("Form1", Me) Then
            Beep()
        End If

        MyBase.ShowDialog()
    End Sub

Doing the same thing via reflection is left as an exercise for the reader... :)

jmoreno
  • 12,752
  • 4
  • 60
  • 91
  • +1 Interesting idea. Have you tried it out? I wonder whether you could check the name of the current instance, and if it is the same as the type name, throw an exception. – MarkJ May 19 '11 at 08:39
  • 1
    It would be nice if there were a way to just break My.Forms. I am a little tempted to think that My.Forms sucks more than it helps. – FastAl May 19 '11 at 14:01
  • @MarkJ: I've tried it, and it works (with a couple of corrections, My.Forms isn't a collection so you have to use reflection or hard code the references). As for throwing an exception, that would depend upon your usage. – jmoreno May 20 '11 at 01:24
  • Cool, post some code here & (IMHO) this should be the accepted answer. – MarkJ May 20 '11 at 08:19