Having discussed some of the main issues related to incorporating modularity into your application’s GUI, we are now ready to look at how to tweak what we have already built into an application that incorporates those ideas. From the user’s standpoint, the program will operate very much as it did before, but with no data transfer hassles: there’s not any data transfers!
Let’s See Them Work!
To see how subpanels work we will modify our existing testbed application. However, these modifications will only require minor changes to the application. Remember, it is not an accident that this sort of modification can be accomplished without a major rework of the code. This sort of adaptability results from a conscious decision to use techniques and methods that are inherently adaptable. Always be thinking ahead…
And if you do think about what we are doing here you realize the required modifications will be minor in scope because there really isn’t very much in the code that fundamentally cares how the interface is structured. In fact, there are only two places that will need to be changed. The GUI needs to know what to do when a different screen is selected — this logic needs to change. Likewise, the acquisition processes (which previously ran unseen in the background) now need to be prettier, but on the whole they will be doing less than they did before.
Making Background Task Presentable
So let’s start by turning the background tasks into something we would want the world to see on our application’s main interface. It only takes a glance to notice that the existing graphs are too small, so the first step in the transformation is to resize the graphs on our three data sources and make them all the same size as the existing graph in the GUI. In addition, because all three processes will be going into the same subpanel, we also need to make sure the front panels of the three acquisition processes are all the same size: slightly larger than the graph. While we are doing cosmetic things, we also want to make sure that the front panels are the same color as the main GUI.
The functional modification needed to make these VIs live happily in a subpanel is to go into their VI properties and in the
Window Appearance section, turn off both scrollbars.
We now turn to the code changes. In order to put a VI into a subpanel you write a reference to the VI to a subpanel control method called
Insert VI. The thing is, retaining references to background processes that you launch can get tricky. This point is especially true when you need to launch multiple instances of a reentrant VI. Although the testbed isn’t (currently) using reentrant processes, the software does support it so we need to think about how to get references to the processes that will be going into the subpanel. One simple way to accomplish this goal is to have the processes “publish” a reference to themselves that can be used elsewhere in the code. To implement this approach, I created a buffer that has two public interface VIs. One appends a reference to the buffer contents and one reads the buffer contents. Note that we won’t take the time to look at the code for these functions here because we have examined this structure before.
To use these VIs, we put an instance of the insert VI at the very beginning of the call chain in each of the acquisition processes. Like so:
The only other changes we need to make to the acquisition logic both relate to the timeout value for the loop. We no longer need the acquisition to wait before starting so a single keystroke will change the “-1” timeout in the initialization logic back to “1”.
Likewise, we don’t need the
Change Source event right now, but we may need it again (soon) so we’ll leave the event handler in place, but modify the code so it doesn’t do anything.
…and that is all we have to do to the acquisition processes.
Adding the Subpanel to the GUI
With the acquisition processes modified, all we have to left to do is change the front panel control on the GUI, and alter what happens when you make a
Data Source selection.
Changing the front panel control consists of simply removing the graph and replacing it with a subpanel. One thing you need to think about is the size that you are going to make the subpanel. I have found through experience that you get a better looking result if you make the subpanel a skosh bigger than the front panel of the VIs that you are putting in it. For the purposes of this discussion a “skosh” is an empirically determined constant equal to 4 pixels, so make the subpanel 4 pixels taller and wider than the acquisition process front panels.
You’ll note that when you place a subpanel on a front panel, LabVIEW also installs an invoke node for it on the block diagram, we’ll use that in a little bit. The only other thing you need to do that is related to the graph is remove the VIs implementing the update UDE, and delete the associated event handler.
Note: Normally I would put a screenshot here illustrating the change, but in this case I can’t think of a good way of showing something which is no longer present, and isn’t being replaced by something…
Data Source value change event, this is where we get to use the invoke node that LabVIEW created when you instantiated the subpanel. Remove the VI for firing the UDE, and wire in the invoke node in its place. As you can see the node has one input that is a reference to the VI that is to appear in the subpanel. Now, our technique for getting that reference is remarkably similar to what we did to get the data value for the UDE. The difference is that instead of selecting one element from an array of strings, the same array index node is now selecting one element from an array of VI references — references that come from the read VI for the reference buffer we discussed earlier.
Running the Result
If you run the application that we have created, you will notice that (with the exception of a couple points) the operation is very similar to the way it worked in the last release. As you make selections, you can observe that the data displayed still changes as it did before. But if you watch carefully, you will notice that the plugins continue to acquire data even when you aren’t watching them. Moreover, if you go back to a screen after having not looked at it for few seconds, you will notice that it will initially show the previous data and them update all the “missing” datapoints at once. This might seem strange, but it is the side effect of a very good thing that LabVIEW does for you.
Knowing that updating screens that aren’t currently visible can use a lot of computer resources unnecessarily (especially if they have charts on them), LabVIEW keeps track of front panels that are visible and only takes the time to update the data displays if the screen is visible. The delayed update that you see happening all at once is the result of LabVIEW realizing that the front panel is now visible and sending all the updates to the chart at once. On simple controls and indicators, these updates typically happen so fast that you have to really be looking for them, but I wanted you to see the effect.
Note that in the previous paragraph I did not refer to front panels that are “open”. The thing is, not all screens that are open are visible. For example, VI’s running in the background (like the data acquisition processes used to be) are open but
Hidden, i.e. not visible. Conversely, the front panels of VIs in subpanels are visible, but they aren’t open. In any case, here’s the updated code.
Before we move on to the next thing I want to cover, I’m going to hang-out with subpanels for one more post. Specifically, designing VIs in subpanels to be completely autonomous processes is a very powerful technique, but sometimes a good way to foster reusability is to have the main GUI provide some standardized, reusable resources that the various subpanel plugins can use. To cover that situation, next time I’ll look at ways to manage shared resources like buttons and menus from subpanels.
Until next time…