Key to using event structures effectively is to keep things moving. This maxim applies with any sort of event-driven structure — even (especially!) XControls. So if you don’t want to ever stop an event loop, you can see how you are going to have problems with dialog boxes. However, the last time we were together I alluded to approaches that provide the functionality of dialog boxes, but without the execution blocking that normally occurs. This post will start the discussion of how to implement this functionality by creating a simple “Are you sure?” -type dialog; in a later post we will expand on this idea to include dialog boxes that support the entering of setup or configuration data.
The first idea you need to get your head wrapped around is that a dialog box is just another LabVIEW program, not unlike any other program you write. The only significant difference is that when this program runs, it looks like a dialog box. So what makes a good “dialog box” program? In addition to the usual traits of a good program, there are a few that are unique — or at least have a special significance for dialog boxes:
No program should be inherently complicated, but with a dialog box this point is particularly important. Your users should be able to look at the dialog and easily determine what information the system expects and/or wants from them. To aide your users in making that determination, don’t be afraid to state on the front panel exactly what you expect from them, but no bloviating: keep it pithy. If you feel there are aspects of the operation that need long-form explanations, considering having a pop-up help screen that provides all the gory details.
You might, however, also consider whether the task at hand is too large for a dialog box. You don’t want to try to do too much in a single dialog box — or at least on a single dialog box screen. A very effective technique for dealing with complexity can be to create a “wizard” interface, where the user is logically stepped through a large task. Just be careful how many screens you give the wizard, more than 3 or 4 and the user can lose track of where they are in process.
Finally, I have seen applications where the program’s basic user interface was one (very large) dialog box. While I understand that the goal of such a misguided effort is often to prevent the user from gaining access computer desktop, this sort of structure is very bad form. To begin with, the goal itself is problematic in that it complicates working with the code, but more importantly it never really works very well anyway.
One of the things that many people don’t think about is that sometimes the very presence of a dialog box can cause uncertainty — like what happens if I inadvertently select the wrong thing and a dialog box I wasn’t expecting opens. I might press the wrong button, and then what?
There are a couple of ways of preventing this sort of stress. First, given that the dialog is really needed (sometimes a static configuration setting is just as good — or even better!) write the code such that nothing changes in the application’s state until the “OK” button is pressed. I know, this sounds like a small, common-sense thing, but users can get worried if they see things starting to change in anticipation of a button selection that they haven’t yet made. However, it can complicate life for the developer as well. What happens if the user doesn’t click “OK” but “Cancel” instead? Now you have to change everything back to the way it was — assuming of course that it is possible to get the system back to where it was before. I have seen troubles caused when an external input change caused the device under test (DUT) to change it’s internal state in a way that couldn’t be easily reversed.
The second way of instilling confidence is to have a robust “Cancel” button. By that I mean that pressing “Cancel” should undo any modifications that are pending so no change is made in the application’s state. “But,” you might ask, “what about applications where changes have to be made along the way?” That is a valid point. I have written dialog boxes that served as an interface for editing system setups that, for various reasons, had to be saved as the user went along. The correct solution in those cases is not to have a “Cancel” button. Instead have one labelled, “Done”. The logical implication now goes from, “I am abandoning all my changes” to, “I am finished making changes”.
Before we move on, consider that this point brings up a larger issue: Always make sure the buttons are labelled for what they do. In many cases, buttons relate to verbs, things you want to do. However, in others, they may answer a question, like, “Are you sure you want to quit?” In that situation, Buttons labelled “Yes” and “No” are appropriate, “OK” and “Cancel” are not.
Provides Validated Output
A common use of dialog boxes is to enter configuration data. However, this sort of usage means that it is possible for the user to enter something that is incorrect. There are a number of ways to address this issue, many of which are just good user interface design; but some do involve the logic of the dialog box itself. For example, you should always design data-entry dialog boxes such that they don’t let the user click the “OK” button until all the data they have input has been validated.
Sometimes this validation procedure is simple, other times it will be complex with many interactions between various input values. Regardless of how simple or complex, this validation is worth the effort because working proactively to prevent an error is always easier than dealing with the error after the fact.
Our First Dialog
So with these points in mind, let’s get back to the issue that we ended with last time: How can we verify the user’s desire to shut down the application without blocking the execution of the code?
Well, the first thing we need to do is make a decision. To wit: Should the dialog box be a reusable VI that could be used anywhere a two-button dialog box is needed? Or should it be a custom design that is only used in this specific place in the code? To tell the truth, there are good arguments to be made on both sides. One line of reasoning says that using a common dialog simplifies the process of creating a professional, standardized interface. Plus, the code is reusable. The other school of thought asserts that quitting the application is a major operation and that, while you want the verification dialog to be of a similar style, you do want it to be different enough to reduce the likelihood that the user will inadvertently hit the wrong button and quit the application.
I typically prefer the second approach, so that is the one that we will take – to start with, at least. Here is the block diagram of the dialog box VI itself.
You’ll first note that the diagram is missing one thing that you typically see wrapped around an event structure: a While loop. If you think about it, however, this omission makes sense. When you get right down to it, the whole purpose of this code is to determine whether to fire the
Stop Application UDE to shut down the application, or bypass the event and let execution continue. For this reason the VI’s front panel only has two buttons (one for each potential outcome) and clicking either one will stop the execution of the VI and close its front panel. The event handler shown deals with the case where the user affirms their choice to quit, it contains the event generator for the
Stop Application UDE. A second event handler (which is empty) is for bypassing the shutdown.
However, before the event structure is reached, the code first registers to receive the
Stop Application event. The justification for this added code is that there is actually a third situation that the VI needs to handle. What if while this dialog box is open, Windows starts to shut down? Many devices like uninterruptible power supplies have the ability to shut down Windows automatically when needed. You don’t want the shutdown process to hang-up while the user contemplates the dialog box. By registering to receive the
Stop Application event, the application will shutdown even if the dialog is open. The event handler for the UDE is empty like the one for the “No” button.
The front panel for the dialog box is as uncomplicated as the block diagram:
The only non-visible settings are in the VI Properties where I used the predefined
Dialog setting for the VI appearance, and used the Run-time Position properties to center the VI’s front panel in the primary monitor.
Launching the Dialog
Now that we have a dialog box that will happily run as a separate process, we need a way to get the VI loaded into memory and launched. There are two basic techniques, each with their own pros and cons. In a future post, we’ll compare and contrast the two options, but for now we’ll just use asynchronous call-and-forget, since it is very easy.
The code starts with a static reference to the dialog box VI. This reference node loads the VI into memory, but does not reserve it for execution. The subsequent property node uses the reference to get the VI’s name, and the name drives the
Open VI Reference node. The input to which the name is attached normally expects a complete path to the VI but if the VI is already in memory, the name is all that it needs — and the static reference holds the VI in memory. The 0x80 option input value tells LabVIEW that the asynchronous run will be using the call-and-forget method so it doesn’t have to wait for execution to finish before going on. Finally, the
Start Asynchronous Call starts the dialog box running. One last screen shot shows the display process modified to use the non-blocking dialog box. You will note that I also modified the logic for handling the
Stop button event in the same manner.
When you run this version of the code, you’ll notice that when the dialog box opens, the display continues updating. The modified project is available at:
That’s enough for now. When we again get together, I’ll expand on this basic dialog box structure to show how to qualify input in a more complex dialog.
Until next time…