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