Skip to content

GUI Basics

William Wood edited this page Jan 11, 2019 · 22 revisions

GUI Basics

This page is to give you an overview of how to build a simple GUI to observe the progress, and potentially control your experiment.

In general, GUIs can be fairly complicated to make from scratch. Java provides a rather good set of libraries for creating GUIs in the form of "JavaFX". There will be a tutorial on how to create your own GUI from scratch using JavaFX later, for those interested.

However, if you're not feeling up to that, you can build up a basic GUI using JISA. This revolves around using pre-defined "blocks" that you can then stick together in a tiled formation, or have them appear in individual windows. These blocks mostly centre around displaying the contents of a ResultList object so if you haven't done so already, take a look at the Result Handling Basics page.

This page serves as a tutorial for using these pre-defined GUI blocks.

Note that data shown in these examples is all completely ficticious, mostly radomly generated since I didn't have any instruments connected to perform actual measurements.

Contents

  1. Set-Up
  2. Tables
  3. Plots
  4. Progress Bars
  5. Grid Containers
  6. Input Fields
  7. Tabbed Containers
  8. Dialogue Boxes

Set-Up

If you want to use GUI elements in your application you will need to first make a small change to you Main class. Specifically you will need to make it extend GUI, so that it now looks like:

public class Main extends GUI {

  public static void run() throws Exception {

  }

  public static void main() {

    try {
      run();
    } catch (Exception e) {
      Util.exceptionHandler(e);
    }

  }

}

The reason for this that GUIs operate on a different set of principles to command-line driven programs. They have to keep running waiting for the user to do something (like click a button) and not just quit when it has reached the end of whatever code it is currently running. By extending GUI we ensure that the program keeps running so long as there is a window open.

Alternatively you can start and stop the GUI thread manually like so:

public class Main {

  public static void run() throws Exception {

    GUI.startGUI();
    
    // Code goes here

    GUI.stopGUI();

  }

  public static void main() {

    try {
      run();
    } catch (Exception e) {
      Util.exceptionHandler(e);
  

this way, the GUI thread will keep going until GUI.stopGUI() is called. Trying to use any GUI elements before GUI.startGUI() is called or after GUI.stopGUI() will cause an exception.

If you wish to use these GUI elements in Python, then the manual start and stop methods are your only choice.

from JISA.GUI import GUI;

def main():
    GUI.startGUI();

    GUI.stopGUI();

main();

Table

The simplest of these GUI elements is Table. This is a window that will display the contents of a ResultList object in real-time as a table. Let's say we have a ResultList object called list,

ResultList list = new ResultList("Frequency", "Voltage", "Current");
list.setUnits("Hz", "V", "A");

then we can create a Table to display its contents like so:

Table table = new Table("My Results", list);

Now that the Table is created, we can make it show itself by use of show():

table.show();

then to temporarily hide it:

table.hide();

and to permanently close it:

table.close();

The result looks like this:

This table updates automatically every time something new is added to our ResultList, so it gives us a real-time view of our results.

When clear() is called on the ResultList object it is watching, the Table will also clear:

list.clear();

If you want to clear the Table only (ie keep the data in the ResultList):

table.clear();

Plot

Perhaps the coolest of them all is the plot element. This will plot one column of the ResultList against another, displaying updates in real-time with some nifty animations.

Let's assume the same ResultList as before:

ResultList list = new ResultList("Frequency", "Voltage", "Current");
list.setUnits("Hz", "V", "A");

Then if we do

Plot plot = new Plot("My Plot Title", list);
plot.show();

like with Table we will get a plot of the first two columns (ie Frequency on the x-axis and Voltage on the y-axis). To specify different columns you just need to provide their column number. For example, to plot Current against Voltage:

Plot plot = new Plot("My Plot Title", list, 1, 2);

This results in a plot like this:

Alternatively, if you want to plot multiple things, you can create the Plot like so:

Plot plot = new Plot("My Plot Title", "X-Axis Label", "Y-Axis Label");

Then add each data series manually:

plot.watchList(list, 1, 2, "Current", Color.RED);
plot.watchList(list, 1, 0, "Frequency", Color.BLUE);

This will tell the Plot to plot Current vs Voltage in red labelled as "Current" in the legend, and Frequency vs Voltage in blue labelled as "Frequency" in the legend.

The same principles apply to Plot objects as do to Table objects when it comes to clearing:

list.clear(); // Clears the data and the plot
plot.clear(); // Clears the plot only

Progress

In many cases, it is useful to display the current progress of an operation by use of a progress bar. This is easily implemented using JISA by use of the Progress class. Simple create a Progress object and show() it like so:

Progress bar = new Progress("Title Goes Here");
bar.show();

then at any point you can update its value like so:

bar.setProgress( value, maxValue );

You can also change the title at any time by use of:

bar.setTitle("New Title");

and add an extra message to show underneath the bar (ie to tell the user what is currently happening) by use of:

bar.setStatus("Measuing voltage...");

To see this, consider the following code:

Progress bar = new Progress("Current Progress");
bar.setStatus("This is a demo");
bar.setProgress(0,10);
bar.show();

This results in

Code Result
bar.setProgress(1,10);
bar.setProgress(5,10);
bar.setStatus("Half way there!");
bar.setProgress(10,10);
bar.setStatus("All done!");

Grid

If you have multiple GUI elements, it can be annoying to have each in a separate window. To remedy this, JISA provides the Grid class. This will take other GUI elements at display them in a signal window, in a tiled formation. This is done very easily like so:

// Create list to store results
ResultList list = new ResultList("Frequency", "Voltage", "Current");
list.setUnits("Hz", "V", "A");

// Create GUI elements, but don't show() them yet
Table    table = new Table("Results", list);
Plot     plot  = new Plot("Results", list, 1, 2);
Progress prog  = new Progress("Experiment Progress");

// Create a grid, adding the elements in order
Grid grid  = new Grid("Experiment Status", prog, table, plot);

// Show the grid to show all the elements in the grid
grid.show();

The results of which looks like:

A Grid is also a good way of creating a GUI to control your experiment. You can add bottons to the top of the grid in a toolbar by using the addToolbarButton(...) method. Clearly you will need to define what you want the program to do when the button is clicked. This can be done in a couple of different ways. The first is to directly write the code you want to run when clicked like so:

grid.addToolbarButton("Button Text", () -> {
  // Code to run when button is clicked goes here
});

(the () -> {} is called a lambda expression)

Alternatively, if you have a function defined somewhere and you want it to run that when clicked you can simply write its name (including class name) like so:

public class Main extends GUI {

  public static void run() {
    ...
    grid.addToolbarButton("Button Text", Main::doTheThing);
  }

  public static void doTheThing() {
    // The thing is done here
  }

  ...

}

The end result looks like this:

As an example, let's look at how we'd go about making a GUI much like the one above that only causes the experiment to start when the button is pressed:

public class Main extends GUI {

  // Declare variable up here so that it's accessible in all methods
  static ResultList list;
  static Progress   bar;
  static Table      table;
  static Plot       plot;
  static Grid       grid;
  static SR830      lockIn;
  static K2450      smu;

  public static void run() throws Exception {

    lockIn = new SR830(new GPIBAddress(0,30));
    smu    = new K2450(new GPIBAddress(0,15));

    list  = new ResultList("Frequency", "Voltage", "Current");
    list.setUnits("Hz", "V", "A");

    bar   = new Progress("Experiment Progress");
    table = new Table("Results", list);
    plot  = new Plot("Plot of Results", list, 1, 2);
    grid  = new Grid("Experiment Control", bar, table, plot);

    grid.addToolbarButton("Start", Main::startExperiment);
    grid.show();

  }

  public static void startExperiment() throws Exception {

    // Take 10 readings
    for (int i = 0; i < 10; i++) {

      list.addData(
        lockIn.getFrequency(),
        smu.getVoltage(),
        smu.getCurrent()
      );

      bar.setProgress(i+1,10);

    }

  }

  public static void main() {

    try {
      run();
    } catch (Exception e) {
      Util.exceptionHandler(e);
    }

  }

}

Before pressing the button:

After pressing the button:

Fields

It is reasonable to assume that your program might require some user-input. To achieve this, the Fields GUI element can be used. To a Fields element you can add (the types of variable they return are shown in brackets):

  • Text inputs (String)
  • Integer inputs (int)
  • Double inputs (double)
  • File selectors (String)
  • Check boxes (boolean)
  • Drop down / choice boxes (int)

Creating a Fields object is done like this:

// Create the fields object
Fields               inputs = new Fields("Title Goes Here");

// Add the fields
Field<String>  name   = inputs.addTextField("Name");
Field<Integer> age    = inputs.addIntegerField("Age");
Field<Double>  height = inputs.addDoubleField("Height (cm)");
Field<String>  save   = inputs.addFileSave("Write data to");
Field<String>  open   = inputs.addFileOpen("Get data from");

// Show the window
inputs.show();

This results in the following:

Clicking on a "browse" button will present the user with a file save or open dialogue (depending on whether we used addFileSave(...) or addFileOpen(...) respectively). For example, on a Linux (Gnome) system, the "Write data to" one will give us:

You should notice that each time we added a field, we got a Field<...> object in return. The name between the <...> indicates what type of value we expect the field to hold (ie addTextField will hold strings, addIntegerField will hold integers). These are our handle onto these fields and allow us to retrieve what is currently written in their respective field or set the value ourselves.

Field objects provide four methods, the two most important of which are: set(...) and get(). set(...) lets you set the value of whichever input the Field is representing, whereas get() will return the value currently stored in the field.

For example, let's say the user has typed in their info:

We can then retrieve what they have written by use of get():

String userName   = name.get();
int    userAge    = age.get();
double userHeight = height.get();
String savePath   = save.get();
String openPath   = open.get();

which would be the equivalent of:

String userName   = "Bob Smith";
int    userAge    = 34;
double userHeight = 175.4;
String savePath   = "/home/william/Desktop/test.txt";
String openPath   = "/home/william/data.txt";

If needed you can use set() to set the value of the fields too:

name.set("Bary Merry");
age.set(73);
height.set(150.64);
save.set("/home/william/file.txt");
open.set("/usr/share/file.csv");

which will result in:

Field objects also allow you to define what should happen when the contents of an input field is changed, like so:

Field<String> name = inputs.addTextField("Name");
name.setOnChange(() -> {
	System.out.printf("Name changed to: %s\n", name.get());
});

Now, whenever the text in the "Name" text-box is changed, the new value is output to the terminal. For example, if it is empty and we type in "Bob", the terminal output will look like this:

Name changed to: B
Name changed to: Bo
Name changed to: Bob

You can add buttons, like the one in the above example, to the Fields container by use of addButton(...), specifying text for the button and what to do when clicked like so:

input.addButton("Accept", () -> {
    System.out.printf("Name:   %s\n", name.get());
    System.out.printf("Age:    %d\n", age.get());
    System.out.printf("Height: %f\n", height.get());
    System.out.printf("Save:   %s\n", save.get());
    System.out.printf("Open:   %s\n", open.get());
});

or

public class Main {

    static Field<String>  name;
    static Field<Integer> age;
    static Field<Double>  height;
    static Field<String>  save;
    static Field<String>  open;

    public static void main(String[] args) {

        GUI.startGUI();

        // Create the fields object
        Fields inputs = new Fields("Title Goes Here");

        // Add the fields
        name   = inputs.addTextField("Name");
        age    = inputs.addIntegerField("Age");
        height = inputs.addDoubleField("Height (cm)");
        save   = inputs.addFileSave("Write data to");
        open   = inputs.addFileOpen("Get data from");

        inputs.addButton("Accept", Main::onClick);

        // Show the window
        inputs.show();

    }

    public static void onClick() {
        System.out.printf("Name:   %s\n", name.get());
        System.out.printf("Age:    %d\n", age.get());
        System.out.printf("Height: %f\n", height.get());
        System.out.printf("Save:   %s\n", save.get());
        System.out.printf("Open:   %s\n", open.get());
    }

}

Clicking "Accept" will now cause the following to be output to the terminal:

Name:   Bary Merry
Age:    73
Height: 150.640000
Save:   /home/william/file.txt
Open:   /usr/share/file.csv

Just with all the other GUI blocks, you can add a Fields to a Grid to make a cohesive GUI. Below is an example:

public class Main {

    static double     min   = 0;
    static double     max   = 20;
    static int        steps = 10;
    static SMU        smu;
    static ResultList list;

    public static void main(String[] args) throws Exception {

        smu  = new K2450(new GPIBAddress(0, 15));
        list = smu.createSweepList();

        // Start the GUI thread
        GUI.startGUI();

        // Create the GUI blocks
        Fields inputs = new Fields("Experiment Parameters");
        Table  table  = new Table("Table of Results", list);
        Plot   plot   = new Plot("Plot of Results", list);

        // Add the fields to the Fields block
        Field<Double>  minV = inputs.addDoubleField("Min Voltage [V]");
        Field<Double>  maxV = inputs.addDoubleField("Max Voltage [V]");
        Field<Integer> step = inputs.addIntegerField("No. Steps");

        // Set default values
        minV.set(min);
        maxV.set(max);
        step.set(steps);

        // Add button the Fields block
        inputs.addButton("Set", () -> {
            saveParams(minV.get(), maxV.get(), step.get());
        });

        // Create grid out of blocks
        Grid grid = new Grid("Experiment", inputs, table, plot);

        // Add button to grid toolbar
        grid.addToolbarButton("Start", Main::startMeasurements);

        // Show the grid
        grid.show();

    }

    public static void saveParams(double minV, double maxV, int nSteps) {

        min   = minV;
        max   = maxV;
        steps = nSteps;
        GUI.infoAlert("Experiment", "Success", "Parameters have been set.");

    }

    public static void startMeasurements() throws Exception {

        smu.doLinearSweep(
                SMU.Source.VOLTAGE,
                min,
                max,
                steps,
                500,
                false,
                list
        );

    }
}

The result is the following:

Then we can enter new sweep parameters in the left-most block and press "Set" to apply them resulting in the following message:

Then we can press the "Start" button to run the SMU sweep resulting in:

You can add a check-box by use of:

Field<Boolean> tick = inputs.addCheckBox("Name goes here");

To add a drop-down "choice box", use to following:

Field<Integer> choice = inputs.addChoice("Name", "Option 1", "Option 2", etc...);

When you use choice.get(), it will return an integer representing which option is selected (ie 0 for "Option 1", 1 for "Option 2" etc).

Additionally for choices, you can use the editValues() method to change what options are available:

choice.editValues("Option A", "Option B", "Option C");

The above example will change our choice box to now offer the choices of "Option A", "Option B" and "Option C" instead of whatever the options were before.

Tabs

You may find that you will want multiple GUI elements grouped into separate sections. This is where the Tabs element comes into play. Much like Grid, it takes other GUI elements (including Grid elements) and sorts them into tabs. Each element you add will appear in its own tab bearing the title it was given when created.

For example, let's say we have two Grid elements, the first holding a Table and Plot for displaying results and the second for configuring the experiment.

ResultTable list = new ResultList("Voltage", "Current");
list.setUnits("V", "A");

Table table   = new Table("Table of Results", list);
Plot  plot    = new Plot("Plot of Results", list);

Fields params = new Fields("Parameters");
Fields output = new Fields("Output");

Field<Double>  minVoltage = params.addDoubleField("Min Voltage [V]");
Field<Double>  maxVoltage = params.addDoubleField("Max Voltage [V]");
Field<Integer> numVoltage = params.addIntegerField("No. Steps");
Field<String>  outputFile = output.addFileSave("Output File");


Grid results = new Grid("Results", table, plot);
Grid config  = new Grid("Configuration", params, output);

Then we can link both of these grids together into the same window, each as a separate tab, like so:

Tabs window = new Tabs("My Experiment", results, config);
window.show();

or

Tabs window = new Tabs("My Experiment");
window.addTab(results);
window.addTab(config);
window.show();

which will result in the following:

Dialogue Boxes

Fancy making your application work like a mid-2000s website with endless pop-ups? Well then, look no further! You can make a variety of different dialog alert boxes pop-up very simply by use of the GUI class like so:

// Error message
GUI.errorAlert("Window Title", "Message Title", "Message Content");

// Information message
GUI.infoAlert("Window Title", "Message Title", "Message Content");

// Warning message
GUI.warningAlert("Window Title", "Message Title", "Message Content");

Each one of these will halt your code until the user presses "Okay".

You can also ask for confirmation with confirmWindow(...) like so:

boolean result = GUI.confirmWindow("Window Title", "Message Title", "Message Content");

if (result) {
    // User pressed "Okay"
} else {
    // User pressed "Cancel"
}
Method Result
GUI.errorAlert(...);
GUI.infoAlert(...);
GUI.warningAlert(...);
GUI.confirmWindow(...);

You can also use the GUI class to create a browser window to select a VISA instrument like so:

InstrumentAddress address = GUI.browseVISA();

This will create a window that looks like:

Clicking on one of the Select > buttons will return the VISA address object of that instrument. Clicking cancel will cause it to return null

Clone this wiki locally