Javafx 状态更改时更新fxml标记

Javafx 状态更改时更新fxml标记,javafx,fxml,Javafx,Fxml,我想创建某种“security”fxml标记,根据某种SecurityManager类的状态禁用/使其子项不可见 我遇到的困难如下。当SecurityManager类的状态更改时,我希望所有SecurityTag更新其可见属性。当然,我可以在每次调用标记构造函数时,将所有SecurityTag节点添加到静态列表中,并在SecurityManger类更改状态时在其上循环。但是,如果一个安全标记节点从父节点中删除,该怎么办?我怎样才能把它从列表中去掉?或者也许有更好的方法来处理这个问题 public

我想创建某种“security”fxml标记,根据某种SecurityManager类的状态禁用/使其子项不可见

我遇到的困难如下。当SecurityManager类的状态更改时,我希望所有SecurityTag更新其可见属性。当然,我可以在每次调用标记构造函数时,将所有SecurityTag节点添加到静态列表中,并在SecurityManger类更改状态时在其上循环。但是,如果一个安全标记节点从父节点中删除,该怎么办?我怎样才能把它从列表中去掉?或者也许有更好的方法来处理这个问题

public class SecurityTag extends Pane {

    public Security() {
        super();
        this.setVisible(false);
    }

}

最简单的方法是将
SecurityManager
中的
authorized
属性设置为JavaFX属性:

package org.jamesd.examples.security;

import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;

public class SecurityManager {

    private final ReadOnlyBooleanWrapper authorized ;

    public SecurityManager() {
        this.authorized = new ReadOnlyBooleanWrapper(false) ;
    }



    public void login() {
        this.authorized.set(true);
    }

    public void logout() {
        this.authorized.set(false);
    }

    public ReadOnlyBooleanProperty authorizedProperty() {
        return authorized.getReadOnlyProperty();
    }

    public boolean isAuthorized() {
        return authorizedProperty().get();
    }

}
现在,您可以简单地将相关属性绑定到
SecurityManager
authorized
属性。根据要绑定的属性,可以直接在FXML或控制器中执行此操作。您可以将
SecurityManager
实例放置在
fxmloader
命名空间中,使其可用于FXML文件,只需将其作为参数传递给控制器构造函数并手动设置控制器即可(即不使用
fxmloader
上的
fx:controller
属性)

下面是一个FXML文件示例。请注意“特权操作”按钮是如何将其可见性绑定到安全管理器的

visible = "${securityManager.authorized}" 
你也可以这样做

disable = "${ !securityManager.authorized}" 
如果你只是想禁用它

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets ?>
<?import javafx.scene.layout.BorderPane ?>
<?import javafx.scene.layout.VBox ?>
<?import javafx.scene.control.Button ?>
<?import javafx.scene.control.Label ?>

<BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <top>
        <Label fx:id = "securityStatus"></Label>
    </top>
    <center>
        <VBox spacing="5" fillWidth="true">
            <Button text="Regular Action" maxWidth="Infinity"></Button>
            <Button text="Privileged Action" visible = "${securityManager.authorized}" maxWidth="Infinity"></Button>

            <padding>
                <Insets top="5" left="5" right="5" bottom="5"/>
            </padding>
        </VBox>
    </center>
    <left>
        <VBox spacing="5" fillWidth="true">
            <Button text="login" onAction="#login" maxWidth="Infinity"/>
            <Button text="logout" onAction="#logout" maxWidth="Infinity"/>

            <padding>
                <Insets top="5" left="5" right="5" bottom="5"/>
            </padding>
        </VBox>
    </left>
</BorderPane>
最后,以下是所有组件的组装方式:

package org.jamesd.examples.security;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class SecurityApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        SecurityManager securityManager = new SecurityManager();

        FXMLLoader loader = new FXMLLoader(getClass().getResource("SecurityExample.fxml"));
        loader.getNamespace().put("securityManager", securityManager);
        loader.setController(new SecurityController(securityManager));

        Scene scene = new Scene(loader.load());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}
请注意,这种方法避免了任何不必要的JavaFX节点子类化(例如,
窗格
),这可能会导致问题(例如,您可能希望将安全相关节点放置在现有布局窗格中,这使得使用标准布局更加困难)


如评论中所建议的,如果您希望
SecurityManager
类与JavaFX无关(一般来说可能是桌面Java),那么您可以简单地为使用JavaFX属性的UI创建一个委托,并安排在“真正的”安全管理器更新时对其进行更新

例如,这里有一个
SecurityManager
,它实现了一个经典的“侦听器”模式:

最后用

package org.jamesd.examples.security;

import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class SecurityController {

    private final UISecurityDelegate securityManager ;

    @FXML
    private Label securityStatus ;

    public SecurityController(UISecurityDelegate securityManager) {
        this.securityManager = securityManager ;
    }

    public void initialize() {
        securityStatus.textProperty().bind(Bindings
            .when(securityManager.authorizedProperty())
            .then("Logged In")
            .otherwise("Logged Out")
        );
    }

    @FXML
    private void login() {
        securityManager.login();
    }

    @FXML
    private void logout() {
        securityManager.logout();
    }
}


此功能的标准模式称为“模型视图控制器”,或MVC。有几种变体。基本思想是使用JavaFX
BooleanProperty
for
authorized
实现您的
SecurityManager
类,然后将节点的可见属性绑定到该类。请看,可能是问题,可能是我对此要求太严格,使用这种方法需要添加JavaFX functi我想避免的SecurityManager类中的个性化。最坏的情况下,您会使用JavaFX属性,它与UI代码没有任何关系。您还可以使用带有属性更改侦听器的标准Java Bean,并使用
JavaBeanBooleanProperty
作为
SecurityManager
和UI代码之间的接口。不相关:如果您不添加任何与布局相关的功能,请不要扩展布局作为一种小小的好奇,您可以使用CSS来完成此操作。向任何需要授权的控件添加样式类(例如
.privileged
)。创建自定义CSS
伪类(例如
authorized
)当用户登录/注销时,只需在UI的根节点上设置/取消设置它。然后只需使用规则
.privileged{visibility:false;}
.root:authorized.privileged{visibility:visible}
在您的外部CSS文件中。这样,当安全管理器更改状态时,只有根节点需要以任何方式更新,并且您可以避免对
窗格
进行不必要的子类化,这会带来许多问题。感谢您详细的回答。
package org.jamesd.examples.security;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class SecurityApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        SecurityManager securityManager = new SecurityManager();

        FXMLLoader loader = new FXMLLoader(getClass().getResource("SecurityExample.fxml"));
        loader.getNamespace().put("securityManager", securityManager);
        loader.setController(new SecurityController(securityManager));

        Scene scene = new Scene(loader.load());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}
package org.jamesd.examples.security;

@FunctionalInterface
public interface AuthorizationListener {
    void authorizationChanged(boolean newStatus);
}
package org.jamesd.examples.security;

import java.util.ArrayList;
import java.util.List;

public class SecurityManager  {

    private boolean authorized ;
    private final List<AuthorizationListener> listeners ;

    public SecurityManager() {
        this.listeners = new ArrayList<>();
    }

    public void login() {
        setAuthorized(true);
    }

    public void logout() {
        setAuthorized(false);
    }

    public void addListener(AuthorizationListener listener) {
        listeners.add(listener);
    }

    public void removeListener(AuthorizationListener listener) {
        listeners.remove(listener);
    }

    public boolean isAuthorized() {
        return authorized;
    }

    private void setAuthorized(boolean authorized) {
        if (! this.authorized == authorized) {
            this.authorized = authorized ;
            listeners.forEach(l -> l.authorizationChanged(authorized));
        }
    }

}
package org.jamesd.examples.security;

import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;

public class UISecurityDelegate  {

    private final ReadOnlyBooleanWrapper authorized ;

    private final SecurityManager manager ;

    public UISecurityDelegate(SecurityManager manager) {
        this.manager = manager ;
        this.authorized = new ReadOnlyBooleanWrapper(manager.isAuthorized()) ;
        manager.addListener(authorized::set);
    }

    public void login() {
        manager.login();
    }
    public void logout() {
        manager.logout();
    }

    public ReadOnlyBooleanProperty authorizedProperty() {
        return authorized.getReadOnlyProperty();
    }

    public boolean isAuthorized() {
        return authorizedProperty().get();
    }

}
package org.jamesd.examples.security;

import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class SecurityController {

    private final UISecurityDelegate securityManager ;

    @FXML
    private Label securityStatus ;

    public SecurityController(UISecurityDelegate securityManager) {
        this.securityManager = securityManager ;
    }

    public void initialize() {
        securityStatus.textProperty().bind(Bindings
            .when(securityManager.authorizedProperty())
            .then("Logged In")
            .otherwise("Logged Out")
        );
    }

    @FXML
    private void login() {
        securityManager.login();
    }

    @FXML
    private void logout() {
        securityManager.logout();
    }
}
package org.jamesd.examples.security;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class SecurityApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        // probably created by data or service layer, etc:
        SecurityManager securityManager = new SecurityManager();

        UISecurityDelegate securityDelegate = new UISecurityDelegate(securityManager) ;

        FXMLLoader loader = new FXMLLoader(getClass().getResource("SecurityExample.fxml"));
        loader.getNamespace().put("securityManager", securityDelegate);
        loader.setController(new SecurityController(securityDelegate));

        Scene scene = new Scene(loader.load());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}