Difference between revisions of "Java: Custom Widgets"

From Deep Blue Robotics Wiki
Jump to: navigation, search
(Abstract Table Widgets)
(Static Widgets)
Line 28: Line 28:
  
  
Static widgets do not rely on an incoming value, so they can be added to the SmartDashboard at any time. To create a static widget, extend the StaticWidget class. Otherwise, they can be written the same way as basic widgets, except that they do not contain a setValue method or a TYPES constant.
+
Static widgets do not rely on an incoming value, so they can be added to the SmartDashboard at any time. To create a static widget, extend the <span style="color:#20AB7E">StaticWidget</span> class. Otherwise, they can be written the same way as basic widgets, except that they do not contain a setValue method or a <span style="color:#20AB7E">TYPES</span> constant.
 
+
  
 
== Abstract Table Widgets ==
 
== Abstract Table Widgets ==

Revision as of 22:10, 13 October 2016

Custom Widgets

SmartDashboard widgets are all subclasses of JComponent, which means that it is very easy to write our own customized widgets. If you have not yet learned how to perform custom painting, please do so before reading this lesson. A copy of the SmartDashboard and Widget source code can be found here. There are also some useful tools for debugging SmartDashboard in the Debugging Tools section.

Creating a Widget Project

As of right now, widgets do not work with Eclipse, so use Netbeans instead. To begin, create a regular Java project. Next, you must add the SmartDashboard JAR file to the libraries used by the project so that your code can refer to the SmartDashboard classes. To add the JAR file: Right click the project in Netbeans and click properties Select libraries and go to the compile tab Press the Add JAR/Folder button Navigate to the SmartDashboard JAR file (probably C:\Users\Student\wpilib\tools\SmartDashboard.jar) Press the OK button.

Basic Widgets

A basic widget is a widget that uses a value that has already been added to the SmartDashboard. To create a basic widget, simply have a java class extend Widget. The IDE will then prompt you to import edu.wpi.first.smartdashboard.gui.Widget and implement the abstract methods of the widget class. There are 3 of these methods: setValue, init, and propertyChanged. There are also two public static final constants that you should declare: a String NAME and a DataType[] TYPES. NAME is the name of the widget that will show up in SmartDashboard. TYPES is an array of the different data types that the widget accepts. Possible data types include DataType.NUMBER, DataType.STRING, DataType.BOOLEAN, DataType.BASIC, and DataType.TABLE. The init method is called once when the widget is added to the SmartDashboard. It should be used for any initialization code. This method is also a good place to set the preferred size of the widget, e.g. setPreferredSize(new Dimension(width, height)); The setValue method is called every time the value of the widget changes (i.e. the robot code called SmartDashboard.putSomething). Use this method to update the appearance of the widget. The propertyChanged method is called when the user changes one of the widget's properties. See the section on Widget Properties below. Another method that you have the option of overriding is the paint method (i.e. public void paint(Graphics g)). In this method you can call methods of the Graphics object to create customized graphics for your widget. For a tutorial on using Java graphics, click here.


Static Widgets

Static widgets do not rely on an incoming value, so they can be added to the SmartDashboard at any time. To create a static widget, extend the StaticWidget class. Otherwise, they can be written the same way as basic widgets, except that they do not contain a setValue method or a TYPES constant.

Abstract Table Widgets

An abstract table widget displays multiple SmartDashboard values in a table. To create an abstract table widget, extend the AbstractTableWidget class. Abstract table widgets have the same methods as static widgets, but they also allow you to create AbstractTableWidget fields. These include AbstractTableWidget.NumberField, AbstractTableWidget.BooleanField, and AbstractTableWidget.StringField, among others. Once created, these fields can be added to the widget, and they will automatically update their values. Abstract table widgets have NAME and TYPES constants just like basic widgets, but you need to create your own data type for the widget to use. The code for a custom data type looks like this:

public class MyCustomDataType extends NamedDataType {
    public static final String LABEL = "Name of the data type";
    private MyCustomDataType () {
        super(LABEL, MyCustomAbstractTableWidget.class);
    }
    public static NamedDataType get() {
        if (NamedDataType.get(LABEL) != null) {
            return NamedDataType.get(LABEL);
        } else{
            return new MyCustomDataType();
        }
    }
}

Building the Widget JAR File

After the widget code is finished, clean and build the widget project. This should generate a JAR file for the widget in the project's dist directory. Copy the JAR file, then navigate to the SmartDashboard/extensions folder. If the folder does not already exist you may need to create it. Finally, paste the JAR file into the extensions folder. If SmartDashboard is already open, you must close and reopen the program before the widgets will work.


Using Network Tables

All SmartDashboard key-value pairs are stored in NetworkTables. To access NetworkTables in your code, call the static method NetworkTable.getTable(String name). This method returns an ITable object with the given name. SmartDashboard values are all located in the "SmartDashboard" table, or you can store values in your own custom table. To read and write from a table, use the same methods as when using SmartDashboard in robot code (e.g. putNumber, getNumber, etc.). To listen to changes in a table or determine what information is stored in a table without already knowing the keys, you can use the addTableListener method (see here for a tutorial on EventListeners). This method has two parameters: an ITableListener and a boolean. If the boolean is true, then the listener will respond to all events, even if they occurred before the listener was added. ITableListener is abstract, so you must create your own subclass of it, and override the valueChanged method. This method is called whenever a value is changed in the table. It has parameters for the table that was changed, the key and value for the changed data, and whether the value was added for the first time. The easiest way to write the code is to use an anonymous inner class, like this:

ITable myCustomTable = NetworkTable.getTable("Name of the table");
myCustomTable.addTableListener(new ITableListener() {
    @Override
    public void valueChanged(ITable itable, String key, Object value, boolean isNew) {
        // Put your code here
    }
}, true);

Widget Properties

Properties allow users to change settings for your widget without rebuilding the jar. To give your widget a property, declare a public StringProperty field and assign a new StringProperty object to it. The StringProperty constructor takes the widget that it belongs to (i.e. this), the name of the property, and the property's initial value. When the property is modified, the propertyChanged method will be called with the modified property as the parameter.

Tips

  • Use the setPreferredSize method to set the dimensions of the widget instead of setSize
  • The widget may be transparent by default; call setOpaque(true) to make the widget opaque
  • To access the robot preferences table, call Robot.getPreferences()
  • To save robot preferences, call putBoolean(Robot.PREF_SAVE_FIELD, true)
  • Layout managers automatically organize your widget (use the setLayout method to choose the layout)
    • BorderLayout is useful for fitting a component to its container
    • CardLayout is useful for switching between different components in the same location
    • GridLayout is useful for organizing components into a grid pattern
    • Flow layout is useful for setting the top-level layout of a widget
    • Setting the layout to null allows you to manually specify all sizes and locations
  • All widgets are JComponents, so using existing widgets like LinePlots in your widget is easy
    • Create an instance of the widget and use the add method to add it to your widget
    • Manually call the widget's initialize method (you may need to call setFieldName first)
    • Depending on your layout you may need to call setPreferredSize to set the dimensions
    • The widget will not automatically communicate with network tables, so you need to create your own event listeners
    • Creating an anonymous nested widget subclass will cause an error because SmartDashboard cannot find a static TYPES variable
    • The easiest way to reset a LinePlot widget is to create a new one and replace the original
// Example of creating a graph with a reset button
LinePlot graph = new LinePlot();
graph.setPreferredSize(new Dimension(width, height);
graph.setFieldName(name);
graph.init();
panel.add(graph);
table.addTableListener((ITable t, String key, Object value, boolean b) -> {
    if(key.equals(name)) graph.setValue(value);
});
resetButton.addActionListener((ActionEvent e) -> {
    panel.remove(graph);
    graph = new LinePlot();
    graph.setPreferredSize(new Dimension(width, height);
    graph.setFieldName(name);
    graph.init();
    panel.add(graph);
    panel.revalidate();
    panel.repaint();
});