cmadsen
cmadsen

Reputation: 372

JXTable with glazedlist filtering rendering issue

I have a rendering issue using a JXTable (swingx-all:1.6.5-1) being filtered by a TextComponentMatcherEditor (glazedlists:1.11.0) from Glazedlists under openjdk 17.

Entering "es 11" in the JTextfield produces the following rendering issue:

Screenshot showing issue

Any hints on what I'm doing wrong will be appreciated.

The following code can be used to reproduce the problem:

import static javax.swing.SwingUtilities.*;

import java.util.Comparator;
import java.util.stream.IntStream;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXTable;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.AdvancedTableModel;
import ca.odell.glazedlists.swing.GlazedListsSwing;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
import net.miginfocom.swing.MigLayout;


public class GlazedListsJXTableFilterIssue extends JPanel {

    public static void main(String[] args) {
        invokeLater(() -> {
            JXFrame frame = new JXFrame();
            frame.add(new GlazedListsJXTableFilterIssue());
            frame.pack();
            frame.setVisible(true);
        });
    }

    private JTextField filterField = new JTextField();

    private JTable itemsTable = new JXTable();

    // private JTable itemsTable = new JTable();

    GlazedListsJXTableFilterIssue() {
        initComponents();
        EventList<Item> itemEventList = new BasicEventList<>();
        IntStream.range(0, 100).forEach(i -> itemEventList
                .add(new Item("ES " + i, null, "December", null, null)));
        SortedList<Item> sortedItems = new SortedList<>(itemEventList,
                Comparator.comparing(Item::getName));
        TextComponentMatcherEditor<Item> textComponentMatcherEditor = new TextComponentMatcherEditor<>(
                filterField, GlazedLists.textFilterator("name"));
        FilterList<Item> filterList = new FilterList<>(sortedItems,
                textComponentMatcherEditor);
        AdvancedTableModel<Item> tableModel = GlazedListsSwing
                .eventTableModelWithThreadProxyList(filterList,
                        new ItemTableFormat());
        // uncomment next 2 lines for stacktrace and no rendering issue
        itemsTable.setAutoCreateRowSorter(false);
        itemsTable.setRowSorter(null);
        itemsTable.setModel(tableModel);
    }

    private void initComponents() {
        filterField.setColumns(20);
        setLayout(
                new MigLayout("insets dialog", "[grow, fill]", "[grow, fill]"));
        add(filterField, "cell 0 0, growx, growy 0");
        add(new JScrollPane(itemsTable), "cell 0 1, growx, width 300");
    }

    public static class Item {
        private String name;
        private String desc;
        private String month;
        private Integer number;
        private Boolean bool;

        Item(String name, String desc, String month, Integer number,
                Boolean bool) {
            this.name = name;
            this.setDesc(desc);
            this.month = month;
            this.setNumber(number);
            this.setBool(bool);
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getMonth() {
            return month;

        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public Integer getNumber() {
            return number;
        }

        public void setNumber(Integer number) {
            this.number = number;
        }

        public Boolean getBool() {
            return bool;
        }

        public void setBool(Boolean bool) {
            this.bool = bool;
        }

    }

    private static class ItemTableFormat implements TableFormat<Item> {

        private static String[] COLUMN_NAMES = { "Name", "Description", "Month",
                "Integer", "Boolean", };

        @Override
        public int getColumnCount() {
            return COLUMN_NAMES.length;
        }

        @Override
        public String getColumnName(int column) {
            return COLUMN_NAMES[column];
        }

        @Override
        public Object getColumnValue(Item item, int column) {
            switch (column) {
            case 0:
                return item.getName();
            case 1:
                return item.getDesc();
            case 2:
                return item.getMonth();
            case 3:
                return item.getNumber();
            case 4:
                return item.getBool();
            }
            throw new IllegalStateException();
        }
    }
    
}

Upvotes: -1

Views: 48

Answers (1)

Abra
Abra

Reputation: 20914

For what it's worth, here is a pure Swing implementation. I concentrated only on the filtering aspect.

My IDE is Eclipse 2024-06 on Windows 11 using the JDK that comes with Eclipse which is Temurin 21.0.3

Below code uses record classes, instanceof pattern matching and switch expressions.

(Needless to say, no rendering problems. :-)

Notes after the code.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.text.Document;

public class TblFltrT implements DocumentListener, Runnable {
    private JFrame  frame;
    private JTable  itemsTable;
    private TableRowSorter<ItemsTableModel>  sorter;
    private JTextField  filterField;

    @Override
    public void changedUpdate(DocumentEvent e) {
        // Never called.
    }

    @Override
    public void insertUpdate(DocumentEvent e) {
        updateFilter();
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        updateFilter();
    }

    public void run() {
        createAndDisplayGui();
    }

    private void createAndDisplayGui() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(createField(), BorderLayout.PAGE_START);
        frame.add(createTable(), BorderLayout.CENTER);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JPanel createField() {
        JPanel panel = new JPanel();
        filterField = new JTextField(20);
        Document doc = filterField.getDocument();
        doc.addDocumentListener(this);
        panel.add(filterField);
        return panel;
    }

    private JScrollPane createTable() {
        ItemsTableModel model = new ItemsTableModel();
        itemsTable = new JTable(model);
        sorter = new TableRowSorter<>(model);
        itemsTable.setRowSorter(sorter);
        JScrollPane scrollPane = new JScrollPane(itemsTable);
        return scrollPane;
    }

    private void updateFilter() {
        String text = filterField.getText();
        NameRowFilter filter = new NameRowFilter(text);
        sorter.setRowFilter(filter);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new TblFltrT());
    }
}

record Item(String name,
            String desc,
            String month,
            Integer number,
            Boolean bool) {

    public boolean equals(Object obj) {
        boolean equal = this == obj;
        if (!equal) {
            if (obj instanceof Item i) {
                if (name == null) {
                    equal = i.name() == null;
                }
                else {
                    equal = name.equals(i.name());
                }
            }
        }
        return equal;
    }
}

@SuppressWarnings("serial")
class ItemsTableModel extends AbstractTableModel {
    private static final String[] COLUMN_NAMES = {"Name",
                                                  "Description",
                                                  "Month",
                                                  "Integer",
                                                  "Boolean"};
    private List<Item>  itemEventList;

    public ItemsTableModel() {
        itemEventList = new ArrayList<>();
        IntStream.range(0, 100)
                 .forEach(i -> itemEventList.add(new Item("ES " + i,
                                                          null,
                                                          "December",
                                                          null,
                                                          null)));
    }

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

    public String getColumnName(int column) {
        return COLUMN_NAMES[column];
    }

    @Override
    public int getRowCount() {
        return itemEventList == null ? 0 : itemEventList.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Item item = itemEventList.get(rowIndex);
        return switch (columnIndex) {
            case 0 -> item.name();
            case 1 -> item.desc();
            case 2 -> item.month();
            case 3 -> item.number();
            case 4 -> item.bool();
            default -> null;
        };
    }
}

class NameRowFilter extends RowFilter<ItemsTableModel, Integer> {
    private String  name;

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

    @Override
    public boolean include(Entry<? extends ItemsTableModel, ? extends Integer> entry) {
        Object val = entry.getValue(0);
        String str = String.valueOf(val);
        return str.startsWith(name);
    }
}

Whenever the text in filterField is changed, a new RowFilter is created and assigned to itemsTable. The filter causes the table to display only those rows where the Name column starts with the text in filterField.

If you fail to overcome your problems using Glazed Lists and SwingX, I hope the above can help you to develop a pure Swing solution.

Upvotes: 0

Related Questions