I mamaged to figure out something that works with the help of ChatGPT.
It’s an ugly proof of concept but I’m still playing around.

If anyone would like to see I can put it up on Github.

I’m sure some of the seasoned Platform developers here have something more 
polished ;-)



> On May 29, 2025, at 2:20 PM, Tim Mullé <tmu...@gmail.com> wrote:
> 
> 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: users-unsubscr...@netbeans.apache.org
> For additional commands, e-mail: users-h...@netbeans.apache.org
> 
> For further information about the NetBeans mailing lists, visit:
> https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@netbeans.apache.org
For additional commands, e-mail: users-h...@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to