【问题标题】:Equivalent of FocusEvent.getOppositeComponent in JavaFx等效于 JavaFx 中的 FocusEvent.getOppositeComponent
【发布时间】:2019-05-16 16:05:07
【问题描述】:

在我的 JavaFx 应用程序中,我想在主框架获得焦点时调用一个方法。但是,我只想在焦点在我的应用程序之外并回来的情况下做出反应(例如,当对话框关闭时)。

当应用程序在Swing中时,我可以使用该方法

FocusEvent.getOppositeComponent

(对应于失去焦点的元素),如果它为 null,我知道焦点之前在我的应用程序之外。

我没有在 JavaFX 中找到任何等价物。

我尝试通过在我的窗口上添加事件过滤器来查看窗口事件:

primaryStage.addEventFilter(Event.ANY, e -> System.out.println("event " + e));

但它不跟踪焦点事件。

【问题讨论】:

    标签: java javafx focus


    【解决方案1】:

    JavaFX 中没有等价物。每个窗口的焦点更改分别作为boolean property 处理,因此您只能判断窗口是否接收或失去焦点。如果您为应用程序中的所有窗口注册一个侦听器,您可以判断其中一个窗口是否在另一个获得焦点时失去焦点。

    JavaFX 中没有“FocusEvent”,您可以找到Event 中列出的所有事件类型。

    您可以申请功能here

    【讨论】:

    • 不幸的是,focused 属性是不够的,因为前一个窗口在下一个窗口获得焦点之前失去焦点;换句话说,即使在应用程序中切换窗口时,有时也没有一个窗口具有焦点(至少在 Java 端)。否则我的(已删除)答案将提供有效的解决方案。
    • 这几乎是我想出来的。我希望一些 JavaFX 大师会告诉我反思性地操纵一些晦涩的 Skin 类或内部类来解决我的问题 :-) 我会建议进行更改,尽管您的链接对我来说似乎不太好,JavaFX 不是 JDK 的一部分从 Java 11 开始,但我发现的只是 openjfx 邮件列表。我会在那里碰碰运气,并及时通知您。
    • @Balderk 您可以通过链接提交,JavaFX 仍然使用 Oracle 设置的相同数据库和程序,只是 jar 是单独发货的。邮件列表也是一个不错的选择。
    • @user1803551 好吧,我以为他们完全独立了。我没有运气向邮件列表发送了一封电子邮件。那我试试问题跟踪器。
    【解决方案2】:

    我终于找到了一种处理问题的半令人满意的方法,使用 JavaFX 中的事件顺序,所以我将它作为答案发布,以防它可以帮助其他人。

    当窗口 w1 关闭时,将焦点放在窗口 w2 上,事件顺序如下:

    1. w1 收到事件 WINDOW_HIDING
    2. w2 focusProperty 更改为 true
    3. w1 收到事件 WINDOW_HIDDEN

    所以我写了下面的代码让我知道焦点是否来自内部窗口:

    public class MainStage {
        private Stage primaryStage;
        private AtomicBoolean triggerEventOnFocusGain = new AtomicBoolean(true);
    
        ...
    
        primaryStage.focusedProperty.addListener((prop, oldVal, newVal) -> {
            if(newVal.booleanValue() && triggerEventOnFocusGain.get()) {
                doStuff();
            }
        });
    }
    
    public class SomeDialog {
        private MainStage mainStage;
        private Window dialogWindow;
    
        ...
    
        dialogWindow.addEventHandler(WindowEvent.WINDOW_HIDING, event ->
            mainStage.setTriggerEventOnFocusGain(false));
        dialogWindow.addEventHandler(WindowEvent.WINDOW_HIDDEN, event ->
            mainStage.setTriggerEventOnFocusGain(true));
    }
    

    唯一的问题是我必须对所有内部窗口/对话框都这样做。

    在我的情况下,我最终决定只对少数对话框执行此操作,因为它们触发事件会出现问题,而忽略其他对话框。

    当然,另一种方法是为执行上述代码的所有视图类引入一个公共抽象父级。

    【讨论】:

    • 这在两个阶段之间简单地切换(不关闭一个阶段)时是否有效?另外,请注意,从 JavaFX 9 开始,有一个 Window.getWindows() 方法返回一个 ObservableList<Window>;它包含在任何给定时间的所有显示窗口。我认为 JavaFX 8 也有类似的内部方法。
    • 公平点,我没有考虑到这一点。我的大多数窗口都是模态的(所有有问题的都是模态的)所以这对我有用。我想我应该在问题中这么说。我想在他们实施我的增强请求之前,我们不会有合适的解决方案。关于列出窗口的 API,我很确定它存在,因为 TestFX 可以列出它们。我会在星期一回到办公室时检查它是如何做到的,并在此处发布我的发现以确保完整性。
    • @Slaw 我刚刚发现您的方法返回一个 ObservableList,因此可以添加一个侦听器并自动将窗口事件侦听器附加到任何新窗口,这真是天才? 我一定会检查 JavaFX 8 way 也返回一个 ObservableList,那太好了。
    • @Slaw 不幸的是,在 JavaFX 8 中,您必须使用方法 Window.impl_getWindows(),它返回一个迭代器而不是 ObservableValue。不过,我会记住您的建议,以了解何时允许我切换到 Java 11。谢谢。
    【解决方案3】:

    JavaFX 层次结构基于:Stage -> Scene -> Nodes -> ... -> Nodes:

    如果你想监听Stage(窗口)的焦点,你可以在StageStagefocusedProperty中添加监听器:

    Stage stage = ...
    stage.focusedProperty()
            .addListener((observable, oldValue, newValue) -> {
                        if (!stage.isFocused()) { 
                           //action
                        }
                    }
            );
    

    这并不能解决问题中的问题。你不能在这里告诉哪个组件有焦点。 oldValuenewValue 是布尔值,所以你的 if 是微不足道的

    你可以检查你们Stages 都输了 重点(实现自定义ChangeListener):

    class AllStageUnfocusedListener implements ChangeListener<Boolean>{
        //IdentitySet and Runnable use only as example
        private final Set<Stage> stageSet;
        private final Runnable runnable;
    
        public AllStageUnfocusedListener(Runnable runnable) {
            this.stageSet =  Collections.newSetFromMap(new IdentityHashMap<>());
            this.runnable =  runnable;
        }
    
        public ChangeListener<Boolean> add(Stage stage){
            stageSet.add(stage);
            return this;
        }
    
        @Override
        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
            if(isAllStageLostFocus()){
                runnable.run();
            }
        }
    
        private boolean isAllStageLostFocus() {
            for (Stage stage : stageSet) {
                if (stage.isFocused()) {
                    return false;
                }
            }
            return true;
        }
    } 
    

    并将Listener 添加到Focused Property

    AllStageUnfocusedListener changeListener = new AllStageUnfocusedListener(() -> { /* action */ });
    Stage stage = ...
    stage.focusedProperty()
                .addListener(changeListener.add(stage))
    

    【讨论】:

    • 这并不能解决问题中的问题。你不能在这里告诉哪个组件有焦点。 oldValuenewValuebooleans,所以你的 if 是微不足道的。
    • 是的,你可以分辨出哪个组件有焦点,但你可以高出那个组件失去它=> 例如:使用集合IdentitySet&lt;Stage&gt;(Collections.newSetFromMap( new IdentityHashMap&lt;&gt;())将所有Stages 添加到它并检查所有都失去焦点
    • 编辑后效果更好。您还必须检查 Dialog 焦点,而不仅仅是阶段。
    • 感谢您的回答。不幸的是,我不想将侦听器添加到我所有的内部对话框和窗口中,它们太多了......
    • 此外,您建议的代码只会告诉我它是具有焦点的主窗口而不是内部窗口,它不会跟踪刚刚失去焦点的人。这对代码来说会很烦人,因为它是基于时间的(我的一个窗口在最后 X 毫秒或其他时间失去了焦点),并且由于不同桌面上的性能差异,这种代码不可靠。不算我需要在关闭内部窗口时处理侦听器删除以避免内存泄漏的事实。这将变得难以维护,而且我的团队中并不孤单......
    猜你喜欢
    • 2014-04-11
    • 1970-01-01
    • 2015-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多