Virtual Developer Workshop: Containerized Development with Docker
The VB6 IDE was jam packed with little quirks and bugs; many were fixed with the release of service packs. Almost any program, no matter how big or small, has bugs and quirks. I am going to cover a few that have been found and have personally checked. You will also look at a few quirks that require a change in programming techniques.
- This Page
- For Next and For Each
- Timers.Timer and Process threads
- Null versus ""
- IDE control clicks to go to Default versus Created event code
- Page 2
- IDE Debugging locks up the forms
- IDE Find and code Regions
- Region Naming
For-Next and For Each Loops
For-Next loops are in every program that you write, and are probably the second most used method, second to If-Then-Else statements. So, what is the problem with them? Well, nothing, if you use them to count UP. But, a lot of the time you need to count down.
For Loop = 200 to 1 Step -1 Next Loop
At first look, you'll say you can declare Loop as a Byte because its upper limit is 255, which you definitely are not exceeding. But, the IDE will return an error: Constant expression not representable in type 'Byte'.
Why does this happen, you ask, when Loop will never actually hold a negative value? Yes, that's true, but within the IDE there are checks applied to all lines of code as you enter them, including Out of Range checks. When the IDE checks the For statement, all values have to be within the declared data-type, including the Step number. In this case, you can't use the Signed Byte Sbyte type because it ranges from -127 to 128, so you need to use a Signed 2-byte type, Short.
For Each loops are also often used, as it just makes it easier to reference a collection of objects, and perform repetitive tasks on them. How ever there is one instance where the For Next loop has to be used instead of the For Each. Look at the following code snip, this is code that I wrote for a project.
For Each Childnode As TreeNode In TreeView1.Nodes If Childnode.Nodes.Count = 0 Then Childnode.Remove() End If Next
Looking quickly at the code, you would say: Well what's wrong with it? It simply removes empty Nodes from a collection. I don't blame you for thinking that; however, there is one fatal flaw here that you don't see until it's spelled out.
To borrow a phrase from another Codeguru member, "Modifying a collection invalidates the iterator. This is inherent in the design.". So, let me now spell it out for everyone to see. Expand the For Each loop and see exactly whats going on inside it.
For Index As Integer = 0 To TreeView1.Nodes.Count - 1 Dim Childnode As TreeNode = TreeView1.Nodes(Index) If Childnode.Nodes.Count = 0 Then For TmpInd as Integer = Index to TreeView1.Nodes.Count - 2 TreeView1.Nodes(TmpInd) = TreeView1.Nodes(TmpInd +1) Next TreeView1.Nodes.Count = TreeView1.Nodes.Count - 1 End If Next
I've expanded the code a little further than really necessary, but once you see the code expanded this far you will realize that the problem is simple. Any programmer knows that when deleting or removing items for a sequentially indexed list, you need to step through the list backwards; else, you tend to skip items.
Also, the line TreeView1.Nodes.Count = TreeView1.Nodes.Count - 1 is what is mostly responsible for invalidating the iterator. What does that mean? Well, the loop has been initiated with a recorded amount of items, and before the loop has been completed, you have adjusted the number of items. That invalidates the loop count.
Once the problem is realized, its easy to find a solution, or solutions.
If For Each loops forwards in a list, you simply have to loop backwards. Your Final Code will look as follows.
For Index As Integer = TreeView1.Nodes.Count - 1 To 0 Step -1 Dim Childnode As TreeNode = TreeView1.Nodes(Index) If Childnode.Nodes.Count = 0 Then Childnode.Remove() End If Next
OR you use a gather/process method, which is a two-pass process but has the advantage of not relying on indexes. Code as follows:
Dim Tmpnode As New TreeNode For Each Childnode As TreeNode In TreeView1.Nodes If Childnode.Nodes.Count = 0 Then Tmpnode.Nodes.Add (Childnode.Name, Childnode.Text) End If Next For Each Childnode As TreeNode In Tmpnode.Nodes TreeView1.Nodes.RemoveByKey (Childnode.Name) Next
So, now you know that For Each Loops are not the ideal method to use when you need to remove items from a collection.
Note: The above code snips are specifically for working with Treeview Nodes, but with minor adjustments will work with any collection.
Timers are needed all the time, so what is the problem here? Well, look at a simple timer definition that uses the Timers.Timer class.
Private WithEvents Timer_1 As Timers.Timer
What's wrong with using this timer class? If you run your application in the IDE, the timer Class actually spawns off a new thread. Any code that's executed from the Timer's Tick event is executed in this alternate thread. This then raises problems with any timers that call functions to update the UI because the UI can be updated only by the UI thread.
This problem, however, does not occur if you compile the application. So, if you're not constantly using the IDE's Debug to run and check your application, you can use this class. Also, if your timer is going to be used only to set flags and variables, this class is safe to use.
But, this is not always the case, so how do you solve this problem? Thankfully, .NET provides you with a secondary timer class, System.Windows.Forms.Timer. So, when a timer is called for that needs to update the UI or perform other functions that are not Cross thread safe, use this declaration:
Private WithEvents Timer_1 As System.Windows.Forms.Timer
Here, you going to look at some changes that have been made to the IDE to help improve the quality of applications written in .NET.
Most programmers have the nasty habit of not been too bothered to pre-set a string variable before using it as a return variable for some functions. Now, look at this a little closer. When you define a string variable, its contents are actually Null and not "".
What's the difference?
Null means that the variable has not been assigned a value (refer to Four rules for Nulls). "" means the Variable has been assigned a blank value.
Why does this matter? Go on the assumption that NULL means that no memory has been allocated to the variable. If you pass this variable By-ref to a function that does not have rights to allocate memory within your application (as in external DLLs and APIs), an error would occur. The effects of the error are unpredictable, ranging from a simple error box to system crashes due to memory corruption. (I've been victim to system crashes because of this in VB6.)
To prevent this type of problem, the IDE will pop up an error Null exception could occur, and a simple Variable = "" can save the day here.
IDE Control Clicks
Many of you are used to using the form editor to find your controls and click on them to get to the relevant event code. VB.NET has this similar functionality; however, in VB.NET clicks are locked to the default event.
In VB6, if you removed the default event—Command1_Click—and used the Command1_MouseDown event for your code, every IDE click on Command1 would take you to the event already in the code block, Command1_MouseDown. In VB.NET, all clicks would always open the method code Command1_Click. Even if you delete the method from your code, the click will re-create the method.