【问题标题】:JavaFx ImageView binding image through fxml expression bindingsJavaFx ImageView 通过 fxml 表达式绑定图像绑定
【发布时间】:2020-04-29 12:20:00
【问题描述】:

我知道那里有很多 javafx 教程。但是其中 99% 没有提供好的 fxml 东西。 在 fxml 中,我们可以进行数据绑定或“表达式绑定”,如何在 javafx 上调用它。

通过表达式绑定将 Image 绑定到 ImageView 有效。但如果图像发生变化,实际显示的图像不会改变。如何在 javafx 中使用尽可能少的代码隐藏来做到这一点。

控制器文件

class MyController {
    private Image _imageA;
    private Image_ imageB;
    private Image_ image;

    public MyController(){
        _imageA = new Image(this.getClass().getResourceAsStream("imageA.png"));
        _imageB = new Image(this.getClass().getResourceAsStream("imageB.png"));
        _image = _imageA;
    }

    public getImage(){
        return _image;
    }

    public void mouseEnter(MouseEvent mouseEvent){
        _image = _imageA;
    }

    public void mouseLeave(MouseEvent mouseEvent){
        _image = _imageB;
    }
}

fxml 文件

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="_anchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="868.0" prefWidth="1124.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MyController">

   <ImageView fx:id="_imgview" image="${controller.image}" onMouseEnter="#mouseEnter" onMouseExited="#mouseLeave"/>


</AnchorPane>

当应用程序启动时,图像就会显示出来。但是当鼠标光标移动到图像上时,getImage 方法会被调用,但图像不会改变。 有没有办法用最少的代码隐藏来解决这个问题?

我不想做的是这里

class MyController implements Initializable {
    //..other stuff
    @FXML
    private ImageView _imgview;
    SimpleObjectProperty<Image> _propertyImage = new SimpleObjectProperty<>();

    public void initialize(URL url, ResourceBundle resourceBundle) {
        _imageViewSettings.imageProperty().bind(_propertySettingsImage);
    }

    public void mouseEnter(MouseEvent mouseEvent){
        _imageProperty.setValue(_imageB);
    }

    public void mouseLeave(MouseEvent mouseEvent){
        _imageProperty.setValue(_imageA);
    }
}

使用 TableView 已经可以通过可观察的集合绑定数据。 如果列表内容发生变化,tableview 也会发生变化。所以在这里它起作用了。

<TableView items="${controller.items}">
<!-- other stuff-->
 </TableView>

class TableController{

   private ObservableList<Integer> _list = FXCollections.observableArrayList();

   ObservableList<Integer> getItems(){
        return _list;
   }
}

正如我之前提到的,有很多 javafx 示例。但只是几个覆盖fxml。大部分的人 使用

GridPane p = new GridPane();;
p.setAlignment(...);
p.setChildren(...);

但我认为引入 FXML 是为了摆脱 WPF/XAML 中的所有这些代码隐藏内容。 长文本短问题。如何将 Image 绑定到 ImageView 以便在 Image 更改时 ImageView 将显示新图像?

亲切的问候, 鲍勃。

【问题讨论】:

  • “我不想在这里做的是……”。为什么不?这就是预期的方法。如果你想绑定到某个东西,你必须在 JavaFX 的意义上使它成为Observable,这意味着使用一个属性或绑定。而且,在你的控制器中,你有一个对ImageView的引用,那么为什么不在事件处理程序中直接调用setImage(...)呢?
  • 顺便说一句,我发现在变量名中使用所有这些多余的下划线来阅读您的代码真的很困难。你能坚持标准Java naming conventions吗?
  • 虽然我的回答显示了(我相信)您想要的示例,但请记住,并非所有事情都可以直接在 FXML 中完成。视图的动态部分以及其他更复杂的视图相关逻辑可能必须在代码中定义,通常在 FXML 控制器中。根据您使用的架构(例如 MVC、MVP、MVVM 等),您的控制器实际上可能非常复杂。

标签: java javafx data-binding expression fxml


【解决方案1】:

TL;DR:要在 FXML 文件中进行绑定,您需要使图像属性可观察(例如,通过 ObjectProperty&lt;Image&gt;)。


FXML 本身并没有什么特别之处。它只是定义了一种用 XML 语法描述对象图的方法,该语法由 FXMLLoader 解释并转换为普通的 Java 对象。它使用相对基本的规则和 Java(FX)Bean 约定来实现这一点。更多信息可以在Introduction to FXML文档中找到。

当您使用表达式绑定时,您使用的是 JavaFX Property 接口的绑定 API。换句话说,当你有:

<ImageView image="${controller.image}"/>

基本上和在代码中做如下一样:

public class FooController {

  private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
  // property-getter, getter, and setter omitted for brevity

  @FXML private ImageView imageView; // corresponding fx:id="imageView" in FXML file

  @FXML
  private void initialize() {
    // the "expression binding"
    imageView.imageProperty().bind(imageProperty());
  }
}

这就是您的示例中的问题所在。您的控制器没有公开 observable 图像属性;你只有一个getImage() 方法。由于没有可以观察到的内容,ImageView 无法在 Image 更改时自动更新(即没有可绑定的内容)。

这是控制器公开ObjectProperty&lt;Image&gt; 的示例。

App.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.StackPane?>

<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="Controller" prefWidth="1000" prefHeight="650">

  <top>
    <Button text="Browse..." onAction="#handleBrowse" BorderPane.alignment="CENTER_RIGHT">
      <BorderPane.margin>
        <Insets topRightBottomLeft="5"/>
      </BorderPane.margin>
    </Button>
  </top>

  <center>
    <ScrollPane fitToHeight="true" fitToWidth="true" vbarPolicy="NEVER" hbarPolicy="NEVER" 
                pannable="true">
      <StackPane minWidth="-Infinity" minHeight="-Infinity">
        <ImageView image="${controller.image}"/>
      </StackPane>
    </ScrollPane>
  </center>

</BorderPane>

Controller.java:

import java.io.File;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Window;

public class Controller {

  private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
  public final void setImage(Image image) { this.image.set(image); }
  public final Image getImage() { return image.get(); }
  public final ObjectProperty<Image> imageProperty() { return image; }

  @FXML
  private void handleBrowse(ActionEvent event) {
    event.consume();

    Window window = ((Button) event.getSource()).getScene().getWindow();

    FileChooser chooser = new FileChooser();
    chooser
        .getExtensionFilters()
        .addAll(new ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif"));
    File file = chooser.showOpenDialog(window);
    if (file != null) {
      setImage(new Image(file.toURI().toString()));
    }
  }
}

Main.java

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) throws IOException {
    Parent root = FXMLLoader.load(Main.class.getResource("App.fxml"));
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
  }
}

顺便说一句,请至少在公共论坛发帖时关注Java naming conventions。变量/字段名称中的那些下划线 (_) 不符合常规,使其他 Java 程序员更难理解您的代码。

【讨论】:

    【解决方案2】:

    您只能绑定到可以观察到的东西(即在更改其值时触发通知的东西)。标准 Java 引用不会这样做,因此如果该值发生更改,简单地绑定到从 get...() 方法返回的常规值不会自动更新。

    因此,如果您想完全使用 FXML 表达式绑定来执行此操作,则需要使用 JavaFX 属性模式,即

    class MyController implements Initializable {
        //..other stuff
    
        // you may not even need this if you use expression bindings uniquely:
        @FXML
        private ImageView imgview;
    
        private Image imageA;
        private Image imageB;
    
        public MyController(){
            imageA = new Image(this.getClass().getResourceAsStream("imageA.png"));
            imageB = new Image(this.getClass().getResourceAsStream("imageB.png"));
        }
    
    
        private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
    
        public ObjectProperty<Image> imageProperty() {
            return image ;
        }
    
        public final Image getImage() {
            return imageProperty().get();
        }
    
        public final void setImage(Image image) {
            imageProperty().set(image);
        }
    
        public void initialize(URL url, ResourceBundle resourceBundle) {
            // ...
        }
    
        public void mouseEnter(MouseEvent mouseEvent){
            image.set(imageB);
        }
    
        public void mouseLeave(MouseEvent mouseEvent){
            image.set(imageA);
        }
    }
    

    然后

    <ImageView fx:id="imgview" image="${controller.image}" onMouseEnter="#mouseEnter" onMouseExited="#mouseLeave"/>
    

    将按预期工作。

    或者,由于您的控制器(在以下场景中更像是演示者)引用了ImageView,您可以这样做:

    <ImageView fx:id="imgview" onMouseEnter="#mouseEnter" onMouseExited="#mouseLeave"/>
    

    class MyController implements Initializable {
        //..other stuff
    
        @FXML
        private ImageView imgview;
    
        private Image imageA;
        private Image imageB;
    
        public MyController(){
            imageA = new Image(this.getClass().getResourceAsStream("imageA.png"));
            imageB = new Image(this.getClass().getResourceAsStream("imageB.png"));
        }
    
        public void initialize(URL url, ResourceBundle resourceBundle) {
            imageView.setImage(imageA);
        }
    
    
        public void mouseEnter(MouseEvent mouseEvent){
            imgview.setImage(imageB);
        }
    
        public void mouseLeave(MouseEvent mouseEvent){
            imageview.setImage(imageA);
        }
    }
    

    最后,请注意,对于这个特定功能,您可以使用 CSS 完成所有这些操作:

    <ImageView fx:id="imgview" id="#my-image" />
    

    在 CSS 文件中:

    #my-image {
        -fx-image: url("@imageA.png");
    }
    #my-image:hover {
        -fx-image: url("@imageB.png");
    }
    

    并且不需要发布任何控制器代码(您可能需要在此处调整图像的路径,因为在这种情况下它们将与 CSS 资源相关)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-20
      相关资源
      最近更新 更多