I am updating some code from a vb6 application to VB.NET.
I have a problem that occurs when I try to open a form from the main form.
It calls this function:
Public Sub optDrawSafeFile_CheckedChanged(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles optDrawSafeFile.CheckedChanged
If eventSender.Checked Then
'--------------------------------------------------------------------------------
' JRL 11-03-06
' change the enables
UpdateGUI((False))
cboProject.SelectedIndex = frmMain.cboProjects.SelectedIndex
SelectJob()
End If
End Sub
And when it goes to execute this line:
cboProject.SelectedIndex = frmMain.cboProjects.SelectedIndex
It blows up and says this:
frmMain is declared like this:
How can I fix this error?
TL;DR
It is described in more detail in this video.
Short answer: change frmMain to My.Forms.frmMain.
cboProject.SelectedIndex = My.Forms.frmMain.cboProjects.SelectedIndex
Long answer:
In VB6, referencing a form by its name allowed you to access it both as a class and an instance of that class. The instance that you access in this manner is called the default instance. This is not possible in VB.NET. However, VB.NET includes a dynamically generated class, My.Forms, that provides functionality similar to that of default instances.
See http://msdn.microsoft.com/en-us/library/ms379610%28v=vs.80%29.aspx#vbmy_topic3 for more information about My.Forms and the "My" namespace.
A better and more object-oriented way to handle this, however, would be to pass the instance of the main form to the constructor of the frmAddMethod form and store it in an instance field.
So, within the class definition in frmAddMethod.vb:
Sub New(ByVal mainForm As frmMain)
_mainForm = mainForm
End Sub
Private _mainForm as frmMain
And when you create the frmAddMethod instance from frmMain, pass in "Me" to the constructor:
Dim addMethodForm as new frmAddMethod(Me)
"Me" is the instance of the class from which a non-shared class method was called.
This will allow you to use the _mainForm class field to access the instance of the main form from non-shared methods of frmAddMethod.
*Edited to recommend My.Forms instead of DefInstance per Plutonix's comment.
Nothing is "blowing up", your program is not crashing. Using a type name, like frmMain, where an object reference is expected is something that the VB.NET compiler allows. Specifically for the Form class, an appcompat hack for VB6 code. It is the debugger that doesn't think much of it. It merely gives you a diagnostic on your watch expression since it doesn't have the same appcompat hack as the compiler does. So doesn't know what to display.
You can use My.Forms instead to get the active form object reference. So make your watch expression:
My.Forms.frmMain.cboProjects.SelectedIndex
Only do this when you are single-stepping the code, it will still go wrong if you use Debug + Break All to break into the program. Setting a watch expression on Me.cboProject is otherwise the obvious workaround in this specific case.
Related
I know this appears to be a rather common topic and should have been resolved from earlier posts. But what I am experiencing still does not seem to have a solution online:
I have a form called ExpenseEntry in which there is a sub procedure called Public Sub OpenVoucher.
I want to call this sub from another form for which I use the following code:
Dim ExpForm As New ExpenseEntry
ExpForm.Show()
ExpForm.OpenVoucher()
While this works well enough, the problem is everytime I click the button, a new window of ExpenseEntry is launched. As per how I have designed the application, repeat windows is not permissible and only one window should be available at a time.
I have tried various methods to restrict more than one form such as by using a variable to control the form but that gives rise to other issues.
If I use Application.OpenForms but still does not resolve the issue.
I have earlier queried in this regard in the following link:
Textbox not refreshing
I am using VB.NET 2019 which does not allow the launch of default instance of a form like Form.Show. I know this is bad practice but it was easier to manage with that till VB.NET 2017.
Now by creating a form variable and launching that creates an infinite loop where I cannot have just one instance of a form running on a single thread.
The really simple way to handle this is to use the default instance of the form type. In VB, since 2005, each form type has a default instance that you can access via the type name. Read here for more info. In your case, you can do this:
'Display the form if it is not already displayed.
ExpenseEntry.Show()
'Activate the form if it is already displayed.
ExpenseEntry.Activate()
'Do the deed.
ExpenseEntry.OpenVouncher()
That said, default instances are a bit dodgy. They do enable beginners to access forms from anywhere in their project under certain circumstances but they also have limitations that can cause issues. Most importantly though, they help to prevent you learning proper OOP by treating forms differently to other types. If you want to do this the way a proper developer would then simply declare a variable to refer to the current instance of the form:
Private expenseEntryDialogue As ExpenseEntry
When it's time to use the form, you simply check whether that variable refers to a usable instance and use it if it does, otherwise create a new one:
If expenseEntryDialogue Is Nothing OrElse expenseEntryDialogue.IsDisposed Then
expenseEntryDialogue = New ExpenseEntry
End If
expenseEntryDialogue.Show()
expenseEntryDialogue.Activate()
expenseEntryDialogue.OpenVoucher()
A third option would be to implement your own singleton, i.e. a type that can only ever have a single instance. You probably wouldn't do that in VB, given that the default instance is basically a thread-specific singleton and does more automatically but, if you wanted to, you could do this:
Public Class ExpenseEntry
Private Shared _instance As ExpenseEntry
'The one and only instance of the type.
Public Shared ReadOnly Property Instance As ExpenseEntry
Get
If _instance Is Nothing OrElse _instance.IsDisposed Then
_instance = New ExpenseEntry
End If
Return _instance
End Get
End Property
'The constructor is private to prevent external instantiation.
Private Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
End Class
and then this:
ExpenseEntry.Instance.Show()
ExpenseEntry.Instance.Activate()
ExpenseEntry.Instance.OpenVoucher()
I have a VB.net program that I got from someone else. It is comprised of a main form and 6 other modules (all .vb files). These files all have a "VB" icon next to them in the Explorer pane. I am trying to make a call to a sub-routine in one of the modules from the main form. My line of code is:
QuoteMgr.StartGettingQuotesLevel2(sSym)
where QuoteMgr is the name of the module and StartGettingQuotesLevel2(sSym) is the name of the sub-routine. When I enter this, I get the error message:
Reference to a non-shared member requires an object reference.
The sub-routine is defined in the QuoteMgr Module as follows:
Public Sub StartGettingQuotesLevel2(ByVal oSymbol As String)
What is strange is when I enter:
QuoteMgr.
(the name of the module with a period), it does not show me all the sub-routines and functions in the module. It only shows:
Update_Level1
Update_Level12
Update_Level2
These are Public Const in the module.
Can you tell me what I need to do?
What the compiler is trying to tell you with this error message
Reference to a non-shared member requires an object reference
is that the StartGettingQuotesLevel2 subroutine is an instance method not a shared or class method, see a more detailed explanation here
To call an instance method, you need to have an object instance to call it on. In your case, an object instance of the class type QuoteMgr. Like in the example below:
' create a new QuoteMgr object instance
Dim myQuoteMgr As QuoteMgr = New QuoteMgr()
' call its instance method with "abc" as its oSymbol argument.
myQuoteMgr.StartGettingQuotesLevel2("abc")
It is possible that you only want a single QuoteMgr object instance to be created and used by your main form. In that case, you can make it a member variable of your main form and create it once.
Public Partial Class MainForm
' Create it as a private member variable of the main form
Private m_QuoteMgr As QuoteMgr = New QuoteMgr()
' Use it when "some" button is pressed
Private Sub btnSome_Click(sender As Object, e As EventArgs) Handles btnSome.Click
m_QuoteMgr.StartGettingQuotesLevel2(txtSymbol.Text)
' And possibly do something with the results.
End Sub
End Class
Also, if instances of your QuoteMgr class depend on other object instances for their tasks, you will have to supply these to the constructor method of the QuoteMgr class as the arguments for its constructor's method parameters. Constructors (Sub New(...)) look like this:
Public Class QuoteMgr
' This is a constructor that takes two arguments
' - oMainSymbol: a string value
' - oKernel: an instance of the type Kernel
Public Sub New(oMainSymbol As String, ByRef oKernel As Kernel)
' ....
End Sub
End Class
That means, that when you create a QuoteMgr instance, you have to call its constructor method with the things it need, for example
' There must be an instance of Kernel created somewhere.
Dim myKernel As Kernel = ....
' create a new QuoteMgr object instance with these arguments:
' - oMainSymbol = "SYMABC"
' - oKernel = myKernel
Dim myQuoteMgr As QuoteMgr = New QuoteMgr("SYMABC", myKernel)
Some other recommendations
The explanations I have provided, are about basic VB.NET language features (e.g. the terms highlighted in bold). I suggest that before you make any changes to the code you have, you (1) make a backup of it, and (2) try to read a tutorial and practice on something smaller.
The compiler is (virtually) always right. When it gives you an error message, read it carefully, it will indicate the line where something is wrong and a message that tells you what it needs or is missing.
It is not the purpose of Stack Overflow to provide tutorials or code. It is a Q&A site where the best questions and answers deal with specific, delineated programming problems, for which succinct answers are possible.
Right click your application and go to Properties.
Make sure your application type is "Windows Forms Application".
It means that the routine you are trying to call needs to reference an instance of the form to access the routine. You can either reference an instance as Alex says, or you can make the routine 'Shared', so it doesn't need an instance. To do this, change the definition in QuoteMgr.vb to
Friend Shared Sub StartGettingQuotesLevel2(ByVal oSymbol As String)
Switching it to `Shared' may start showing compiler errors, if the routine accesses form controls or module-level variables. These will need to be added to the parameter list.
Specifically aimed at winforms development.
I suspect that the answer to this is probably No but S.O. has a nice way of introducing me to things I didn't know so I thought that I would ask anyway.
I have a class library with a number of defined methods therein. I know from personal experimentation that it is possible to get information about the application within which the class library is referenced. What I would like to know is whether it would be possible to get information about the value of a property of a control on a form when a routine on that form calls a method in my class library without passing a specific reference to that form as a parameter of the method in the class library?
So purely as an example (because it's the only thing I can think of off the top of my head). Is there a way that a message box (if it had been so designed to do so in the first place) could 'know' from which form a call to it had been made without that form being specifically referenced as a parameter of the message box in the first place?
Thanks for any insights you might have.
To address the example of the MessageBox, in many of the cases you can use the active form. You can retrieve it by using Form.ActiveForm. Of course, as regards the properties that you can request, you are limited to the properties provided by the Form or an interface that the Form implements and that the method in the other assembly also knows. To access other properties you can use Reflection, but this approach would neither be straightforward nor would it be clean.
In a more general scenario, you could provide the property value to the method as a parameter. If it is to complex to retrieve the value of the property and the value is not needed every time, you can provide a Func(Of TRESULT) to the method that retrieves the value like this (sample for an integer property):
Public Sub DoSomethingWithAPropertyValue(propValFunc As Func(Of Integer))
' Do something before
If propertyValueIsNeeded Then
Dim propVal = propValFunc()
End If
' Do something afterwards
End Sub
You call the method like this:
Public Sub SubInForm()
Dim x As New ClassInOtherAssembly()
x.DoSomethingWithAPropertyValue(Function() Me.IntegerProperty)
End Sub
I kind of question your intentions. There's no problem sending the information to a function or the constructor.
Instead of giving the information to the class, the class would ask for the information instead using an event.
Module Module1
Sub Main()
Dim t As New Test
AddHandler t.GetValue, AddressOf GetValue
t.ShowValue()
Console.ReadLine()
End Sub
Public Sub GetValue(ByRef retVal As Integer)
retVal = 123
End Sub
End Module
Class Test
Public Delegate Sub DelegateGetValue(ByRef retVal As Integer)
Public Event GetValue As DelegateGetValue
Public Sub ShowValue()
Dim val As Integer
RaiseEvent GetValue(val)
Console.WriteLine(val)
End Sub
End Class
Is it possible to call a public sub located in a UserForm from a Class Module? I want to put a callback in the Form Module but I can't seem to get it to expose.
Is this a fundamental limitation of UserForms in VBA?
It is exposed inside the UserForm Code Module, I can see it in the intelisense for the Me object, but I can't seem to access it from outside the Form Module.
The real answer to my question is to have a better understanding of UserForms and since I could not find a good reference for that I thought I would answer my own question to share my learnings.
Thanks to #Dick Kusleika for the key insight!
First of all, this is not a UserForm:
It is no more a Form than a Class Module is a variable.
UserForm1 is a Class Module with a GUI and with the following default, inherited properties
These properties are like a standard Interface that is common to all Form Class Modules and therefore instances. The Name property is in parentheses because it is not the name of the object, it is the name of the the Type that is used to declare variables to instantiate the particular Form Class.
More properties and methods can be added by the user at design time and this is done in exactly the same way as a Class Module.
For example, in a Form Module...
Option Explicit
Dim mName As String
Property Let instName(n As String)
mName = n
End Property
Property Get instName() As String
If Len(mName) = 0 Then mName = Me.Name
instName = mName
End Property
In this example, the Form Class Name is used as the default Instance Name.
When you add Controls to the form, its like graphically adding
Public WithEvents controlName As MSForms.ControlType
...in a Class Module.
The Methods inherited in the standard interface include one called Show.
You can create an instance of a form using UserForm1.Show and this is very confusing and misleading. To me it implies that you are showing the Object called UserForm1 but you are not. I don't know why you would want to use this method because, apart from being confusing, it does not deliver any direct reference to the object created. Its a bit like Dim v as New Type only worse, because there is no referencing variable.
You can instantiate a Form Class in exactly the same way you can a Custom Class object and then use the show method to deploy it...
Dim f As UserForm1
Set f = New UserForm1
f.Show
For me, this is the preferred method.
You can add custom properties and controls to the UserForm1 Class and you can give it a meaningful name when creating it, but you can also reference it using the standard UserForm interface.
For example
'In a Class Module
Dim mForm as UserForm1
Property let Form(f as MSForms.UserForm)
Set mForm = f
End Property
For me, after understanding the above, all of my confusion about UserForms and my frustration at not being able to find a decent reference disappears. I just treat them as Class Modules and its fine.
The only difference between a Userform and Class Module is that a Userform has a UI element that a Class Module doesn't. So a Userform is just a special type of Class Module. That means that Public Subs inside a Userform behave just as they do in any other class - as a method of the class.
To access a Public Sub inside a class module (such as a userform), you need to instantiate the class, then call the method.
Dim uf1 As UserForm1
Set uf1 = New UserForm1
Uf1.MyPublicSub
I have 20+ MDI forms with consistently named Public variables. When the child form closes a method on the MDI Parent is called passing Me as a generic form type. How can I access the public variables by name via the Form reference? I only need to read the variables. Of course the Variables() method does not exist...
Public Sub CleanupForm(ByVal frm As Form)
Dim sTable_Name As String = frm.Variables("TABLE_NAME") ' Public at form level
Dim cLock As clsRecLocks
cLock = frm.Variables("Rec_Lock")
cLock.DeleteThisLock()
'..
I've seen some posts on similar requests but most start out with "don't do it that way..." then go off in the weeds not answering the question. I concede it is poor design. I can't change all the calling forms in the short term so I need to use this approach.
VS2010, VB.Net, Win Forms, .Net 2.0
I was able to get to a simple variable using CallByName:
Try
Dim s As String = CallByName(frm, "TABLE_NAME", CallType.Get)
Stop
Catch ex As Exception
MsgBox(ex.Message)
End Try
On to the class object. Perhaps I can add a default Get for the class that returns the ID I need.
Default property won't work as the Locks object was not declared Public - at least for the CallByName() approach.
Can Reflection get to form level variables not declared Public? Seems like a security issue, but...
Can I get a "Parent" reference in the instantiated Locks class? i.e. A reference to the form that established the Locks object? I can change the clsRecLocks() class.
I found a property I could get to that told me the form was "read-only" and I can use that tidbit to delete the correct (or more correct - still not 100%) lock record. So the bug is 90% fixed. I think I need update all the forms with code that records the info I need to get to 100%.
Thanks to all!
Poor design but you can do this:
Public Sub CleanupForm(ByVal frm As Form)
Dim frmTest as object = frm
? = frmTest.TABLE_NAME
? = frmTest.Rec_Lock
End Sub
This will compile and if the variables exist, it will return them but if not, you get an error.
Converting to an interface after the fact is not that hard, you should do it now rather than later.