How to implement dynamic GUI in swing
Asked Answered
S

4

12

First of all, apologies for posting something perhaps a bit excessively specific, but I'm not very experienced with Swing, and can't seem to find good examples that fit my needs.

So I'm trying to figure out the best way to implement the a dynamic GUI for choosing filtering criteria in Swing:

Mockup of GUI to implement

The underlying model is a class containing a list of criteria that can be negated (i.e. applied with a NOT-prefix), and a property indicating whether these should be combined with AND or OR.

The GUI would allow the user to add, change or remove criteria, and select the combination operator (and/or). The first criterium would naturally not have a combination-selector, and the third and subsequent criteria would simply use the same combination-operator as the second one.

The X-buttons on the right would be used to delete a criterium. When the Add-button is pressed, a new line of components would be added to the bottom. As changes are made, these would be reflected in the underlying model.

Of course I could implement this quite "primitively" by simply adding components to a JPanel and then updating the model accordingly, but I would prefer a neater solution, such as that provided by a TableModel.

So I'm wondering if a table with a custom TableModel and TableCellRenderer/Editor would be the best approach, or if there is a better way to implement something like this. If table is indeed the best approach, I would appreciate some pointers to how one would use TableCellRenderers or -Editors to accomplish this.

Thanks in advance.

Scrivings answered 15/6, 2011 at 9:19 Comment(2)
For reference, the singular of "criteria" is "criterion". See also criterium, a kind of bike race.Ellata
Whoops, not my native language :)Scrivings
H
18

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());
    }
}
Hallel answered 15/6, 2011 at 9:52 Comment(5)
-1 for calling fireXX from outside the model, -1 for disrespecting java naming conventions ... come on, you can do better than that :-)Locomotive
still not satisfied - the fireDataChanged is not necessary: default is well-behaved enough to fire an rowsInserted :)Locomotive
@Locomotive agreed with that, plus removed other my "Adds"Hallel
@Locomotive o.k really excelent catch, but to avoid play with useless value needed for JTable#fireTableRowsInserted(int firstRow, int lastRow); then I disabled JTable#fireTableDataChanged(), no worries that's DefalutTableModel :-)Hallel
+1 Good example. I think you mean "all method names start with lowerCase." Also, I wonder if Comp could be enum, one for type and one for relation.Ellata
A
4

I think such a custom TableMOdel and TableCellRenderer/Editor is the best choice. http://download.oracle.com/javase/tutorial/uiswing/components/table.html That would be good to start.

Anaphase answered 15/6, 2011 at 9:32 Comment(0)
C
3

add all Components for a search criteria to a panel and add/remove the specific Panel. I don't think a Tablemodel is a good choice here.

Caprification answered 15/6, 2011 at 9:31 Comment(4)
Could you justify that opinion somehow? Does it just seem overkill, or what?Scrivings
while certainly a matter of taste, I tend to agree - @Rolf: I don't see much of "table-ness" in the requirement. mKorbel's code example is basically a deflated (one-column) table, which isn't a list for the sole reason that JList doesn't support editing :-)Locomotive
I agree that the GUI isn't particularly tabular. However, like you said, JList doesn't support editing, so what's wrong with using a table, even if it's posing as a list? Actually, I was thinking more along the lines of a four column table, where the combination selector, negation, criterium and remove button would have their own columns.Scrivings
More to the point, do you have a suggestion of an elegant way to implement this without a table?Scrivings
O
0

Netbeans has a nice ui that does something similar to what you describe: Task List Filter KeywordPanel

Why not stand on the shoulders of giants? The Netbeans panels look nice and work well. The implementation is even cleanly separated between ui and model code. If I was in your shoes (and it was June of 2011), I'd base my solution on the source here:

http://source.apidesign.org/hg/netbeans/releases/file/cb41fb91bc28/tasklist.ui/src/org/netbeans/modules/tasklist/filter

KeywordPanel.java contains this comment: "The GUI is based on the one in Mozilla's mail tool".

Sorry for the late answer.

Orel answered 31/12, 2013 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.