javafx中双向绑定ToggleGroup

问题描述 投票:0回答:5

想象一下有一个定义鼠标模式的枚举:

public enum MouseMode {
SELECTION,
EDITING,
DELETING }

想象一下有一个由 3 个按钮组成的切换组:

    ToggleButton selection = new ToggleButton("Select");
    ToggleButton editing = new ToggleButton("Edit");
    ToggleButton deleting = new ToggleButton("Delete");
    ToggleGroup mouseSelection = new ToggleGroup();

我想要一个字段

MouseMode currentMode
双向链接到切换组。每当设置切换时,当前模式都会相应地切换,但如果某些外部进程更改当前模式(可能是按键),则切换组也会相应地进行调整。

我可以用 2 个监听器来完成此操作,但我想知道是否有办法创建自定义双向地图。

java javafx javafx-2 javafx-8
5个回答
8
投票

我认为没有办法直接做到这一点。虽然是通用的

Bindings.bindBidirectional(Property<S> property1, Property<T> property2, Function<S,T> mapping, Function<T,S> inverseMapping)

可能会对 API 做出很好的补充,即使在这种情况下也没有帮助,因为

ToggleGroup
selectedProperty
是只读的(因为当每个
Toggle
setSelected(...)
方法时需要处理选择)被调用,以及由
ToggleGroup
selectedProperty
) 调用。

在这种情况下,使用几个侦听器是可行的方法。

最接近“自定义双向地图”的是

Bindings.bindBiDirectional(StringProperty stringProperty, ObjectProperty<T> otherProperty, StringConverter<T> converter)

方法。如果您有(可写)

ObjectProperty<S>
和(可写)
ObjectProperty<T>
,理论上您可以使用两个双向绑定和一个中间
StringProperty
将它们绑定在一起。实际上,这几乎总是比仅使用两个侦听器需要更多代码,而且效率也较低。


3
投票

我已成功使用 JFXtras 项目中的 ToggleGroupValue 类。

这是一个例子:

import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class Main extends Application {
    Child myChild = new Child();
    @Override
    public void start( Stage stage ) throws Exception {
        stage.setTitle( "ToggleGroupValue Example" );
        GridPane gridPane = new GridPane();
        int rowIndex = 0;
        gridPane.add( new Label("Nickname: "), 0, rowIndex );
        
        ToggleGroupValue toggleGroupValue = new ToggleGroupValue();
        rowIndex = createAddRadioButtons( gridPane, rowIndex, toggleGroupValue );
        
        gridPane.add( new Label("Selected Nickname: "), 0, rowIndex );
        Label selectedNickNameValueLabel = new Label();
        gridPane.add( selectedNickNameValueLabel, 1, rowIndex );
        
        myChild.nicknameProperty().bindBidirectional( toggleGroupValue.valueProperty() );
        selectedNickNameValueLabel.textProperty().bind( toggleGroupValue.valueProperty() );
        
        stage.setScene( new Scene( gridPane, 300, 100 ) );
        stage.show();
    }

    private int createAddRadioButtons( GridPane gridPane, int rowIndex, ToggleGroupValue toggleGroupValue ) {
        RadioButton radioButtonPunkin = new RadioButton();
        radioButtonPunkin.setUserData( "Punkin" );
        RadioButton radioButtonLittleBoy = new RadioButton();
        radioButtonLittleBoy.setUserData( "Little Boy" );
        RadioButton radioButtonBuddy = new RadioButton();
        radioButtonBuddy.setUserData( "Buddy" );
        List<RadioButton> radioButtons = Arrays.asList( radioButtonPunkin, radioButtonLittleBoy, radioButtonBuddy );
        for ( RadioButton radioButton : radioButtons ) {
            toggleGroupValue.add( radioButton, radioButton.getUserData() );
            radioButton.setText( radioButton.getUserData().toString() );
            gridPane.add( radioButton, 1, rowIndex++ );
        }
        return rowIndex;
    }

    private static class Child {
        private StringProperty nickname = new SimpleStringProperty();
        public StringProperty nicknameProperty() {
            return nickname;
        }
        public String getNickname() {
            return nickname.get();
        }
        public void setNickname( String notesProperty ) {
            this.nickname.set( notesProperty );
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

screenshot of javafx example application


1
投票

我正在使用 Java bean 属性适配器,但您可以只使用此代码的最后一行并绑定它。

JavaBeanObjectProperty<fooEnum> property = null;
    try {
        property = new JavaBeanObjectPropertyBuilder<fooEnum>().bean(fooBean).name(fooField).build();
    } catch (NoSuchMethodException e1) {
        e1.printStackTrace();
    }
    property.addListener((obs, oldValue, newValue) -> {
        System.out.println("Property value changed from " + oldValue + " to " + newValue);
    });
BindingUtils.bindToggleGroupToProperty(fooToggleGroup, property);

您需要为 ToggleGroup 提供一个小型 BindingUtils 类。

public final class BindingUtils {

private BindingUtils() {
}

public static <T> void bindToggleGroupToProperty(final ToggleGroup toggleGroup, final ObjectProperty<T> property) {
    // Check all toggles for required user data
    toggleGroup.getToggles().forEach(toggle -> {
        if (toggle.getUserData() == null) {
            throw new IllegalArgumentException("The ToggleGroup contains at least one Toggle without user data!");
        }
    });
    // Select initial toggle for current property state
    for (Toggle toggle : toggleGroup.getToggles()) {
        if (property.getValue() != null && property.getValue().equals(toggle.getUserData())) {
            toggleGroup.selectToggle(toggle);
            break;
        }
    }
    // Update property value on toggle selection changes
    toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
        property.setValue((T) newValue.getUserData());
    });
}

0
投票

这个答案的灵感来自于tunabot。这个答案将使用

RadioButton
,而不是使用
ToogleButton
,并且为了使其看起来更漂亮,我们将使用
ControlsFX
中的 SegmentedButton。我们可以使用
valueProperty
中的
ToggleGroupValue
来绑定双向选定的切换按钮。

有一个调试按钮,当我们单击此按钮时,所选按钮将更改为

DELETING
按钮。

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import jfxtras.scene.control.ToggleGroupValue;
import org.controlsfx.control.SegmentedButton;

public class ToggleBindingDemo extends Application{
    public static void main(String[] args){
        launch(args);
    }

    private final ObjectProperty<MouseMode> mouseModeObjectProperty = new SimpleObjectProperty<>(MouseMode.SELECTION);;

    @Override
    public void start(Stage stage){
        ToggleGroupValue<MouseMode> toggleGroupValue = new ToggleGroupValue<>();

        ToggleButton selection = new ToggleButton("Selection");
        selection.setUserData(MouseMode.SELECTION);
        selection.setToggleGroup(toggleGroupValue);

        ToggleButton editing = new ToggleButton("Editing");
        editing.setUserData(MouseMode.EDITING);
        editing.setToggleGroup(toggleGroupValue);

        ToggleButton deleting = new ToggleButton("Deleting");
        deleting.setUserData(MouseMode.DELETING);
        deleting.setToggleGroup(toggleGroupValue);

        toggleGroupValue.valueProperty().bindBidirectional(mouseModeObjectProperty);
        mouseModeObjectProperty.addListener(new ChangeListener<MouseMode>(){
            @Override
            public void changed(ObservableValue<? extends MouseMode> observable, MouseMode oldValue, MouseMode newValue){
                System.out.println("MouseMode: " + newValue);
            }
        });

        SegmentedButton segmentedButton = new SegmentedButton(selection, editing, deleting);
        segmentedButton.setToggleGroup(toggleGroupValue);
        Button debugButton = new Button("Debug");
        debugButton.setOnMouseClicked(event -> handleDebugClick());

        VBox vBox = new VBox(segmentedButton, debugButton);
        vBox.setSpacing(10);
        StackPane root = new StackPane(vBox);

        Scene scene = new Scene(root, 400, 400);
        stage.setScene(scene);
        stage.show();
    }

    void handleDebugClick(){
        mouseModeObjectProperty.set(MouseMode.DELETING);
    }

    public enum MouseMode{
        SELECTION,
        EDITING,
        DELETING
    }
}

enter image description here


0
投票

我参加聚会有点晚了,但这里有一种简单且无依赖的方法,可以将切换组的 selectedToggle 属性包装在可以双向绑定的内容中

public static SimpleObjectProperty<Toggle> wrapToggleGroupSelectedProperty(ToggleGroup tg) {
    SimpleObjectProperty<Toggle> bidir = new SimpleObjectProperty<>(tg.getSelectedToggle()) {
        @Override
        public Toggle get() {
            Toggle superGet = super.get();
            if(tg.selectedToggleProperty().get() != superGet)
                set(superGet = tg.selectedToggleProperty().get());
            return superGet;
        }

        @Override
        public void set(Toggle newValue) {
            if(tg.selectedToggleProperty().get() != newValue)
                tg.selectToggle(newValue);
            super.set(newValue);
        }
    };
    tg.selectedToggleProperty().addListener((s,a,b)-> {
        tg.selectedToggleProperty().get();
        bidir.get();
    });
    return bidir;
}

同样,您可以像这样简单地用映射来包装属性

public static SimpleObjectProperty<MouseMode> wrapToggleGroupSelectedProperty(ToggleGroup tg) {
    SimpleObjectProperty<MouseMode> bidir = new SimpleObjectProperty<>() {
        @Override
        public MouseMode get() {
            MouseMode superGet = super.get();
            MouseMode selectedToggle = map(tg.selectedToggleProperty().get());
            if(selectedToggle != superGet)
                set(superGet = selectedToggle;
            return superGet;
        }

        @Override
        public void set(MouseMode newValue) {
            Toggle mapped = map(newValue);
            if(tg.selectedToggleProperty().get() != mapped)
                tg.selectToggle(mapped);
            super.set(newValue);
        }

        private Toggle map(MouseMode mode) {
            return switch(mode) {
                case SELECTION: return toggleA;
                case EDITING: return toggleB;
                case DELETING: return toggleC;
            };
        }
        private MouseMode map(Toggle toggle) {
            if(toggle == toggleA)
                return MouseMode.SELECTION;
            else if(toggle == toggleB)
                return MouseMode.EDITING;
            else if(toggle == toggleC)
                return MouseMode.DELETING;
            return null;
        }
    };
    tg.selectedToggleProperty().addListener((s,a,b)-> {
        tg.selectedToggleProperty().get();
        bidir.get();
    });
    return bidir;
}
© www.soinside.com 2019 - 2024. All rights reserved.