controlsfx 是javaFx开源UI框架,里面有很多默认组件的补充,最近在使用其中的NotificationPane组件时,遇到一些问题,记录下来。
官方demo链接:
NotificationPane 是一个通知组件,效果如图:
controlsfx框架中还有一个通知组件是:Notifications,显示效果是从桌面边缘(8个方向)及中间弹出通知 (用法比较简单,就不多说了)。相比较于NotificationPane,NotificationPane更专注于当前操作,而Notifications适用于非重要的消息通知
这里按照官方的Demo做一个测试。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest1 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest1");
SplitPane splitPane = new SplitPane();
Button button = new Button("通知按钮");
NotificationPane notificationPane = new NotificationPane();
button.setOnAction(event -> {
notificationPane.setText("这个是NotificationPane的通知");
notificationPane.show();
});
// 这里与NotificationPane notificationPane = new NotificationPane(button);效果一致,指定通知显示位置
notificationPane.setContent(button);
splitPane.getItems().add(notificationPane);
Scene scene = new Scene(splitPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
实现效果如图:
由上面代码可以看出,notificationPane.setContent() 代码用来设置通知显示位置,并且显示的通知覆盖了定义的按钮组件。
那么如果不设置通知的显示位置会发生什么。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest2 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest2");
SplitPane splitPane = new SplitPane();
Button button = new Button("通知按钮");
NotificationPane notificationPane = new NotificationPane();
button.setOnAction(event -> {
notificationPane.setText("这个是NotificationPane的通知");
notificationPane.show();
});
splitPane.getItems().add(notificationPane);
Scene scene = new Scene(splitPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
效果如图:
由图片发现,什么内容都没有,连定义的按钮组件都没有,经过仔细观察我们的组件并没有被SpliPane(根Node管理),那么我们让根节点管理我们的按钮组件,看会发生什么。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest3 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest3");
SplitPane splitPane = new SplitPane();
Button button = new Button("通知按钮");
NotificationPane notificationPane = new NotificationPane();
button.setOnAction(event -> {
notificationPane.setText("这个是NotificationPane的通知");
notificationPane.show();
});
splitPane.getItems().add(button);
Scene scene = new Scene(splitPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
效果如图:
我们发现点击按钮不在有通知产生了。
通过上面3处代码,及实验结果我们发现:
代码1:NotificationPane设置了通知显示位置,且根节点管理了NotificationPane,结果显示正常
代码2:NotificationPane未设置通知显示位置,且根节点管理了NotificationPane,结果什么都没有显示
代码3:NotificationPane未设置通知显示位置,而根节点管理了按钮组件,结果点击按钮无通知
此时我们知道,如果想要实现通知显示,必须满足两个条件:
1.定义通知显示位置
2.通知组件要被根节点管理
有了上面两个经验,我们看看下面的代码:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest4 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest4");
SplitPane splitPane = new SplitPane();
Button button = new Button("通知按钮");
// 随便定义一个组件,只用来显示通知
VBox vBox = new VBox();
NotificationPane notificationPane = new NotificationPane(vBox);
button.setOnAction(event -> {
notificationPane.setText("这个是NotificationPane的通知");
notificationPane.show();
});
splitPane.getItems().add(button);
splitPane.getItems().add(notificationPane);
Scene scene = new Scene(splitPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
效果如图:
这次我们的根节点,同时添加了按钮组件和NotificationPane通知组件,而通知组件则显示在一个空的VBox组件上,同样也实现了通知效果,虽然在我编写的代码里根节点并没有管理那个空的VBox,但是由于NotificationPane通知组件设置在了VBox上,我们也可以看到,实际上根节点也是添加了这个VBox,为了更直观的看到效果,我们对代码稍作改动。
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest5 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest5");
SplitPane splitPane = new SplitPane();
Button button = new Button("通知按钮");
// 随便定义一个组件,只用来显示通知
VBox vBox = new VBox();
Label label = new Label("如果根节点未管理VBox,你就看不到我");
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().add(label);
NotificationPane notificationPane = new NotificationPane(vBox);
button.setOnAction(event -> {
notificationPane.setText("这个是NotificationPane的通知");
notificationPane.show();
});
splitPane.getItems().add(button);
splitPane.getItems().add(notificationPane);
Scene scene = new Scene(splitPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
效果如图:
从图中我们可以看到确实如预想的那样,根节点确实管理了由NotificationPane设置的组件。
此时我们可以总结一下NotificationPane与其他Pane类似都是一个布局组件,区别在于NotificationPane不管理子组件的相对位置,方向等,可以理解为一个空白且透明的布局组件,在使用时,需要注意要设置NotificationPane的显示位置,同时要将NotificationPane交由相对的根节点管理。
这两个缺一不可。
最后还有三个点需要注意:
1.NotificationPane.setContent()设置显示位置时,最好不要设置为空的布局组件,否则可能无法显示,但是在某些布局中可以显示如SplitPane
2.NotificationPane.show()方法在构造方法中无效
3.在使用NotificationPane.show()方法时,应先判断当前NotificationPane的状态是否是显示,如果是显示状态,则应先hide(),再进行show()
我们将用代码来验证以上三点:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest6 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest6");
VBox root = new VBox();
Button button = new Button("通知按钮");
root.setAlignment(Pos.CENTER);
// 随便定义一个空Pane,只用来显示通知
VBox content = new VBox();
NotificationPane notificationPane = new NotificationPane(content);
notificationPane.setText("这个是NotificationPane的通知");
button.setOnAction(event -> {
notificationPane.show();
});
root.getChildren().addAll(button,notificationPane);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
显示效果:
从图中可以看到点击按钮,没有通知产生。那么我们跟content中添加些组件试一试
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest7 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest7");
VBox root = new VBox();
Button button = new Button("通知按钮");
root.setAlignment(Pos.CENTER);
VBox content = new VBox();
// 这次往conten中塞入一个Label
content.getChildren().add(new Label());
NotificationPane notificationPane = new NotificationPane(content);
notificationPane.setText("这个是NotificationPane的通知");
button.setOnAction(event -> {
notificationPane.show();
});
root.getChildren().addAll(button,notificationPane);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
显示效果:
可以看出在如果通知组件的显示位置设置在了空的布局组件中时,通知不会弹出,验证了第一点
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest8 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest8");
AnchorPane anchorPane = new AnchorPane();
Button button = new Button("通知按钮");
AnchorPane.setBottomAnchor(button, 300.0);
AnchorPane.setTopAnchor(button, 300.0);
AnchorPane.setLeftAnchor(button, 150.0);
AnchorPane.setRightAnchor(button, 150.0);
anchorPane.getChildren().add(button);
button.setOnAction(event -> {
Dialog<Object> dialog = new Dialog<>();
DialogPane dialogPane = dialog.getDialogPane();
MyDialog myDialog = new MyDialog();
dialogPane.setContent(myDialog);
ObservableList<ButtonType> buttonTypes = dialogPane.getButtonTypes();
buttonTypes.addAll(ButtonType.APPLY);
dialog.show();
});
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyDialog extends VBox {
NotificationPane notificationPane = new NotificationPane();
public MyDialog() {
setPrefWidth(150);
setPrefHeight(200);
VBox vBox = new VBox();
Button button = new Button("我就是个填充空布局的组件");
vBox.getChildren().add(button);
notificationPane.setContent(vBox);
notificationPane.setText("这个是NotificationPane的通知");
getChildren().add(notificationPane);
// 构造方法中开启显示
notificationPane.show();
}
}
}
效果如图:
上面代码,我把NotificationPane.show()方法直接定义在了我们自定义Dialog的构造方法了里,结果发现通知组件并没有显示。
这里我们做一个猜想,之所以没有显示通知是不是自定的Dilog还没有实例化完成呢?因此这里我们做一点小改动,让show()方法异步执行。
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest8 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest8");
AnchorPane anchorPane = new AnchorPane();
Button button = new Button("通知按钮");
AnchorPane.setBottomAnchor(button, 300.0);
AnchorPane.setTopAnchor(button, 300.0);
AnchorPane.setLeftAnchor(button, 150.0);
AnchorPane.setRightAnchor(button, 150.0);
anchorPane.getChildren().add(button);
button.setOnAction(event -> {
Dialog<Object> dialog = new Dialog<>();
DialogPane dialogPane = dialog.getDialogPane();
MyDialog myDialog = new MyDialog();
dialogPane.setContent(myDialog);
ObservableList<ButtonType> buttonTypes = dialogPane.getButtonTypes();
buttonTypes.addAll(ButtonType.APPLY);
dialog.show();
});
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyDialog extends VBox {
NotificationPane notificationPane = new NotificationPane();
public MyDialog() {
setPrefWidth(150);
setPrefHeight(200);
VBox vBox = new VBox();
Button button = new Button("我就是个填充空布局的组件");
vBox.getChildren().add(button);
notificationPane.setContent(vBox);
notificationPane.setText("这个是NotificationPane的通知");
getChildren().add(notificationPane);
// 构造方法中异步开启显示
Platform.runLater(new Runnable() {
@Override
public void run() {
notificationPane.show();
}
});
}
}
}
显示效果如下:
我们发现在构造方法里异步执行show()方法也是可行的,但是并不推荐这么做,如果是dialog的场景,其实可以添加dialog的时间,在dialog显示后,调用show()方法,推荐的写法如下:
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest8 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest8");
AnchorPane anchorPane = new AnchorPane();
Button button = new Button("通知按钮");
AnchorPane.setBottomAnchor(button, 300.0);
AnchorPane.setTopAnchor(button, 300.0);
AnchorPane.setLeftAnchor(button, 150.0);
AnchorPane.setRightAnchor(button, 150.0);
anchorPane.getChildren().add(button);
button.setOnAction(event -> {
Dialog<Object> dialog = new Dialog<>();
DialogPane dialogPane = dialog.getDialogPane();
MyDialog myDialog = new MyDialog();
dialogPane.setContent(myDialog);
ObservableList<ButtonType> buttonTypes = dialogPane.getButtonTypes();
buttonTypes.addAll(ButtonType.APPLY);
// dialog显示完成后执行,注意需要写在dialog.show()之前,否则不生效
dialog.setOnShown(e -> {
myDialog.show();
});
dialog.show();
});
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyDialog extends VBox {
NotificationPane notificationPane = new NotificationPane();
public MyDialog() {
setPrefWidth(150);
setPrefHeight(200);
VBox vBox = new VBox();
Button button = new Button("我就是个填充空布局的组件");
vBox.getChildren().add(button);
notificationPane.setContent(vBox);
notificationPane.setText("这个是NotificationPane的通知");
getChildren().add(notificationPane);
}
public void show() {
notificationPane.show();
}
}
}
以上就验证了第二点。
验证第三点需要写一些错误代码:
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest9 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest9");
AnchorPane anchorPane = new AnchorPane();
Button button = new Button("通知按钮");
AnchorPane.setBottomAnchor(button, 300.0);
AnchorPane.setTopAnchor(button, 300.0);
AnchorPane.setLeftAnchor(button, 150.0);
AnchorPane.setRightAnchor(button, 150.0);
anchorPane.getChildren().add(button);
button.setOnAction(event -> {
Dialog<Object> dialog = new Dialog<>();
MyDialog myDialog = new MyDialog();
DialogPane dialogPane = dialog.getDialogPane();
ObservableList<ButtonType> buttonTypes = dialogPane.getButtonTypes();
buttonTypes.addAll(ButtonType.APPLY);
dialogPane.setContent(myDialog);
// 1.开启了通知显示操作
dialog.setOnShown(e -> {
myDialog.show();
});
dialog.show();
});
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyDialog extends VBox {
NotificationPane notificationPane = new NotificationPane();
public MyDialog() {
setPrefWidth(150);
setPrefHeight(200);
VBox vBox = new VBox();
Button button = new Button("这是一个随便的按钮");
vBox.getChildren().add(button);
notificationPane.setContent(vBox);
notificationPane.setText("这个是NotificationPane的通知");
getChildren().add(notificationPane);
// 2.开启了通知显示操作
notificationPane.show();
}
public void show(){
notificationPane.show();
}
}
}
效果如图:
这里看到通知组件并没有弹出,而我们的代码里也写了两处show(),如果说构造方法中的show()方法不显示的话,可以说的通,毕竟之前也实验过了,但是在dilog.setOnShown()中也设置了show(),此时应该显示通知才对的。
我们更改下代码:
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest10 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest10");
AnchorPane anchorPane = new AnchorPane();
Button button = new Button("通知按钮");
AnchorPane.setBottomAnchor(button, 300.0);
AnchorPane.setTopAnchor(button, 300.0);
AnchorPane.setLeftAnchor(button, 150.0);
AnchorPane.setRightAnchor(button, 150.0);
anchorPane.getChildren().add(button);
button.setOnAction(event -> {
Dialog<Object> dialog = new Dialog<>();
MyDialog myDialog = new MyDialog();
DialogPane dialogPane = dialog.getDialogPane();
ObservableList<ButtonType> buttonTypes = dialogPane.getButtonTypes();
buttonTypes.addAll(ButtonType.APPLY);
dialogPane.setContent(myDialog);
dialog.setOnShown(e -> {
myDialog.show();
});
dialog.show();
});
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyDialog extends VBox {
NotificationPane notificationPane = new NotificationPane();
Label label = new Label();
public MyDialog() {
setPrefWidth(150);
setPrefHeight(200);
VBox vBox = new VBox();
// 设置子组件间距
vBox.setSpacing(20);
Button button = new Button("这是一个随便的按钮");
vBox.getChildren().add(button);
label.setText("通知状态是:false");
vBox.getChildren().add(label);
notificationPane.setContent(vBox);
notificationPane.setText("这个是NotificationPane的通知");
getChildren().add(notificationPane);
notificationPane.show();
}
public void show() {
if (notificationPane.isShowing()) {
label.setText("111111");
notificationPane.hide();
notificationPane.show();
} else {
label.setText("2222");
notificationPane.show();
}
}
}
}
这里我们加了一个Label用来表示状态,如果当前已经是显示状态,则label显示为1111,否则为2222
效果如图:
可以看出当前通知状态是showing
此时我们将构造方法里show()方法去掉看一下结果:
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.NotificationPane;
public class NotificationPaneTest10 extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("NotificationPaneTest10");
AnchorPane anchorPane = new AnchorPane();
Button button = new Button("通知按钮");
AnchorPane.setBottomAnchor(button, 300.0);
AnchorPane.setTopAnchor(button, 300.0);
AnchorPane.setLeftAnchor(button, 150.0);
AnchorPane.setRightAnchor(button, 150.0);
anchorPane.getChildren().add(button);
button.setOnAction(event -> {
Dialog<Object> dialog = new Dialog<>();
MyDialog myDialog = new MyDialog();
DialogPane dialogPane = dialog.getDialogPane();
ObservableList<ButtonType> buttonTypes = dialogPane.getButtonTypes();
buttonTypes.addAll(ButtonType.APPLY);
dialogPane.setContent(myDialog);
dialog.setOnShown(e -> {
myDialog.show();
});
dialog.show();
});
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyDialog extends VBox {
NotificationPane notificationPane = new NotificationPane();
Label label = new Label();
public MyDialog() {
setPrefWidth(150);
setPrefHeight(200);
VBox vBox = new VBox();
// 设置子组件间距
vBox.setSpacing(20);
Button button = new Button("这是一个随便的按钮");
vBox.getChildren().add(button);
label.setText("通知状态是:false");
vBox.getChildren().add(label);
notificationPane.setContent(vBox);
notificationPane.setText("这个是NotificationPane的通知");
getChildren().add(notificationPane);
}
public void show() {
if (notificationPane.isShowing()) {
label.setText("111111");
notificationPane.hide();
notificationPane.show();
} else {
label.setText("2222");
notificationPane.show();
}
}
}
}
效果如图:
可以看到这次label的状态是2222,通知是hide状态,这就验证了第三点
由此需要知道当 调用NotificationPane.show()方法时,是会更改NotificationPane的状态的,而更改了NotificationPane的状态并不代表NotificationPane会显示,所以在NotificationPane的状态是showing时,再次调用NotificationPane.show()方法,NotificationPane是不会显示的,需要先调用
NotificationPane.hide()方法,再调用NotificationPane.show()方法