문제

Imagine having an enum defining mouse-modes:

public enum MouseMode {
SELECTION,
EDITING,
DELETING }

And imagine having a toggle-group made of 3 buttons:

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

I want a field MouseMode currentMode to be bidirectionally linked to the toggle-group. Whenever a toggle is set, currentMode is switched accordingly but also if some external process changes currentMode (maybe a key press) then the togglegroup adapts accordingly.

I can do this with 2 listeners but I wonder if there is a way to create a custom bidirectional map.

도움이 되었습니까?

해결책

I don't think there is a way to do this directly. While a general-purpose

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

might make a good addition to the API, even that wouldn't help in this case as the ToggleGroup's selectedProperty is read only (since selection needs to be handled when each Toggle's setSelected(...) method is invoked, as well as by the ToggleGroup's selectedProperty).

Using a couple of listeners is the way to go in this case.

The closest thing to the "custom bidirectional map" is the

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

method. In the case where you have an (writeable) ObjectProperty<S> and (writeable) ObjectProperty<T> you can in theory use two bidirectional bindings and an intermediate StringProperty to bind them together. In practice, this is almost always more code than just using two listeners, and is also less efficient.

다른 팁

I have successfully made use of the ToggleGroupValue class in the JFXtras project.

Here is an example:

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

I'm using Java bean property adapter, but you can just use the last line of this code and bind it.

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

You need to have a small BindingUtils class for ToggleGroup.

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

This answer is inspired by tunabot. Instead of using RadioButton, this answer will use ToogleButton, and to make it look more beautiful, we will use SegmentedButton from ControlsFX. We can bind bidirectional selected toggle button by using valueProperty from ToggleGroupValue.

There is a debug button that when we click this button the selected button will change to DELETING button.

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top