The EntityTablePanel provides a table view of entities: a searchable, filterable, sortable grid with a toolbar, a popup menu, in-table editing, a summary panel and a status bar — all driven by an underlying EntityTableModel.
1. Configuration
Each panel is configured via a Config instance, supplied to the constructor. Most configuration values also exist as system properties, configuring the default for all table panels in an application — Config.INCLUDE_FILTERS, Config.INCLUDE_EXPORT and company — with the per-panel configuration overriding the default.
public InvoiceTablePanel(SwingEntityTableModel tableModel) {
super(tableModel, config -> config
// The TOTAL column is updated automatically when invoice lines are updated,
// see InvoiceLineEditModel, so we don't want it to be editable via the popup menu.
.editable(attributes -> attributes.remove(Invoice.TOTAL))
// The factory providing our custom condition panel.
.conditionPanel(new InvoiceConditionPanelFactory(tableModel))
// Start with the SIMPLE condition panel view.
.conditionView(SIMPLE));
}
2. Editing
The table supports editing the selected rows directly, in two ways:
-
Edit value — the Edit popup menu (and SHIFT-INSERT) edits a single attribute for all selected rows, in a dialog. Which attributes are editable this way is controlled via Config.editable(), and Config.editComponent() provides a custom input component for a given attribute.
-
In-cell editing — standard table cell editing, using components provided by Config.cellEditor().
The track panel below configures both, along with custom cell renderers, and starts cell editing on INSERT rather than on typing:
public TrackTablePanel(TrackTableModel tableModel) {
super(tableModel, config -> config
// Custom component for editing track ratings
.editComponent(Track.RATING, new RatingEditComponent())
// Custom component for editing track durations
.editComponent(Track.MILLISECONDS, new DurationEditComponent())
// Custom cell renderer for ratings
.cellRenderer(Track.RATING, TrackTablePanel::ratingRenderer)
// Custom cell renderer for track duration (min:sec)
.cellRenderer(Track.MILLISECONDS, TrackTablePanel::durationRenderer)
// Custom cell editor for track ratings
.cellEditor(Track.RATING, ratingEditor(tableModel.entityDefinition()))
// Custom cell editor for track durations (min:sec:ms)
.cellEditor(Track.MILLISECONDS, durationEditor())
// Start editing when the INSERT key is pressed
.table(table -> table
.autoStartsEdit(false)
.startEditing(keyStroke(VK_INSERT)))
.includeLimitMenu(true));
A custom edit component for a foreign key, here a track selector used when editing the track of the selected invoice lines:
.editComponent(InvoiceLine.TRACK_FK, new TrackEditComponent(InvoiceLine.TRACK_FK)));
3. Custom controls
The panel’s controls — refresh, add, edit, delete, print and the rest — are identified by ControlKeys. Overriding setupControls() is the idiomatic place to assign custom controls to standard keys; a control assigned to a standard key appears wherever that key is used — popup menu, toolbar, keyboard shortcut.
@Override
protected void setupControls() {
// Assign a custom report action to the standard PRINT control,
// which is then made available in the popup menu and on the toolbar
control(PRINT).set(Control.builder()
.command(this::viewCustomerReport)
.caption(BUNDLE.getString("customer_report"))
.icon(FrameworkIcons.instance().print())
.enabled(tableModel().selection().empty().not())
.build());
}
The popup menu layout itself is configurable via configurePopupMenu() — here a custom control is placed at the top, above the default menu:
// Add a custom control to the top of the table popup menu.
// Start by clearing the popup menu layout
configurePopupMenu(layout -> layout.clear()
// add our custom control
.control(Control.builder()
.command(this::raisePriceOfSelected)
.caption(BUNDLE.getString("raise_price") + "...")
.enabled(tableModel().selection().empty().not()))
// and a separator
.separator()
// and add all the default controls
.defaults());
}
3.1. Adding a print action
The most common custom control is an action for printing reports or acting on the selected rows. For the simplest case, where a single print action is required, a custom control can be associated with the PRINT ControlKey, appearing in the Print submenu in the table popup menu as well as on the table toolbar. For more complex cases, where multiple print controls are required, custom controls can be associated with the PRINT_CONTROLS ControlKey.
public class CustomerTablePanel extends EntityTablePanel {
public CustomerTablePanel(SwingEntityTableModel tableModel) {
super(tableModel);
// associate a custom Control with the PRINT control key,
// which calls the viewCustomerReport method in this class,
// enabled only when the selection is not empty
control(PRINT).set(Control.builder()
.command(this::viewCustomerReport)
.caption("Customer report")
.icon(FrameworkIcons.instance().print())
.enabled(tableModel().selection().empty().not())
.build());
}
private void viewCustomerReport() {
List<Entity> selectedCustomers = tableModel().selection().items().get();
if (selectedCustomers.isEmpty()) {
return;
}
Collection<String> customerIds = Entity.values(Customer.ID, selectedCustomers);
Map<String, Object> reportParameters = new HashMap<>();
reportParameters.put("CUSTOMER_IDS", customerIds);
JasperPrint customerReport = tableModel().connection()
.report(Customer.REPORT, reportParameters);
Dialogs.builder()
.component(new JRViewer(customerReport))
.owner(this)
.modal(false)
.title("Customer Report")
.size(new Dimension(800, 600))
.show();
}
}
4. Condition and filter panels
The panel above the table provides query conditions — what is fetched from the database — while filters narrow down what is shown, without a round trip. The condition panel has three views: HIDDEN, SIMPLE (a single row of fields) and ADVANCED (operators and bounds per column), toggled via the toolbar or CTRL-ALT-S, with CTRL-S moving focus to a condition field. The initial view and a custom condition panel implementation are configurable — see the conditionPanel() and conditionView() calls in the invoice panel configuration above, where a custom condition panel provides invoice-specific search fields.
Filter panels are excluded by default and included via Config.INCLUDE_FILTERS (globally) or includeFilters() (per panel).
5. Exporting data
A configurable denormalized data export tool — including attributes of referenced entities via foreign key traversal — can be included in the EntityTablePanel Copy table popup submenu, via the EntityTablePanel.Config.INCLUDE_EXPORT configuration value or the panel configuration. See Exporting data.
6. Query Inspector
A Query Inspector can be enabled globally via the EntityTablePanel.Config.INCLUDE_INSPECTOR configuration value or for a single panel via the panel configuration.
The Query Inspector displays the SELECT query, dynamically updated according to the underlying query conditions.
EntityTablePanel.Config.INCLUDE_INSPECTOR.set(true);
The Query Inspector can be opened using the CTRL-ALT-Q keyboard shortcut, when the table panel is focused.
7. Keyboard shortcuts
Each ControlKey carries its default keystroke — INSERT adds a new row, CTRL-INSERT edits the selected row, SHIFT-INSERT edits a single value for the selection, DELETE deletes the selection. A default keystroke can be modified before the panels are created, typically during application startup — here a CTRL modifier is added to the DELETE shortcut, application-wide:
// Add a CTRL modifier to the DELETE key shortcut for table panels
EntityTablePanel.ControlKeys.DELETE.defaultKeystroke().update(keyStroke ->
keyStroke(keyStroke.getKeyCode(), MENU_SHORTCUT_MASK));