TableView 中 RadioButton 的奇怪行为
Posted
技术标签:
【中文标题】TableView 中 RadioButton 的奇怪行为【英文标题】:Strange behavior of a RadioButton in a TableView 【发布时间】:2018-07-02 15:56:12 【问题描述】:我的问题是关于 tableView 中化合物的奇怪行为。 目的是在 tableView 中显示参与比赛的玩家列表。显示的信息是玩家的姓名、他的得分、他的连续失败次数以及一个指示是否轮到他上场的指示器。
这个指示器是一个 RadioButton,因为它看起来比一个复选框更好。当轮到玩家时,RadioButton 将被 setSelected(true),否则,它将被 setSelected(false)。真假信息由tableView中使用的玩家信息给出。当然,RadioButton 处于“只读”模式。
这是我的 tableView 代码:
TableView<PlayerProgressInformations> playersProgressTable = new TableView<PlayerProgressInformations>();
defineColumns(playersProgressTable);
playersProgressTable.setItems(playersAtThisTable);
对于defineColumns方法:
TableColumn<PlayerProgressInformations, Boolean> colPlaying = new TableColumn<PlayerProgressInformations, Boolean>("Tour");
colPlaying.setPrefWidth(70);
TableCell<PlayerProgressInformations, Boolean>>) new RadioButton());
colPlaying.setCellValueFactory(new Callback<CellDataFeatures<PlayerProgressInformations, Boolean>, ObservableValue<Boolean>>()
public ObservableValue<Boolean> call(CellDataFeatures<PlayerProgressInformations, Boolean> p)
return new SimpleBooleanProperty(p.getValue().isPlaying());
);
colPlaying.setCellFactory(new Callback<TableColumn<PlayerProgressInformations, Boolean>, TableCell<PlayerProgressInformations, Boolean>>()
@Override
public TableCell<PlayerProgressInformations, Boolean> call( TableColumn<PlayerProgressInformations, Boolean> param)
RadioButtonCell<PlayerProgressInformations, Boolean> radioButtonCell =
new RadioButtonCell<PlayerProgressInformations, Boolean>();
return radioButtonCell;
);
还有 RadioButtoCell 类:
private class RadioButtonCell<S, T> extends TableCell<S,T>
public RadioButtonCell ()
@Override
protected void updateItem (T item, boolean empty)
System.out.println("Count value : "+count); //Indicator to check how many times is used the method "updateItem"
count+=1;
if (item instanceof Boolean)
Boolean myBoolean = (Boolean) item;
if (!empty)
System.out.println("Valeur du boolean : "+item);
RadioButton radioButton = new RadioButton();
radioButton.setDisable(true);
radioButton.setSelected(myBoolean);
radioButton.setStyle("-fx-opacity: 1");
setGraphic(radioButton);
奇怪的行为如下:
问题 1:当我将第一个玩家加入游戏桌时,updateItem 方法被调用了 17 次。 如果第二个玩家加入,第一个玩家的这个数字会增加到 57,或者第二个玩家会增加到 60 和 17。 最后,如果第三个人加入,第一个玩家是 90 次,第二个是 57 或 60 次,第三个是 17 次。 为什么这种方法经常被调用?为什么这些具体数字? 更重要的是,在这个“初始化”之后,该方法在每一轮之后被调用了 2 次,正如我预期的那样:一次取消选择 RadioButton,一次选择下一个。
问题 2:当第一个玩家加入牌桌时,他当然是第一个玩的,并且在他的屏幕上选择了 RadioButton。 当第二个玩家加入牌桌时,该第二个玩家会看到为第一个玩家选择的 RadioButton 和未为自己选择的 RadioButton。这是预期的行为。但是对于第一个玩家,2 个 RadioButtons 未被选中。 如果第三位玩家加入牌桌,他将看到为第一位玩家选择了 RadioButton,而为他自己和第二位玩家取消了选择。这也是预期的结果。但是,对于第二个和第一个玩家,所有 3 个 RadioButtons 都未选中。 为什么会出现这种奇怪的行为?此外,在第一轮之后,所有 RadioButtons 都按预期显示为选中或未选中,就好像 bug 消失了一样。
您能否帮助我了解正在发生的事情以及如何解决这些错误?
非常感谢
【问题讨论】:
好的。我将使用您的提示来更好地了解它是如何工作的。谢谢詹姆斯 另外,不清楚你在描述问题时的意思:“当第二个玩家加入牌桌时,第二个玩家看到一个 RadioButton 被选中......但对于第一个玩家,2单选按钮未选中。”所以有两个屏幕?或者这是一个点对点类型的应用程序?不同玩家如何查看同一个应用程序? 屏幕很多。游戏在服务器上运行,每个玩家都运行一个“查看器”来玩游戏。因此,在 Eclipse 上,我启动了一个项目服务器并启动了 2 或 3 个项目客户端来模拟 2 或 3 个游戏玩家。 如果不同的播放器显示的内容不同,这似乎是客户端和服务器之间的通信问题。大概你有一些机制让服务器在新玩家加入时通知现有客户端等。这个问题似乎完全是一个不同的问题,与你发布的代码无关。 是的,你是对的!这是一个 bean 的通信问题。谢谢詹姆斯:-D 【参考方案1】:示例代码
这是一个有效的表格实现。我想你可以将它与你的实现进行比较,看看有什么区别。可能主要的“修复”是updateItem
处理空值和空值。
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class PlayerViewer extends Application
private final ObservableList<Player> data =
FXCollections.observableArrayList(
new Player("Jacob", true),
new Player("Isabella", false),
new Player("Ethan", true)
);
public static void main(String[] args)
launch(args);
@Override
public void start(Stage stage)
TableView<Player> table = new TableView<>(data);
table.setPrefHeight(130);
table.setPrefWidth(150);
TableColumn<Player, String> handleCol = new TableColumn<>("Handle");
handleCol.setCellValueFactory(new PropertyValueFactory<>("handle"));
table.getColumns().add(handleCol);
TableColumn<Player, Boolean> playingCol = new TableColumn<>("Playing");
playingCol.setCellValueFactory(new PropertyValueFactory<>("playing"));
playingCol.setCellFactory(param -> new TableCell<>()
RadioButton indicator = new RadioButton();
indicator.setDisable(true);
indicator.setStyle("-fx-opacity: 1");
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
@Override
protected void updateItem(Boolean isPlaying, boolean empty)
super.updateItem(isPlaying, empty);
if (empty || isPlaying == null)
setGraphic(null);
else
indicator.setSelected(isPlaying);
setGraphic(indicator);
);
table.getColumns().add(playingCol);
stage.setScene(new Scene(table));
stage.show();
public static class Player
private final SimpleStringProperty handle;
private final SimpleBooleanProperty playing;
private Player(String handle, boolean playing)
this.handle = new SimpleStringProperty(handle);
this.playing = new SimpleBooleanProperty(playing);
public SimpleStringProperty handleProperty()
return handle;
public String getHandle()
return handle.get();
public void setHandle(String handle)
this.handle.set(handle);
public SimpleBooleanProperty playingProperty()
return playing;
public boolean isPlaying()
return playing.get();
public void setPlaying(boolean playing)
this.playing.set(playing);
关于您的问题的其他 cmets
就“问题 1”而言,updateItem 被调用了多少次,这是一个内部工具包的事情,您的代码不应该真正关心这一点,它只需要确保无论何时调用它,它都会执行正确的事情。
关于你的“问题2”,关于多个玩家的多个视图的交互,谁知道?如果没有进一步的附加代码,就不可能说,这最终可能会使这个问题过于宽泛。如果您有关于如何处理多个玩家的显示器交互的具体问题,您需要扩展并澄清您的问题(可能是mcve 的新问题)。
对于您的实现,我建议重新设置单选按钮的样式(通过 CSS),使其看起来不像标准的用户可选择单选按钮(因为您已禁用选择功能,然后删除了默认的禁用不透明度设置)。或者,您可以使用自定义指标控件,例如 this answer 中的 Bulb 类,这可能是首选。
【讨论】:
此解决方案完美运行。现在,我必须阅读更多 Oracle 的文档才能完全理解它是如何工作的。比你宝石海【参考方案2】:这是另一个有效的示例。 (@jewelsea 发布时我几乎完成了它,所以我想我还是会继续发布它。它很相似,但两者之间的差异可能有用。)
我可以看到您的代码中的主要问题是:
-
您的单元格值工厂在每次调用时都会返回一个新的
BooleanProperty
。这意味着单元格无法观察到正确的属性,并且在值更改时没有机会更新自身。您应该在模型类中使用 JavaFX 属性,以便单元格可以观察到单个属性。
您的单元实现不调用updateItem(...)
的超类实现。这意味着基本功能不会发生,因此在需要时可能会或可能不会发生更新,并且选择不起作用等等。
您的单元格实现不处理空单元格,因此它们不一定会正确显示。
在这个实现中,我使用Game
类来保留“当前玩家”,并为每个玩家提供对(相同)游戏实例的引用。 Player
中的 playing
属性公开为只读属性,其值绑定到游戏的当前玩家。这意味着只有一个玩家可以拥有playing==true
,玩家之间没有很多“连线”。
这些按钮允许测试添加新玩家(自动设置为“当前”玩家)或将当前玩家移动到下一个玩家。
import java.util.Iterator;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class PlayerTable extends Application
@Override
public void start(Stage primaryStage)
TableView<Player> table = new TableView<>();
TableColumn<Player, String> playerCol = new TableColumn<>("Name");
playerCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getName()));
TableColumn<Player, Boolean> playingColumn = new TableColumn<>("Playing");
playingColumn.setCellValueFactory(cellData -> cellData.getValue().playingProperty());
playingColumn.setCellFactory(tc -> new RadioButtonTableCell<>());
table.getColumns().add(playerCol);
table.getColumns().add(playingColumn);
Game game = new Game();
Button newPlayerButton = new Button("New Player");
newPlayerButton.setOnAction(e -> addNewPlayer(game, table.getItems()));
Button nextPlayerButton = new Button("Next player");
nextPlayerButton.setOnAction(e -> selectNextPlayer(game, table.getItems()));
HBox controls = new HBox(5, newPlayerButton, nextPlayerButton);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(controls);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
private void addNewPlayer(Game game, List<Player> players)
int playerNumber = players.size() + 1 ;
Player newPlayer = new Player(game, "Player "+playerNumber);
game.setCurrentPlayer(newPlayer);
players.add(newPlayer);
private void selectNextPlayer(Game game, List<Player> players)
if (players.isEmpty()) return ;
for (Iterator<Player> i = players.iterator() ; i.hasNext() ;)
if (i.next() == game.getCurrentPlayer())
if (i.hasNext())
game.setCurrentPlayer(i.next());
else
game.setCurrentPlayer(players.get(0));
return ;
game.setCurrentPlayer(players.get(0));
public static class RadioButtonTableCell<S> extends TableCell<S, Boolean>
private RadioButton radioButton ;
public RadioButtonTableCell()
radioButton = new RadioButton();
radioButton.setDisable(true);
@Override
protected void updateItem(Boolean item, boolean empty)
super.updateItem(item, empty);
if (empty)
setGraphic(null);
else
radioButton.setSelected(item);
setGraphic(radioButton);
;
public static class Game
private final ObjectProperty<Player> currentPlayer = new SimpleObjectProperty<>() ;
public ObjectProperty<Player> currentPlayerProperty()
return currentPlayer ;
public Player getCurrentPlayer()
return currentPlayerProperty().get();
public void setCurrentPlayer(Player player)
currentPlayerProperty().set(player);
public static class Player
private final String name ;
private final ReadOnlyBooleanWrapper playing ;
public Player(Game game, String name)
this.name = name ;
playing = new ReadOnlyBooleanWrapper() ;
playing.bind(game.currentPlayerProperty().isEqualTo(this));
public String getName()
return name ;
public ReadOnlyBooleanProperty playingProperty()
return playing.getReadOnlyProperty() ;
public boolean isPlaying()
return playingProperty().get();
public static void main(String[] args)
launch(args);
【讨论】:
以上是关于TableView 中 RadioButton 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章
android 开发 listview绑定radiobutton控件 如何实现listview列表中只有一个radiobutton被选中?