Best practice for using main form controls - vb.net

So lets say I have a form1 that opens on startup. Form1 contains a textbox named textbox1 and a button named button1. When I click button1 this is code that is called:
Class form1
Sub CapLetters () 'buttons click event
Dim myObj as new MyObject
MyObj.CapAllLetters
Textbox1.text = MyObj.GetThoseCapLetters
End sub
End class
Now, I know this is kinda dumb, but my main question is, since im creating a new object, the textbox1 will not be available in my object unless I specifically call it like:
Form1.textbox1.text
Is this good practice or is there a better way? Now in my program I have about 10 textbox and comboboxs I need my object to use. I know i could do something like this:
MyObj.CapAllLetters (textbox1.text)
But that doesn't seem like a good idea to pass that many values in a method.
I think I need a way for my object to gather all the info upon initating it?
Thoughts?

You basically seem to be asking whether it's OK to use the default instance of a form. The answer is yes. In recent versions of VB, each form that has a parameterless constructor also has a default instance, which is a single instance that can be accessed anywhere and at any time via the class name. This feature exists because it makes it easier for beginners to access forms from different places in a project without having to worry about passing references to forms around that project.
In the case of the startup form in a project, it is always the default instance of its type, assuming that you haven't disabled the application framework. That means that you can always access your startup form anywhere in your project using the default instance of its type.
Now, you'll find that experienced developers rarely use default instances. That's because they are never required and rarely add value if you know what you're doing. That means that you can always access your application's startup form is reasonable locations throughout your project without using the default instance.

Related

What to use Public Sub New(), i.e. InitializeComponent, for?

I understand what the InitializeComponent() does in the background - it creates the form and all the controls that were added in the designer. However, what I have not found is WHEN you would add a Public Sub New() constructor to a form and what you would add to that versus the Load() sub.
I did find that this is a good place to put my BackColor and BackGroundColor settings since they are user-preference and stored in Settings. What else should I put in there? I have always used the Load() sub to do any work with controls. Examples: Adding handlers, loading comboboxes, setting DGV columns, loading DataTables) Should I be doing that in the New constructor? Does it make any difference?
The Load event is fired just before the form is displayed for the first time.
... but that might not be right away. It's possible — common, even, in certain environments — to create a form and have it available to code long before the form is ever shown on screen. The form might even never be shown on screen.
Take, for example, a settings form, where properties are defined in the class that map to user preference fields on the form. Someone might decide to build an application that looks directly at a known form object instance to read preferences, but if the user never goes to change anything that form might never display to the screen, and the Load event would never fire. That's just one example, and whether or not it's a good idea is another story; it's enough to know I've seen it happen in real code.
However, what I have not found is WHEN you would add a Public Sub
New() constructor to a form and what you would add to that versus the
Load() sub.
Here is an example to answer (the core?) question of yours. Suppose you have a Form to edit a product. Let's call it ProductEditForm. Your use case for this Form is to edit the values of an existing Product in your system. How would you tell this form what product to edit? By requiring the passing of a Product object when you instantiate the form. For example, in the code page of ProductEditForm:
Private _product As Product = Nothing
Public Sub New(ByVal p As Product)
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_product = p
' Now, you can reference the values of passed-in product "p" via variable _product, anywhere in this Form.
End Sub
And you would instantiate ProductEditForm like this:
Dim p As New Product
p.ID = 1234
p.Name = "Widget"
p.Cost = "10.00"
p.SalePrice = "30.00"
Dim f As New ProductEditForm(p)
In this example, you are making good use of code in Sub New(). You could also, perhaps, assign the values to _product to TextBox controls, check values in _product to set colors or fonts, etc.
(Side note: this method of requiring a target object in the constructor completely avoids the way-too-common bad habit of some WinForms programmers to pass data between Forms via public form properties or global variables.)
If you're talking about Windows Forms, I've found that it's best to do your UI setup in Load rather than in the constructor (New), because sometimes if you do it in New then certain things won't be initialized yet and you'll run into null reference exceptions.

VBA - Import namespaces?

Is it possible to import namespaces in VBA? I'm writing Word macros and I want to put all functions inside Modules or Classes. But once I do that I see that I cannot reach the controls in the VBA form easily. I have to include the name of the form first, then access the control within that one. I can't just go
TextBox1.Caption
I have to go
Form1.TextBox1.Caption
Is there a way to get around this?
You shouldn't be coupling your classes/modules so tightly. A worker module shouldn't require knowledge of the Form1 class, because then it can't be used separately in another project.
Instead, you probably want to pass the function in your helper class an argument on which it performs work, and then returns the result (if necessary). As a completely useless, trivial example:
Public Sub SetLabelText(ByVal lbl As Label, ByVal caption As String)
lbl.Caption = caption
End Sub
And you would call it from within the form class, like so:
MyHelpers.SetLabelText(Label1, "New Label Caption")
That way, you can use the functions in your helper class from any form.
But, as far as your actual question, no. VBA doesn't have any concept of "namespaces". The best you can do is the With statement, but having to do this frequently is more likely an indication of a design flaw, as I discussed above.

does VB6 allow referencing Form instance as a singleton just by naming its data type? or what is happening?

I am seeing code like "Unload frmMain" where from what I can tell frmMain is the type/module name, and I don't think it could also be simultaneously a variable name of the "ObjFrmMain" sort. Nevertheless, this command does successfully induce the form in question to unload.
So is the data type being used as an alias for its single existing instance? Or maybe for all of its instances?
Does VB6 do similar things to data types other than those derived from Form?
Yes, VB6 has odd object behavior. It gives you some shortcuts for dealing with form objects.
Load frmMain
...will load a single instance of that form under that variable name. In fact:
frmMain.lblSomeLabel.Caption = "some caption"
... will load that instance. However:
frmMain.SomeStringMember = "some value"
... will not load the form object (meaning the window itself) but you can access these variables, so in essence, the name of the form is a global variable.
You can, however, create new instances:
Dim newForm As MyForm
Set newForm = New MyForm
newForm.Show vbModal
That will actually create a new instance of MyForm, load it and show it, so you can have multiple instances of one form.
Also beware of the oddness in the New keyword:
Dim newObject As New MyClass
Set newObject = Nothing
newObject.SomeStringProperty = "some value"
This works without an "Object Reference Not Set ..." error. When you declare a reference variable using the As New syntax, you can destroy the object by setting it to Nothing and then reference that variable again and it will create a new instance.
In fact that's what's really going on with the forms. There is an implicit:
Dim frmMain As New frmMain
Personally I prefer not to use the As New syntax because it's confusing and dangerous. It also has a performance penalty, vs. this:
Dim newObject As MyClass
Set newObject = New MyClass
... but you're stuck with it for the forms.
What's happening when you call Unload frmMain is that it unloads the window (and all the controls) so all the data in those are gone, but the object frmMain is still hanging around. Therefore even after you unload it, you can still access any member variables and properties. However, if anything references any control on the form, it will trigger an implicit Load frmMain. This is the source of a lot of subtle programming errors in VB6, especially when you're trying to shut down.
Yes, it's a special functionality in VB6 and earlier. I normally tried to avoid doing it, since I saw it more as a source of confusion rather than a help.
The following comment In Visual Basic 6.0 and earlier versions, a special default instance of each form is automatically created for you, and allows you to use the form's name to access this instance. is taken from this MSDN page: Working with Multiple Forms in Visual Basic .NET: Upgrading to .NET

custom constructors for forms in vb.net: Best practices

I'm quite new to vb.net, and windows forms developement as a whole, so this might all be very basic, but here goes.
I would like to open a new form from some other form, and pass some selected object from a control on that form to the new form. The sensible way to do this, I thought, was as a parameter to the forms constructor. Now I know that the visual studio GUI creates partial classes for my forms, that hold the properties that I can drag onto there in the designer. I assume it also holds a default constructor. Since it might do all sorts of stuff that is needed to initialise the form, I figured I should call it from my custom constructor ala
public sub new(byval my_parameter as Foo)
Me.new()
Me.my_parameter = my_parameter
do_some_initialisation()
end sub
That clearly wasn't it, because it can't find a default constructor. The thing is, visual studio goes trough great lengths to prevent me from seeing the generated constructor, so I know how to access it. This leads me to believe that I am actually doing it wrong, and should have set out on some different path, as the path you are forced in to usually is the sensible thing to do, which I usualy find out way too late.
So how should I be doing something like this?
This is a fairly simple example.
This goes into your "main" form (the one you want to call your new form from):
Dim childForm1 As New form2Name(item)
childForm1.Text = "Title of your new form"
Call childForm1.Show()
form2Name(item) breaks up like "form2Name" is the name of the form you want to open and "item" is the parameter to be passed.
In your new form (form2Name) add this code:
Public Sub New(ByVal item As String)
InitializeComponent() ' This call is required by the Windows Form Designer.
MsgBox(item)
End Sub
You can do whatever else you need in your form.
Hope this helps.
For VB.Net I think the call you are after is
MyBase.New()
Your derived form class automatically inherits the default constructor for System.Windows.Forms.Form. This default constructor is invoked automatically before your derived constructor code executes. The reason you can't find any code for the default constructor is because the derived class does not specialize the default constructor. If you wish to define your own default constructor, you may. You can also define a constructor without parameters.
You code should work fine if you remove this line:
Me.New()

Force multi-threaded VB.NET class to display results on a single form

I have a windows form application that uses a Shared class to house all of the common objects for the application. The settings class has a collection of objects that do things periodically, and then there's something of interest, they need to alert the main form and have it update.
I'm currently doing this through Events on the objects, and when each object is created, I add an EventHandler to maps the event back to the form. However, I'm running into some trouble that suggests that these requests aren't always ending up on the main copy of my form. For example, my form has a notification tray icon, but when the form captures and event and attempts to display a bubble, no bubble appears. However, if I modify that code to make the icon visible (though it already is), and then display the bubble, a second icon appears and displays the bubble properly.
Has anybody run into this before? Is there a way that I can force all of my events to be captured by the single instance of the form, or is there a completely different way to handle this? I can post code samples if necessary, but I'm thinking it's a common threading problem.
MORE INFORMATION: I'm currently using Me.InvokeRequired in the event handler on my form, and it always returns FALSE in this case. Also, the second tray icon created when I make it visible from this form doesn't have a context menu on it, whereas the "real" icon does - does that clue anybody in?
I'm going to pull my hair out! This can't be that hard!
SOLUTION: Thanks to nobugz for the clue, and it lead me to the code I'm now using (which works beautifully, though I can't help thinking there's a better way to do this). I added a private boolean variable to the form called "IsPrimary", and added the following code to the form constructor:
Public Sub New()
If My.Application.OpenForms(0).Equals(Me) Then
Me.IsFirstForm = True
End If
End Sub
Once this variable is set and the constructor finishes, it heads right to the event handler, and I deal with it this way (CAVEAT: Since the form I'm looking for is the primary form for the application, My.Application.OpenForms(0) gets what I need. If I was looking for the first instance of a non-startup form, I'd have to iterate through until I found it):
Public Sub EventHandler()
If Not IsFirstForm Then
Dim f As Form1 = My.Application.OpenForms(0)
f.EventHandler()
Me.Close()
ElseIf InvokeRequired Then
Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
Else
' Do your event handling code '
End If
End Sub
First, it checks to see if it's running on the correct form - if it's not, then call the right form. Then it checks to see if the thread is correct, and calls the UI thread if it's not. Then it runs the event code. I don't like that it's potentially three calls, but I can't think of another way to do it. It seems to work well, though it's a little cumbersome. If anybody has a better way to do it, I'd love to hear it!
Again, thanks for all the help - this was going to drive me nuts!
I think it is a threading problem too. Are you using Control.Invoke() in your event handler? .NET usually catches violations when you debug the app but there are cases it can't. NotifyIcon is one of them, there is no window handle to check thread affinity.
Edit after OP changed question:
A classic VB.NET trap is to reference a Form instance by its type name. Like Form1.NotifyIcon1.Something. That doesn't work as expected when you use threading. It will create a new instance of the Form1 class, not use the existing instance. That instance isn't visible (Show() was never called) and is otherwise dead as a doornail since it is running on thread that doesn't pump a message loop. Seeing a second icon appear is a dead give-away. So is getting InvokeRequired = False when you know you are using it from a thread.
You must use a reference to the existing form instance. If that is hard to come by (you usually pass "Me" as an argument to the class constructor), you can use Application.OpenForms:
Dim main As Form1 = CType(Application.OpenForms(0), Form1)
if (main.InvokeRequired)
' etc...
Use Control.InvokeRequired to determine if you're on the proper thread, then use Control.Invoke if you're not.
You should look at the documentation for the Invoke method on the Form. It will allow you to make the code that updates the form run on the thread that owns the form, (which it must do, Windows forms are not thread safe).
Something like
Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)
Public sub UpdateStatus(ByVal newStatus as String)
If Me.InvokeRequired Then
Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus)
Me.Invoke(d,new Object() {newStatus})
Else
'Update the form status
End If
If you provide some sample code I would be happy to provide a more tailored example.
Edit after OP said they are using InvokeRequired.
Before calling InvokeRequired, check that the form handle has been created, there is a HandleCreated property I belive. InvokeRequired always returns false if the control doesn't currently have a handle, this would then mean the code is not thread safe even though you have done the right thing to make it so. Update your question when you find out. Some sample code would be helpful too.
in c# it looks like this:
private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(StatusHandler, sender, e);
}
else
{
//do work
}
}