0

It is my first post in StackOverflow.

I did not find a SSCCE on a JTable whose row cells contain JRadioButton's - in several (not all) columns, contiguous columns and not always the same columns for each row - and whose ButtonGroup's correspond with the rows. So I tried to elaborate it after revisiting the topics of Renderer and Editor in 'How to use Tables - http://docs.oracle.com/javase/tutorial/uiswing/components/table.html and also after studying the following discussions : - Adding (a column of) JRadioButton into JTable Adding jRadioButton into jTable (Guillaume Polet) - How to add JRadioButton to group in JTable How to add JRadioButton to group in JTable

I make use of ... - Swing - TableCellEditor and TableCellRenderer - Beans.PropertyChangeSupport, PropertyChangeListener

My Java code works ... but with an error of behaviour and incompletely. It is even though going on a functionnality that is - I think - of great interest to much Java programmers.

A personal style rule : All syntax words of Java, Swing, ... : English, as in the libraries. All other items, particular to my application : Another language; here French.

Finally, here is the "One copy and paste" class file [TestTable2] containing 4 sub-classes [RendeurEditeur_CelluleBoutonRadio], [MonModeleTable], [GestionnObjetsDUneRangee] and [MonObjet]. It works. 420 lines for this SSCCE to copy and paste :

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
// import javax.swing.ButtonGroup;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;     // main()
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;


public class TestTable2
{
    private JFrame cadre;
    private JTable table;


    protected void initUI()
    {
        table = new JTable(new MonModeleTable());
        short nbreColo = (short) table.getColumnCount();
        table.setRowHeight(25);
        TableColumn col;

        // With Java, you can specify cell renderers and editors either by  column or by data type.
        for (int i = 0 ; i < nbreColo ; i++)
        {
            col = table.getColumnModel().getColumn(i);
            col.setCellEditor  (new RendeurEditeur_CelluleBoutonRadio());
            col.setCellRenderer(new RendeurEditeur_CelluleBoutonRadio());
        }


        cadre = new JFrame();
        cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cadre.add(new JScrollPane(table), BorderLayout.CENTER);
        cadre.pack();
        cadre.setVisible(true);
    }


    /**
     * @param args the command line arguments
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws UnsupportedLookAndFeelException
     */
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, 
                                        IllegalAccessException, UnsupportedLookAndFeelException
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override public void run()     // Implements method from java.lang.Runnable
            {
                new TestTable2().initUI();
            }
        }                       );
    }



    private class RendeurEditeur_CelluleBoutonRadio extends AbstractCellEditor
        implements TableCellRenderer, TableCellEditor, ActionListener
    {
        private final JRadioButton radioButton;

        public RendeurEditeur_CelluleBoutonRadio()
        {
            this.radioButton = new JRadioButton();
            radioButton.addActionListener(this);
            radioButton.setOpaque(false);
        }


        @Override public Component getTableCellRendererComponent(JTable table, Object valeur, 
                                boolean estSelectionne, boolean hasFocus, int row, int column)
        {
            radioButton.setSelected(Boolean.TRUE.equals(valeur));
            return radioButton;
        }


        @Override public Component getTableCellEditorComponent(JTable table, Object valeur, 
                                             boolean estSelectionne, int row, int column)
        {
            radioButton.setSelected(Boolean.TRUE.equals(valeur));
            return radioButton;
        }


        @Override public void actionPerformed(ActionEvent e)
        {
            stopCellEditing();
        }


        @Override public Object getCellEditorValue()
        {
            return radioButton.isSelected();
        }
    }



    /**
     *
     */
    class MonModeleTable extends AbstractTableModel implements PropertyChangeListener
    {   
        // Manages the rows of radioButtons and their groupings.
        private GestionnObjetsDUneRangee gestionn_DonneesDUneRang;
        private final List<GestionnObjetsDUneRangee>     gestionn_DonneesTteLatable = new ArrayList<>();


        public MonModeleTable()
        {
            super();            // AbstractTableModel()

            MonObjet objet;

            for (short idxRang = 0 ; idxRang < 5 ; idxRang++)
            {   
                gestionn_DonneesDUneRang = new GestionnObjetsDUneRangee();

               /* To record 'this' (= MonModeleTable) as listener of the messages sent 
                * by the object ...
                * - to which a property (gestionn_DonneesDUneRang) of 'MonModeleTable'-itself 
                * refers to, and 
                * - pertaining to a class (GestionnObjetsDUneRangee) containing a property (suppoChangePropri)
                * refering to an instanciation of PropertyChangeSupport. This later has as argument  
                * the object of class 'GestionnObjetsDUneRangee'.        */
                // Leaking 'this' in constructor : Passing suspicious parameter in the constructor.
                gestionn_DonneesDUneRang.getSuppoChangePropri().addPropertyChangeListener(this);

                for (short idxColo = 0 ; idxColo < 4 ; idxColo++)
                {
                    objet = new MonObjet(idxRang, idxColo);
                    gestionn_DonneesDUneRang.ajoutObjetÀRangee(objet);
                    if (idxColo == 0)
                        objet.setSelectionne(true);
                   /* To record 'MonModeleTable' as listener of the messages sent by each  
                    * object of the list 'gestionn_DonneesDUneRang'.*/
                    objet.getSupportChangtPropri().addPropertyChangeListener(this);
                }

                gestionn_DonneesTteLatable.add(gestionn_DonneesDUneRang);
            }
        }


        // Rem.: Identity of the object's row and column (JRadioButton) available in 'evt.getSource();'. 
        @Override public void propertyChange(PropertyChangeEvent evt)
        {
            System.out.print(evt.getPropertyName() + "\t");
            Object objet2 = evt.getSource();

            if (objet2 == gestionn_DonneesDUneRang)
            {
                if (evt.getPropertyName().equals("objecten"))   // MonModeleTable
                {
                    ((MonObjet) evt.getNewValue()).getSupportChangtPropri().addPropertyChangeListener(this);
                }
                fireTableDataChanged();
                System.out.println("");
            } else if (objet2 instanceof MonObjet)
            {   // When another button of the row has been activated ("geselecteerd")
                short[] coordBP = ((MonObjet) objet2).getCoordBP();
                fireTableRowsUpdated(coordBP[0], coordBP[0]);     // [0] : 2 x row

                System.out.println("");
            }
        }


        @Override public int getColumnCount()
        {
            return gestionn_DonneesDUneRang.getObjetsDUneRangee().size();
        }


        @Override public int getRowCount()
        {
            return gestionn_DonneesTteLatable.size();
        }


        // Exporting non-public type through public API
        public MonObjet getValueAt(int col)
        {
            return gestionn_DonneesDUneRang.getObjetsDUneRangee().get(col);
        }


        // Implements method from javax.swing.table.TableModel
        @Override public Object getValueAt(int idxRang, int idxColo)
        {   
            return getValueAt(idxColo).isSelectionne();
        }


        // This method can also import idxRang2 and idxColo2 in this class. 
        // Overrides method from javax.swing.table.AbstractTableModel
        @Override public void setValueAt(Object bpActionne, int idxRang2, int idxColo2)
        {
            getValueAt(idxColo2).setSelectionne(Boolean.TRUE.equals(bpActionne));
        }


        // Overrides method from javax.swing.table.AbstractTableModel
        @Override public boolean isCellEditable(int idxRang3, int idxColo3)
        {
            return true;       // idxRang3 == 1;
        }


        /** There must be a specification of a renderer for the cells for the table not to invoke the 
         * table model's 'getColumnClass' method (which gets the data type of the column's cells). */
        // Overrides method from javax.swing.table.AbstractTableModel
            @Override public Class<?> getColumnClass(int rang)
        {
            return Object.class;
        }


        Class<?> getClasseDeCellule(int idxRang, int idxColo)
        {   
// To be modified.
            return Object.class;
        }


        // Overrides method from javax.swing.table.AbstractTableModel
        @Override public String getColumnName(int colo)
        {   // Column titles
            return "Col " + (colo + 1);
        }

    }



    /** Bean. 
     */
    class GestionnObjetsDUneRangee
    {   
        private List<MonObjet> objetsDUneRangee = new ArrayList<>();
    //    private final ButtonGroup groupeHorizBoutRad = new ButtonGroup();
        private final PropertyChangeSupport suppoChangePropri = new     PropertyChangeSupport(this);


        // GestionnObjetsDUneRangee()    {   }


        public void ajoutObjetÀRangee(MonObjet objet)
        {
            objetsDUneRangee.add(objet);
            setGestionn_ObjetsDUneRangee2(objet);
            suppoChangePropri.firePropertyChange("objecten", null, objet);
        }


        public List<MonObjet> getObjetsDUneRangee()
        {
            return objetsDUneRangee;
        }


        // ?
        public void setObjetsDUneRangee2(List<MonObjet> objetsDUneRangee2)
        {
            List<MonObjet> ancienObjetsDUneRangee = objetsDUneRangee;
            objetsDUneRangee = objetsDUneRangee2;
            suppoChangePropri.firePropertyChange("objectenVanEenRij",
                                 ancienObjetsDUneRangee, objetsDUneRangee);
        }


        // Method called from 'ajoutObjetÀRangee(...)' in this class.
        void setGestionn_ObjetsDUneRangee2(MonObjet monObjet)
        {
            monObjet.gestionn_ObjetsDUneRangee = this;
            monObjet.getSupportChangtPropri().firePropertyChange("dataVanEenRij", null,                                 monObjet.gestionn_ObjetsDUneRangee);
        }


        // Puts all the JRadioButton's of a row in their right states (Only one selected) 
        public void miseàjourTousBP_Rangee(MonObjet myObject)    // setSelectionne()
        {
            for (MonObjet obj : objetsDUneRangee)
            {
                obj.setSelectionne(myObject == obj);
            }
        }


        GestionnObjetsDUneRangee getGestionn_ObjetsDUneRangee2(MonObjet monObjet)
        {
            return monObjet.gestionn_ObjetsDUneRangee;
        }


        PropertyChangeSupport getSuppoChangePropri()
        {
            return suppoChangePropri;
        }

    /*    
        public void
        addPropertyChangeListener(PropertyChangeListener listener)
        {
            suppoChangePropri.addPropertyChangeListener(listener);
        }


        public void
        removePropertyChangeListener(PropertyChangeListener listener)
        {
            suppoChangePropri.removePropertyChangeListener(listener);
        }
    */
    }



    /**
     * Bean
     */
    class MonObjet
    {   
        private boolean selectionne;
        GestionnObjetsDUneRangee gestionn_ObjetsDUneRangee;
        private final PropertyChangeSupport supportChangtPropri;
        short[] coordBP = new short[2];     // idxRang and idxColo of the JRadioButton object


        MonObjet(short idxRang, short idxColo)
        {
            supportChangtPropri = new PropertyChangeSupport(this);
            coordBP[0] = idxRang;
            coordBP[1] = idxColo;
        }


        PropertyChangeSupport getSupportChangtPropri()
        {
            return supportChangtPropri;
        }


        boolean isSelectionne()
        {
            return selectionne;
        }


        // Called by 'positionnerSelectionne(objet)' in 'gestionn_DObjetsDUneRang').
        void setSelectionne(boolean selectionne2)
        {
            if (this.selectionne != selectionne2)
            {   // Passes here only if a change of the state occured.
                this.selectionne = selectionne2;
                if (selectionne)
                {   // Passes here only when the radiobutton just was activated.
                    // 'selectionne' of the object is already 'true'.
                    gestionn_ObjetsDUneRangee.miseàjourTousBP_Rangee(this);
                }
                supportChangtPropri.firePropertyChange("geselecteerd", !selectionne, selectionne);
            }
        }


        short[] getCoordBP()
        {
            return coordBP;
        }


        public void setGestionn_ObjetsDUneRangee(GestionnObjetsDUneRangee gestionn_ObjetsDUneRangee2)
        {
            GestionnObjetsDUneRangee ancienGestionn_ObjetsDUneRangee = gestionn_ObjetsDUneRangee;
            gestionn_ObjetsDUneRangee = gestionn_ObjetsDUneRangee2;
            supportChangtPropri.firePropertyChange("ManagerObjectenVanEenRij",
                                ancienGestionn_ObjetsDUneRangee, gestionn_ObjetsDUneRangee2);
        }

    /*    
        public void addPropertyChangeListener(PropertyChangeListener listener)
        {
            supportChangtPropri.addPropertyChangeListener(listener);
        }


        public void removePropertyChangeListener(PropertyChangeListener     listener)
        {
            supportChangtPropri.removePropertyChangeListener(listener);
        }
    */
    }

}

}

The issue : The RadioButton's in the rows under the one where one was just activated, in the column of the activated one, also change. Observe by running my code, please. In 'MonModèleTable', in 'void propertyChange(PropertyChangeEvent evt)', the 'gestionn_ObjetsDUneRangée' of 'objet2' always refers to the one of the last row. How to get there the right 'gestionn_ObjetsDUneRangée', I mean the one which corresponds to the row of the JRadioButton that was just activated ?

Is there an expert agreeing that this case is of general interest and who can help us (Guillaume Polet ?) ? Many thanks in advance.

Note: I'm using JRE rev. 1.8.0_73 and NetBeans IDE rev. 8.0.2.

Community
  • 1
  • 1
Chavadam
  • 1
  • 4
  • 2
    For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). – Andrew Thompson Apr 14 '16 at 08:03
  • @AndrewThomson | SSCCE is the code in 4 files that I put in my question, isn't it ?.It is working. Don't I understand what you meant ? – Chavadam Apr 16 '16 at 13:54
  • How many lines of code in those 4 files? How many are actually needed to display the problem? Plus it needs to be one copy paste. That can be done with more than one class, they just need to be not declared public. – Andrew Thompson Apr 17 '16 at 00:35
  • @AndrewThomson | Really, in this SSCCE, there are no superfuous line i e that are not directly necessary to make this minimum code produce the table containing the radiobuttons. If you copy each of the 4 files (classes) in a new project in an IDE, it runs and generates the table. In one week, I could assemble the 4 classes in a main 'TestTabel' class, if you deem it absollutely necessary. Thanks for your reaction and your help. – Chavadam Apr 20 '16 at 20:24
  • *"..If you copy each of the 4 files .."* I won't. Did you read the bit *"Plus it needs to be **one** copy paste."*? Do you understand that? – Andrew Thompson Apr 20 '16 at 23:54
  • @AndrewThomson | Done. – Chavadam May 04 '16 at 09:05

0 Answers0