How can I load a 'future' forms controls before I use them? Explained in detail: - vb.net

I am performing a migration on a vb6 program, to vb.net. The basic knowledge you need to understand this question is that there are two forms that need to talk to each other, frmInput1 and frmInput2. I have the following code (behind frmInput1) that checks if a textbox on frmInput2 has a certain value, seemingly before it has loaded:
If frminput2.lblInputMac.Text <> "(no filename)" Then
Dim calc As CalculationCaster = New CalculationCaster
Call calc.FillMac()
cmdNext.Enabled = False
frminput2.FraInner.Enabled = True
I get the following error on the If line when i run it:
"Object reference not set to an instance of an object."
Which i assume means that the object in frmInput2 has not been loaded yet. How can i load frmInput2 before i show it?
Thanks
Nick

frminput2 is probably the implicit global instance of the type frminput2.
If you define a form type in VB6 called MyForm, the platform automatically creates an implicit global variable of the same name MyForm. Whenever you refer to this variable in code, it automatically loads an instance of the form for you.
It's rather as if you had this code.
Public Function MyForm() As MyForm
Static f As MyForm
If f Is Nothing Then
f = New MyForm
End If
Return f
End Function

dim frm1 as new frmInput1
dim frm2 as new frmInput2
At this point, you should be able to communicate between forms without them being displayed. You should not reference forms without explicitly instantiating them.

Create an instance of the form.
Dim f As New frmInput2
Then you can use any properties, methods, or controls on the form.
If f.lblInputMac.Text <> "(no filename)" Then
...
End If

Related

Method to return extending type instance from extended control

In my solution, I have a custom component which implements IExtenderProvider in order to provide properties to other controls. I would like to implement a method for that component which, taking a control as argument, would return the instance of the extender component it is associated with, something like this:
Public Function GetErrorProvider(c As Control) As MyErrorProvider
Dim errorProvider as MyErrorProvider
'Some code here
Return errorProvider
End Function
I thought of simply looking at the form and looping for a control of the MyErrorProvider type and use that, as I am not going to have more than one of this component per form, but I would like a more direct approach. I want this for some logic that depends on runtime defined values for that instance, outside the scope of forms.
Any ideas/suggestions?
Thanks
For completeness purposes, I am adding the solution that worked converted from the C# code linked above and adjusted slightly. It appears this can only be accomplished with reflection (correct me if I am wrong!):
Public Shared Function GetErrorProvider(control As Control) As MyErrorProvider
'get the containing form of the control
Dim form = control.GetContainerControl()
'use reflection to get to "components" field
Dim componentField = form.[GetType]().GetField("components", BindingFlags.NonPublic Or BindingFlags.Instance)
If componentField IsNot Nothing Then
'get the component collection from field
Dim components = componentField.GetValue(form)
'locate the ErrorProvider within the collection
Return TryCast(components, IContainer).Components.OfType(Of MyErrorProvider)().FirstOrDefault()
Else
Return Nothing
End If
End Function

Getting A NullRefrenceException And Cannot Find Out Why?

My goal here is to create a web browser that has a tab system in VB. Since I cannot explicitly name every single new tab the user will use, I have to make more generalized callings. Here's the conflicting code (my btnGo):
Dim thisBrowser As newWebBrowser = Me.tabBrowser.SelectedTab.Tag
If txtAdressSearch.Text.Contains(".com") Or txtAdressSearch.Text.Contains(".net") Or txtAdressSearch.Text.Contains(".gov") Or txtAdressSearch.Text.Contains(".edu") Or txtAdressSearch.Text.Contains(".org") Then 'More to be checked for
thisBrowser.Navigate(txtAdressSearch.Text)
Else
thisBrowser.Navigate("https://www.google.com/search?sourceid=chrome-psyapi2&rlz=1C1ASAA_enUS445&ion=1&espv=2&ie=UTF-8&q=" + txtAdressSearch.Text)
End If
And here's the newWebBrowser code:
Public Class newWebBrowser
Inherits WebBrowser
Private Sub webBrowserComplete() Handles Me.DocumentCompleted
Dim newTab As TabPage = frmBrowser.Tag()
Dim frmSK As New frmBrowser
Dim hi As String
newTab.Text = Me.DocumentTitle
frmSK.txtAdressSearch.Text = Me.Url.ToString
End Sub
End Class
Any time I enter something into txtAdressSearch, Visual Studio raises a NullRefrenceException and highlights thisBrowser.Navigate(txtAdressSearch.Text). As a side note, it says "Object reference not set to an instance of an object."
Anyone know whats the problem here? Thank you.
After debugging for more than an hour, I looked over my code and saw I was missing a big part of it. I wrote it all in and it worked fine. The issue was the tags weren't being defined correctly (and in some cases, not at all) so .Tag was returning Nothing.
Thanks to all who helped.

Form Controls not updated from Class methods

I am a newcomer to Visual Studio 2013, so my question is probably -and hopefully- a simple one.
I am currently writing a small program (in VB) that will essentially add/update/delete users from a table.
The main form has three TextBoxes (ID#, name, last name), a button to check if the user already exists and a couple more buttons (Save and Cancel)
I have also created a Class (dataLookup) where all the functions for adding, updating or deleting users are stored.
The way the program works is as follows:
1.- The user enters an ID# in the main Form's ID field and clicks on the "check user" button.
2.- The system calls a function stored in the datalookup Class that verifies if the ID# already exists.
3.- If it does, the function retrieves the name and last name from the table, assigns them to two local variables (vName and vLastName) populates the corresponding fields on the Main Form and returns TRUE. User can then either update data or Cancel (See code sample below)
MainFormName.TextBox1.Text = vName
MainFormName.TextBox2.Text = vLastName
return True
4.- If the ID# doesn't exist, the function returns FALSE. User is then able to enter new data in the three textboxes.
My problem is that I can't populate the TextBox fields from the function stored in the dataLookup Class. After the instructions are processed, the TextBoxes (which are both Enabled and have their Read Only property set to false) remain empty.
If I add the exact same code that populates the fields to the Main Form code, and assign values to the vName and vLastName variables, it works perfectly:
vName = "John"
vLastName = "Doe"
MainFormName.TextBox1.Text = vName
MainFormName.TextBox2.Text = vLastName
FYI, no errors are reported when I compile/run the program.
I am aware that I can modify the function so it will also return the name and last name and then I will be able to update the TextBox fields from the Main Form, but I am just curious: Why can't I do that from the function stored in the Class?
Hope my description was reasonably clear :) Any help will be much appreciated. Many thanks in advance!
Randy
Forms are classes (it says so at the top of each of them):
Public Class MainForm
....
As a class, an instance should be created however, VB allows what is called a default instance using the class name: MainForm.Show()
Under the hood, the compiler creates an instance of MainForm named MainForm and uses it. This is handy for developers hobbyists dabbling in code, but there are numerous ways this can bite you:
Sub DoSomeThing()
Dim frm As New Form1
frm.TextBox1.Text = cp.ToString
End Sub
Here a local instance of Form1 is created and used which has no relation to the Global Form1 which VB created. The local object goes out of scope at the end of the Sub never to be used by the rest of the app.
' in sub main:
Application.Run(New Form1) ' main form/pump
... elsewhere
Form1.TextBox1.Text = "hello, world!"
In spite of using the same name, these are actually different instances. The text will not show up and if the next line was Form1.Show(), a second copy of Form1 would display complete with the 'hello, world' text. These will also create/show new instances:
Form2.Show()
' or
Dim frm As New Form2
frm.Show()
In general, the more complex the app, the less appropriate using default instances is. For serious apps, create explicit form instances:
Dim myFrm = New Form7() ' myFrm is an instance of Form7
...
myFrm.TextBox1.Text = vName
myFrm.TextBox2.Text = vLastName
myFrm.Show
Classes or other forms can be told about this form various ways such as via a property or the constructor:
Class Foo
Private myFrm As Form7
Public Sub New(frm As Form7)
myFrm = frm
End Sub
...
End Class
Dim myFoo = New Foo(Me)
For the main/startup form, it can help to create a global reference:
Module Program
Friend frmMain As MainForm
End Module
Then set the variable when the main form loads:
Public Class MainForm
Private Sub MainForm_Load(sender ...)
frmMain = Me
End Sub
...
frmMain will be a valid reference to the main form for the entire application.

How to set current object (Me) to a new object stored in an array in Visual Basic for Excel

I have an array of objects in an excel vba project. I have created another instance of the same class and set 1 of its properties. I am then trying to search through the array of objects to find the object in the array that matches the current one on the same property. I would like to set the current object to the one in the array inside one of the current object's methods using the self reference Me.
I tried:
Set Me = objectArray(index)
This does not work. It says that this is an improper use of the Me keyword. Is there a way to set the current object to another object of the same type? Thanks!
Edit:
I have an object that has child objects:
Me.friShift.shiftType.loadFromArray
Here, shiftType is the object of type CVocabulary, which is my self defined class. It has a sub called loadFromArray that looks like this:
Public Sub loadFromArray()
Dim index As Integer
index = searchVocabArray(Me.typed)
If (index = -1) Then
Exit Sub
End If
Set Me = vocabArray(index)
End Sub
vocabArray() is a global array containing CVocabulary objects.
If it is not possible to Set an object from within itself, I can try something else. This is just the easiest and most direct way of doing this. I'm sure I can just set each parameter from the current object to the value of the parameter from the object in the array, but if it was possible to do something like the above, that would have been my preferred method.
You can do it by giving itself to the function as a parameter. I'll show it in VBScript because the classes are more clear, but the concept is the same as in VBA:
public myObject
set myObject = new x
myObject.ChangeMe MyObject
msgbox typename(myObject) ' <- outputs 'y'
class x
public sub changeMe(byref object)
set object = new y
end sub
end class
class y
' just an empty class
end class
But this is not a good programming pattern and could cause messy code (maintenance and debugging would be an issue) and even memory leaks. You should create an (Abstract) Factory, Builder or Provider that returns an object as you ask for it.
Factory: creates a new predefined object
Builder: creates a new object that is configured in the builder
Provider: returns an existing object that is predefined earlier
I don't beleive you can use Me in this context - you are trying to use Me as it was used in VB6 (which was equivalent to 'this' in C#). This is not appropriate in VBA.
Without some code snippets its hard to see what you are doing. Can you perform the search in a module and create instances of this class there? You can then do:
Set class2 = objectArrayofClass1(index)
As you've already seen that Me cannot be changed. You can handle memorized objects through
a function in a public Module like basExternal:
Public Function loadFromArrayByIndex(ByVal lIndex)
dim xobj as Object
Set xobj = vocabArray(lIndex)
'
' do modifications and handling on this object:
' ...
'
End Function
.

Visual Basic: dynamically create objects using a string as the name

Is there a way to dynamically create an object using a string as the class name?
I've been off VB for several years now, but to solve a problem in another language, I'm forced to develop a wrapper in this one. I have a factory method to dynamically create and return an object of a type based on input from elsewhere. The provided input is meant to be the class name from which to create an object from. Normal syntax means that the entire class has to be explicitly spelled out. To do it this way, there could literally be hundreds of if/then's or cases to handle all the available class/object choices within the referenced libs:
If c_name = "Button" then obj = new System.Windows.Forms.Button
If c_name = "Form" then obj = new System.Windows.Forms.Form
....
I'm hoping instead to reduce all this case handling to a single line: IE...
my_class_name = "whateverclass"
obj = new System.Windows.Forms.my_class_name()
In PHP, this is handled like so...
$my_class_name = "whateverclass";
$obj = new $my_class_name();
Edit: Looking at some of the answers, I think I'm in way over my head here. I did manage to get it working using this CreateInstance method variation of the Assembly class, even though I'm more interested in this variation giving more options, including supplying construct parameters...
my_type_name = "System.Windows.Forms.Button"
asmb_name = "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
button1 = Reflection.Assembly.Load(asmb_name).CreateInstance(my_type_name)
In other words, it takes a method to do this, and not any inherent language syntax? This Activator variation also worked when the full assembly string and class path is used. I'm suspicious CreateInstance may not have the full ability to let me treat objects as if they were called normally, ie obj = new System.Windows.Forms.Button. This is why I can't use simply CreateObject. If there is no natural language feature allowing you to substitute a class name for a string, does anyone have any insight into what sort of limitations I can expect from using CreateInstance?
Also, is there even a difference between basic Activator.CreateInstance (after Unwrap) and Assembly.CreateInstance methods?
This will likely do what you want / tested working; switch the type comment at the top to see.
Imports System.Reflection
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Dim fullyQualifiedClassName as String = "System.Windows.Forms.TextBox"
Dim fullyQualifiedClassName As String = "System.Windows.Forms.Button"
Dim o = fetchInstance(fullyQualifiedClassName)
' sometime later where you can narrow down the type or interface...
Dim b = CType(o, Control)
b.Text = "test"
b.Top = 10
b.Left = 10
Controls.Add(b)
End Sub
Private Function fetchInstance(ByVal fullyQualifiedClassName As String) As Object
Dim nspc As String = fullyQualifiedClassName.Substring(0, fullyQualifiedClassName.LastIndexOf("."c))
Dim o As Object = Nothing
Try
For Each ay In Assembly.GetExecutingAssembly().GetReferencedAssemblies()
If (ay.Name = nspc) Then
o = Assembly.Load(ay).CreateInstance(fullyQualifiedClassName)
Exit For
End If
Next
Catch
End Try
Return o
End Function
I'm pretty sure Activator is used for remoting. What you want to do is use reflection to get the constor and invoke it here's an example http://www.eggheadcafe.com/articles/20050717.asp
EDIT: I was misguided about Activator until jwsample corrected me.
I think the problem your having is that your assembly is the one that GetType is using to try and find Button. You need to call it from the right assembly.
This should do it
Dim asm As System.Reflection.Assembly = System.Reflection.Assembly.LoadWithPartialName("System.Windows.Forms")
Dim obj As Object = Activator.CreateInstance(asm.GetType("System.Windows.Forms.Button"))
Take a look at the Activator.CreateInstance(Type) method.
If your input is the name of a class you should be able do this:
Dim obj As Object = Activator.CreateInstance(GetType("Name_Of_Your_Class"))
You'll have to fiddle with the GetType call to make sure you give it enough information but for most cases just the name of the class should work.
Here is a really easy way I have found while rummaging through the internet:
dynamicControl = Activator.CreateInstance(Type.GetType("MYASSEMBLYNAME." + controlNameString))