JavaFX - 简单的自定义最小窗口实现

时间:2016-04-07 19:17:26

标签: eclipse javafx custom-controls customization fxml

我刚发现JavaFX,我真的很喜欢它。我讨厌java-default GUI,所以我立即决定个性化我的窗口。我有很多尝试,但我有一个很大的限制和一个大目标;局限性?我必须使用MVC模式。目的?使自定义窗口可重复使用。

所以......这就是我现在的观点: wstaw.org/m/2016/04/07/resoruces.png

我制作了一个包含App.java的通用包应用程序,它将启动应用程序。然后我制作另一个内部包,包含" MinimalWindow"逻辑,我需要的所有资源。

我实现了这个FXML代码来执行窗口:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.Label?>

<StackPane fx:id="minimalWindowShadowContainer" id="minimalWindowShadowContainer" stylesheets="@style.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" onMousePressed="#updateXY" onMouseDragged="#windowDragging" onMouseReleased="#updateStatus" >
    <BorderPane fx:id="minimalWindowContainer" id="minimalWindowContainer">
        <!-- This padding will create the dropshadow effect for the window behind -->
        <padding>
            <Insets top="5" right="5" bottom="5" left="5"/>
        </padding>

        <!-- "Title Bar" -->
        <top>
            <HBox id="titleBar" alignment="CENTER" spacing="5" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="30.0" prefWidth="600.0">
                <padding>
                    <Insets top="5" right="5" bottom="5" left="5"/>
                </padding> 

                <ImageView fx:id="logo" fitWidth="20" fitHeight="20"></ImageView> 
                <Label fx:id="lblTitle" id="title" text="MinimalWindow"></Label>
                <Region HBox.hgrow="ALWAYS" prefHeight="30.0" prefWidth="200.0"></Region>

                <HBox alignment="CENTER_RIGHT">
                    <Button id="btnMin" onMouseClicked="#minimizeApp" minHeight="20" minWidth="20" maxHeight="20" maxWidth="20"></Button>
                    <Button fx:id="btnMax" id="btnMax" onMouseClicked="#maximizeApp" minHeight="20" minWidth="20" maxHeight="20" maxWidth="20"></Button>
                    <Button id="btnCls" onMouseClicked="#closeApp" minHeight="20" minWidth="20" maxHeight="20" maxWidth="20"></Button>
                </HBox>
            </HBox>
        </top>

        <!-- The content of the window will go here -->
        <center>
            <StackPane fx:id="contentArea" id="contentArea"></StackPane>
        </center>

        <!-- Footer -->
        <bottom>
            <HBox id="footer">
                <padding>
                    <Insets top="5" right="5" bottom="5" left="5"/>
                </padding> 

                <Button fx:id="btnResize" id="btnResize" alignment="BOTTOM_RIGHT" onMouseClicked="#updateXY" onMouseEntered="#setMouseCursor" onMouseExited="#resetMouseCursor" onMouseDragged="#resizeWindow" minHeight="10" minWidth="10" maxHeight="10" maxWidth="10"></Button>      
            </HBox>
        </bottom>
    </BorderPane>
</StackPane>

我实现了控制器类:

package application.minimalWindow;


import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class MinimalWindow extends Application {

    @FXML
    Label lblTitle;

    @FXML
    Button btnMax, btnResize;

    @FXML
    StackPane minimalWindowShadowContainer, minimalWindowContainer,contentArea;

    @FXML
    Double SHADOW_SPACE;

    final private static int MIN_WIDTH = 730, MIN_HEIGHT = 500;


    private double actualX, actualY;
    private boolean isMovable;
    private String source, title;

    private Stage mainStage;

    //
    // Public logic of the class
    //

    public MinimalWindow() {
        //TODO must work...
    }


    //Show the window
    public void show() {
        mainStage.show();
    }


    //
    // MIMIZIE | MAXIMIZE | CLOSE 
    //

    //When pressed, will minimize the window to tray
    @FXML
    private void minimizeApp(MouseEvent e) {
        mainStage.setIconified(true);
    }

    //When pressed, check if it must maximize or restore the window
    @FXML
    private void maximizeApp(MouseEvent e) {
        if (mainStage.isMaximized()) {
            setMin();
            isMovable = true;
        }

        else {
            setMax();
            isMovable = false;
        }
    }

    //When pressed, will kill the window
    @FXML
    private void closeApp(MouseEvent e) {
        mainStage.close();
        System.exit(0);
    }


    //
    // WINDOW MOVING
    //

    //When i must update the XY of the click
    @FXML
    private void updateXY(MouseEvent e){
        actualX = e.getScreenX() - mainStage.getX();
        actualY = e.getScreenY() - mainStage.getY();
    }

    //When pressing and dragging the mouse it will move the window
    @FXML
    private void windowDragging(MouseEvent e) {
        if (isMovable) {
            mainStage.setX(e.getScreenX() - actualX);
            mainStage.setY(e.getScreenY() - actualY);
        }

        else {
            //setMin();
            mainStage.setX(e.getScreenX());
            mainStage.setY(e.getScreenY());
        }
    }

    //Update the status of the window from not movable to movable, after "normalize" effect
    //from the dragging it when it's maximized
    @FXML
    private void updateStatus(MouseEvent e) {
        if (mainStage.isMaximized() == false) { 
            isMovable = true;
        }
    }


    //
    // WINDOW RESIZING
    //

    /*onMouseEntered="#setMouseCursor" onMouseExited="#resetMouseCursor" onMouseDragged="#resizeWindow"*/

    @FXML
    private void setMouseCursor (MouseEvent e) {
        minimalWindowContainer.setCursor(Cursor.CROSSHAIR);
    }

    @FXML
    private void resetMouseCursor (MouseEvent e) {
        minimalWindowContainer.setCursor(Cursor.DEFAULT);
    }

    @FXML
    private void resizeWindow (MouseEvent e) {
        actualX = e.getScreenX() - mainStage.getX() + 13;
        actualY = e.getScreenY() - mainStage.getY() + 10;

        if (actualX % 5 == 0 || actualY % 5 == 0) {
            if (actualX > MIN_WIDTH) {
                mainStage.setWidth(actualX);
            } else {
                mainStage.setWidth(MIN_WIDTH);
            }

            if (actualY > MIN_HEIGHT) {
                mainStage.setHeight(actualY);
            } else {
                mainStage.setHeight(MIN_HEIGHT);
            }
        }
    }


    //
    // Internal methods
    //

    //Will set the window to MAXIMIZE size
    private void setMax() {
        mainStage.setMaximized(true);
        btnResize.setVisible(false);
        btnMax.setStyle("-fx-background-image: url('/res/dSquare.png');");
        minimalWindowContainer.setPadding(new Insets(0, 0, 0, 0));
    }

    //Will set the window to NORMAL size
    private void setMin() {
        mainStage.setMaximized(false);
        btnResize.setVisible(true);
        btnMax.setStyle("-fx-background-image: url('/res/square.png');");
        minimalWindowContainer.setPadding(new Insets(SHADOW_SPACE, SHADOW_SPACE, SHADOW_SPACE, SHADOW_SPACE));
    }

    @Override
    public void start(Stage primaryStage) {

        /* //NOT SURE IF DOING RIGHT YA'
        try {
            //Prepare the resource with the FXML file
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/application/minimalWindow/MainWindow.fxml"));

            //Load the main stackpane
            Parent root = loader.load();

            loader.setController(this);

            //Prepare the content of the window, with a minWidth/Height
            Scene scene = new Scene(root, MIN_WIDTH, MIN_HEIGHT);

            //Making the scene transparent
            scene.setFill(Color.TRANSPARENT);

            //Undecorate the window due its persolalisation
            primaryStage.initStyle(StageStyle.TRANSPARENT);

            //Set the content of the window
            primaryStage.setScene(scene);   *   
        }

        catch (Exception e) {
            e.printStackTrace();
        }       */
    }

和造型的CSS:

* {
    /* Some general colors */
    primaryColor: #f9f9f9;  
    secondaryColor: derive(primaryColor, -75%);

    textColor: white;
    closeBtnColor: red;

}

#titleBar, #footer {
    -fx-background-color: secondaryColor;
}

#title {
    -fx-text-fill: textColor;
}

#contentArea {
    -fx-background-color: primaryColor;
}

#minimalWindowShadowContainer {
    -fx-background-color: transparent;      
    -fx-effect: dropshadow( gaussian , black , 5,0,0,0 );
    -fx-background-insets: 5;
}

#btnCls, #btnMax, #btnMin, #btnResize {
    -fx-background-color: transparent;
    -fx-background-radius: 0;
    -fx-border-color: transparent;
    -fx-border-width: 0;
    -fx-background-position: center;
    -fx-background-repeat: stretch;
}

#btnMax:hover, #btnMin:hover {
    -fx-background-color: derive(secondaryColor, 20%);  
}

#btnCls:hover {
    -fx-background-color: derive(red, 45%); 
}

#btnCls {
    -fx-background-image: url('/res/x.png');    
}

#btnMax {
    -fx-background-image: url('/res/square.png');
}

#btnMin {
    -fx-background-image: url('/res/line.png');
}

#btnResize {
    -fx-background-image: url('/res/resize.png');
}

在App.java中,我应该像这样使用它:

public class App {

    public static void main(String[] args) {        
        //Initialize the minimal window
        MinimalWindow mainWindow = new MinimalWindow();

        //Show the window, after all
        mainWindow.show();
    }
}

我在这里发布我的解决方案因为在互联网上我发现在MVC模式中没有关于自定义样式(是的......我需要为考试项目做这件事)。

有什么问题?它必须易于使用和可重复使用。试着像这样构建构造函数:

public MinimalWindow(String title, String source) {
        this.title = title;
        this.source = source;       
        start(mainStage);
    }

它在解析11行(定义stackpanel的第一行)中的XAML文件时出错,或者给我一个错误&#34;引起:java.lang.IllegalStateException:Toolkit未初始化&#34;。 首先,我不知道是什么导致了它。对于第二种,互联网上的解决方案建议从应用程序扩展我的类,然后覆盖&#34;开始&#34;方法,但它没有奏效。

提问时间:任何解决方案?建议?

PS:我使用不同风格的非mvc模式编写此代码,并且效果很好:wstaw.org/m/2016/04/07/ezgif.com-crop.gif < / em>的

2 个答案:

答案 0 :(得分:2)

Application类代表整个应用程序。它不代表一个窗口。 JavaFX中的Windows由Stage类表示。 Application.start()方法是JavaFX应用程序的入口点(开始):您应该将其视为“常规”Java应用程序中main的替代。 Application子类实例是作为启动过程的一部分为您创建的,它也启动了FX工具包。在Oracle JDK中,可以通过调用Java运行时(例如,从命令行调用java)并指定Application子类作为要执行的类来启动启动过程。对于不支持直接启动JavaFX应用程序的环境,应包括调用main的{​​{1}}方法,即

Application.launch(args)

因此

  1. public class MyApp extends Application { @Override public void start(Stage primaryStage) { // create objects and set up GUI, etc } public static void main(String[] args) { launch(args); } } 子类本质上是不可重用的,你应该尽可能地保持Application方法(它应该基本上什么也不做,但是,启动应用程序)。
  2. 您应该只在任何JVM
  3. 中拥有start(...)子类的一个实例
  4. 由于(2),you should never use the Application class as the controller class
  5. 所以要做你想做的事情,我想你想创建一个不是Application子类的单独的MinimalWindow类。使用FXML文档中描述的Custom Component模式使其加载自己的FXML并将其自身设置为控制器类。然后,您可以创建一个最小的主要类,扩展Application方法创建并展示Application的{​​{1}}。

答案 1 :(得分:0)

好的,我跟着从你那里学到的一切,我几乎完成了所有工作。那么,我现在拥有的是:

  

wstaw.org/m/2016/04/10/project.png

现在,我有MinimalWindow的FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>

<!-- Container that will do the "shadow" effect -->
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="BorderPane" fx:id="root" id="root" stylesheets="@MinimalWindowStyle.css" onMousePressed="#updateXY" onMouseDragged="#windowDragging" onMouseReleased="#updateStatus">
    <center>
        <!-- Main content -->
        <BorderPane fx:id="mainWindow" id="mainWindow">
            <!-- Padding will show the shadow effect under the window -->
            <padding>
                <Insets top="5" right="5" bottom="5" left="5"></Insets>
            </padding>

            <!-- Top bar of the window -->
            <top>
                <HBox id="titleBar" alignment="CENTER" spacing="5" prefHeight="30">
                    <padding>
                        <Insets top="5" right="5" bottom="5" left="5"/>
                    </padding> 

                    <ImageView fx:id="logo" fitWidth="20" fitHeight="20"></ImageView> 
                    <Label fx:id="lblTitle" id="title" text="MinimalWindow"></Label>
                    <Region HBox.hgrow="ALWAYS" prefHeight="30.0" prefWidth="200.0"></Region>

                    <HBox alignment="CENTER_RIGHT">
                        <Button id="btnMin" onMouseClicked="#minimizeApp" minHeight="20" minWidth="20" maxHeight="20" maxWidth="20"></Button>
                        <Button fx:id="btnMax" id="btnMax" onMouseClicked="#maximizeApp" minHeight="20" minWidth="20" maxHeight="20" maxWidth="20"></Button>
                        <Button id="btnCls" onMouseClicked="#closeApp" minHeight="20" minWidth="20" maxHeight="20" maxWidth="20"></Button>
                    </HBox>
                </HBox>
            </top>

            <!-- Window content -->
            <center>
                <GridPane fx:id="contentArea" id="contentArea"></GridPane>
            </center>

            <!-- Footer of the window -->
            <bottom>
                <HBox id="footer" prefHeight="20" alignment="BOTTOM_RIGHT">
                    <padding>
                        <Insets top="5" right="5" bottom="5" left="5"/>
                    </padding> 

                    <Button fx:id="btnResize" id="btnResize" onMouseClicked="#updateXY" onMouseEntered="#setMouseCursor" onMouseExited="#resetMouseCursor" onMouseDragged="#resizeWindow" minHeight="10" minWidth="10" maxHeight="10" maxWidth="10"></Button>       
                </HBox>
            </bottom>

        </BorderPane>
    </center>
</fx:root>

它的风格:

* {
    /* Some general colors */
    primaryColor: #f9f9f9;  
    secondaryColor: derive(primaryColor, -75%);

    textColor: white;
    closeBtnColor: red; 
}

#titleBar, #footer {
    -fx-background-color: secondaryColor;
}

#title {
    -fx-text-fill: textColor;
}

#contentArea {
    -fx-background-color: primaryColor;
}

#root {
    -fx-background-color: transparent;      
    -fx-effect: dropshadow( gaussian , black , 5,0,0,0 );
    -fx-background-insets: 5;
}

#btnCls, #btnMax, #btnMin, #btnResize {
    -fx-background-color: transparent;
    -fx-background-radius: 0;
    -fx-border-color: transparent;
    -fx-border-width: 0;
    -fx-background-position: center;
    -fx-background-repeat: stretch;
}

#btnMax:hover, #btnMin:hover {
    -fx-background-color: derive(secondaryColor, 20%);  
}

#btnCls:hover {
    -fx-background-color: derive(red, 45%); 
}

#btnCls {
    -fx-background-image: url("/resources/x.png");  
}

#btnMax {
    -fx-background-image: url('/resources/square.png');
}

#btnMin {
    -fx-background-image: url('/resources/line.png');
}

#btnResize {
    -fx-background-image: url('/resources/resize.png');
}

它的控制器类:

package controller.minimalWindow;

import application.Main;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class MinimalWindowCtrl extends BorderPane {

    //Values injected from the FXML
    @FXML
    private BorderPane root, mainWindow;

    @FXML
    private Label lblTitle;

    @FXML
    private Button btnMax, btnResize;

    @FXML
    private GridPane contentArea;

    //Reference to the primaryStage
    final private Stage stage;

    //References to min/max width/height and the shadow effect
    final private int MINWIDTH, MINHEIGHT, SHADOWSPACE = 5;

    //Things for the resizing/moving window
    private double actualX, actualY;
    private boolean isMovable = true;




    public MinimalWindowCtrl (Stage stage, int minwidth, int minheight) {
        //First, take the reference to the stage
        this.stage = stage;

        //Taking the references to the window
        MINWIDTH = minwidth;
        MINHEIGHT = minheight;

        //Then load the window, setting the root and controller
        FXMLLoader loader = new FXMLLoader(getClass().getResource("../../view/minimalWindow/MinimalWindow.fxml"));
        loader.setRoot(this);
        loader.setController(this);



        //Try to load
        try {
            loader.load();
        }
        catch (Exception e) {
            e.printStackTrace();
            //TODO Show a message error
            Main.close();
        }
    }

    public void setTitle(String s) {
        lblTitle.setText(s);
    }

    public void setContent(Node node) {
        contentArea.getChildren().add(node);
    }



    //
    // MIMIZIE | MAXIMIZE | CLOSE 
    //

    //When pressed, will minimize the window to tray
    @FXML
    private void minimizeApp(MouseEvent e) {
        stage.setIconified(true);
    }

    //When pressed, check if it must maximize or restore the window
    @FXML
    private void maximizeApp(MouseEvent e) {
        if (stage.isMaximized()) {
            setMin();
            isMovable = true;
        }

        else {
            setMax();
            isMovable = false;
        }
    }

    //When pressed, will kill the window
    @FXML
    private void closeApp(MouseEvent e) {
        stage.close();
        System.exit(0);
    }


    //
    // WINDOW MOVING
    //

    //When i must update the XY of the click
    @FXML
    private void updateXY(MouseEvent e){
        actualX = e.getScreenX() - stage.getX();
        actualY = e.getScreenY() - stage.getY();
    }

    //When pressing and dragging the mouse it will move the window
    @FXML
    private void windowDragging(MouseEvent e) {
        if (isMovable) {
            stage.setX(e.getScreenX() - actualX);
            stage.setY(e.getScreenY() - actualY);
        }

        else {
            //setMin();
            stage.setX(e.getScreenX());
            stage.setY(e.getScreenY());
        }
    }

    //Update the status of the window from not movable to movable, after "normalize" effect
    //from the dragging it when it's maximized
    @FXML
    private void updateStatus(MouseEvent e) {
        if (stage.isMaximized() == false) { 
            isMovable = true;
        }
    }


    //
    // WINDOW RESIZING
    //

    /*onMouseEntered="#setMouseCursor" onMouseExited="#resetMouseCursor" onMouseDragged="#resizeWindow"*/

    @FXML
    private void setMouseCursor (MouseEvent e) {
        mainWindow.setCursor(Cursor.CROSSHAIR);
    }

    @FXML
    private void resetMouseCursor (MouseEvent e) {
        mainWindow.setCursor(Cursor.DEFAULT);
    }

    @FXML
    private void resizeWindow (MouseEvent e) {
        actualX = e.getScreenX() - stage.getX() + 13;
        actualY = e.getScreenY() - stage.getY() + 10;

        if (actualX % 5 == 0 || actualY % 5 == 0) {
            if (actualX > MINWIDTH) {
                stage.setWidth(actualX);
            } else {
                stage.setWidth(MINWIDTH);
            }

            if (actualY > MINHEIGHT) {
                stage.setHeight(actualY);
            } else {
                stage.setHeight(MINHEIGHT);
            }
        }
    }


    //
    // Internal methods
    //

    //Will set the window to MAXIMIZE size
    private void setMax() {
        stage.setMaximized(true);
        btnResize.setVisible(false);
        btnMax.setStyle("-fx-background-image: url('/res/dSquare.png');");
        mainWindow.setPadding(new Insets(0, 0, 0, 0));
    }

    //Will set the window to NORMAL size
    private void setMin() {
        stage.setMaximized(false);
        btnResize.setVisible(true);
        btnMax.setStyle("-fx-background-image: url('/res/square.png');");
        mainWindow.setPadding(new Insets(SHADOWSPACE, SHADOWSPACE, SHADOWSPACE, SHADOWSPACE));

    }
}

在Main.java中我做了:

package application;



import controller.MainWindowCtrl;
import controller.minimalWindow.MinimalWindowCtrl;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.scene.Scene;
import javafx.scene.control.TabPane;

public class Main extends Application {

    final private int MINWIDTH = 750,  MINHEGIHT = 500;


    @Override
    public void start(Stage primaryStage) {
        try {
            //Preparing the model
            //TODO the interface of model
            Object m = new Object();

            //Loading main content
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/MainWindow.fxml"));
            TabPane mainPane = loader.load();

            //Setting the model for the controller
            ((MainWindowCtrl) loader.getController()).setModel(m);

            //Creating the style for the custom window
            MinimalWindowCtrl minimalWindowCtrl = new MinimalWindowCtrl(primaryStage, MINWIDTH, MINHEGIHT);
            minimalWindowCtrl.setContent(mainPane);

            //Making new scene
            Scene scene = new Scene(minimalWindowCtrl, MINWIDTH, MINHEGIHT);

            //Setting the style to the window (undecorating it)
            primaryStage.initStyle(StageStyle.TRANSPARENT);

            //Setting the scene on the window
            primaryStage.setScene(scene);

            //Showing the window
            primaryStage.show();

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

    public static void close() {        
        System.exit(0);
    }
}

它缺少一些功能,例如“我不知道为什么按钮的图标没有显示”,阴影仍然是错误的,但它通常有效。

结果如下:

enter image description here