How to Implement Dynamic Gui in Swing

How to implement dynamic GUI in swing

only example, everything is hardcoded, for good understanding

EDIT:

as kleopatra's noticed, moved JTable#fireTableDataChanged() from ActionListener to the TableModel, amended all ClassNames start with lowerCase

import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.table.*;

public class ComponentTableTest {

private JFrame frame;
private JTable CompTable = null;
private CompTableModel CompModel = null;
private JButton addButton = null;

public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {

@Override
public void run() {
new ComponentTableTest().makeUI();
}
});
}

public void makeUI() {
CompTable = CreateCompTable();
JScrollPane CompTableScrollpane = new JScrollPane(CompTable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JPanel bottomPanel = CreateBottomPanel();
frame = new JFrame("Comp Table Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(CompTableScrollpane, BorderLayout.CENTER);
frame.add(bottomPanel, BorderLayout.SOUTH);
frame.setPreferredSize(new Dimension(800, 400));
frame.setLocation(150, 150);
frame.pack();
frame.setVisible(true);
}

public JTable CreateCompTable() {
CompModel = new CompTableModel();
CompModel.addRow();
JTable table = new JTable(CompModel);
table.setRowHeight(new CompCellPanel().getPreferredSize().height);
table.setTableHeader(null);
CompCellEditorRenderer compCellEditorRenderer = new CompCellEditorRenderer();
table.setDefaultRenderer(Object.class, compCellEditorRenderer);
table.setDefaultEditor(Object.class, compCellEditorRenderer);
return table;
}

public JPanel CreateBottomPanel() {
addButton = new JButton("Add Comp");
addButton.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();

if (source == addButton) {
CompModel.addRow();
//CompModel.fireTableDataChanged(); // moved to TableModel
}
}
});
JPanel panel = new JPanel(new GridBagLayout());
panel.add(addButton);
return panel;
}
}

class CompCellEditorRenderer extends AbstractCellEditor implements TableCellRenderer, TableCellEditor {

private static final long serialVersionUID = 1L;
private CompCellPanel renderer = new CompCellPanel();
private CompCellPanel editor = new CompCellPanel();

@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
renderer.setComp((Comp) value);
return renderer;
}

@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
editor.setComp((Comp) value);
return editor;
}

@Override
public Object getCellEditorValue() {
return editor.getComp();
}

@Override
public boolean isCellEditable(EventObject anEvent) {
return true;
}

@Override
public boolean shouldSelectCell(EventObject anEvent) {
return false;
}
}

class CompTableModel extends DefaultTableModel {

private static final long serialVersionUID = 1L;

@Override
public int getColumnCount() {
return 1;
}

public void addRow() {
super.addRow(new Object[]{new Comp(0, 0, "", "")});
//super.fireTableDataChanged();
}
}

class Comp {

int type;
int relation;
String lower;
String upper;

public Comp(int type, int relation, String lower, String upper) {
this.type = type;
this.relation = relation;
this.lower = lower;
this.upper = upper;
}
}

class CompCellPanel extends JPanel {

private static final long serialVersionUID = 1L;
private JLabel labelWith = new JLabel("With ");
private JComboBox typeCombo = new JComboBox(new Object[]{"height", "length", "volume"});
private JComboBox relationCombo = new JComboBox(new Object[]{"above", "below", "between"});
private JTextField lowerField = new JTextField();
private JLabel labelAnd = new JLabel(" and ");
private JTextField upperField = new JTextField();
private JButton removeButton = new JButton("remove");

CompCellPanel() {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
relationCombo.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
enableUpper(relationCombo.getSelectedIndex() == 2);
}
});
enableUpper(false);
removeButton.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
JTable table = (JTable) SwingUtilities.getAncestorOfClass(JTable.class, (Component) e.getSource());
int row = table.getEditingRow();
table.getCellEditor().stopCellEditing();
((DefaultTableModel) table.getModel()).removeRow(row);
}
});
add(labelWith);
add(typeCombo);
add(relationCombo);
add(lowerField);
add(labelAnd);
add(upperField);
add(Box.createHorizontalStrut(100));
add(removeButton);
}

private void enableUpper(boolean enable) {
labelAnd.setEnabled(enable);
upperField.setEnabled(enable);
}

public void setComp(Comp Comp) {
typeCombo.setSelectedIndex(Comp.type);
relationCombo.setSelectedIndex(Comp.relation);
lowerField.setText(Comp.lower);
upperField.setText(Comp.upper);
enableUpper(Comp.relation == 2);
}

public Comp getComp() {
return new Comp(typeCombo.getSelectedIndex(), relationCombo.getSelectedIndex(), lowerField.getText(), upperField.getText());
}
}

Java Swing: How to change GUI dynamically

For reference, here's an sscce that shows the essential method, validate(). This more elaborate example shows both requirements: it changes the layout and adds components dynamically.

import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;

/** @see http://stackoverflow.com/questions/5750068 */
public class DynamicLayout extends JPanel {

private static final LayoutManager H = new GridLayout(1, 0);
private static final LayoutManager V = new GridLayout(0, 1);

public DynamicLayout() {
this.setLayout(H);
this.setPreferredSize(new Dimension(320, 240));
for (int i = 0; i < 3; i++) {
this.add(new JLabel("Label " + String.valueOf(i), JLabel.CENTER));
}
}

private void display() {
JFrame f = new JFrame("DynamicLayout");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
JPanel p = new JPanel();
p.add(new JButton(new AbstractAction("Horizontal") {

@Override
public void actionPerformed(ActionEvent e) {
DynamicLayout.this.setLayout(H);
DynamicLayout.this.validate();
}
}));
p.add(new JButton(new AbstractAction("Vertical") {

@Override
public void actionPerformed(ActionEvent e) {
DynamicLayout.this.setLayout(V);
DynamicLayout.this.validate();
}
}));
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
new DynamicLayout().display();
}
});
}
}

java - How would I dynamically add swing component to gui on click?

Sample code to add Buttons on the fly dynamically.

panel.add(new JButton("Button"));
validate();

Full code:

import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JPanel;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.awt.FlowLayout;
import java.awt.BorderLayout;

public class AddComponentOnJFrameAtRuntime extends JFrame implements ActionListener {

JPanel panel;

public AddComponentOnJFrameAtRuntime() {
super("Add component on JFrame at runtime");
setLayout(new BorderLayout());
this.panel = new JPanel();
this.panel.setLayout(new FlowLayout());
add(panel, BorderLayout.CENTER);
JButton button = new JButton("CLICK HERE");
add(button, BorderLayout.SOUTH);
button.addActionListener(this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 500);
setVisible(true);
}

public void actionPerformed(ActionEvent evt) {
this.panel.add(new JButton("Button"));
this.panel.revalidate();
validate();
}

public static void main(String[] args) {
AddComponentOnJFrameAtRuntime acojfar = new AddComponentOnJFrameAtRuntime();
}
}
  • Resource

Creating GUI components dynamically in java

Create a variable for your JButton:

JButton jButton = new JButton("Button");
panel.add(jButton);
validate();
/*
*
*
100 lines of code
*
*/

// add an event listener
jButton.addActionListener((ActionEvent) -> {
// do something
});

Java Swing GUI creation - How to get dynamically created buttons to update properly

Again, the issue is creating a model and somehow tying it to your view (the GUI). Best is to create and maintain good separation between these two classes or groups of classes, but to keep things simpler in this instance, we can combine them in the GUI. To show you what I mean, consider this simplification of your code, one that uses very simple Car and ParkingSlot classes:

public class Car {
private String name;

public Car(String name) {
this.name = name;
}

public String getName() {
return name;
}

@Override
public String toString() {
return "Car: " + name;
}
}

public class ParkingSlot {
private Car car;
private String name;

public ParkingSlot(String name) {
this.name = name;
}

public void setCar(Car car) {
this.car = car;
}

public Car getCar() {
return car;
}

public void clear() {
car = null;
}

public String getName() {
return name;
}

@Override
public String toString() {
String value = "Slot: " + name + "; ";
if (car == null) {
value += "empty";
} else {
value += car.toString();
}
return value;
}
}

I want a model that ties a String to a ParkingSlot, and for this, best is to use a Map<String, ParkingSlot>. This is a collection that holds ParkingSlot objects and makes them accessable by the slot's "name", the String. I would often initialize this as a new HashMap<String, ParkingSlot>(), but I also want to maintain insertion order in the Map so that if I iterate through it, it is not in some random order (as HashMaps tend to be), and so, I'd initialize this using a LinkedHashMap<String, ParkingSlot>, since this is just like a HashMap but retains insertion order:

private Map<String, ParkingSlot> slotMap = new LinkedHashMap<>();

And so, if later I want to iterate through the collection, display the status of the ParkingSlot objects, I could simply use a for loop:

for (Entry<String, ParkingSlot> entry : slotMap.entrySet()) {
System.out.printf("%s%n", entry.getValue());
}

So, now when creating your JButton grid, associate it with the collection of ParkingSlot objects, using the String that defines the slot. This can be the key for the Map and also can be associated with the JButton by using it to set the button's actionCommand, a String that every button carries with it:

JPanel gridPanel = new JPanel(new GridLayout(0, 1));
for (int i = 0; i < slotCount; i++) {
String slotName = String.format("V%03d", i);
ParkingSlot slot = new ParkingSlot(slotName);
slotMap.put(slotName, slot);

JButton button = new JButton(slotName + ": Empty");
button.setActionCommand(slotName);
button.addActionListener(e -> parkingSlotButtonAction(e));
gridPanel.add(button);
}

When any of the buttons are pressed, the parkingSlotButtonAction(ActionEvent e) method is called, and it can get the source JButton by calling e.getSource() on the parameter, and can get the button's actionCommand by calling e.getActionCommand() on the same parameter. It can get the associated ParkingSlot object by using the slotMap:

private void parkingSlotButtonAction(ActionEvent e) {
String slotName = e.getActionCommand();
JButton source = (JButton) e.getSource();
ParkingSlot slot = slotMap.get(slotName);

// .....
}

A complete simple example could look like this:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.*;
import javax.swing.event.ChangeListener;

public class SimpleLotMain {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
LotGui gui = new LotGui(20);

JFrame frame = new JFrame("Lot");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gui);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}

@SuppressWarnings("serial")
class LotGui extends JPanel {
private Map<String, ParkingSlot> slotMap = new LinkedHashMap<>();

public LotGui(int slotCount) {
JPanel gridPanel = new JPanel(new GridLayout(0, 1));
for (int i = 0; i < slotCount; i++) {
String slotName = String.format("V%03d", i);
ParkingSlot slot = new ParkingSlot(slotName);
slotMap.put(slotName, slot);

JButton button = new JButton(slotName + ": Empty");
button.setActionCommand(slotName);
button.addActionListener(e -> parkingSlotButtonAction(e));
gridPanel.add(button);
}

JScrollPane scrollPane = new JScrollPane(gridPanel);
scrollPane.getViewport().setPreferredSize(new Dimension(400, 400));

JButton displaySlotsBtn = new JButton("Display Parking Spots");
displaySlotsBtn.addActionListener(e -> displaySlots());

JPanel bottomPanel = new JPanel();
bottomPanel.add(displaySlotsBtn);

setLayout(new BorderLayout());
add(scrollPane);
add(bottomPanel, BorderLayout.PAGE_END);
}

private void parkingSlotButtonAction(ActionEvent e) {
String slotName = e.getActionCommand();
JButton source = (JButton) e.getSource();
ParkingSlot slot = slotMap.get(slotName);

// JOptionPane to query whether to add/remove car

String carName = JOptionPane.showInputDialog("Enter Name of Car to Add");

// if the user cancels the dialog or enters only white-space text: exit
if (carName == null || carName.trim().isEmpty()) {
return;
}

// else, use the name
Car car = new Car(carName);
slot.setCar(car);

String text = slotName + ": " + carName;
source.setText(text);
}

private void displaySlots() {
for (Entry<String, ParkingSlot> entry : slotMap.entrySet()) {
System.out.printf("%s%n", entry.getValue());
}
}

}

//class SLot {
// List<ParkingSlot> slots = new ArrayList<>();
//
// public SLot(int carCount) {
// for (int i = 0; i < carCount; i++) {
// String name = String.format("S%03d", i);
// slots.add(new ParkingSlot(name));
// }
// }
//
// @Override
// public String toString() {
// return "SLot [slots=" + slots + "]";
// }
//
// public void addChangeListener(ChangeListener l) {
//
// }
//}

class Car {
private String name;

public Car(String name) {
this.name = name;
}

public String getName() {
return name;
}

@Override
public String toString() {
return "Car: " + name;
}
}

class ParkingSlot {
private Car car;
private String name;

public ParkingSlot(String name) {
this.name = name;
}

public void setCar(Car car) {
this.car = car;
}

public Car getCar() {
return car;
}

public void clear() {
car = null;
}

public String getName() {
return name;
}

@Override
public String toString() {
String value = "Slot: " + name + "; ";
if (car == null) {
value += "empty";
} else {
value += car.toString();
}
return value;
}
}

Dynamically creating Swing GUI from JSON Schema (using Metawidget)

A core principle of Metawidget is allowing you to mix-and-match various approaches to suit your architecture. So I can answer this question in pieces.

A basic SwingMetawidget:

// UI

final JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

// Metawidget

final SwingMetawidget metawidget = new SwingMetawidget();
...configure Metawidget by setting inspectors, inspection result processors, widget builders, etc...
metawidget.setToInspect( myData );
frame.add( metawidget, BorderLayout.CENTER );

To read JSON type data, and JSON schemas, use a CompositeInspector:

String json = "{ \"firstname\": \"Richard\", \"surname\": \"Kennard\", \"notes\": \"Software developer\" }";
String jsonSchema = "{ properties: { \"firstname\": { \"required\": true }, \"notes\": { \"large\": true }}}";

...
metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
new JsonInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( json.getBytes() ) ) ),
new JsonSchemaInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( jsonSchema.getBytes() ) ) ) )

To map types, consider adding in a TypeMappingInspectionResultProcessor:

metawidget.addInspectionResultProcessor(
new TypeMappingInspectionResultProcessor<SwingMetawidget>(
new TypeMappingInspectionResultProcessorConfig()
.setTypeMapping( "foo", "bar" )
.setTypeMapping( "abc", "def" )));

Or, possibly a better approach, add in a custom WidgetBuilder to handle widgets for your unknown types:

metawidget.setWidgetBuilder( new CompositeWidetBuilder( new ompositeWidgetBuilderConfig()
.setWidgetBuilders(
new OverriddenWidgetBuilder(), new ReadOnlyWidgetBuilder(),
new MyWidgetBuilder(), new SwingWidgetBuilder()
)));

where MyWidgetBuilder does something like

class MyWidgetBuilder
implements WidgetBuilder<JComponent, SwingMetawidget> {

public JComponent buildWidget( String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {

if ( "my.special.type".equals( attributes.get( TYPE ) ) )

return new JSuperWidget();
}

// Fall through to other WidgetBuilder

return null;
}

By default, JComponents will not save their data anywhere. You need to add something like BeansBindingProcessor for that. Of course BeansBinding only binds to JavaBeans. If you want to bind to something else (like a JSON Map) you can add your own MapWidgetProcessor:

/**
* MapWidgetProcessor uses the Metawidget's <code>toInspect</code> to retrieve/store values.
*/

public class MapWidgetProcessor
implements AdvancedWidgetProcessor<JComponent, SwingMetawidget> {

//
// Public methods
//

@Override
public void onStartBuild( SwingMetawidget metawidget ) {

getWrittenComponents( metawidget ).clear();
}

/**
* Retrieve the values from the Map and put them in the Components.
*/

@Override
public JComponent processWidget( JComponent component, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {

String attributeName = attributes.get( NAME );
getWrittenComponents( metawidget ).put( attributeName, component );

// Fetch the value...

Map<String, Object> toInspect = metawidget.getToInspect();
Object value = toInspect.get( attributeName );

if ( value == null ) {
return component;
}

// ...and apply it to the component. For simplicity, we won't worry about converters

String componentProperty = metawidget.getValueProperty( component );
ClassUtils.setProperty( component, componentProperty, value );

return component;
}

@Override
public void onEndBuild( SwingMetawidget metawidget ) {

// Do nothing
}

/**
* Store the values from the Components back into the Map.
*/

public void save( SwingMetawidget metawidget ) {

Map<String, Object> toInspect = metawidget.getToInspect();

for ( Map.Entry<String,JComponent> entry : getWrittenComponents( metawidget ).entrySet() ) {

JComponent component = entry.getValue();
String componentProperty = metawidget.getValueProperty( component );
Object value = ClassUtils.getProperty( component, componentProperty );

toInspect.put( entry.getKey(), value );
}
}

//
// Private methods
//

/**
* During load-time we keep track of all the components. At save-time we write them all back
* again.
*/

private Map<String,JComponent> getWrittenComponents( SwingMetawidget metawidget ) {

@SuppressWarnings( "unchecked" )
Map<String,JComponent> writtenComponents = (Map<String,JComponent>) metawidget.getClientProperty( MapWidgetProcessor.class );

if ( writtenComponents == null ) {
writtenComponents = CollectionUtils.newHashMap();
metawidget.putClientProperty( MapWidgetProcessor.class, writtenComponents );
}

return writtenComponents;
}
}

how to dynamically add swing components to gui on click and make it permanent

This has little to do with Swing per se and all to do with persistence: What you are trying to do is set the state of an object (or group of objects) and have it persist when the program exits. There are several ways to do this including serialization, XML serialization (which I favor) using XML beans or JAXB, or properties. I suggest checking out JAXB and persisting your class's state with this by marshalling and unmarshalling the state to XML. Otherwise if you have a lot of information that must persist, then consider using a database.

Dynamic changing of layouts in swing

OK -- so it looks like what you want to do (and please correct me if I'm wrong) is to add a new component to a JPanel that is displayed within a JScrollPane. If so, then you do not want to change or swap layouts, and you certainly don't want to keep adding new JScrollPanes. Instead consider doing:

  • Create one JScrollPane and add to your GUI. Don't re-add this as you'll only need one.
  • add a JPanel to the JScrollPane's viewport that uses a layout that allows multiple components to be easily added to it. Perhaps a GridLayout or a BoxLayout, depending on what you need.
  • Also consider not adding the above JPanel directly to the viewport but rather adding it to another JPanel, one that uses BorderLayout, adding the first JPanel to the BorderLayout-using JPanel's BorderLayout.PAGE_START position, and then add this to the JScrollPane's viewport. This way the first JPanel won't stretch to fill the viewport initially.
  • Then in your button's ActionListener, add your components to the first JPanel by calling .add(...) on it, and then call revalidate() and repaint() on that first JPanel to layout the newly added components and repaint the JPanel and its contents.

Dynamically adding toolbars in Java Swing GUI

After adding another toolbar to the BoxLayout, you may need to (re|in)?validate the panel.

I've done this repeatedly but I can't understand the logic behind the 3 or so method calls; so I just try them until I hit on the one that works:

topPanel.validate();
topPanel.invalidate();
topPanel.revalidate();
topPanel.layout();

(at least) one of those should force your GUI to re-calculate its layout, making the north panel larger and thus showing the 2nd (and successive) toolbar(s) you've added.



Related Topics



Leave a reply



Submit