Help with JTable sync with ExplorerManager

Tim Mullé <[email protected]>
Newsgroups gmane.comp.java.ide.netbeans.user
Message-ID <[email protected]>
Hi,

Can anyone please tell me what I’m doing wrong below?

I need to use legacy JTables in our application because we have very customized renderers and dynamic column add/remove features and for the time being I 
have to keep what we have. Besides, from my limited research it seems the TableView and OutlineView are limited in their customization of renderers and being able to dynamically add/remove custom columns, etc? At least out of the box..

So, I thought I could basically make an adapter/bridge between the JTable selection and the ExplorerManager API so other components (ie. Properties, selection handling) can work elsewhere.

But, for some reason I’m not getting my node published. I see the debug statements saying things SHOULD be published, but the Properties window isn’t showing anything.


This is my TopComponent as a test:

Thanks!

— CODE ——
/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 * Click nbfs://nbhost/SystemFileSystem/Templates/NetBeansModuleDevelopment-files/templateTopComponent637.java to edit this template
 */
package org.mullet.tables;

import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.TableRowSorter;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.windows.TopComponent;
import org.openide.util.NbBundle.Messages;

/**
 * Top component which displays something.
 */
@ConvertAsProperties(
        dtd = "-//org.mullet.tables//Tims//EN",
        autostore = false
)
@TopComponent.Description(
        preferredID = "TimsTopComponent",
        //iconBase="SET/PATH/TO/ICON/HERE",
        persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "explorer", openAtStartup = false)
@ActionID(category = "Window", id = "org.mullet.tables.TimsTopComponent")
@ActionReference(path = "Menu/Window" /*, position = 333 */)
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_TimsAction",
        preferredID = "TimsTopComponent"
)
@Messages({
    "CTL_TimsAction=Tims",
    "CTL_TimsTopComponent=Tims Window",
    "HINT_TimsTopComponent=This is a Tims window"
})
public final class TimsTopComponent extends TopComponent implements ExplorerManager.Provider {

    private final ExplorerManager em = new ExplorerManager();
    private final JTable table;
    private final PersonTableModel model;
    
    public TimsTopComponent() {
//        initComponents();
        setLayout(new BorderLayout());
        setName(Bundle.CTL_TimsTopComponent());
        setToolTipText(Bundle.HINT_TimsTopComponent());
        
        // Create a JTableModel
        List<Person> peopleList = List.of(
            new Person("Alice", 25),
            new Person("Bob", 30),
            new Person("Carol", 22)
        );

        // Add the model to the JTable
        model = new PersonTableModel(peopleList);
        table = new JTable(model);
        
        // Set selection and sorting
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        TableRowSorter<PersonTableModel> sorter = new TableRowSorter<>(model);
        table.setRowSorter(sorter);

        // Hookup the JTable selection with explorer manager
        syncSelectionWithExplorer();

        // Right-click menu on header for "Add Column..."
        JPopupMenu headerMenu = new JPopupMenu();
        JMenuItem addColumnItem = new JMenuItem("Add Column…");
        addColumnItem.addActionListener(e -> showAddColumnDialog());
        headerMenu.add(addColumnItem);

        table.getTableHeader().addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                if (SwingUtilities.isRightMouseButton(e)) {
                    headerMenu.show(table.getTableHeader(), e.getX(), e.getY());
                }
            }
        });
        
        // Clicking the button doesn't do anything
        // I don't see anything in the Properties window
        JButton debugButton = new JButton("Force Node");
        debugButton.addActionListener(e -> {
            Person p = new Person("Zed", 99);
            PersonNode node = new PersonNode(p);
            try {
                em.setSelectedNodes(new Node[]{node});
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
        add(debugButton, BorderLayout.SOUTH);

        // Hookup the explorer manager
        associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
        
        // Set the nodes
        em.setRootContext(new AbstractNode(new PersonChildren(peopleList)));
        
        // Add the table
        add(new JScrollPane(table), BorderLayout.CENTER);

    }
    
    /**
     * Sync the JTable selection to publish using the explorer manager
     * so we can use built in components/selection management
     * 
     * NOTE: I see all the debug come out but nothing shows up in the Properties window
     */
    private void syncSelectionWithExplorer() {
        table.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
            if (!e.getValueIsAdjusting()) {
                int viewRow = table.getSelectedRow();
                if (viewRow >= 0) {
                    int modelRow = table.convertRowIndexToModel(viewRow);
                    Person selected = model.getPersonAt(modelRow);
                    System.out.println("[DEBUG] Table row selected: " + selected.getName());

                    Node selectedNode = new PersonNode(selected);
                    System.out.println("[DEBUG] Created node: " + selectedNode.getDisplayName());
                    System.out.println("[DEBUG] Node properties:");
                    for (Node.PropertySet ps : selectedNode.getPropertySets()) {
                        for (Node.Property<?> prop : ps.getProperties()) {
                            try {
                                System.out.println(" - " + prop.getName() + " = " + prop.getValue());
                            } catch (Exception ex) {
                                System.out.println(" - " + prop.getName() + " [ERROR getting value]");
                            }
                        }
                    }

                    try {
                        em.setSelectedNodes(new Node[] { selectedNode });
                        System.out.println("[DEBUG] ExplorerManager setSelectedNodes() called");
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                } else {
                    System.out.println("[DEBUG] Table row deselected");
                    try {
                        em.setSelectedNodes(new Node[0]);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });
    }

    private void showAddColumnDialog() {
        String name = JOptionPane.showInputDialog(this, "Enter column name:");
        if (name != null && !name.isBlank()) {
            model.addComputedColumn(name.trim(), p -> {
                System.out.println("[DEBUG] Computing value for: " + p.getName() + " with column: " + name);
                return "[" + p.getName().toUpperCase() + ":" + name.length() + "]";
            });
        }
    }

    @Override
    public ExplorerManager getExplorerManager() {
        return em;
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }// </editor-fold>                        

    // Variables declaration - do not modify                     
    // End of variables declaration                   
    @Override
    public void componentOpened() {
        // TODO add custom code on component opening
    }

    @Override
    public void componentClosed() {
        // TODO add custom code on component closing
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    void readProperties(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version
    }
}


— PersonNode
package org.mullet.tables;

import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.lookup.Lookups;
import org.openide.nodes.PropertySupport;

public class PersonNode extends AbstractNode {

    private final Person person;

    public PersonNode(Person person) {
        // Explorer views rely on the Node to be lookup-aware (for Properties window)
        super(Children.LEAF, Lookups.singleton(person));
        this.person = person;
        setDisplayName(person.getName()); // Optional: name shown in tree column
    }

    @Override
    protected Sheet createSheet() {
        Sheet sheet = Sheet.createDefault(); // creates root Sheet object
        Sheet.Set set = Sheet.createPropertiesSet(); // default "properties" set

        try {
            // These must match getName() / getAge() in your Person class
            set.put(new PropertySupport.Reflection<>(person, String.class, "getName", null));
            set.put(new PropertySupport.Reflection<>(person, int.class, "getAge",null));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Failed to reflect on Person bean", e);
        }

        sheet.put(set);
        return sheet;
    }
}

— PersonChildren
package org.mullet.tables;

import java.util.List;
import org.openide.nodes.Children;
import org.openide.nodes.Node;

public class PersonChildren extends Children.Keys<Person> {

    private final List<Person> data;

    public PersonChildren(List<Person> people) {
        this.data = people;
    }

    @Override
    protected void addNotify() {
        setKeys(data);
    }

    @Override
    protected Node[] createNodes(Person person) {
        System.out.println("DEBUG => Creating a person Node");
        return new Node[] { new PersonNode(person) };
    }
}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.