简体   繁体   English

JavaFx ImageView 绑定图像通过 fxml 表达式绑定

[英]JavaFx ImageView binding image through fxml expression bindings

I know there are a lot of javafx tutorials out there.我知道那里有很多 javafx 教程。 But 99% of them do not provide good fxml stuff.但是其中 99% 没有提供好的 fxml 东西。 Within fxml we can do databinding or "expression bindings" how it is called on javafx.在 fxml 中,我们可以在 javafx 上进行数据绑定或“表达式绑定”。

Binding an Image to the ImageView through expression binding works.通过表达式绑定工作将图像绑定到 ImageView。 But if the image changes, the actual displayed image won't change.但如果图像发生变化,实际显示的图像不会改变。 How can I do this in javafx with less code-behind as possible.如何在 javafx 中使用尽可能少的代码隐藏来做到这一点。

The controller file controller 文件

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;
    }
}

The fxml file 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>

When the application starts the image gets displayed.当应用程序启动时,图像就会显示出来。 But when the mouse cursor moves over the image then the getImage method gets invoked but the image wont change.但是当鼠标 cursor 在图像上移动时,会调用 getImage 方法但图像不会改变。 Is there a way with minimum code-behind to solve this?有没有办法用最少的代码隐藏来解决这个问题?

What I do not want to do is this here我不想做的是这里

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);
    }
}

With the TableView it is already possible to bind the data through an observable collection.使用 TableView 已经可以通过可观察的集合绑定数据。 If the list content changes the tableview also changes.如果列表内容发生变化,tableview 也会发生变化。 So here it works.所以在这里有效。

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

class TableController{

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

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

As I mentioned before, there are a lof of javafx examples.正如我之前提到的,有很多 javafx 示例。 But just a few cover fxml.但只是几个覆盖fxml。 Most of the people use大多数人使用

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

But I thought that FXML was introduced to get rid of all this code-behind stuff like in WPF/XAML.但我认为引入 FXML 是为了摆脱 WPF/XAML 中的所有这些代码隐藏内容。 Long text short question.长文本短问题。 How do I bind the Image to the ImageView so that when the Image changes the ImageView will display the new image?如何将图像绑定到 ImageView 以便在图像更改时 ImageView 将显示新图像?

kind regards, Bob.亲切的问候,鲍勃。

TL;DR : To make the binding within the FXML file you need to make your image property observable (eg via an ObjectProperty<Image> ). TL;DR :要在 FXML 文件中进行绑定,您需要使图像属性可观察(例如,通过ObjectProperty<Image> )。


There's nothing inherently special about FXML. FXML 本身并没有什么特别之处。 It just defines a way to describe an object graph in XML syntax which is interpreted by an FXMLLoader and converted into normal Java objects.它只是定义了一种在 XML 语法中描述 object 图的方法,该语法由FXMLLoader解释并转换为普通的 Java 对象。 It uses relatively basic rules and the Java(FX)Bean conventions to accomplish this.它使用相对基本的规则和 Java(FX)Bean 约定来实现这一点。 More information can be found in the Introduction to FXML document.更多信息可以在FXML 简介文档中找到。

When you use expression bindings you are using the binding API of the JavaFX Property interface.当您使用表达式绑定时,您使用的是 JavaFX Property接口的绑定 API。 In other words, when you have:换句话说,当你有:

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

It's basically the same as doing the following in code:这与在代码中执行以下操作基本相同:

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());
  }
}

And that's where the problem in your example comes from.这就是您示例中的问题所在。 Your controller doesn't expose an observable image property;您的 controller 没有公开可观察的图像属性; you only have a getImage() method.你只有一个getImage()方法。 Since there's nothing that can be observed the ImageView cannot automatically update when the Image changes (ie there's nothing to bind to).由于没有什么可以观察到的, ImageView不能在Image更改时自动更新(即没有什么可以绑定的)。

Here's an example where the controller exposes an ObjectProperty<Image> .这是 controller 公开ObjectProperty<Image>的示例。

App.fxml:应用程序.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: 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主.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();
  }
}

As an aside, please follow Java naming conventions , at least when posting to a public forum.顺便说一句,请至少在发布到公共论坛时遵循Java 命名约定 Those underscores ( _ ) in your variable/field names are not conventional and make it harder for other Java programmers to understand your code.变量/字段名称中的那些下划线 ( _ ) 不是常规的,并且使其他 Java 程序员更难理解您的代码。

You can only bind to something that can be observed (ie something that fires notifications when it changes its value).您只能绑定到可以观察到的东西(即在更改其值时触发通知的东西)。 A standard Java reference will not do this, so simply binding to a regular value returned from a get...() method will not automatically update if that value changes.标准的 Java 引用不会这样做,因此如果该值更改,简单地绑定到从get...()方法返回的常规值不会自动更新。

So if you want to do this entirely with FXML expression bindings, so would need to use the JavaFX Property pattern, ie因此,如果您想完全使用 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);
    }
}

Then然后

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

will work as intended.将按预期工作。

Alternatively, since your controller (which in the following scenario works more as a presenter) has a reference to the ImageView , you can do:或者,由于您的 controller (在以下场景中更像是演示者)引用了ImageView ,您可以这样做:

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

and

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);
    }
}

Finally, note that for this particular functionality, you can do all of this with CSS:最后,请注意,对于此特定功能,您可以使用 CSS 完成所有这些操作:

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

and in the CSS file:在 CSS 文件中:

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

and none of the controller code posted is needed (you may need to tweak the paths to the image here, as they will be relative to the CSS resource in this case).并且不需要发布的 controller 代码(您可能需要在此处调整图像的路径,因为在这种情况下它们将与 CSS 资源相关)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM