伪类:设置自定义状态时状态中断的通知
PseudoClass: notification from states broken on setting custom state
下面是一个示例,它的行为并不像我期望的那样——想知道行为或我的期望是否错误(很可能是我做了一些愚蠢的错误;)。
它的作用:
- 它使用自定义伪class实现自定义按钮,与code example in the java doc
中的方式非常相似
- 它在按钮
getPseudoClassStates()
返回的 observableSet 上注册一个侦听器,记录更改时的活动状态
- 它有 ui(一个简单的按钮)切换自定义按钮的特殊状态
预期行为:通过mouse/keyboard(悬停、聚焦、准备...)与自定义按钮交互的通知
实际行为:如果没有设置特殊状态,和预期的一样,一旦设置了特殊状态就没有通知
问题:
- 错误或功能?
- 如何更改始终通知的设置?
范例:
public class PseudoStateNotification extends Application {
private SpecialButton specialButton;
private SetChangeListener sl;
public static class SpecialButton extends Button {
private static PseudoClass SPECIAL = PseudoClass.getPseudoClass("special");
private BooleanProperty special = new SimpleBooleanProperty(this, "special", false) {
@Override
protected void invalidated() {
pseudoClassStateChanged(SPECIAL, get());
}
};
public SpecialButton(String text) {
super(text);
}
public void setSpecial(boolean sp) {
special.set(sp);
}
public boolean isSpecial() {
return special.get();
}
public BooleanProperty specialProperty() {
return special;
}
}
private Parent createContent() {
sl = change -> LOG.info("pseudo-changed: " + change.getSet());
specialButton = new SpecialButton("custom buttom");
specialButton.getPseudoClassStates().addListener(sl);
BorderPane pane = new BorderPane(specialButton);
Button toggle = new Button("toggle special state");
toggle.setOnAction(e -> {
specialButton.setSpecial(!specialButton.isSpecial());
});
pane.setBottom(toggle);
return pane;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 200, 200));
URL uri = getClass().getResource("pseudo.css");
stage.getScene().getStylesheets().add(uri.toExternalForm());
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(PseudoStateNotification.class.getName());
}
样式表pseudo.css:
.button:special {
-fx-text-fill: red;
}
更新:
在 , the underlying reason is that ObservaleSet<PseudoClass> Node.getPseudoClassStates()
returns a free-flying unmodifiable wrapper around the states which makes the returned set garbage-collectable. In my book that's a bug 中进行了很好的跟踪 - 与 api 相比,例如 TableView.getVisibleLeafColumns()
已正确实现并保持对包装器的强引用。变通方法是在客户端代码中保持对该集合的强烈引用。
这不是自定义状态的问题,而是弱听众在某些时候被垃圾收集的常见情况:您的 SetChangeListener
会在一段时间后消失。
您可以完全不点击切换按钮来验证它。只需将鼠标悬停在按钮上几次,然后单击它。一段时间后您将不会收到任何通知。
例如,这是一系列更改:
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
在最后一个之后,我不再收到通知了。
为了避免这种情况,我们需要做的就是持有伪 类:
的可观察集合的强引用
private ObservableSet<PseudoClass> states;
private Parent createContent() {
sl = change -> LOG.info("pseudo-changed: " + change.getSet());
specialButton = new SpecialButton("custom buttom");
states = specialButton.getPseudoClassStates();
states.addListener(sl);
...
}
现在它可以处理来自按钮或切换按钮的任何状态更改。
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: []
INFO: pseudo-changed: [special]
INFO: pseudo-changed: [hover, special]
INFO: pseudo-changed: [hover, pressed, special]
INFO: pseudo-changed: [hover, pressed, focused, special]
...
INFO: pseudo-changed: [focused, special]
INFO: pseudo-changed: [special]
INFO: pseudo-changed: []
INFO: pseudo-changed: [hover]
...
编辑
虽然 finalize
已被弃用,但它仍可用于检查侦听器是否在第一种情况下被 gc:
specialButton.getPseudoClassStates().addListener(new SetChangeListener<PseudoClass>() {
@Override
public void onChanged(Change<? extends PseudoClass> change) {
LOG.info("pseudo-changed: " + change.getSet());
}
@Override
protected void finalize() throws Throwable {
super.finalize();
LOG.info("SetChangeListener finalized by gc");
}
});
所以在上面的测试中,你会得到:
...
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: SetChangeListener finalized by gc
如前所述,此后不再有通知。
但是,如果您持有强引用,即使您显式调用 System.gc()
,侦听器也不会被 gc。
还值得检查 FXCollection::UnmodifiableObservableSet
实现,其中 WeakSetChangeListener
是 used,同时持有对侦听器本身的引用:
private SetChangeListener<E> listener;
private void initListener() {
if (listener == null) {
listener = c -> {
callObservers(new SetAdapterChange<E>(UnmodifiableObservableSet.this, c));
};
this.backingSet.addListener(new WeakSetChangeListener<E>(listener));
}
}
这意味着引用 private SetChangeListener<E> listener;
并不是真正需要的。
下面是一个示例,它的行为并不像我期望的那样——想知道行为或我的期望是否错误(很可能是我做了一些愚蠢的错误;)。
它的作用:
- 它使用自定义伪class实现自定义按钮,与code example in the java doc 中的方式非常相似
- 它在按钮
getPseudoClassStates()
返回的 observableSet 上注册一个侦听器,记录更改时的活动状态 - 它有 ui(一个简单的按钮)切换自定义按钮的特殊状态
预期行为:通过mouse/keyboard(悬停、聚焦、准备...)与自定义按钮交互的通知
实际行为:如果没有设置特殊状态,和预期的一样,一旦设置了特殊状态就没有通知
问题:
- 错误或功能?
- 如何更改始终通知的设置?
范例:
public class PseudoStateNotification extends Application {
private SpecialButton specialButton;
private SetChangeListener sl;
public static class SpecialButton extends Button {
private static PseudoClass SPECIAL = PseudoClass.getPseudoClass("special");
private BooleanProperty special = new SimpleBooleanProperty(this, "special", false) {
@Override
protected void invalidated() {
pseudoClassStateChanged(SPECIAL, get());
}
};
public SpecialButton(String text) {
super(text);
}
public void setSpecial(boolean sp) {
special.set(sp);
}
public boolean isSpecial() {
return special.get();
}
public BooleanProperty specialProperty() {
return special;
}
}
private Parent createContent() {
sl = change -> LOG.info("pseudo-changed: " + change.getSet());
specialButton = new SpecialButton("custom buttom");
specialButton.getPseudoClassStates().addListener(sl);
BorderPane pane = new BorderPane(specialButton);
Button toggle = new Button("toggle special state");
toggle.setOnAction(e -> {
specialButton.setSpecial(!specialButton.isSpecial());
});
pane.setBottom(toggle);
return pane;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 200, 200));
URL uri = getClass().getResource("pseudo.css");
stage.getScene().getStylesheets().add(uri.toExternalForm());
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(PseudoStateNotification.class.getName());
}
样式表pseudo.css:
.button:special {
-fx-text-fill: red;
}
更新:
在 ObservaleSet<PseudoClass> Node.getPseudoClassStates()
returns a free-flying unmodifiable wrapper around the states which makes the returned set garbage-collectable. In my book that's a bug 中进行了很好的跟踪 - 与 api 相比,例如 TableView.getVisibleLeafColumns()
已正确实现并保持对包装器的强引用。变通方法是在客户端代码中保持对该集合的强烈引用。
这不是自定义状态的问题,而是弱听众在某些时候被垃圾收集的常见情况:您的 SetChangeListener
会在一段时间后消失。
您可以完全不点击切换按钮来验证它。只需将鼠标悬停在按钮上几次,然后单击它。一段时间后您将不会收到任何通知。
例如,这是一系列更改:
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [hover, pressed, focused]
INFO: pseudo-changed: [hover, pressed, focused, armed]
INFO: pseudo-changed: [hover, focused, armed]
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
在最后一个之后,我不再收到通知了。
为了避免这种情况,我们需要做的就是持有伪 类:
的可观察集合的强引用private ObservableSet<PseudoClass> states;
private Parent createContent() {
sl = change -> LOG.info("pseudo-changed: " + change.getSet());
specialButton = new SpecialButton("custom buttom");
states = specialButton.getPseudoClassStates();
states.addListener(sl);
...
}
现在它可以处理来自按钮或切换按钮的任何状态更改。
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: []
INFO: pseudo-changed: [special]
INFO: pseudo-changed: [hover, special]
INFO: pseudo-changed: [hover, pressed, special]
INFO: pseudo-changed: [hover, pressed, focused, special]
...
INFO: pseudo-changed: [focused, special]
INFO: pseudo-changed: [special]
INFO: pseudo-changed: []
INFO: pseudo-changed: [hover]
...
编辑
虽然 finalize
已被弃用,但它仍可用于检查侦听器是否在第一种情况下被 gc:
specialButton.getPseudoClassStates().addListener(new SetChangeListener<PseudoClass>() {
@Override
public void onChanged(Change<? extends PseudoClass> change) {
LOG.info("pseudo-changed: " + change.getSet());
}
@Override
protected void finalize() throws Throwable {
super.finalize();
LOG.info("SetChangeListener finalized by gc");
}
});
所以在上面的测试中,你会得到:
...
INFO: pseudo-changed: [hover, focused]
INFO: pseudo-changed: [focused]
INFO: pseudo-changed: [hover, focused]
INFO: SetChangeListener finalized by gc
如前所述,此后不再有通知。
但是,如果您持有强引用,即使您显式调用 System.gc()
,侦听器也不会被 gc。
还值得检查 FXCollection::UnmodifiableObservableSet
实现,其中 WeakSetChangeListener
是 used,同时持有对侦听器本身的引用:
private SetChangeListener<E> listener;
private void initListener() {
if (listener == null) {
listener = c -> {
callObservers(new SetAdapterChange<E>(UnmodifiableObservableSet.this, c));
};
this.backingSet.addListener(new WeakSetChangeListener<E>(listener));
}
}
这意味着引用 private SetChangeListener<E> listener;
并不是真正需要的。