Last week we covered the basics of creating a dialog box that doesn’t block the execution of the application that called it. This time I want to expand on that discussion to look at dialog boxes that allow the user to enter data – which in turn brings up the topic of data validation.
What We’ll Build
To demonstrate the various ideas that I want to cover, I have created a dialog box that shows some typical situations. As you can see from this screen shot of the front panel we will look at two ways of validating numeric data, as well as a couple more techniques for dealing with strings.
Remember, the one thing that is foundational to this discussion is the concept that the
Ok button doesn’t get enabled until all the data is valid.
Building the Infrastructure
As you can see from the screen shot below, the basic infrastructure for determining when to enable the
Ok consists of a cluster containing one Boolean value (I call them validation flags) for each item that needs to be validated, and for which the user could make an invalid entry. The second part of that definition is important because it’s perfectly feasible for there to be parameters that have no invalid values. Moreover, a couple of the techniques I will show you can actually prevent the user from entering incorrect data so they don’t require explicit validation.
Note also that I always use a cluster to store these validation flags because, in a cluster, you can give each value a meaningful name. Consequently, the validation cluster in our example contains two flags named, “Numeric 1” and “String 1”. The other two values will be the ones where the user won’t be able to enter incorrect data.
The key idea behind this cluster of validation tags is that each value needing validation has its own flag. Consequently, the various tests can’t interfere with each other. All you have to do is wait until all the flags show that their associated values are good and you can enable the
Ok button. To be consistent with the error logic that LabVIEW uses, a true value indicates a parameter that is in error, while a false indicates no error. To determine the enabled state of the button I created a polymorphic VI that accepts either a single Boolean or an array of Booleans, and outputs the enumeration that the
Disabled control property expects. Any true input results in a
Disabled and Grayed Out output value.
Getting Down to Specifics
To see how all this fit together, we’ll start with the two values where users can enter incorrect values.
Validating Numeric 1
We’ll start with this input because it is the easiest case. The technique is basically to take the
NewVal value from the event data and compare it to the constraints to which it has to conform. In this case, upper and lower limits.
If the new data, is not in the desired range, the code writes a true to the input’s validation flag. If there is in range, it writes a false. Once the flag is updated, the cluster is converted to an array and the result drives the polymorphic VI I discussed earlier to determine the
Ok button’s new state.
Validating Numeric 2
This input is the first of the “no error possible” solutions but is, frankly not the way I prefer to deal with simple limits — and you’ll see why in a few moments. Note that in this example, the code implementing this method actually exits outside the event loop in the form of two property nodes.
The first of these nodes define the control’s behavior if an out-of-range value is entered. To ban invalid data, we want the control to coerce the value to the closest valid number. The second node defines what the high and low limits are. The API for this functionality also allows you to control the minimum amount a value can change. For example, you could define an input that is settable between 1 and 10 in increments of 0.1.
But why isn’t this one of my preferred techniques? This method can ensure that the input is technically valid, but it can’t guarantee that it is what the user wanted. If this field is one of many on the screen, it is too easy for the user to miss it and fail to enter the number that they want. The result is a value that is valid, but nevertheless incorrect. Allowing the presence of invalid data on the screen forces the user to correct the field’s contents.
The best place to use this approach is with data fields where the default value is correct 90% or more of the time. Also, the ranges can also be set at any time — not just during initialization.
Validating String 1
This field is the other one that needs to be programmatically validated, and as you can see it’s not very different from the Numeric 1 example. The test for this string is that it can’t be empty and it can’t be more than 15 characters long.
But even this simple pair of constraints can provide us with some food for thought. For example, you can and should tell your users what the expected criteria are for each input. However, telling a user that the string can’t be more than 15 characters long isn’t going to as helpful as you might think it would be. The problem is that most people can’t look at a string that long and tell how many characters are in it. They have to count the letters — which is a pain.
A better approach is to simply manipulate the input such that it will only accept 15 characters. Now I’m not going to show how to do that. Instead I will leave that (as they say in fancy college textbooks) as an exercise for the reader. But to get “the reader” off on the right foot, let’s look at what I’m doing with the other string input.
Validating String 2
For this example I am drawing on an experience where a customer wanted a numeric input that was blank until the operator typed a number into it. The problem, of course, is that numeric inputs always have a number in them. The solution is to create a string control that operates like a numeric input. And to accomplish that, we need to look at each keystroke as the operator is making it and only allow those keys that are valid. The alchemy that allows such magic is the
Key Down? filter event.
But you also need to know a bit about how a PC keyboard works. Some of the keys have ASCII values associated with them, like 0x35 for the character “5” or 0x2E for the period. LabVIEW returns the ASCII value of keys in the
Char ;event data node. However, some keystrokes return an ASCII code of 0. These are control keys that do things and don’t represent printable characters. Here I’m talking about things like the delete or arrow keys. When you see an ASCII value of 0, the
VKey event data value, which is an enumeration, will tell you what key was pressed.
Our event handler will need to manage both. So let’s first consider the ASCII codes we need to handle. Clearly, the 10 digits will need to be accepted (0x30 through 0x39) so when any of those values are returned we will set the
Discard? node to false.
Likewise if we are to accept floating point numbers we need to be able to type a period as well — but we can only have one so we need a tiny bit more logic.
So we’re done right? Not really, no. To begin with, you’ll notice that there are two other ASCII codes in the same case as the numeric digits: tab and backspace. They are there because while your users are entering information, they still need to be able to navigate the screen, so these two codes are also needed — and several more besides that fall in the area of control keys. Check out the code in the repository…
So that’s all I have for now. I do however want to point out that the last technique I showed you is very powerful and I have used it many time to “synthesize” custom inputs that don’t natively exist in LabVIEW, like an IP address entry field. The modified code is in SVN at:
Check it out and if you have any questions, post ’em.
Until next time…