1.首先阅读文档,了解Javafx2.0中的属性和绑定:Using JavaFX Properties and Binding
2.简单总结:
(1)JavaBean不再是以前的pojo了,Javafx添加了一系列的封装类,进一步封装了Java中的基本类型的封装类,使得它可以被绑定或者绑定,也就是它实现了Observable接口,具体请看API。
(2)上面的以Simple开头的是相应的property的简单实现类,所以在类(Javabean)中一般是使用初始化为simple...
(3)property都有一些方法用于绑定特定的对象,例如,绑定其他的property,或者其他的property组合而成的,例如,StringProperty
(4)除了使用bind方法绑定其他的property之外,它还可以添加listener,比如,当这个属性发生变化的时候就可以通知给其他的对象!
下面显示了StringProperty的实现的接口
3.实践
编写一个JavaBean,符合JavaFx2.0的规范
LightButton.java
package light; import util.ProtocolUtil; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; public class LightButton { private IntegerProperty id = new SimpleIntegerProperty();// init here private StringProperty name = new SimpleStringProperty(); private IntegerProperty state = new SimpleIntegerProperty(ProtocolUtil.STATE_OFF); // public LightButton(){//可以没有 // // } public LightButton(int id, String name) { setId(id); setName(name); } public void setState(int value) { state.set(value); } public int getState() { return state.get(); } public IntegerProperty stateProperty() { return state; } public void setId(int value) { id.set(value); } public int getId() { return id.get(); } public IntegerProperty idProperty() { return id; } public void setName(String value) { name.set(value); } public String getName() { return name.get(); } public StringProperty nameProperty() { return name; } }
LightView.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.effect.*?> <?import javafx.scene.image.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.paint.*?> <?import javafx.scene.text.*?> <?import light.Light?> <AnchorPane id="AnchorPane" fx:id="lightAnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="150.0" xmlns:fx="http://javafx.com/fxml" fx:controller="light.LightViewController"> <children> <StackPane id="StackPane" fx:id="lightStackPane" onMouseEntered="#handleShowButtons" onMouseExited="#handleHideButtons" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <children> <ImageView fx:id="lightImageView" onMouseClicked="#handleLight" style="-fx-cursor:hand;" StackPane.alignment="TOP_CENTER"> <effect> <Reflection bottomOpacity="0.2" fraction="0.5" /> </effect> <image> <Image url="@light_off.png" preserveRatio="true" smooth="true" /> </image> <StackPane.margin> <Insets top="20.0" /> </StackPane.margin> </ImageView> <Text id="text1" fx:id="textLightName" boundsType="VISUAL" scaleX="1.3438942202036481" scaleY="1.5467894807101856" stroke="#0c9900" strokeLineCap="ROUND" strokeLineJoin="ROUND" text="LightName" StackPane.alignment="TOP_CENTER"> <effect> <Reflection bottomOpacity="0.2" fraction="0.6" /> </effect> <StackPane.margin> <Insets top="120.0" /> </StackPane.margin> </Text> <Button fx:id="btnOpen" alignment="CENTER" contentDisplay="CENTER" defaultButton="true" onAction="#handleOpenLight" text="*Open" StackPane.alignment="CENTER_LEFT"> <StackPane.margin> <Insets left="4.0" /> </StackPane.margin> </Button> <Button fx:id="btnClose" onAction="#handleCloseLight" text="*Close" StackPane.alignment="CENTER_RIGHT"> <StackPane.margin> <Insets right="4.0" /> </StackPane.margin> </Button> </children> </StackPane> </children> </AnchorPane>
LightViewController.java
package light; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import util.ProtocolUtil; import javafx.animation.FadeTransition; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; import javafx.scene.text.Text; import javafx.util.Duration; import app.AppControllers2; public class LightViewController implements Initializable { private LightButton lightButton; private Image lightOnImage; private Image lightOffImage; @FXML private AnchorPane lightAnchorPane; @FXML private StackPane lightStackPane; @FXML private ImageView lightImageView; @FXML private Button btnOpen; @FXML private Button btnClose; @FXML private Text textLightName; private List<Button> buttons = new ArrayList<Button>(); @Override public void initialize(URL arg0, ResourceBundle arg1) { lightOnImage = new Image(getClass().getResourceAsStream("light_on.png")); lightOffImage = new Image(getClass().getResourceAsStream("light_off.png")); lightButton = AppControllers2.getNewLightButton(); changeImage(); textLightName.setText(lightButton.getName()); // lightButton.nameProperty().bind(textLightName.textProperty());//(1)fails! // java.lang.RuntimeException: A bound value cannot be set. // textLightName.textProperty().bind(lightButton.nameProperty());//(2)ok textLightName.textProperty().bindBidirectional(lightButton.nameProperty());// (3)ok lightButton.nameProperty().bindBidirectional(textLightName.textProperty());// (4)ok // (3)+(4) ok // lightButton.nameProperty().bind(textLightName.textProperty());//(1)+(2) fails // can not do this!StackOverflowError lightButton.stateProperty().addListener(new ChangeListener<Number>() { public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { changeImage(); } }); buttons.add(btnOpen); buttons.add(btnClose); handleHideButtons(null); } private void changeImage() { if (lightButton.getState() == ProtocolUtil.STATE_OFF) { lightImageView.setImage(lightOffImage); } else { lightImageView.setImage(lightOnImage); } } @FXML private void handleOpenLight(ActionEvent event) { System.out.println("handle open"); lightButton.setName("open"); lightButton.setState(ProtocolUtil.STATE_ON); } @FXML private void handleCloseLight(ActionEvent event) { System.out.println("handle close"); lightButton.setName("close"); lightButton.setState(ProtocolUtil.STATE_OFF); } @FXML private void handleLight(MouseEvent event) { System.out.println("handle light"); lightButton.setName("name"); lightButton.setState(1 - lightButton.getState()); } @FXML private void handleShowButtons(MouseEvent me) { System.out.println("handle show buttons"); for (Button button : buttons) { FadeTransition f = new FadeTransition(Duration.millis(250), button); f.setFromValue(0.2); f.setToValue(0.8); f.play(); } } @FXML // TODO:bug here!if the mouse moves very fast,the buttons will not disappear! private void handleHideButtons(MouseEvent me) { System.out.println("handle hide buttons"); for (Button button : buttons) { button.setOpacity(0); } } }
然后用FXMLLoader加载fxml文件,显示在stage中,即可看到效果:
文字会发生变化,图片也会发生变化!其中文字使用的是bind,而图片使用的是addListener
注意:请注意Controller类中的注释代码,这个很重要,绑定是有方向性的,一般是UI控件的值绑定到了bean的某个属性对象上。
并且要注意,不要以为 a.bind(b) 再加上 b.bind(a) 就可以是想双向绑定,这个是错误的,会发生栈溢出错误!例如(1)+(2)
代码片段反映了不同的情况下的情况,注意两个不同的方法:bind 和 bindBidirectional 方法,后者是由StringProperty提供的,可以实现双向绑定(貌似是,没有看过源码)
所以,使用(3)和使用(4)是一样的,写上一个就可以了。
4.但是,实际开发中,我们的bean对象中不仅仅只是int,float,double,string等等这些基本类型吧!那如果是一些比较复杂的Java类怎么进行绑定呢?
仔细查看API,不能发现还有一个ObjectProperty,它对应一个简单的实现类SimpleObjectProperty,看看它怎么使用吧,其实和其他的XXXProperty是一样的
下面是一个例子:目的是让rectangle的color和bill中的color属性一致!
package demo; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.NumberBinding; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; public class BindingDemo { public static void main(String[] args) { Bill bill1 = new Bill(); Bill bill2 = new Bill(); NumberBinding total = Bindings.add(bill1.dpProperty(), bill2.dpProperty()); total.addListener(new InvalidationListener() { public void invalidated(Observable arg0) { System.out.println("now invalid!"); } }); bill1.setDp(12);//valid->invalid // System.out.println(total.getValue());// (1)//invalid->valid bill2.setDp(13);//valid->invalid System.out.println(total.getValue());// (2)//invalid->valid // if (1)+(2) // now invalid! // 12.0 // now invalid! // 25.0 //if (1) commented // now invalid! // 25.0 Rectangle rectangle = new Rectangle(20, 20, Color.RED); System.out.println(rectangle.getFill());// Color[red=255,green=0,blue=0,opacity=1.0] rectangle.fillProperty().bind(bill1.colorProperty()); bill1.setColor(Color.GREEN); System.out.println(rectangle.getFill());// Color[red=0,green=128,blue=0,opacity=1.0] // ChangeListener<Object> right and ChangeListener<Color> wrong // rectangle.fillProperty().addListener(new ChangeListener<Object>() { // public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) { // //when the rectangle color changes,this method will be invoked // } // }); } } class Bill { private DoubleProperty dp = new SimpleDoubleProperty(); private ObjectProperty<Color> color = new SimpleObjectProperty<Color>(); public final Color getColor() { return color.get(); } public final void setColor(Color color) { this.color.set(color); } public ObjectProperty<Color> colorProperty() { return color; } public final double getDp() { return dp.get(); } public final void setDp(double dp) { this.dp.set(dp); } public DoubleProperty dpProperty() { return dp; } }