【问题标题】:JavaFX How to set String values to TableViewJavaFX 如何将字符串值设置为 TableView
【发布时间】:2026-02-02 11:35:01
【问题描述】:

提供一些背景知识:我现在可以将文件加载到我的 mp3 程序中并播放它们,但是我的 tableview 中的所有值都是空的?

我的歌曲课

package application;

//imports here

public class Song {
private String title;
private String artist;
private String album;
private SimpleStringProperty pTitle;
private SimpleStringProperty pArtist;
private SimpleStringProperty pAlbum;
private Media music;
private MediaPlayer mp;
private Image coverArt;

public Song(File file) {
    music = new Media(file.toURI().toString());
    music.getMetadata().addListener((Change<? extends String, ? extends Object> c) -> {
        if (c.wasAdded()) {
            if ("artist".equals(c.getKey())) {
                System.out.println(c.getKey()+":"+c.getValueAdded());
                this.pArtist = new SimpleStringProperty(c.getValueAdded().toString());
                //pArtist.set(c.getValueAdded().toString());
                artist = c.getValueAdded().toString();  
            } else if ("title".equals(c.getKey())) {
                title = c.getValueAdded().toString();
                System.out.println(c.getKey()+":"+c.getValueAdded());
            } else if ("album".equals(c.getKey())) {
                album = c.getValueAdded().toString();
                System.out.println(c.getKey()+":"+c.getValueAdded());
            } else if ("image".equals(c.getKey())) {
                coverArt = (Image) c.getValueAdded();
            }
        }
    });
    mp = new MediaPlayer(music);
    System.out.println(pArtist);
    System.out.println(artist);
    //artist = (String) mp.getMedia().getMetadata().get("artist");
    //title = (String) music.getMetadata().get("title");
    //album = (String) music.getMetadata().get("album");
    //artist = "test";
    //album = "test";
    //title = "test";
}

public void play() {
    mp.play();
}

public void pause() {
    mp.pause();
}

public void stop() {
    mp.stop();
}

public String getTitle(){
    return title;
}

public void setTitle(String title){
    this.title = title;
}

public String getArtist(){
    return artist;
}

public void setArtist(String artist){
    this.artist = artist;
}

public String getAlbum(){
    return album;
}

public void setAlbum(String album){
    this.album = album;
}

public Image getCover(){
    return coverArt;
}

public MediaPlayer getMP(){
    return mp;
}





}

奇怪的是,起初我认为这是因为我的 String 变量设置不正确并且设置为 null,因为当我在构造 Song 对象时放置这些打印行来测试它时,它在控制台中显示为 null。这是我测试时的控制台示例。

null
null
artist:Foo Fighters
album:Saint Cecilia EP
title:Saint Cecilia

这是我的控制器类

public class SceneController implements Initializable{
@FXML
private Button stopBtn;
@FXML
private Slider volume;
@FXML
private Button loadBtn;
@FXML
private Button playBtn;
@FXML
private TableView<Song> table;
@FXML
private Label label;
@FXML
private ProgressBar proBar;
private TableColumn songCol,artistCol,albumCol;
ObservableList<Song> songList = FXCollections.observableArrayList();
List<File> list;
FileChooser fileChooser = new FileChooser();
Desktop desktop;
Song mySong;



@Override
public void initialize(URL arg0, ResourceBundle arg1) {
    TableColumn songCol = new TableColumn("Song");
    TableColumn artistCol = new TableColumn("Artist");
    TableColumn albumCol = new TableColumn("Album");

    songCol.setCellValueFactory(
            new PropertyValueFactory<Song,String>("title"));
    //songCol.setCellFactory(new Callback);
    artistCol.setCellValueFactory(
            new PropertyValueFactory<Song,String>("artist"));
    albumCol.setCellValueFactory(
            new PropertyValueFactory<Song,String>("album"));
    volume.setMin(0);
    volume.setMax(100);
    volume.setValue(100);
    volume.valueProperty().addListener(new InvalidationListener() {
        @Override
        public void invalidated(Observable observable) {
            mySong.getMP().setVolume(volume.getValue()/100.0);
        }

    });

}

// Event Listener on Button[#loadBtn].onAction
@FXML
public void loadFile(ActionEvent event) {
    Node source = (Node) event.getSource();
    Window theStage = source.getScene().getWindow();
    //set fileChooser filter
    FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("MP3 files", "*.mp3");
    fileChooser.getExtensionFilters().add(extFilter);
    fileChooser.setTitle("Select MP3 files");
    //File file = fileChooser.showOpenDialog(theStage);
    //mySong = new Song(file);
    list = fileChooser.showOpenMultipleDialog(theStage);
    if(list!=null){
        for(File x: list) {
            mySong = new Song(x);
            System.out.println(mySong.getTitle());
            songList.add(mySong);
        }
    }
    table.setItems(songList);
}

@FXML
public void playSong(ActionEvent event) {
    mySong.play();
}

@FXML
public void stopSong(ActionEvent event) {
    //mySong.pause();
    System.out.println("song title: "+mySong.getArtist()+mySong.getTitle());
    ImageView img = new ImageView(mySong.getCover());
    //img.fitWidthProperty().bind(label.widthProperty());
    //img.fitHeightProperty().bind(label.heightProperty());
    img.setFitHeight(120);
    img.setFitWidth(200);
    label.setGraphic(img);
    //label.setGraphic(new ImageView(mySong.getCover()));
}

但是我在控制器类中为我的“停止”按钮制作了另一条测试打印线,在加载完所有内容并按下它之后,它会很好地打印出艺术家和标题。我看到了这个other thread 并检查了我的getter 方法,它们似乎是正确的?我真的很迷茫,如果有人能提供一些见解和解决方案,是因为我的变量是空的还是我的 PropertyValueFactory 没有正确完成

我还注意到空值首先出现,尽管它们不应该是最后打印的东西,因为当我在控制器类中创建新歌曲对象时,运行的第一行打印行在 if 语句中?

【问题讨论】:

  • 当您调用setCellValueFactory(...) 时,请尝试使用全部小写。我认为引号中的单词必须与Song 类中的变量名匹配。我说认为,因为我以不同的方式设置单元格值。
  • 我尝试使用小写和大写没有区别
  • 您在哪里将列添加到TableView?我看到你在哪里添加了songList,但没有看到TableColumns。此外,您似乎已在顶部声明了三列,但随后在 initialize 中重新声明并初始化它们。尝试删除 initialize 方法中的声明并在那里初始化它们。
  • 我相信这些列已经添加到我的 fxml 文档中的 tableview 中,如果您查看图像的样子,则在原始帖子中,否则我不确定如何将列添加到表格视图
  • 查看@jewelsea 的回答,了解您出错的地方。

标签: java javafx reflection


【解决方案1】:

您拥有当前代码的方式存在几处问题,从您在问题中发布的有限示例中可以看出:

  1. 您的Song 类没有正确遵循JavaFX properties pattern。特别是,您将每个“属性”存储两次,一次在“传统”JavaBean 样式字段中,例如private String title,一次在JavaFX 属性中:private StringProperty pTitle;。每个属性都应该存储一次。如果您希望表知道值何时更改,则应使用 JavaFX 属性,并让“标准”getXXX()setXXX() 检索并设置存储在这些属性中的基础值。
  2. 您附加到媒体元数据的侦听器在未来某个不确定的时间点被异步调用。当您将歌曲添加到表的列表中时,附加到列的单元格值工厂将在某个时候被执行,并从 Song 实例中检索分配的属性。使用您当前拥有的代码,这些属性实例仅在调用元数据上的侦听器后才实际创建。因此,在实例化 JavaFX 属性之前,单元格值工厂可能(或许很可能)会检查 Song 实例的属性,从而使表无法正确观察属性并响应其中的更改。您应该在创建 Song 实例时实例化 JavaFX 属性,并在元数据的侦听器中设置它们的值。
  3. 您绝不会将在控制器中创建的列添加到表中。如果您在 FXML 文件中创建它们(您没有在问题中发布),您应该将这些列注入控制器并使用单元格值工厂初始化 那些 列。 (由于屏幕截图显示表格中有列,我假设它们是在 FXML 文件中定义的,并且具有适当的fx:ids。)

所以你的Song 类应该是这样的:

public class Song {

    private final StringProperty title = new SimpleStringProperty();
    private final StringProperty artist = new SimpleStringProperty();
    private final StringProperty album = new SimpleStringProperty();
    private Media music;
    private MediaPlayer mp;
    private Image coverArt;

    public Song(File file) {
        music = new Media(file.toURI().toString());
        music.getMetadata().addListener((Change<? extends String, ? extends Object> c) -> {
            if (c.wasAdded()) {
                if ("artist".equals(c.getKey())) {
                    setArtist(c.getValueAdded().toString());
                } else if ("title".equals(c.getKey())) {
                    setTitle(c.getValueAdded().toString());
                } else if ("album".equals(c.getKey())) {
                    setAlbum(c.getValueAdded().toString());
                } else if ("image".equals(c.getKey())) {
                    // maybe this needs to be a JavaFX property too: it is not clear from your question:
                    coverArt = (Image) c.getValueAdded();
                }
            }
        });
        mp = new MediaPlayer(music);
    }

    public void play() {
        mp.play();
    }

    public void pause() {
        mp.pause();
    }

    public void stop() {
        mp.stop();
    }

    public StringProperty titleProperty() {
        return title ;
    }

    public final String getTitle(){
        return titleProperty().get();
    }

    public final void setTitle(String title){
        titleProperty().set(title);
    }

    public StringProperty artistProperty() {
        return artist ;
    }

    public final String getArtist(){
        return artistProperty().get();
    }

    public final void setArtist(String artist){
        artistProperty.set(artist);
    }

    public StringProperty albumProperty() {
        return album ;
    }

    public final String getAlbum(){
        return albumProperty().get();
    }

    public final void setAlbum(String album){
        albumProperty().set(album);
    }

    public Image getCover(){
        return coverArt;
    }

    public MediaPlayer getMP(){
        return mp;
    }


}

对于您的控制器,我将假设您的 FXML 文件已定义表列,其中 fx:ids 分别为 "songCol""artistCol""albumCol"。您需要像处理其他列一样将它们注入控制器。我也强烈建议不要使用PropertyValueFactory 类,它使用反射并且在编译时检查方面缺乏很多,并且自己实现回调。使用 lambda 表达式使这非常容易。

所以你的控制器应该是这样的:

public class SceneController implements Initializable{

    // non-table code omitted...

    @FXML
    private TableView<Song> table;
    @FXML
    private Label label;
    @FXML
    private ProgressBar proBar;
    @FXML
    private TableColumn<Song, String> songCol ;
    @FXML
    private TableColumn<Song, String> artistCol ;
    @FXML
    private TableColumn<Song, String> albumCol;

    ObservableList<Song> songList = FXCollections.observableArrayList();
    List<File> list;
    FileChooser fileChooser = new FileChooser();
    Desktop desktop;
    Song mySong;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {

        songCol.setCellValueFactory(cellData -> cellData.getValue().titleProperty());
        artistCol.setCellValueFactory(cellData -> cellData.getValue().artistProperty());
        albumCol.setCellValueFactory(cellData -> cellData.getValue().albumProperty());

        // ...

    }

    // other non-table code omitted...

}

您没有发布最小、完整、可验证的示例,因此您的代码中很可能存在其他错误,导致表格无法正确显示。不过,这应该可以帮助您入门。

【讨论】:

  • 你是最棒的,上次你也帮我解决了我的另一个问题。我按照你的建议做了,而且我下次会记住 mcve,因为当我有一个 fxml 文档时,我似乎把很多人弄糊涂了
【解决方案2】:

通常 TableColumns 将在 FXML 中定义并通过@FXML 注入。

如果你不想那样做,你需要这样做:

table.getColumns().add(songCol);

对于您的其他专栏也是如此。

此外,正如 HypnicJerk 在 cmets 中指出的那样,您在使用 PropertyValueFactory 时还需要遵循适当的命名约定。

songCol.setCellValueFactory(
    new PropertyValueFactory<Song,String>("title")
);

更多详情见:

【讨论】:

  • 我将该部分更改为小写,但它仍然显示为 null 我还有一个 fxml 文档,其中也定义了所有内容
  • 如果不提供 fxml,(请参阅mcve),您将无能为力。