Managing the runtime behavior of a JavaFX MenuBar - java

I have a BorderPane, onto which I placed a MenuBar. At the center of the BorderPane I display differnt AnchorPanes depending on the MenuItem selected. So far so good.
Now, how do I make sure that the Menus change behavior in response to the item selected in the child AnchorPane? So for example if the user selects "Edit", there will be a different action depending on whether the item currently higlighted is a user account, a file etc.
So far I made something along these lines:
The BorderPane controller:
public class MenuTest implements Initializable{
#FXML
private BorderPane borderPaneMain;
#FXML
private AnchorPane anchorPaneMain;
#FXML
private Menu menuEdit;
#FXML
private MenuItem itemEdit;
static String menuMode;
static String entityName;
public MenuTest(){
menuMode ="";
entityName = "";
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
AnchorPane anchor;
try {
anchor = (AnchorPane) new FXMLLoader().load(getClass().getResource("views/MainView.fxml"));
borderPaneMain.setCenter(anchor);
} catch (IOException e) {
e.printStackTrace();
}
}
protected static void setMenuMode(String menuMd, String entityNm){
entityName = entityNm;
menuMode = menuMd;
}
#FXML
private void onEditClick(){
if(entityName.equals(AnchorTest.FILE)){
//Launches correct edit view
new FXMLLoader().load(getClass().getResource("views/EditFile.fxml"));
//Passes the name of the entity so that the controller can retrieve its data
FileEditController.setFile(entityName);
}else if(entityName.equals(AnchorTest.PERSON)){
new FXMLLoader().load(getClass().getResource("views/EditPerson.fxml"));
PersonEditController.setFile(entityName);
}
}
}
The child AnchorPane controller:
public class AnchorTest implements Initializable{
public static final String PERSON = "PERSON";
public static final String FILE = "FILE";
ObservableList<String> peopleList;
ObservableList<String> fileList;
#FXML
private ListView<String> listPeople;
#FXML
private ListView<String> listFiles;
#Override
public void initialize(URL location, ResourceBundle resources) {
peopleList = FXCollections.observableArrayList("Frank","Matin","Anne");
fileList = FXCollections.observableArrayList("hello.txt","holiday.jpg","cv.doc");
listPeople.setItems(peopleList);
listFiles.setItems(fileList);
}
#FXML
private void personSelected(){
MenuTest.setMenuMode(this.PERSON, listPeople.getSelectionModel().getSelectedItem());
}
#FXML
private void fileSelected(){
MenuTest.setMenuMode(this.FILE, listFiles.getSelectionModel().getSelectedItem());
}
}
However I'm not sure that it's the best solution, especially considering the if/elseif statement will need to be altered whenever I add a new element type and its corresponding edit options. So is there any way that I can do this better?

I think if your application has only a few (2-4) different types of "things" that are represented by a AnchorPane, then your approach is totally fine. An alternative to your approach is the state pattern. In that case, your currently selected "item type" would be your state.

Related

Struggling to get JavaFx app working, objects stop existing?

So i'm working on a pet project, essentially a digitalisation of a do it yourself role playing adventure book.
I switched to using Scenebuilder because of the freedom it allows when crafting a GUI.
I'm having trouble binding the data to the screen. It seems the objects I am calling either stop existing or are not the ones i need.
I am using a SQLite database for my data, and that seems to work fine.
I am using Maven to import the things i need, this also seems to work fine however this requires me to use
public class DdAppLauncher {
public static void main(String[] args) {
DdApp2.main(args);
}
}
into ->
public class DdApp2 extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/mainWindow.fxml"));
stage.setTitle("Deathtrap Dungeon");
stage.setScene(new Scene (root, 800,600));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This brings up the titlescreen that contains a start button which is handled by the following controller.
public class MainController {
LocationsPool loc;
Location currentLocation;
#FXML
private ListView<String> inventory;
#FXML
private Text locationDescription;
#FXML
private Text descrA;
#FXML
private Text descrB;
#FXML
private Text descrC;
#FXML
private Text descrD;
#FXML
private Text descrE;
#FXML
private Text descrF;
#FXML
public void startButtonClicked(ActionEvent event){
try {
System.out.println("Starting game");
Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
// Swap screen
Parent root = FXMLLoader.load(getClass().getResource("/fxml/gameWindow.fxml"));
stage.setScene(new Scene(root,800,600));
} catch (IOException e) {
e.printStackTrace();
}
//Setup
loc = new LocationsPool();
currentLocation = loc.getLocation(1);
System.out.println(currentLocation.getDescription());
locationDescription = new Text(currentLocation.getDescription());
System.out.println(locationDescription.getText());
System.out.println(locationDescription);
}
#FXML
public void handleButtonA(){
System.out.println(currentLocation==null);
}
}
Output on console ->
starting game
The clamour... (works)
The clamour... (works)
On button press ->
True
So it seems the app "forgets" fields when it runs? or is it the controller that stop existing and is remade?
Furthermore when trying to bind data to fields with fx:id, it doesn't seem to link those fields to anything until i press that button.
Am i structuring this all wrong? What am i not getting?
The final product should have the description loaded and all the choices loaded for that location.
Then on a selection should load up a new location and new choices so the text needs to be updated.
Thanks in advance.
Kev

Thread for controlling components in JFXDrawer

I'm using a JFXDrawer, SalesDrawer containing a VBox and 2 JFXButtons inside it. The contents of the JFXDrawer have a separate controller, SalesDrawerController.java. The controller file, SalesController.java that contains the JFXDrawer consists of 2 anchor panes that I want to set visible on click of the buttons in the JFXDrawer. Till now I was using a set of static boolean variables and making sure on click of a button in the JFXDrawer one of the variables is set to true. Then in the SalesController.java, I used a TimerTask object to check which of these variables were true and set the needed anchor pane to visible. Is there a better way of doing this?
SalesDrawerController.java
public class SalesDrawerController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void button1Hit(MouseEvent event) {
SalesController.SD[0]=true;
}
#FXML
private void button2Hit(MouseEvent event) {
SalesController.SD[1]=true;
}
}
SalesController.java
public class SalesController implements Initializable {
public static boolean SD[]= {false,false};
static boolean tock=true;
#FXML
private AnchorPane eq_newpane;
#FXML
private AnchorPane eq_delpane;
#FXML
private JFXHamburger SalesHam;
#FXML
private JFXDrawer SalesDraw;
public void initialize(URL url, ResourceBundle rb) {
eq_newpane.setVisible(true);
eq_delpane.setVisible(false);
eq_newpane.setDisable(false);
eq_delpane.setDisable(true);
VBox box = FXMLLoader.load(getClass().getResource("/fxml/SalesDrawer.fxml"));
SalesDraw.setSidePane(box);
HamburgerBackArrowBasicTransition transition = new HamburgerBackArrowBasicTransition(SalesHam);
transition.setRate(-1);
SalesHam.addEventHandler(MouseEvent.MOUSE_PRESSED,(e)->{
transition.setRate(transition.getRate()*-1);
transition.play();
if(SalesDraw.isShown()){
SalesDraw.close();
SalesDraw.toBack();
}
else{
SalesDraw.toFront();
SalesDraw.open();
}
});
threadtock();
}
public void threadtock() {
final java.util.Timer timer = new java.util.Timer();
final TimerTask delayedThreadStartTask;
delayedThreadStartTask = new TimerTask() {
public void run() {
try
{
if(tock){
if(SD[0])
{
eq_newpane.setVisible(true);
eq_delpane.setVisible(false);
eq_newpane.setDisable(false);
eq_delpane.setDisable(true);
}
else if(SD[1]){
eq_delpane.setVisible(true);
eq_newpane.setVisible(false);
eq_newpane.setDisable(true);
eq_delpane.setDisable(false);
}
if(tock)
{
threadtock();
}
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
};
timer.schedule(delayedThreadStartTask, 500);
};
}
Use synchronization mechanisms so you do not inherit deadlocks in your code from 2 or more threads attempting to operate on the same variable at once.
Figuring out how to properly synchronize your threads can be a pain, and it is very hard to explain, let alone debug. So, my best advice is to use google for "Java Concurrency" and "Java Thread Synchronization".
Otherwise, you should start a thread/threads from your main application to preform your intended operations

javaFX getting nullpointer exception when trying to set BarChart from different controller

In my JavaFx application I want to update my BarChart whenever calculate button is clicked. The problem is that I am getting:
java.lang.NullPointerException at
application.StatController.setActivityData(StatController.java:47)
It is always pointing to:`xAxis.setCategories(optionsNames);
But it has elements on the list (See printscreen:https://image.ibb.co/b9YO8Q/Capture.png
In my StatController class I have setActivityData which is called from FormController class.
StatController class:
public class StatController {
#FXML
private BarChart<String, Double> barChart;
#FXML
private CategoryAxis xAxis;
Activities activities = new Activities();
private Map<String, List<Double>> uniqueActivityOptions = new HashMap<>();
private ObservableList<String> optionsNames = FXCollections.observableArrayList();
public StatController(){}
#FXML
private void initialize() {
}
public void setActivityData(Activities activitiesList) {
for(Activity activity : activities.getActivityList()) {
String optionName = activity.getOption();
if(uniqueActivityOptions.containsKey(optionName)) {
uniqueActivityOptions.get(optionName).add((double) activity.getNumber());
} else {
List<Double> activityOptionList = new ArrayList<>();
activityOptionList.add((double) activity.getNumber());
uniqueActivityOptions.put(optionName, activityOptionList);
}
}
for (Map.Entry<String, List<Double>> entry : uniqueActivityOptions.entrySet()) {
optionsNames.add(entry.getKey());
}
xAxis.setCategories(optionsNames);
XYChart.Series<String, Double> series = new XYChart.Series<>();
for (Map.Entry<String, List<Double>> entry : uniqueActivityOptions.entrySet()) {
Double average = calculateAverage(entry.getValue());
series.getData().add(new XYChart.Data<>(entry.getKey().toString(), average));
}
barChart.getData().add(series);
}
private double calculateAverage(List<Double> values) {
double result = 0;
for (Double value : values) {
result += value;
}
return result / values.size();
}
}
FormController class:
public class FormController {
Activities act = new Activities();
List<Activity> activities = act.getActivityList();
private ObservableList<String> opt = FXCollections.observableArrayList(
"Option 1",
"Option 2",
"Option 3"
);
#FXML
private Button calculateButton;
#FXML
private TextField numberField;
#FXML
private ComboBox<String> options;
private String selectedOption;
#FXML
private void initialize() {
options.getItems().addAll(opt);
options.getSelectionModel().selectedItemProperty()
.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
selectedOption = newValue;
}
});
}
#FXML
public void calculateButtonClicked(){
activities.add(new Activity(selectedOption, Integer.parseInt(numberField.getText())));
StatController sc = new StatController();
sc.setActivityData(act);
}
}
I tested setActivityData method in StatsController and it is working correctly when I am passing Activities.
Please advise what to change in the code to pass and update BarChart.
I know that this is something trivial but I really don't know how to do it.
`
Thank you very much for the help!
Your problem is in FormController#calculateButtonClicked(). You create a new instance of StatController manually and then call the setActivityData() method. This is not how JavaFX works and will result in the xAxis member being null, hence your NullPointerException.
Controller members annotated with the #FXML annotation will be injected by the FXMLLoader class when you call the FXMLLoader#load() method. You will need to use the FXMLLoader class to load the .fxml file that corresponds to the StatController class, which will then create an instance of your StatController object for you and will also inject your xAxis and barChart instances.
Here's a quick (not production ready) example that you will have to adapt for your specific scenario:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(filePath); // your path to the .fxml file
loader.load();
StatController controller = (StatController) loader.getController();
controller.setActivityData(activities); // your activities data
How and when you do that depends on how your scene graph is setup. Looking at your source code, I would suggest the following changes:
MainController.java
public class MainController
implements Initializable {
#FXML
private TabPane tabPane;
#FXML
private Tab formTabPage;
#FXML
private FormController formTabController; // change name to this
#FXML
private Tab statsTabPage;
#FXML
private StatController statsTabController; // change name to this
private MainApp mainApp;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
#Override // rename your init() method to this
public void initialize(URL location, ResourceBundle resources) {
// add this line
formTabController.setStatController(statTabController);
}
}
FormController.java
public class FormController {
Activities act = new Activities();
List<Activity> activities = act.getActivityList();
private ObservableList<String> opt = FXCollections.observableArrayList(
"Option 1",
"Option 2",
"Option 3"
);
#FXML
private Button calculateButton;
#FXML
private TextField numberField;
#FXML
private ComboBox<String> options;
private String selectedOption;
private StatController statController; // add this member
#FXML
private void initialize() {
options.getItems().addAll(opt);
options.getSelectionModel().selectedItemProperty()
.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
selectedOption = newValue;
}
});
}
#FXML
public void calculateButtonClicked() {
// change this method here
statController.setActivityData(act);
}
// add this method here
public void setStatController(StatController statController) {
this.statController = statController;
}
}
There are other problems that occur, but this fixes your null pointer exception. The reason this works and your previous code didn't, is because you were manually creating a new instance of StatController and not using the instance that the FXMLLoader had already loaded and mapped to the user interface node for you. All I have done above, is capture a reference to the controller you needed, and provided it to the other through a setter method.

communication betwenn Tabs in JavaFx ans Scene builder

i made a mainEntredController which contains a TabPane with two tabs (HomeController) and (PatientController), The MainEntred Controle the Parameter Passage Between this two tabs but i have liitls issu that the tabs doesnet reconize the Parent (MainEntredController) or each other. it retuns an error inn this line
*** labelpatient.setText(mainec.LoadtxtFromlabelhometabhome()); ******
the idea is to get a text from the first tab to show it in a label in the second tab
hear's my code
the first is the MainEntred Class the Container
public class MainEntredController {
#FXML MenuItem closemi;
#FXML HomeController homecontroller;
#FXML PatientController patientcontroller;
public void initialize(URL location, ResourceBundle resources) {
System.out.println("Application Started");
homecontroller.intit(this);
patientcontroller.init(this);
}
public void ClickMenuItemClose(){
System.exit(0);
}
public String LoadtxtFromlabelhometabhome() {
// System.out.println(homecontroller.labelhome.getText());
return homecontroller.labelhome.getText();
}
public void setTabpatientlabel(String text) {
//patientcontroller.labelpatient.setText(text);
}
}
the Second is the First Tab HomeController
public class HomeController {
public MainEntredController mainec;
#FXML public Label labelhome;
#FXML private TextField tfhome;
#FXML private Button savehome;
#FXML private Button sendhome;
//public HomeController(){}
public void btnSaveHomeClicked(ActionEvent event){
System.out.println("le boutton save home est cliqué");
labelhome.setText(tfhome.getText());
}
public void btnSendHomeClicked(ActionEvent event){
System.out.println("le boutton send home est cliqué");
// send data to tab patient
mainec.setTabpatientlabel(labelhome.getText());
}
public void intit(MainEntredController mainEntredController) {
mainec = mainEntredController;
}
}
the Third one is the Second tab PatientController
public class PatientController {
public MainEntredController mainec;
#FXML public Label labelpatient;
#FXML private TextField tfpatient;
#FXML private Button savepatient;
#FXML private Button loadpatient;
public void btnSavePatientClicked(ActionEvent event){
System.out.println("le boutton save Patient est cliqué");
labelpatient.setText(tfpatient.getText());
}
public void btnLoadPatientClicked(ActionEvent event){
System.out.println("le boutton Load patient est cliqué");
labelpatient.setText(mainec.LoadtxtFromlabelhometabhome());
}
public void init(MainEntredController mainEntredController) {
mainec = mainEntredController;
}
}
And the FXML File of the MainEntred
<fx:include id="homecontroller" source="home.fxml" />
<fx:include id="patientcontroller" source="patient.fxml" />
i guess those two are madding the problem any advise how to solve it Please
i dont know why i am having java.lang.NullPointerException at the line declarred erlier he cant get the source (unknown source)
If i understand correctly then you want to communicate between controller. well, this can be done pretty easy.
in you main type something likes this:
FXMLLoader loader = new FXMLLoader(Main.class.getResource("your.fxml"));
Parent node = loader.load(); // this loads view
YourController instance = loader.getController();
some_class.initialize(instance);
Finelly i found what is wrong. the thing is that in the declaration in the MainController of the two Tabs we put the fx:id declared on the fxml file
and adding the word "Controller"
like this
#FXML private HomeController homeController;
#FXML private PatientController patientController;
the fx:id="home"
the second includes fx:id="patient"

PropertyValueFactory / setCellValueFactory JavaFX [duplicate]

This question already has answers here:
Javafx PropertyValueFactory not populating Tableview
(2 answers)
Closed 8 years ago.
I have following basic code in JavaFx. If I want to change the name of PropertyValueFactory("id") to let's say "rid" it does not fill my table anymore. Even if I change the SimpleLongProperty object to rid too. Do you know where the "id" is referencing / pointing to?
public class Main_Controller implements Initializable{
public class Item {
public SimpleLongProperty id = new SimpleLongProperty();
public Long getId() {
return id.get();
}
}
// The table and columns
#FXML TableView<Item> itemTbl;
#FXML TableColumn itemIdCol;
#FXML Button add_Button;
// The table's data
ObservableList<Item> data;
#Override
public void initialize(URL url, ResourceBundle rb) {
// Set up the table data
itemIdCol.setCellValueFactory(
new PropertyValueFactory<Item,Long>("id")
);
data = FXCollections.observableArrayList();
itemTbl.setItems(data);
}
static long nextId = 1;
#FXML
private void handleButtonAction(ActionEvent event) {
Item item = new Item();
item.id.setValue(nextId++);
data.add(item);
}
}
Code With changed PropertyValueFactory:
public class Main_Controller implements Initializable{
public class Item {
public SimpleLongProperty rid = new SimpleLongProperty();
public Long getId() {
return rid.get();
}
}
// The table and columns
#FXML TableView<Item> itemTbl;
#FXML TableColumn itemIdCol;
#FXML Button add_Button;
// The table's data
ObservableList<Item> data;
#Override
public void initialize(URL url, ResourceBundle rb) {
// Set up the table data
itemIdCol.setCellValueFactory(
new PropertyValueFactory<Item,Long>("rid")
);
data = FXCollections.observableArrayList();
itemTbl.setItems(data);
}
static long nextId = 1;
#FXML
private void handleButtonAction(ActionEvent event) {
Item item = new Item();
item.rid.setValue(nextId++);
data.add(item);
}
}
You need a method in your Item class called
public Long getRid(){return rid.get();}
or
public SimpleLongProperty ridProperty(){return rid;}
Note, it's not the name of the variable, which would normally be private, but the name of the getter that matters.
Javadoc link

Categories

Resources