Little Quirks in the VB.NET IDE

Introduction

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.

Take a look at this code snip:

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

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

Nulls

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.

Little Quirks in the VB.NET IDE

Form Debugging

How often do you step through some code and click between the IDE and your running project form to verify and double-check that things are running correctly?

Well, in VB.NET, because of several changes that have been necessary to integrate VB into the .NET framework, this and a few other working methods have changed. Although the Debug system works much the same, stepping through code with 'F8' works and you can still use IntelliSense to view the values of certain variables. However, the form is locked and you are not able to view it while the IDE is in Pause mode.

This, of course, has a few other drawbacks. In VB6, any 'F#' key presses while in debug mode are automatically passed to the IDE and processed there.

In VB.NET, if the running form has the focus because you tried to view it, all key presses are ignored. Also, often the .NET IDE does not get the focus back when you shut down the application.

Why does this happen, many may ask. It's because of how VB6 and VB.NET differ during a debug run.

With VB6, the application actually runs within the IDE, with the IDE managing all aspects of the application, including but not limited to APIs, memory management, keyboard input, and mouse input. Therefore, any input to the application is via the IDE.

With VB.NET, the application runs in its own process and thread, completely independent of the IDE. This limits the interaction between the IDE and the application during debug.

Code Regions Can't be Searched (By Default)

When I heard about this bug, I just had to double-check it over and over.

What is it? Well, a new feature was added to VB.NET; your code is kept in regions, and the regions can be collapsed to take up less space in the IDE. Each region is essentially one or more event, sub, or function that can be collapsed when necessary.

Now, when you're writing a large, involved project, you have the capability to close up sections of code for any number of reasons. The code in that function is completed and fully functional, the module is large, making it difficult to scroll over, or any other reason you may think of.

However, when these code regions are collapsed, a Find or Find and Replace search will not open the collapsed regions to search within them. So, every care must be taken when searching for variables that you are planning to remove or replace because any closed regions will be skipped.

Update: Well, I was slightly mistaken that it's not posible to search within collapsed regions. To search Regions, on the 'Find and Replace' dialog box, expand 'Find Options', and tick 'Search hidden Text'. This option is 'off' by default, and can catch a new user off gaurd.

#Regions Naming Problem

A few days ago while working on a project, I tried to open a collapsed region that I had created in my code. It took several clicks on the region before it decided to open. This was somewhat of an irritation but I soon forgot about it, and carried on developing, and adding more regions to the ever-increasing code.

Eventually, I had several regions that were giving me grief, and refusing to open on the first click. I decided to sit down and debug this IDE problem. After several hours of fiddling with Regions, I came across the cause and solution.

Primary Cause: The naming of regions. Mostly because of how you define the region names, With #Region "", the assumption is that you can use spaces in the region name. Assumptions should never be made; they need to be tested and confirmed.

Testing the use of spaces in the region name yields no errors and no warnings. Collapsing the region works as expected and the region name as typed appears next to the Expander Icon. But, clicking on this icon sometimes tends to be harder than expected. After several clicks, the region will open, and if you haven't overclicked, it stays open.

The problem here is that the bug is not always reproducible, So far, the bug has reared its head several times while developing in VS 2008, but as yet not in VS 2005.

Update: Further testing has revealed that this bug tends to affect the IDE after debug running your project a few times.

After spending time with regions, I have also discovered that sometimes changing the name of a region has some minor side effects. The most common side effect is that closing and opening the renamed region will not open it, but rather display the old name of the region. After clicking the region a second time, it then will open correctly.

So, be careful in how you name and change regions because it could lead to several frustrating moments in front of the computer. I suggest, rather, that you use '_' and not spaces in region names.



About the Author

Richard Newcombe

Richard Newcombe has been involved in computers since the time of the Commodore 64. Today, he has excelled in programming, and designs. Richard is in his mid 30's and, if or when you looking for him look no further than his computer. Always willing to help and give advice where he can in regard to computer related subjects. At present he is working as a .NET 2008 Software Developer for Syncrony Web Services, South Africa.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds