JavaFX - 简单的自定义最小 window 实现
JavaFX - simple custom minimal window implementation
我刚刚发现了 JavaFX,我非常喜欢它。我讨厌 java-默认 GUI,所以我立即决定个性化我的 window。我尝试了很多次,但我有一大局限和一大objective;局限性?我必须使用 MVC 模式。 Objective?使自定义 window 可重用。
所以...这就是我现在的观点:
wstaw.org/m/2016/04/07/resoruces.png
我制作了一个包含 App.java 的通用包应用程序,它将启动该应用程序。然后我制作另一个内部包,包含 "MinimalWindow" 逻辑,以及我需要的所有资源。
我实现了这个 FXML 代码来执行 window:
<?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>
然后我实现了控制器class:
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();
}
}
我 post 这是我的解决方案,因为在互联网上我完全没有发现 MVC 模式中的自定义样式(是的......我需要为考试项目做这件事)。
有什么问题?它必须简单易用且可重复使用。试图使构造函数像这样:
public MinimalWindow(String title, String source) {
this.title = title;
this.source = source;
start(mainStage);
}
在第 11 行(定义堆栈面板的第一行)中解析 XAML 文件时出现错误,或者出现错误 "Caused by: java.lang.IllegalStateException: Toolkit not initialized"。
首先,我不知道是什么原因造成的。第二,网上的解决方案建议从应用程序扩展我的 class 然后覆盖 "start" 方法,但它不起作用。
提问时间:有什么办法吗?建议?
PS:我用非 mvc 模式编写了这段代码,风格不同,效果很好:wstaw.org/m/2016/04/07/ezgif.com-crop.gif
Application
class代表整个应用。它不代表 window。 Windows中的JavaFX由Stage
class表示。 Application.start()
方法是 JavaFX 应用程序的入口点(开始):您应该将其视为 "regular" Java 中 main
的替代品应用。 Application
subclass 实例是作为启动过程的一部分为您创建的,它也会启动 FX 工具包。在 Oracle JDK 中,启动过程可以通过调用 Java 运行时(例如从命令行调用 java
)并指定一个 Application
subclass 作为 class 来执行。对于不支持直接启动 JavaFX 应用程序的环境,您应该包含一个调用 Application.launch(args)
的 main
方法,即
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
subclass 本质上是不可重用的,你应该尽可能地保持 start(...)
方法(它应该基本上什么都不做,但是,启动应用程序).
- 在任何 JVM
中,您的 Application
子 class 应该只有一个实例
- 作为 (2) 的结果,you should never use the
Application
class as the controller class
所以为了做你想做的事,我想你想创建一个单独的 MinimalWindow
class 而不是 Application
subclass。使用 FXML 文档中描述的 Custom Component 模式让它加载自己的 FXML 并将自己设置为控制器 class。然后你可以创建一个最小的 main class,扩展 Application
其 start
方法创建并显示 MinimalWindow
.
好的,我按照从您那里学到的所有知识进行了学习,几乎完成了所有工作。那么,我现在拥有的是:
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');
}
它的控制器class:
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);
}
}
它缺少一些功能,例如 "I don't know why the icons for buttons is not showed",阴影仍然有问题,但它通常可以正常工作。
这是结果:
我刚刚发现了 JavaFX,我非常喜欢它。我讨厌 java-默认 GUI,所以我立即决定个性化我的 window。我尝试了很多次,但我有一大局限和一大objective;局限性?我必须使用 MVC 模式。 Objective?使自定义 window 可重用。
所以...这就是我现在的观点: wstaw.org/m/2016/04/07/resoruces.png
我制作了一个包含 App.java 的通用包应用程序,它将启动该应用程序。然后我制作另一个内部包,包含 "MinimalWindow" 逻辑,以及我需要的所有资源。
我实现了这个 FXML 代码来执行 window:
<?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>
然后我实现了控制器class:
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();
}
}
我 post 这是我的解决方案,因为在互联网上我完全没有发现 MVC 模式中的自定义样式(是的......我需要为考试项目做这件事)。
有什么问题?它必须简单易用且可重复使用。试图使构造函数像这样:
public MinimalWindow(String title, String source) {
this.title = title;
this.source = source;
start(mainStage);
}
在第 11 行(定义堆栈面板的第一行)中解析 XAML 文件时出现错误,或者出现错误 "Caused by: java.lang.IllegalStateException: Toolkit not initialized"。 首先,我不知道是什么原因造成的。第二,网上的解决方案建议从应用程序扩展我的 class 然后覆盖 "start" 方法,但它不起作用。
提问时间:有什么办法吗?建议?
PS:我用非 mvc 模式编写了这段代码,风格不同,效果很好:wstaw.org/m/2016/04/07/ezgif.com-crop.gif
Application
class代表整个应用。它不代表 window。 Windows中的JavaFX由Stage
class表示。 Application.start()
方法是 JavaFX 应用程序的入口点(开始):您应该将其视为 "regular" Java 中 main
的替代品应用。 Application
subclass 实例是作为启动过程的一部分为您创建的,它也会启动 FX 工具包。在 Oracle JDK 中,启动过程可以通过调用 Java 运行时(例如从命令行调用 java
)并指定一个 Application
subclass 作为 class 来执行。对于不支持直接启动 JavaFX 应用程序的环境,您应该包含一个调用 Application.launch(args)
的 main
方法,即
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
subclass 本质上是不可重用的,你应该尽可能地保持start(...)
方法(它应该基本上什么都不做,但是,启动应用程序). - 在任何 JVM 中,您的
- 作为 (2) 的结果,you should never use the
Application
class as the controller class
Application
子 class 应该只有一个实例
所以为了做你想做的事,我想你想创建一个单独的 MinimalWindow
class 而不是 Application
subclass。使用 FXML 文档中描述的 Custom Component 模式让它加载自己的 FXML 并将自己设置为控制器 class。然后你可以创建一个最小的 main class,扩展 Application
其 start
方法创建并显示 MinimalWindow
.
好的,我按照从您那里学到的所有知识进行了学习,几乎完成了所有工作。那么,我现在拥有的是:
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');
}
它的控制器class:
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);
}
}
它缺少一些功能,例如 "I don't know why the icons for buttons is not showed",阴影仍然有问题,但它通常可以正常工作。
这是结果: