Я добавляю некоторые ускорители к моим приложениям JavaFX 8, и я борюсь с ConcurrentModificationException, когда я пытаюсь очистить ускорители сцены. Позвольте мне объяснить, вместо этого используйте одну сцену для каждого диалога, я создал псевдо-диалог, это просто панель с наложением. Когда панель добавляется, ускорители старой панели удаляются и добавляются ускорители новой. Когда диалог закрыт, старые ускорители восстанавливаются:
public abstract class DialogController{
private Map<KeyCombination, Runnable> accelerators;
.
.
.
protected final void loadMainPane() throws IOException {
.
.
.
pane.sceneProperty().addListener(new ChangeListener<Scene>() {
@Override
public void changed(ObservableValue<? extends Scene> observableValue, Scene oldScene, Scene newScene) {
if (newScene != null)
enableAccelerators();
else
disableAccelerators(oldScene);
}
});
}
protected void enableAccelerators() {
accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
pane.getScene().getAccelerators().clear();
pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
@Override
public void run() {
submit(null);
}
});
}
protected void disableAccelerators(Scene scene) {
scene.getAccelerators().putAll(accelerators);
}
}
Когда подклассу необходимо добавить новые ускорители, им нужно только переопределить метод enableAccelerators(), вызвать супер и добавить желаемые новые ускорители. Однако иногда, когда enableAccelerators пытаются очистить ускорители сцены, это исключение:
java.util.ConcurrentModificationException: null
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:588)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:576)
at com.sun.javafx.scene.KeyboardShortcutsHandler.processAccelerators(KeyboardShortcutsHandler.java:340)
at com.sun.javafx.scene.KeyboardShortcutsHandler.dispatchBubblingEvent(KeyboardShortcutsHandler.java:168)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:204)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3949)
at javafx.scene.Scene$KeyHandler.access$2100(Scene.java:3896)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2036)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2493)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:170)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:123)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:197)
at com.sun.glass.ui.View.handleKeyEvent(View.java:517)
at com.sun.glass.ui.View.notifyKey(View.java:927)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.access$200(GtkApplication.java:48)
at com.sun.glass.ui.gtk.GtkApplication$6$1.run(GtkApplication.java:149)
at java.lang.Thread.run(Thread.java:745)
Я подозреваю, что мой код очищает ускорители, в то время как JavaFX выполняет итерацию. Я попытался поставить очищающую часть внутри синхронизированного блока, но это не сработало:
protected void enableAccelerators() {
accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
synchronized (pane.getScene().getAccelerators()){
pane.getScene().getAccelerators().clear();
}
pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
@Override
public void run() {
submit(null);
}
});
}
Как я могу избежать этой проблемы?
Я решил проблему, избегая удаления элемента. Вместо того, чтобы удалить элемент карты, теперь я делаю значение равным null:
protected void enableAccelerators() {
accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
for (KeyCombination combination : accelerators.keySet())
accelerators.put(combination, null);
pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
@Override
public void run() {
submit(null);
}
});
}
Возможно, вам придется поместить код для обновления ускорителей в вызове Platform.runLater()
. Пытаться
public void changed(ObservableValue<? extends Scene> observableValue, Scene oldScene, Scene newScene) {
Platform.runLater(() -> {
if (newScene != null)
enableAccelerators();
else
disableAccelerators(oldScene);
});
}
Если поток приложения FX вызывает код во время обработки событий, он может, как вы полагаете, сделать это, итерации через сборник ускорителей. Использование Platform.runLater()
приведет к тому, что ваш код будет выполнен, если после того, как что-то в настоящее время находится на рассмотрении в потоке приложения FX, было обработано.
(Если это кажется немного взломанным, ну, виноватым, как обвинили...)