Fixing the InputBox
A reader asked for help using InputBox to code a loop to input data to a ListBox control. The InputBox is a VB6 holdover function that probably shouldn't be used now. The Quick Tip Getting Input and Validating It shows why and what to do instead.
If you absolutely must use InputBox, this Quick Tip shows how to solve one of the problems: You can't tell the difference between blank input and the Cancel button.
But there is a serious question about why it works now and why it might stop working at some point in the future.
But first, credit where it is due, the tip was actually supplied by About Visual Basic reader, "SmetsRoger". He wrote, "A few years ago we tackled this problem in night school and came up with (this) solution." Thanks for sending it in!
In case you don't remember, here's the InputBox we're working with.
--------
Click Here to display the illustration
--------
According to Microsoft, when Cancel is clicked in an InputBox, the function returns a zero-length string. The problem is that this is the same thing that is returned when the user simply forgets to enter anything. For that reason, it's a good idea to plug a default value into the InputBox before displaying it. If the user simply presses Enter, this value is returned instead of a zero-length string so you might assume that a zero-length string returned is actually Cancel. But bullet-proof code doesn't make assumptions. Right now, you can plug this code into the example in the previous Quick Tip to be sure.
(Only part of the code is listed here. For the full listing, see the previous article.)
The StrPtr function is where the magic happens. This is a new function that also has to be added to the program. Here's the code for StrPtr:
The key is the GCHandle structure. According to Microsoft, GCHandle "provides a means for accessing a managed object from unmanaged memory." Because unmanaged memory isn't normally used in VB.NET programs, the namespace isn't referenced. Your code also has to include the Imports statement:
This Function returns an integer equal to the GCHandle of object passed to it. Visual Basic programmers with no experience with a language like C or C++ might not understand what's happening here. Handles are just integers that "point to" a location in memory. C uses them a lot to reference objects in code. VB, however, has traditionally not used them at all. An honest C programmer will admit that pointer bugs are one of the biggest sources of frustration and confusion. This example is no exception; it's pretty confusing too, but it does illustrate the fact that a VB.NET programmer can use to pointers if it's necessary.
The reason this works is very interesting but because the actual internal code of InputBox is part of the .NET Framework, I can't verify that my analysis is completely correct. It seems to me that it must work this way, but I might be overlooking something. If anyone has a better explanation, please let me know.
Notice that the variable intCanceled contains the memory location of an empty string. A typical value will be something like "31199652". The key test in the program is:
If InputBox returns a string, StrPtr(str_inputGrade) will be the memory location of that string, even if it's a zero-length string, and it will be different from the memory location of intCanceled. But if the Cancel button is clicked, VB.NET assigns the same memory location as the previously declared intCanceled. It seems that VB.NET, to be as efficient as possible, will use the same memory location for any declared zero-length string. You can confirm this by adding another one ...
... and checking the value assigned. It will be the same as intCanceled.
This could be a problem. Microsoft only guarantees that Cancel returns "a zero-length string". They don't say it will return the same one that was previously declared (intCanceled). At any time, Microsoft might decide to tweak the InputBox code in Framework and return a different zero-length string. As evidence of this possibility, a zero-length string in the InputBox textbox is in a different memory location. If you used this code, it could "break" after some Framework update and, trust me, figuring out why would be nearly impossible. (It will also break when you're on vacation far away. Cries of "I didn't change anything!" would be heard. This is why backups and versioning is required before any update.)
It's a clever idea and working with it is really interesting, but I can't recommend that you use this tip.
If you absolutely must use InputBox, this Quick Tip shows how to solve one of the problems: You can't tell the difference between blank input and the Cancel button.
But there is a serious question about why it works now and why it might stop working at some point in the future.
But first, credit where it is due, the tip was actually supplied by About Visual Basic reader, "SmetsRoger". He wrote, "A few years ago we tackled this problem in night school and came up with (this) solution." Thanks for sending it in!
In case you don't remember, here's the InputBox we're working with.
--------
Click Here to display the illustration
--------
According to Microsoft, when Cancel is clicked in an InputBox, the function returns a zero-length string. The problem is that this is the same thing that is returned when the user simply forgets to enter anything. For that reason, it's a good idea to plug a default value into the InputBox before displaying it. If the user simply presses Enter, this value is returned instead of a zero-length string so you might assume that a zero-length string returned is actually Cancel. But bullet-proof code doesn't make assumptions. Right now, you can plug this code into the example in the previous Quick Tip to be sure.
(Only part of the code is listed here. For the full listing, see the previous article.)
Dim str_inputGrade As StringDim int_counter As Integer = 1Dim intCanceled As IntegerAcceptGrades.Enabled = FalseintCanceled = StrPtr(String.Empty)Dostr_inputGrade =InputBox("Enter grade number " &int_counter,"Grade Calculator", "Enter Grade")If StrPtr(str_inputGrade) = intCanceled ThenIf MessageBox.Show("Do you really want to cancel the inputs","Cancel button clicked",MessageBoxButtons.YesNo,MessageBoxIcon.Question) =Windows.Forms.DialogResult.Yes ThenGradeOutput.Items.Clear()int_counter = 1AcceptGrades.Enabled = TrueExit DoEnd If...
The StrPtr function is where the magic happens. This is a new function that also has to be added to the program. Here's the code for StrPtr:
Private Function StrPtr(ByVal obj As Object) As IntegerDim Handle As GCHandle = _ GCHandle.Alloc(obj, GCHandleType.Pinned)Dim intReturn As Integer = _ Handle.AddrOfPinnedObject.ToInt32Handle.Free()Return intReturnEnd Function
The key is the GCHandle structure. According to Microsoft, GCHandle "provides a means for accessing a managed object from unmanaged memory." Because unmanaged memory isn't normally used in VB.NET programs, the namespace isn't referenced. Your code also has to include the Imports statement:
Imports System.Runtime.InteropServices
This Function returns an integer equal to the GCHandle of object passed to it. Visual Basic programmers with no experience with a language like C or C++ might not understand what's happening here. Handles are just integers that "point to" a location in memory. C uses them a lot to reference objects in code. VB, however, has traditionally not used them at all. An honest C programmer will admit that pointer bugs are one of the biggest sources of frustration and confusion. This example is no exception; it's pretty confusing too, but it does illustrate the fact that a VB.NET programmer can use to pointers if it's necessary.
The reason this works is very interesting but because the actual internal code of InputBox is part of the .NET Framework, I can't verify that my analysis is completely correct. It seems to me that it must work this way, but I might be overlooking something. If anyone has a better explanation, please let me know.
Notice that the variable intCanceled contains the memory location of an empty string. A typical value will be something like "31199652". The key test in the program is:
If StrPtr(str_inputGrade) = intCanceled Then ...
If InputBox returns a string, StrPtr(str_inputGrade) will be the memory location of that string, even if it's a zero-length string, and it will be different from the memory location of intCanceled. But if the Cancel button is clicked, VB.NET assigns the same memory location as the previously declared intCanceled. It seems that VB.NET, to be as efficient as possible, will use the same memory location for any declared zero-length string. You can confirm this by adding another one ...
Dim aNewVar As IntegeraNewVar = StrPtr(String.Empty)
... and checking the value assigned. It will be the same as intCanceled.
This could be a problem. Microsoft only guarantees that Cancel returns "a zero-length string". They don't say it will return the same one that was previously declared (intCanceled). At any time, Microsoft might decide to tweak the InputBox code in Framework and return a different zero-length string. As evidence of this possibility, a zero-length string in the InputBox textbox is in a different memory location. If you used this code, it could "break" after some Framework update and, trust me, figuring out why would be nearly impossible. (It will also break when you're on vacation far away. Cries of "I didn't change anything!" would be heard. This is why backups and versioning is required before any update.)
It's a clever idea and working with it is really interesting, but I can't recommend that you use this tip.