views:

49

answers:

2

This is probably a simple one but I can't seem to figure it out.

I have a bunch of form items created by the form designer declared as (in frmAquRun.Designer.vb)

Public WithEvents btnAquRunEvent1 As VisibiltyButtonLib.VisibilityButton
Public WithEvents btnAquRunEvent2 As VisibiltyButtonLib.VisibilityButton

... etc

And I basically want to be able to supply a number to a function access each of these fields. So I wrote this function. (in frmAquRun.vb)

Const EVENT_BUTTON_PREFIX As String = "btnAquRunEvent"
Public Function getEventButton(ByVal id As Integer) As Windows.Forms.Button

    Dim returnButton As Windows.Forms.Button = Nothing
    Try
        returnButton = DirectCast(Me.GetType().InvokeMember(eventButtonName, Reflection.BindingFlags.GetField Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance, Nothing, Me, Nothing), Windows.Forms.Button)
    Catch ex As Exception
    End Try
    Return returnButton
End Function

But it always seems to be generating field not found exceptions. The message in the exception is "Field 'ATSIS_ControlProgram.frmAquRun.btnAquRunEvent1' not found.".

The namespace and form name in the message are correct. Any idea what i'm doing wrong?

+1  A: 

The problem is that for WithEvents fields, VB actually creates a property that does the necessary event handler attaching and detaching. The generated property has the name of the field. The actual backing field gets renamed to _ + original name.1)

So in order for your code to work just prefix the button name by _ or use the BindingFlag that corresponds to the property getter (instead of GetField).

Alternatively, you can do this a lot easier by using the Controls collection of the form:

returnButton = DirectCast(Me.Controls(eventButtonName), Windows.Forms.Button)

But beware that this only works if the button is top-level, i.e. not nested within a container control on the form.


1) This is an implementation detail of the VB compiler but it’s portable (especially to Mono’s vbnc compiler) since the handling for WithEvents fields is described in great detail in the VB language specifications.

Konrad Rudolph
Thanks, setting the bindingFlags to GetProperty did the trick. I didn't know about the Controls object in the form I'll definetely keep it in mind for future reference but the event buttons are in different containers so it would make things a bit complicated.
Isaiah
A: 

The problem is that the event handlers aren't really fields. As compiled they're really properties that implement add_btnAquRunEventX, remove_btnAquRunEventX and fire_btnAquRunEventX methods. There are ways of using reflection to get around this, but that's probably not the best way to approach the problem. Instead you can simply create a List<> and populate it with the event handlers, then index into that list.

I'm a little rusty in VB syntax but it should look something like this:

Dim events = New List<EventHandler>()
events.Add( btnAquRunEvent1 )
events.Add( btnAquRunEvent2 )

....

events( 0 )( null, EventArgs.Empty )

Take a step back though and evaluate why you're invoking by index. There may be a simpler way of abstracting the whole thing that doesn't involve all this indirection.

Paul Alexander
Your description of event handlers is correct but the OP was *not* asking about event handlers but rather about `WithEvent` fields.
Konrad Rudolph
Thanks, I was previously using a method as you described above but I wanted to have a sort of set and forget way of adding / removing buttons from the from designer (as it may in some rare cases need to be edited by people who are not software designers.)
Isaiah
Well that just highlights my VB wisdom. I thought the WithEvents syntax was basically the same as C#'s event keyword.
Paul Alexander
@Paul: `event` in C# is `Event` in VB. ;-) (Or `Custom Event`, depending on the scenario). `WithEvents` causes VB to auto-wire event handlers without the need to add/remove handlers explicitly.
Konrad Rudolph