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 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

C#中radioButton的使用

C#中radioButton的使用

android 开发 listview绑定radiobutton控件 如何实现listview列表中只有一个radiobutton被选中?

c#的winform程序中,radiobutton的用法

求 android RadioButton属性详解

如何获取radiobutton默认选中的值