Pergunta

Eu quero criar um StackPane onde, independentemente do que for adicionado, um nó de minha escolha estará sempre na frente (em vez de ter que ter cuidado com a ordem em que adiciono as coisas ou lembrar de chamar toFront() nesse nó específico sempre que algo for adicionado.)

Para fazer isso, simplesmente coloco um ouvinte na lista filho do relevante StackPane objeto, de modo que sempre que alguma coisa mudar, ele chame toFront() no nó relevante, por exemplo:

public class Test extends Application {

    @Override
    public void start(Stage stage) {
        StackPane root = new StackPane();
        final Rectangle r1 = new Rectangle(50, 50);
        root.getChildren().add(r1);
        root.getChildren().addListener(new ListChangeListener<Node>() {
            @Override
            public void onChanged(ListChangeListener.Change<? extends Node> change) {
                try {
                    while(change.next()) {
                        if(change.wasAdded()) {
                            r1.toFront();
                        }
                    }
                }
                catch(Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        root.getChildren().add(new Rectangle(50, 50));

        stage.setScene(new Scene(root));
        stage.show();
    }
}

No Java 7, isso funciona perfeitamente.No entanto, no JFX8 (compilação mais recente baixada agora), ele falha com o seguinte:

java.lang.UnsupportedOperationException
        at java.util.Collections$UnmodifiableList.add(Collections.java:1374)
        at javafx.collections.ListChangeBuilder.nextRemove(ListChangeBuilder.java:208)
        at javafx.collections.ObservableListBase.nextRemove(ObservableListBase.java:150)
        at javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:181)
        at com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:284)
        at com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:209)
        at javafx.scene.Parent.impl_toFront(Parent.java:624)
        at javafx.scene.Node.toFront(Node.java:1713)
        at test.Test$1.onChanged(Test.java:34)
        at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:315)
        at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:72)
        at com.sun.javafx.collections.VetoableListDecorator$1.onChanged(VetoableListDecorator.java:77)
        at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:315)
        at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:72)
        at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
        at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
        at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
        at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
        at javafx.collections.ModifiableObservableListBase.add(ModifiableObservableListBase.java:155)
        at java.util.AbstractList.add(AbstractList.java:108)
        at com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:200)
        at test.Test.start(Test.java:41)
        at com.sun.javafx.application.LauncherImpl$8.run(LauncherImpl.java:837)
        at com.sun.javafx.application.PlatformImpl$7.run(PlatformImpl.java:331)
        at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:297)
        at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:294)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl$6.run(PlatformImpl.java:294)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.access$300(WinApplication.java:39)
        at com.sun.glass.ui.win.WinApplication$4$1.run(WinApplication.java:112)
        at java.lang.Thread.run(Thread.java:744)

E sim, test.Test$1.onChanged(Test.java:34) de fato se refere r1.toFront();.

Isso deve ser considerado um bug ou estou quebrando alguma regra que não conheço ao tentar conseguir as coisas dessa maneira?Eu me perguntei se a lista ainda estava sendo alterada enquanto o onChanged() método estava sendo executado, e toFront() também alteraria o conteúdo da lista, daí a exceção - mas o Javadoc para onChanged() diz claramente:

Chamado depois uma alteração foi feita em um ObservableList.

(O negrito é meu.)

EDITAR:Neste ponto estou mais certo de que é um bug, então o relatório de bug relacionado é aqui.

Foi útil?

Solução

Parece que você não tem permissão para modificar uma lista (JavaFX) dentro de um manipulador de eventos que está atualmente manipulando outro evento de modificação (anterior) da mesma lista.Embora isto pareça razoável, não é evidente, pelo que deveria haver uma excepção mais óbvia nesse caso.

Infelizmente, exceções sem fala são muito comuns no JavaFX.

Felizmente, a solução/solução alternativa é bastante fácil:Chame seu código de modificação (aqui: r1.toFront) por Platform.runLater(), isso atrasará sua modificação para acontecer após o evento de origem:

root.getChildren().addListener(new ListChangeListener<Node>() {
    @Override
    public void onChanged(ListChangeListener.Change<? extends Node> change) {
        Platform.runLater(new Runnable() {
            public void run() { r1.toFront(); }                 
        });
    }
});

Nota: toFront não faz nada, se o componente já estiver na frente.Isso evita loops infinitos.No entanto, como isso não é mencionado explicitamente na documentação, você pode não confiar nisso.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top