自定义 ButtonGroup(正如@Harald 已经建议的那样)绝对是要走的路。
由于 buttonModel 和 group 的职责混合有点奇怪,这并不完全是微不足道的:要记住的基本调整是 group 必须保持自己的选择状态(而不是依赖于模型的 selected)。
下面的 POC 实现将其保存在一个矩阵 (list-of-(lists-of-buttonModels)) 中,其中包含 null 或它认为选择的模型。内部更新保留(应该,未正式测试:-)该矩阵,使其在每一行和每一列中都有一个非空元素。当然有很大的清理余地...
/**
* 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");
}
}
它的用法:
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);
}