Reputation: 21
I'm trying to set up a JRadioButton-Matrix, so that in each column and in each row, only one Button can be selected at a time. I have the following code:
JRadioButton[][] button = new JRadioButton[names.length][names.length];
ButtonGroup[] r = new ButtonGroup[names.length];
ButtonGroup[] c = new ButtonGroup[names.length];
for (int i = 0; i < names.length; i++) {
r[i] = new ButtonGroup();
c[i] = new ButtonGroup();
}
for (int i = 0; i < names.length; i++) {
for (int j = 0; j < names.length; j++) {
button[i][j] = new JRadioButton();
r[i].add(button[i][j]);
c[j].add(button[i][j]);
}
}
But when I execute it, only the columns behave properly (i.e. the Buttons in the groups of c). When I comment the parts with c, however, the rows do behave properly.
To clear things a bit up (thanks to peeskillet):
Lets say I have this 4 x 4 matrix of JRadioButtons:
O O O O
O O O O
O O O O
O O O O
And I want to make it possible to make choices like these:
X O O O X O O O O X O O
O X O O O O X O X O O O
O O X O O X O O O O O X
O O O X O O O X O O X O
In the above, each column only has one and each row only has one. The following examples would NOT be possible:
X X O O X O O O
O O O O O X O O
O O X O O X O O
O O O X O O O X
However, the problem is, I CAN choose like in the above left matrix, but not the right. If I comment the following parts:
ButtonGroup[] c = new ButtonGroup[names.length];
c[i] = new ButtonGroup();
c[j].add(button[i][j]);
then the matrix on the above right is possible, but not the left.
Upvotes: 2
Views: 426
Reputation: 51524
A custom ButtonGroup (as already suggested by @Harald) definitely is the way to go.
Not entirely trivial due to the slightly weird mixture of responsibilities of the buttonModel and the group: the basic tweak to keep in mind is that the group has to keep its own selection state (vs. relying on the selected of the model).
The POC implementation below keeps it in a matrix (list-of-(lists-of-buttonModels)), which contain null or the model it deems selected. The internal update keeps (should, not formally tested :-) that matrix such that it has exactly one not-null element in each row and each column. Certainly has much leeway for cleanup ...
/**
* A buttonGroup that organizes selections in a matrix and guarantees
* to have at most one selection in each row and each column.
*/
public static class MatrixButtonGroup extends ButtonGroup {
// matrix of the buttons
private List<List<AbstractButton>> buttonMatrix;
// sparse matrix of the selected models, contains nulls
// everywhere except the unique selection for each row/column
private List<List<ButtonModel>> selectionMatrix;
public MatrixButtonGroup(List<AbstractButton> buttons, int columnCount) {
if (buttons.size() % columnCount != 0) {
throw new IllegalStateException("buttons count must be a multiple of columnCount");
}
int rowCount = buttons.size() / columnCount;
buttonMatrix = new ArrayList<>();
selectionMatrix = new ArrayList<>();
int counter = 0;
for (int row = 0; row < rowCount; row++) {
List<AbstractButton> buttonsInRow = new ArrayList<>();
List<ButtonModel> modelsInRow = new ArrayList<>();
for (int column = 0; column < columnCount; column++) {
modelsInRow.add(null);
buttons.get(counter).getModel().setGroup(this);
buttonsInRow.add(buttons.get(counter++));
}
selectionMatrix.add(modelsInRow);
buttonMatrix.add(buttonsInRow);
}
}
@Override
public boolean isSelected(ButtonModel m) {
for (int row = 0; row < selectionMatrix.size(); row++) {
List<ButtonModel> modelsInRow = selectionMatrix.get(row);
if (modelsInRow.contains(m)) return true;
}
return false;
}
/**
* Implemented to select the model such that it is the
* uniquely selected in the row/column of its button.
*/
@Override
public void setSelected(ButtonModel model, boolean selected) {
if (model == null || !selected) return;
if (isSelected(model)) return;
int row = getRow(model);
int column = getColumn(model);
ButtonModel rowSelected = getSelectedForRow(row);
ButtonModel columnSelected = getSelectedForColumn(column);
// update internal selection state
select(model, row, column);
// unselect the old selection if necessary
if (rowSelected != null) {
rowSelected.setSelected(false);
}
if (columnSelected != null) {
columnSelected.setSelected(false);
}
// select the new model
model.setSelected(true);
}
/**
* Update internal selection state to select the model such
* that there is exactly one model selected in the given
* row and column.
*/
private void select(ButtonModel model, int row, int column) {
// clear all in column
for (int index = 0; index < selectionMatrix.size(); index++) {
selectionMatrix.get(index).set(column, null);
}
List<ButtonModel> selectionRow = selectionMatrix.get(row);
for (int index = 0; index < selectionRow.size(); index++) {
selectionRow.set(index, null);
}
selectionRow.set(column, model);
}
/**
* @return the column of the given model
*/
private int getColumn(ButtonModel model) {
for (int row = 0; row < buttonMatrix.size(); row++) {
int column = getColumnInRow(buttonMatrix.get(row), model);
if (column >= 0) return column;
}
throw new IllegalStateException("model not managed by this group");
}
/**
* @return the row of the given model
*/
private int getRow(ButtonModel model) {
for (int row = 0; row < buttonMatrix.size(); row++) {
if (getColumnInRow(buttonMatrix.get(row), model) >= 0) return row;
}
throw new IllegalStateException("model not managed by this group");
}
/**
* @return the column of the model in the list
*/
private int getColumnInRow(List<AbstractButton> list, ButtonModel model) {
for (int column = 0; column < list.size(); column++) {
if (list.get(column).getModel() == model) return column;
}
return -1;
}
/**
* @return the selected buttonModel in the column or null if none
* selected
*/
private ButtonModel getSelectedForColumn(int column) {
for (List<ButtonModel> selectionRow : selectionMatrix) {
if (selectionRow.get(column) != null) return selectionRow.get(column);
}
return null;
}
/**
* @return the selected buttonModel in the row or null if none
* selected
*/
private ButtonModel getSelectedForRow(int row) {
List<ButtonModel> selectionRow = selectionMatrix.get(row);
for (ButtonModel model : selectionRow) {
if (model != null) return model;
}
return null;
}
/**
* Implemented to return the first selected model, traversing
* rows from first to last column.
*/
@Override
public ButtonModel getSelection() {
for (List<ButtonModel> selectionRow : selectionMatrix) {
for (ButtonModel model : selectionRow) {
if (model != null) return model;
}
}
return null;
}
@Override
public int getButtonCount() {
return buttonMatrix.size() * buttonMatrix.get(0).size();
}
// super overrides that still need to be done or are not supported
@Override
public Enumeration<AbstractButton> getElements() {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public void clearSelection() {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public void add(AbstractButton b) {
throw new UnsupportedOperationException("this button group is unmodifiable");
}
@Override
public void remove(AbstractButton b) {
throw new UnsupportedOperationException("this button group is unmodifiable");
}
}
It's usage:
List<AbstractButton> buttons = new ArrayList<>();
for (int row = 0; row < 4; row++) {
for (int column = 0; column < 4; column++) {
buttons.add(new JRadioButton("row " + row + " col " + column));
}
}
ButtonGroup p = new MatrixButtonGroup(buttons, 4);
JComponent content = new JPanel(new GridLayout(0, 4));
for (AbstractButton button : buttons) {
content.add(button);
}
Upvotes: 1
Reputation: 208944
Is the functionality you're looking for doable? Yes. Below is an example of what I mean. I used a lot of is statements. Maybe there a recursive way to accomplish this, but that just boggles the mind even more. Take a look at the example. Unfortunately it only used 9 buttons. So you if you want to use more, it will require a lot more coding. Basically all I did though was deselect certain buttons for each one being selected.
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
public class MultiButtonGroup extends JPanel implements ActionListener {
JRadioButton rb1 = new JRadioButton("rb1");
JRadioButton rb2 = new JRadioButton("rb2");
JRadioButton rb3 = new JRadioButton("rb3");
JRadioButton rb4 = new JRadioButton("rb4");
JRadioButton rb5 = new JRadioButton("rb5");
JRadioButton rb6 = new JRadioButton("rb6");
JRadioButton rb7 = new JRadioButton("rb7");
JRadioButton rb8 = new JRadioButton("rb8");
JRadioButton rb9 = new JRadioButton("rb9");
public MultiButtonGroup() {
JRadioButton[][] buttons = {
{rb1, rb2, rb3},
{rb4, rb5, rb6},
{rb7, rb8, rb9}
};
JPanel panel = new JPanel(new GridLayout(4, 4));
for (JRadioButton[] rbs : buttons) {
for (JRadioButton rbz : rbs) {
rbz.addActionListener(new RadioListener());
panel.add(rbz);
}
}
JButton doSomething = new JButton("Do SOmething");
setLayout(new BorderLayout());
add(panel, BorderLayout.CENTER);
add(doSomething, BorderLayout.SOUTH);
}
public void actionPerformed(ActionEvent e) {
}
private class RadioListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
JRadioButton source = (JRadioButton) e.getSource();
if (source == rb1) {
if (rb1.isSelected()) {
rb2.setSelected(false);
rb3.setSelected(false);
rb4.setSelected(false);
rb7.setSelected(false);
}
} else if (source == rb2) {
if (rb2.isSelected()) {
rb1.setSelected(false);
rb3.setSelected(false);
rb5.setSelected(false);
rb8.setSelected(false);
}
} else if (source == rb3) {
if (rb3.isSelected()) {
rb2.setSelected(false);
rb1.setSelected(false);
rb6.setSelected(false);
rb9.setSelected(false);
}
} else if (source == rb4) {
if (rb4.isSelected()) {
rb1.setSelected(false);
rb7.setSelected(false);
rb5.setSelected(false);
rb6.setSelected(false);
}
} else if (source == rb5) {
if (rb5.isSelected()) {
rb4.setSelected(false);
rb6.setSelected(false);
rb2.setSelected(false);
rb8.setSelected(false);
}
} else if (source == rb6) {
if (rb6.isSelected()) {
rb3.setSelected(false);
rb9.setSelected(false);
rb4.setSelected(false);
rb5.setSelected(false);
}
} else if (source == rb7) {
if (rb7.isSelected()) {
rb1.setSelected(false);
rb4.setSelected(false);
rb8.setSelected(false);
rb9.setSelected(false);
}
} else if (source == rb8) {
if (rb8.isSelected()) {
rb7.setSelected(false);
rb9.setSelected(false);
rb5.setSelected(false);
rb2.setSelected(false);
}
} else if (source == rb9) {
if (rb9.isSelected()) {
rb6.setSelected(false);
rb3.setSelected(false);
rb8.setSelected(false);
rb7.setSelected(false);
}
}
}
}
public static void createAndShowGui() {
JFrame frame = new JFrame();
frame.add(new MultiButtonGroup());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Upvotes: 1
Reputation: 27054
No, any AbstractButton
subclass using the default ButtonModel
(unsurprisingly named DefaultButtonModel
) may only be in a single ButtonGroup
.
For more information, see ButtonGroup.add(...) and ButtonModel.setGroup(...).
It's possible to create a special ButtonGroup
subclass that has knowledge of your matrix, and allows multiple selection (with some hacks I think). I once created a group of one radiobutton (mutually exclusive) and multiple checkboxes (multiple selection allowed), and it worked for me. :-)
Don't have access to the code right now, but might update with code later, if you're interested.
Upvotes: 2