I'm developing a chat and decided to stick to the following structure:
All the chat messages consist of an outer VBox and inner VBox (chatBlock). Inner VBox contains Pane with Label blocks. One Pane + Label block is for nickname, and another one is for the message.
So I want to colorize nickname panel and message panel separately. But setStyle() works only for inner VBox. Here is the code of what I'm trying to do:
public class ChatMessageBlock extends VBox {
private VBox chatBlock = new VBox();
private Pane nickNameBox = new Pane();
private Label nickname;
private Pane messageBox = new Pane();
private Label message;
public ChatMessageBlock(String nickname, String message) {
this.nickname = new Label(nickname);
this.message = new Label(message);
this.message.setWrapText(true);
initGUI();
}
private void initGUI() {
nickNameBox.setStyle("-fx-background-color: red"); //DOESN'T WORK
messageBox.setStyle("-fx-background-color: green"); //DOESN'T WORK
// chatBlock.setStyle("-fx-background-color: blue"); //WORK
nickNameBox.getChildren().add(nickname);
messageBox.getChildren().add(message);
chatBlock.getChildren().addAll(nickname, message);
this.getChildren().add(chatBlock);
}
}
How to achieve colorization of separate blocks inside VBox panel?
Try this:
nickNameBox.setStyle("-fx-background-color: #992222;");
EDIT:
You should add nickNameBox and messageBox to the VBox. These are not related to anything.
for example write these lines before this.getChildren().add(chatBlock); in initGUI():
this.getChildren().add(nickNameBox);
this.getChildren().add(messageBox);
Related
I am trying to build a dummy webshop with JavaFX. I deliberately do not use fxml, scenebuilder, maven or any other build tool. Just plain JavaFX, in order to really get to understand the basics.
However, I ran into a problem creating and navigating different 'pages'.
I have tried various creative solutions, like this one (is posting links allowed?), but none fully work for me, as I want every 'page', 'view' or scene in a seperate java class file, in order to keep everything structured and orderly.
I figured I'd make a Borderpane as a parent layout for every page
abstract class WindowBase extends BorderPane {
public abstract BorderPane render(App app);
public WindowBase() {
Label labelTop = new Label("Top box");
HBox topBox = new HBox();
topBox.setStyle("-fx-background-color: red;");
topBox.getChildren().addAll(labelTop);
Label labelLeft = new Label("Left box");
VBox leftBox = new VBox();
leftBox.setStyle("-fx-background-color: green;");
leftBox.getChildren().addAll(labelLeft);
Label labelRight = new Label("Right box");
VBox rightBox = new VBox();
rightBox.setStyle("-fx-background-color: blue;");
rightBox.getChildren().addAll(labelRight);
Label labelBottom = new Label("Bottom box");
HBox bottomBox = new HBox();
bottomBox.setStyle("-fx-background-color: yellow;");
bottomBox.getChildren().addAll(labelBottom);
this.setTop(topBox);
this.setLeft(leftBox);
this.setRight(rightBox);
this.setBottom(bottomBox);
}
}
and a child, the home page
public class Homepage extends WindowBase {
public BorderPane render(App app) {
Button button = new Button("Go to shopping cart");
button.setOnAction((event) -> app.toShoppingCart());
StackPane centerPane = new StackPane();
centerPane.getChildren().add(button);
this.setCenter(centerPane);
return this;
}
}
and lastly my App.java that runs everything
public class App extends Application{
private WindowBase view;
public void start(Stage stage) throws Exception {
view = new Homepage();
stage.setScene(new Scene(view.render(this)));
stage.setFullScreen(true);
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
public void toHomepage() {
this.view = new Homepage();
}
public void toShoppingCart() {
this.view = new ShoppingCart();
}
}
I understand that I can't pass this (App) as an argument to view.render(), use the parameter within the method render and expect to be able to manipulate it, because it only creates a new instance of App as soon as it gets there. However, I see no other way either.
I tried placing the navigation buttons in the App class, in order to be able to manipulate view, but then I cannot call on the buttons from the subsequent views.
There must be a way to achieve what I want without writing the complete GUI in one file, right? Should I make my view static in stead, is that it?
Instead of BorderPanes I am of course also okay with using Scenes, whatever works.
I have figured out exactly the solution that I wanted. Posting it here for whoever encounters the same situation.
Class WindowBase
public class WindowBase {
public BorderPane getMainPane() {
Label labelTop = new Label("Top box");
HBox topBox = new HBox();
topBox.setStyle("-fx-background-color: red;");
topBox.getChildren().addAll(labelTop);
Label labelLeft = new Label("Left box");
VBox leftBox = new VBox();
leftBox.setStyle("-fx-background-color: green;");
leftBox.getChildren().addAll(labelLeft);
Label labelRight = new Label("Right box");
VBox rightBox = new VBox();
rightBox.setStyle("-fx-background-color: blue;");
rightBox.getChildren().addAll(labelRight);
Label labelBottom = new Label("Bottom box");
HBox bottomBox = new HBox();
bottomBox.setStyle("-fx-background-color: yellow;");
bottomBox.getChildren().addAll(labelBottom);
BorderPane borderPane = new BorderPane();
borderPane.setTop(topBox);
borderPane.setLeft(leftBox);
borderPane.setRight(rightBox);
borderPane.setBottom(bottomBox);
return borderPane;
}
}
Class Homepage
public class Homepage extends WindowBase {
public BorderPane render(Button toShoppingCart) {
Label label = new Label("This is the homepage.");
label.setStyle(
"-fx-font: normal bold 30px 'elephant'; -fx-text-fill: black; -fx-background-color: red;");
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(label);
BorderPane mainPane = getMainPane();
VBox leftBox = (VBox) mainPane.getLeft();
leftBox.getChildren().add(toShoppingCart);
//I do not understand why this works. I abstracted leftBox from mainPane, then added shoppingCart, but never added the abstracted
// leftBox back to the mainPane before returning it. This should not work, but it does. leftBox.getChildren().add(toShoppingCart)
// should have no effect.
//However, mainPane.getChildren().add(leftBox) throws an IllegalArgumentException about duplicate components, which is to be
// expected if the leftBox is already automatically added back to the mainPane.
mainPane.setCenter(stackPane);
return mainPane;
}
}
Class ShoppingCart
public class ShoppingCart extends WindowBase {
public ShoppingCart() {
super();
}
public BorderPane render(Button toHomepage) {
Label label = new Label("This is the shopping cart.");
label.setStyle(
"-fx-font: normal bold 30px 'elephant'; -fx-text-fill: black; -fx-background-color: red;");
StackPane centerPane = new StackPane();
centerPane.getChildren().add(label);
BorderPane mainPane = getMainPane();
VBox leftBox = (VBox) mainPane.getLeft();
leftBox.getChildren().add(toHomepage);
mainPane.setCenter(centerPane);
return mainPane;
}
}
Class App
public class App extends Application{
Scene scene;
#Override
public void start(Stage stage) throws Exception {
Homepage homePane = new Homepage();
Button toHomepage = new Button("Back to home page");
ShoppingCart shoppingPane = new ShoppingCart();
Button toShoppingCart = new Button("To shopping cart");
toHomepage.setOnAction(e -> scene.setRoot(homePane.render(toShoppingCart)));
toShoppingCart.setOnAction(e -> scene.setRoot(shoppingPane.render(toHomepage)));
scene = new Scene(homePane.render(toShoppingCart), 600, 400);
stage.setScene(scene);
stage.setFullScreen(true);
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
So I want to do a title Menu for a video game project for college. I want to display a message press any key to continue... then when the user pressed any key, including the mouse, a method would run to set the stage to the next menu.
I posted all the relevant code bellow but short version is:
BackgroundImangeDisplayPane extends display pane and adds a background image.
TitleCard extends BackgroundImangeDisplayPane adds a VBox and 2 labels to the VBox
I'm using public void start(Stage primaryStage) throws Exception as the main, I set the setOnActionxxx methods here
I have tried using the set on action method on root and Vbox and non of them work... when I click nothing happens... But when I resize the window The root.setOnActionXXX "activates".
If I write the setOnAction methods on the TitleCard class It kind of works but then I cant switch the stage.
I will post the code bellow as well an explanation of the Scene structure its not to complicated:
// this will be the borderpane for every scene it recives a backgund
//images that will be present in every menu
public BackgroundImangeDisplayPane() {
try {
stream = new FileInputStream(imagePath.toString());
Image image = new Image(stream);
ImageView imageView = new ImageView();
imageView.setImage(image);
imageView.setFitWidth(1920);
imageView.setPreserveRatio(true);
this.getChildren().add(imageView);
BackgroundSize backgoundSize = new BackgroundSize(AUTO, AUTO, true, true, true, true);
BackgroundImage backgroundImage = new BackgroundImage(image, NO_REPEAT, NO_REPEAT, CENTER, backgoundSize);
Background background = new Background(backgroundImage);
this.setBackground(background);
} catch (Exception e) {
}
}
//This extends `BackgroundImangeDisplayPane` and places on top of it a A Vbox with two lables: the title and "press any key to continue..."
// it then adds styles to the labels
public class TitleCard extends BackgroundImangeDisplayPane {
Label title = new Label("Boats & Docks"); // lable 1
Label subtitle = new Label("Press any key to continue ..."); label2
public TitleCard(){
super();
VBox vbox = new VBox();
vbox.getChildren().add(title);
vbox.getChildren().add(subtitle);
this.setCenter(vbox);
this.setAlignment(vbox, Pos.CENTER);
vbox.setAlignment(Pos.CENTER);
title.setFont(new Font(170)); // set to Label
title.setTextFill(Color.SNOW);
title.setEffect(new DropShadow());
subtitle.setFont( new Font (30));
}
}
...
//Works as the "main" in javaFX
private Stage primaryStage;
#Override
public void start(Stage primaryStage) throws Exception {
TitleCard root = new TitleCard();
/*BasicMenu menu = new BasicMenu(5);
menu.ButtonSetOnAction(0, e -> changeScene() );
BackgroundImangeWithCustomMenu background = new
BackgroundImangeWithCustomMenu(menu,50,50);
root.setCenter(background);*/
Button b = new Button();
b.setOnAction(e -> changeSceneToLoginMenu());
System.out.println(root.getChildren().get(1).getClass());
root.getChildren().get(1).setFocusTraversable(true);
root.getChildren().get(1).setOnMouseClicked(e -> changeSceneToLoginMenu());
root.getChildren().get(1).setOnKeyPressed(e -> changeSceneToLoginMenu());
root.getChildren().get(0).setOnMouseClicked(e -> changeSceneToLoginMenu());
root.getChildren().get(0).setOnKeyPressed(e -> changeSceneToLoginMenu());
/*
root.setOnMouseClicked(e -> changeSceneToLoginMenu());
root.setOnKeyReleased(e -> changeSceneToLoginMenu());
root.setOnKeyPressed(e -> changeSceneToLoginMenu());
*/
Scene scene = new Scene(root, 1280, 720);
primaryStage.setScene(scene);
primaryStage.show();
this.primaryStage = primaryStage;
}
As proposed by James in comments, when a key is pressed on the scene, navigate to the next scene (or replace the root in the current scene, and remove the key press handler).
scene.setOnKeyPressed(e -> navigateToNextScene());
I managed to find a very simple working solution but I don't fully undestand why it does work. I Noticed that if I set the handler in the same class the node was instanciated the handler would work fine But if I tried to get the node with a method to the main fuction via root.getChildren().get(1) and then cast it to the VBox element the handler would not work.
As a solution I made the VBox a field and wrote a setter method for the VBox event Handler in the TitleCard Class. This fixed the problem.
I marked the code added as solution code with comments
public class TitleCard extends BackgroundImangeDisplayPane {
Label title = new Label("Boats & Docks"); // lable 1
Label subtitle = new Label("Press any key to continue ..."); label2
VBox vbox = new VBox; // solution code
public TitleCard(){
super();
VBox vbox = new VBox();
vbox.getChildren().add(title);
vbox.getChildren().add(subtitle);
this.setCenter(vbox);
this.setAlignment(vbox, Pos.CENTER);
vbox.setAlignment(Pos.CENTER);
title.setFont(new Font(170)); // set to Label
title.setTextFill(Color.SNOW);
title.setEffect(new DropShadow());
subtitle.setFont( new Font (30));
}
// Solution Code
public void setVBoxHandler(EventHandler<? super MouseEvent> value){
vbox.setOnMouseClicked(value);
}
}
Then I set the handler in the start method:
public void start(Stage primaryStage) throws Exception {
TitleCard root = new TitleCard();
VBox vBox =(VBox) root.getChildren().get(1);
root.setVBoxHandler(e->changeSceneToLoginMenu() ); // solution Code
Scene scene = new Scene(root, 1280, 720);
primaryStage.setScene(scene);
primaryStage.show();
this.primaryStage = primaryStage;
}
public void changeSceneToLoginMenu() {
System.out.println("It finally worked");
Scene currentScene = new Scene(new Group(),100,100); // just a demo
primaryStage.setScene(currentScene);
}
notes: The type of value on the method setVBoxHandler(EventHandler<? super MouseEvent> value) will depend on the setOnXXX method used. For example I tested and this soultion also works for buttons just need to change the type to EventHandler<ActionEvent> value.
Some coments on the question posted links on "how to use handlers", this posts used anomimous classes. I belived This way is outdated. I used lambdas in the code the end result is the same but more redable code
Just for reference if future readers are using anomimous classes the solution would be the same just change the way you set up the handler:
// lambdas
setVBoxHandler( e -> System.out.println("Code run if mouse is clicked "));
// anomimous classes
setVBoxHandler(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event){
System.out.println("Code run if mouse is clicked ");
}
});
I have a GUI that I've created, and I would like to add a ProgressIndicatorwhen the application is doing something in the background. I've created a Tab in the constructor similar to the following:
public class myGUI {
Tab myTab;
myGUI() {
myTab = new Tab("My Tab");
HBox view = new HBox();
VBox left = new VBox();
BorderPane right = new BorderPane();
/*A lot of other things are declared that go in left and right*/
view.getChildren().addAll(left, right);
myTab.setContent(view);
}
...
Later on, I have a button press that starts the application doing a background task, and I would like to add a ProgressIndicator to the center of the BorderPane. I tried something like the following:
private void handleMyAction(MouseEvent e) {
myTab.getContent().getChildren().get(1).setCenter(new ProgressIndicator(-1.0f));
}
I would think that this works, however, getContent returns a Node, and I cannot call getChildren on that Node. How can I access the BorderPane to add another Node without making the BorderPane a field in my class?
Just make the border pane an instance variable:
public class MyGUI {
private Tab myTab;
private BorderPane right ;
MyGUI() {
myTab = new Tab("My Tab");
HBox view = new HBox();
VBox left = new VBox();
right = new BorderPane();
/*A lot of other things are declared that go in left and right*/
view.getChildren().addAll(left, right);
myTab.setContent(view);
}
private void handleMyAction(MouseEvent e) {
right.setCenter(new ProgressIndicator(-1.0f));
}
}
I'm a beginning Java programmer, finishing up the "Java 101" class at my local university. I'm also pushing myself to learn some extra topics on the side, including Java FX. I've worked through the Java FX tutorials on Oracle's website, plus sat through some YouTube videos, plus read "Java FX for Dummies" (which was the best book I could find for a beginner.) All of this material has taught me a lot of the basics, but some stuff that (should be) relatively simple escapes me.
For example: Let's say I have a Java FX program that uses multiple scenes on one stage. When the user clicks a "Switch!" button, the second scene is swapped out for the first. Easy. I can do all of this in one .java file, no problem. (See code below)
But my .java class file is getting really long and cumbersome to troubleshoot. It would be great if I could define/declare/initialize one scene as one class in one .java file and the second scene as another class in another .java file. This would make keeping track of the components of each scene much, much easier. The problem is, I can't figure out how to do this.
I'd imagine that you would write a Scene1.java class and then a Scene2.java class, and simply pass the stage object between the two when you want to switch scenes. But I can't find an example of how this is done, and all my attempts result in compiler errors or really scary runtime errors.
Does anyone know how this can be done? If so, what would I have to do to modify the SwitchScenes2() method below to create the new Scene2 object and pass it the stage?
Thanks! RAO
/*
JavaFXExample.java
*/
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;
import javafx.geometry.*;
public class JavaFXExample extends Application{
public static void main(String[] args){
launch(args);
}
Button btnSw1;
Button btnSw2;
Button btnClose;
HBox hbox1;
VBox vbox1;
Scene scene1;
Scene scene2;
Stage stage;
#Override public void start(Stage primaryStage){
btnSw1 = new Button("Switch Scenes!");
btnSw1.setOnAction(
e -> SwitchScenes2() );
btnSw2 = new Button("Switch back!");
btnSw2.setOnAction(
e -> SwitchScenes1() );
btnClose = new Button();
btnClose.setText("Close me!");
btnClose.setOnAction(e -> CloseWindowClick());
hbox1 = new HBox(10);
hbox1.getChildren().addAll(btnSw1);
vbox1 = new VBox(10);
vbox1.getChildren().addAll(btnSw2, btnClose);
scene1 = new Scene(hbox1, 300, 300);
scene2 = new Scene(vbox1, 200, 400);
stage = primaryStage;
stage.setScene(scene1);
stage.setTitle("Example App");
stage.show();
}
public void SwitchScenes1(){
stage.setScene(scene1);
}
public void SwitchScenes2(){
stage.setScene(scene2);
}
public void CloseWindowClick(){
stage.close();
}
}
Pete as I understand you wish to separate one big java file into small files,create Java classes in each class create method(function) that will return layout(HBox,VBox, Flowpane or ....)then in your main create an object of that Java class and use those methods to build on big application.
in my sample I made one main and one separated class with one function,just to show you how its works. In my main there is 2 lables, 2 buttons one layout and one object of the separated class, by clicking the buttons scenes will change
My Main:
public class SwitchSceneSample extends Application {
public static void main(String[] args) {
launch(args);
}
Stage window;
Scene scene1, scene2;
#Override
public void start(Stage primaryStage) throws Exception {
// I am using window as primaryStage
window = primaryStage;
// Label 1
Label label1 = new Label("Welcome to the first scene!");
// Label 2
Label label2 = new Label("This is second scene!");
// Button 1, by pressing this button primaryStage will be set as scene 2
Button button1 = new Button("Go to scene 2");
button1.setOnAction(e -> window.setScene(scene2));
// Button 2, by pressing this button primaryStage will be set as scene 1
Button button2 = new Button("Click to go scene 1");
button2.setOnAction(e -> window.setScene(scene1));
// Creating an object of the class'LayoutOne.java'
LayoutOne l1 = new LayoutOne();
// set my scene 1(by calling method called 'sceneView1()' from class 'LayoutOne.java')
scene1 = new Scene(l1.sceneView1(label1, button1), 200, 200);
// Set my scene 2 inside my main class
StackPane layout2 = new StackPane();
layout2.getChildren().addAll(label2, button2);
scene2 = new Scene(layout2, 600, 300);
// Making my
window.setScene(scene1);
window.setTitle("Scene Switch Sample");
window.show();
}
}
My Second Class:
public class LayoutOne {
public VBox sceneView1(Label label, Button button) {
// Layout 1 - children are laid out in vertical column
VBox layout1 = new VBox(20);
layout1.getChildren().addAll(label, button);
return layout1;
}
}
What you will want to do is create separate classes that both have functions to return the scene. From there you will want to initialize these classes and with a button call a function that will add data to these scene or create a new blank scene (as a quick way to "delete" the scene). But if you want a more professional way to switch between scenes like this you will want to check out the TabPane().
Scene1 scene1 = new Scene1();
Scene2 scene2 = new Scene2();
TabPane tabPane = new TabPane();
Tab tab1 = new Tab();
tab1.setContent(scene1);
tabPane.getTabs().add(tab1);
Tab tab2 = new Tab();
tab2.setContent(scene2);
tabPane.getTabs().add(tab2);
Create a Manager class that contain the main method & initialize the first screen. eg.
public class VMCSManager extends Application {
private Parent content;
private static VMCSManager instance;
public VMCSManager() {
instance=this;
}
public static void main(String[] args) {
launch(args);
}
public static VMCSManager getInstance() {
return instance;
}
#Override
public void start(Stage primaryStage) throws Exception {
initializePanel();
Scene scene = new Scene(content);
stageStyle(primaryStage);
primaryStage.setScene(scene);
primaryStage.show();
}
private void initializePanel() throws IOException{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("fxml/SimulatorDisplay.fxml"));
content = loader.load();
}
public void openCustomerPanel() throws IOException{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("fxml/CustomerDisplay.fxml"));
content = loader.load();
Scene scene = new Scene(content);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Create main controller class for the first screen. eg;
public class SimulatorController implements Initializable{
#FXML
public void clickCustomer (ActionEvent event) throws IOException{
log.info("Starting Customer Panel");
VMCSManager.getInstance().openCustomerPanel();
}
#FXML
public void clickMaintainer(ActionEvent event) throws IOException{
log.info("Starting Maintainer Panel");
VMCSManager.getInstance().openMaintainerPanel();
}
}
Lastly Create the controller class for the specified screen. eg`
public class CustomerController extends SimulatorController{
#FXML
private Label brand1Lbl;
#FXML
private Label brand2Lbl;
#FXML
private Label brand3Lbl;
#FXML
private Label brand4Lbl;
#FXML
private Label brand5Lbl;
#FXML
private Label statusLbl1;
#FXML
private Label statusLbl2;
private static final Logger log=LoggerFactory.getLogger(CustomerController.class);
public CustomerController() {
context= new BuyingStateContext();
}
public void initialize(URL location, ResourceBundle resources) {
this.location = location;
this.rb = resources;
coinsValidityFlash.setVisible(false);
insertCoinTxt.setDisable(true);
brand1Btn.setStyle("-fx-background-color: #CACACA;");
brand2Btn.setStyle("-fx-background-color: #CACACA;");
brand3Btn.setStyle("-fx-background-color: #CACACA;");
brand4Btn.setStyle("-fx-background-color: #CACACA;");
brand5Btn.setStyle("-fx-background-color: #CACACA;");
populateVending();
}
.
.
.
}
`
I am new in Javafx (and Java).
I am trying to display a label "title" to different scenes. Therefore this label in a instance variable. I create it using the method title().
The problem is that the title does not appear in my scenes. I tried to initialize it at class level, or inside the start(), or inside scene1(). Nothing works but when I create a local label inside the scene1() method.
Thanks in advance for your help.
ps: full repo here if needed
private Label title;
public void start(Stage primaryStage) {
title();
scene1();
scene2();
[some more code...]
}
public Node title() {
title = new Label();
title.setText("SNAKE");
title.setTextFill(Color.YELLOW);
title.setFont(Font.font(STYLESHEET_MODENA, FontWeight.BOLD, 80));
return title;
}
public Scene scene1() {
Label lbl1scene1 = new Label("Welcome to Game !");
Label lbl2scene1 = new Label("Click to start the game");
Button btnStart = new Button("Start the game now!");
btnStart.setOnAction(e -> {
runGame();
});
VBox vbox1 = new VBox(title, lbl1scene1, lbl2scene1, btnStart);
vbox1.setAlignment(Pos.CENTER);
vbox1.setSpacing(10);
scene1 = new Scene(vbox1, width, height);
return scene1;
}
I am trying to display a label "title" to different scenes.
Please note that a node can have only a single parent.
Create multiple instances using local variables instead of a title member field.