I want to make a custom cell in the ListView. Excuse my bad English!
I want to display the picture, name and status in the ListView.
For this I use a different Fxml that contains Hbox .
public class Controller {
CollectionContactForListCollection contactForList = new CollectionContactForListCollection();
#FXML
private ListView<Contact> listContact ;
#FXML
HBox hbox;
#FXML
ImageView avatar;
#FXML
Label labelName;
#FXML
Label lblStatus;
#FXML
Label lblSense;
#FXML
private void initialize(){
contactForList.fieldData();
// listContact.setItems((ObservableList) contactForList.getContactList());
listContact.setCellFactory(new Callback<ListView<Contact>, ListCell<Contact>>() {
#Override
public ListCell<Contact> call(ListView<Contact> param) {
ListCell<Contact> listCell = new ListCell<Contact>() {
#Override
protected void updateItem(Contact item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
//This method does not work download
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/view/boxInContact.fxml"));
fxmlLoader.setController(this);
labelName.setText(item.getName());
lblSense.setText(item.getSense());
lblStatus.setText(item.getStatus());
avatar.setImage(item.getImage());
}
}
};
return listCell;
}
});
listContact.setItems((ObservableList) contactForList.getContactList());
}
As a general, rule, you should use a different controller class for each FXML file. With the code you have, all cells are using the same controller instance, so there is only one reference to each control, even though there are many labelNames, etc (one for each cell).
So define a controller for the FXML defined for the list cell, and define the methods you need to update the controls:
public class ContactCellController {
#FXML
private Label labelName ;
#FXML
private Label labelStatus ;
#FXML
private Label labelSense ;
#FXML
private ImageView avatar ;
public void setName(String name) {
labelName.setText(name);
}
public void setStatus(String status) {
labelStatus.setText(status);
}
public void setSense(String sense) {
labelSense.setText(sense);
}
public void setAvatarImage(Image image) {
avatar.setImage(image);
}
}
Update the FXML file to use a controller attribute with fx:controller="my.package.ContactCellController", and then your cell implementation can look like
listContact.setCellFactory(lv -> new ListCell<Contact>() {
private Node graphic ;
private ContactCellController controller ;
{
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/boxInContact.fxml"));
graphic = loader.load();
controller = loader.getController();
} catch (IOException exc) {
throw new RuntimeException(exc);
}
}
#Override
protected void updateItem(Contact contact, boolean empty) {
super.updateItem(contact, empty);
if (empty) {
setGraphic(null);
} else {
controller.setName(contact.getName());
controller.setStatus(contact.getStatus());
controller.setSense(contact.getSense());
controller.setAvatarImage(contact.getImage());
setGraphic(graphic);
}
}
});
Related
Currently I have a main controller which controls adding elements to a Border Pane's left pane for navigation and center pane for content. I am also using fx:root to load new FXML layouts, each with a separate controller into the center pane of the Border Pane. Trying to call the setCenter method of the MainController from a center pane controller my center pane is not updated. I'm guessing the newly created controller is not being associated with the mainPane in the main Controller. How do I associate them?
Main Ctonroller
This loads both the center and left panes with the appropriate views and associated controllers using either button.
public class MainController {
#FXML
BorderPane mainPane;
#FXML
Button orderBtn, adminBtn;
#FXML
static
Label statusTxt;
public void initialize(){
orderBtn.setOnAction(event -> {
setCenter(new ControllerConnector("/view/OrderView.fxml"));
setNav(new ControllerConnector("/view/OrderNav.fxml"));
});
adminBtn.setOnAction(event -> {
setCenter(new ControllerConnector("/view/ProductView.fxml"));
setNav(new ControllerConnector("/view/AdminNav.fxml"));
});
public static void updateStatus(String string) {
statusTxt.setText(string);
}
public void setCenter(ControllerConnector connector){
this.mainPane.setCenter(connector);
}
public void setNav(ControllerConnector connector){
this.mainPane.setLeft(connector);
}
}
Product Controller
The search function works in this controller which is loaded from the associated view so I know the controller was initialized. When the newBtn is clicked nothing happens.
public class ProductController {
#FXML
TableView<Product> productTable;
#FXML
TableColumn prodCol, sizeCol, categoryCol, priceCol;
#FXML
TextField searchTxt;
#FXML
Button searchBtn, newBtn, editBtn, deleteBtn;
public void initialize() throws SQLException {
ObservableList<Product> products = ProductDAO.getProducts();
prodCol.setCellValueFactory(new PropertyValueFactory<Product, String>("name"));
sizeCol.setCellValueFactory(new PropertyValueFactory<Product, String>("size"));
priceCol.setCellValueFactory(new PropertyValueFactory<Product, Double>("price"));
categoryCol.setCellValueFactory(new PropertyValueFactory<Product, String>("category"));
productTable.setItems(products);
searchBtn.setOnAction(event -> {
ObservableList<Product> searchProducts = FXCollections.observableArrayList();
String searchString = searchTxt.getText();
for (Product product : products) {
if(product.getName().contains(searchString) || product.getSize().contains(searchString)){
searchProducts.add(product);
}
}
products.removeAll(products);
products.addAll(searchProducts);
});
searchTxt.setOnMouseClicked(event -> {
products.removeAll(products);
try {
products.addAll(ProductDAO.getProducts());
} catch (SQLException exception) {
exception.printStackTrace();
}
});
newBtn.setOnAction(event -> {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/MainView.fxml"));
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
MainController mainController = loader.getController();
mainController.setCenter(new ControllerConnector("/view/NewProductView.fxml"));
});
}
}
ControllerConnector
Using this class to create a Node to pass into the Border Pane set mehtods.
public class ControllerConnector extends GridPane {
public ControllerConnector(String fxmlPath){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlPath));
fxmlLoader.setRoot(this);
try{
fxmlLoader.load();
}catch (IOException e){
e.printStackTrace();
}
}
}
I'm making an application with JavaFX and Scene Builder.
I have two controllers:Controller and FontController
I have Main class that launch my program and open Stage with first fontroller (Controller)
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("/card/card.fxml"));
Scene scene = new Scene(root, 1600, 600);
primaryStage.setScene(scene);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setResizable(true);
primaryStage.getIcons().add(new Image("card/resources/logo-icon.png"));
primaryStage.show();
//adding resize and drag primary stage
ResizeHelper.addResizeListener(primaryStage);
//assign ALT+ENTER to maximize window
final KeyCombination kb = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.CONTROL_DOWN);
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (kb.match(event)) {
primaryStage.setMaximized(!primaryStage.isMaximized());
primaryStage.setResizable(true);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
There is a label and a button in Controller. When I click on the button a method is called and new window with second controller appears(FontController):
#FXML private Button btnFont;
#FXML private Label category1
#FXML
void changeFont(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new
FXMLLoader(getClass().getResource("font.fxml"));
Parent rootFont = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("Select Font");
stage.setScene(new Scene(rootFont));
stage.show();
} catch (Exception e) {
System.out.println("can't load new window");
}
}
There is the button "OK" and label in FontCOntroller:
#FXML private Label fontLabel;
#FXML private Button btnFontOk;
Please tell me, what should I do to send and apply text from label in FontController when I click on the burtton "OK" to label in Controller?
SOLUTION FOUND:
I created class "Context" in my project directory to make all controllers communicate each other. You can add as many controllers as you want there.
Here it looks like:
package card;
public class Context {
private final static Context instance = new Context();
public static Context getInstance() {
return instance;
}
private Controller controller;
public void setController(Controller controller) {
this.controller=controller;
}
public Controller getController() {
return controller;
}
private FontController fontController;
public void setFontController(FontController fontController) {
this.fontController=fontController;
}
public FontController getFontController() {
return fontController;
}
}
Controller:
I created getters and setters (ALT + Insert in IDEA) for Label that I wanna change
public Label getCategory1() {
return category1;
}
public void setCategory1(Label category1) {
this.category1 = category1;
}
To get FontController variables and methods through Context class I placed line of code
//getting FontController through Context Class
FontController fontCont = Context.getInstance().getFontController();
I registered Controller in Context class through my initialize method (my class implements Initializable)
#FXML
public void initialize(URL location, ResourceBundle resources) {
//register Controller in Context Class
Context.getInstance().setController(this);
}
FontController:
to get Controller variables and methods I placed this code:
//getting Controller variables and methods through Context class
Controller cont = Context.getInstance().getController();
I also registered FontController in Context class through initialize method:
#Override
public void initialize(URL location, ResourceBundle resources) {
//register FontController in Context Class
Context.getInstance().setFontController(this);
}
Method that send text and text color from label in this FontController to label in Controller when I click on button:
#FXML
void applyFont(ActionEvent event) {
cont.getCategory1().setText(fontLabel.getText());
cont.getCategory1().setTextFill(fontLabel.getTextFill());
}
*By creating Context class you can make controllers communicate each other and create as many controllers as you want there. Controllers see variables and methods of each other
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.
The core of the problem is that I cannot refresh or change the content of a node of a scene (here TablesMain) from another class (here NamePriceCell).
I am building and application with a main StackPane (TableMainController extends StackPane) that contains other nodes, some of which are ListViews. In a specific ListView (say 'readitemslistview') I have created a custom ListCell (public class NamePriceCell extends ListCell) and this NamePriceCell listcell contains a button (plusbtn). When a user clicks on the plusbtn, an arraylist (say 'chosenitemslist') gets filled up with the item displayed in the particular cell and at the same time another listview in the stackpane (say 'ordereditemslist') should be triggered to display the content of the 'chosenitemslist' arraylist.
I haven't found a way to reference the plusbtn of the NamePriceCell from the main controller. Moreover this post
Get ListCell via ListView
Unfortunately right now there is no API to get List Cell by index or
to get All children's(listcells) for ListView.
has turned me away from such an attempt (I haven't been able to understand if the solution provided there is suitable for my needs, as I don't want a refresh of the same listview but from another in the same scene).
So the only way I have found to get a mouse click action from the plusbtn is with an EventHandler in the same class (NamePriceCell). And even though I can get an arraylist filled according to the clicks on the NamePriceCell buttons (the NamePriceCell calls a static method in another class with a static arraylist in it) and I can get the result in my orderedlistview by letting the user click on another button node of the TableMain StackPane, I cannot find a way to do that when the user clicks on the NamePriceCell button.
Among the things I tried is setting a static method in the TablesMainController that I can access it from the NamePriceCell EventHandler (but then I have to make the holder node of the ordereditemslist static, and even so, it hasn't worked),
writing a separate fxml for the VBox containing the ordereditemslist with a corresponding separate controller so that I can have a method there to create the ordereditemslist and set the cellFactory (but even though the fxml file displays correctly and calling the method from the EventHandler of NamePriceCell works - a System.out gets displayed - it won't affect anything on the screen not the ordereditemslist, not even a label I tested.
I am relatively a java newbie and even so more when it comes to javafx. Your help is greatly needed and appreciated.
The code of my last approach is below:
VboxList.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.VBox?>
<VBox>
<Label text="Label" id="label1"/>
<Label text="Label" id="label2"/>
<Label text="sdfsdf" id="label3"/>
<ListView nodeOrientation="LEFT_TO_RIGHT" id="orderedlist" prefHeight="200.0" prefWidth="200.0" />
</VBox>
VboxListController.java
public class VboxListController extends VBox{
FXMLLoader fxmlLoader;
#FXML
private Label label1;
#FXML
private Label label2;
#FXML
private ListView<OrderItem> orderedlist;
public VboxListController() {
fxmlLoader = new FXMLLoader(getClass().getResource("fxml/VboxList.fxml"));
//fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void getorderedlist(ArrayList<OrderItem> chosenitems){
ObservableList<OrderItem> myObservableList = FXCollections.observableList(chosenitems);
this.getChildren().remove(orderedlist);
orderedlist = new ListView<>();
orderedlist.setItems(myObservableList);
orderedlist.setCellFactory(new Callback<ListView<OrderItem>, ListCell<OrderItem>>() {
#Override
public ListCell<OrderItem> call(ListView<OrderItem> p) {
ListCell<OrderItem> cell = new ListCell<OrderItem>() {
#Override
protected void updateItem(OrderItem dataobj, boolean bln) {
super.updateItem(dataobj, bln);
if (dataobj != null) {
setText(dataobj.read_item_name);
}
}
};
return cell;
}
});
this.getChildren().add(orderedlist);
orderedlist.setItems(null);
orderedlist.setItems(myObservableList);
label1 = null;
this.getChildren().remove(label1);
label1 = new Label();
label1.setText("oooops!");
System.out.println("sdf");
this.getChildren().add(label1);
}
}
NamePriceCell.java
public class NamePriceCell extends ListCell<ReadItemChoiceObj> {
int count=0;
#FXML
Label namelbl;
#FXML
Label pricelbl;
#FXML
Button plusbtn;
#FXML
Region spacer;
#FXML
HBox cellHbox;
FXMLLoader mLLoader;
ReadItemChoiceObj readitem;
ArrayList<ReadItemChoiceObj> chosenreaditems;
#Override
protected void updateItem(ReadItemChoiceObj readitem, boolean empty) {
super.updateItem(readitem, empty);
chosenreaditems = new ArrayList<>();
if(empty || readitem == null) {
setText(null);
setGraphic(null);
} else {
this.readitem = readitem;
if (mLLoader == null) {
mLLoader = new FXMLLoader(getClass().getResource("fxml/NamePriceCell.fxml"));
mLLoader.setController(this);
try {
mLLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
namelbl.setText(readitem.getname());
namelbl.setMaxWidth(500);
pricelbl.setText(String.format("%.2f", readitem.getprice()));
pricelbl.setStyle("-fx-font: 8pt \"Arial\";");
pricelbl.setMaxWidth(40);
spacer.setMaxWidth(10);
spacer.setMinWidth(10);
plusbtn.setMaxWidth(20);
cellHbox.setHgrow(namelbl, Priority.ALWAYS);
cellHbox.setAlignment(Pos.BASELINE_LEFT);
setText(null);
setGraphic(cellHbox);
plusbtn.setOnMouseClicked(whattodohandler);
}
}
EventHandler<MouseEvent> whattodohandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
ChosenItemsStat.getplusbtnclicked(readitem);
ChosenItemsStat chos = new ChosenItemsStat();
count+=1;
plusbtn.setText(String.valueOf(count));
new VboxListController().getorderedlist(chos.sendchosenlist());
}
};
}
and a part of TablesMain.java
#FXML
Label label1;
#FXML
BorderPane borderpane;
#FXML
Pane tablepane;
#FXML
ListView<DataObj> tabcatlist;
#FXML
VBox VboxList;
#FXML
VboxListController vboxListController;
/* #FXML
ListView<OrderItem> orderedlist;*/
#FXML
VBox leftVbox;
public TablesMain(Order order) {
this.order = order;
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("fxml/tablesmain.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
ObservableList<DataObj> myObservableList = FXCollections.observableList(gettable_categories());
tabcatlist.setItems(myObservableList);
tabcatlist.setCellFactory(new Callback<ListView<DataObj>, ListCell<DataObj>>() {
#Override
public ListCell<DataObj> call(ListView<DataObj> p) {
ListCell<DataObj> cell = new ListCell<DataObj>() {
#Override
protected void updateItem(DataObj dataobj, boolean bln) {
super.updateItem(dataobj, bln);
if (dataobj != null) {
setText(dataobj.getname());
}
}
};
return cell;
}
});
if (table_categoryID == 0) table_categoryID = tablecategories.get(0).getid();
gettables(table_categoryID);
}
private void make_readitemlist(int itemcategoryID) {
readitems = new ArrayList<>();
.... the readitems arraylist gets filled up...
ObservableList<ReadItemChoiceObj> myObservableList = FXCollections.observableList(readitems);
readitemlist.setItems(myObservableList);
readitemlist.setCellFactory(new Callback<ListView<ReadItemChoiceObj>,
ListCell<ReadItemChoiceObj>>() {
#Override
public ListCell<ReadItemChoiceObj> call(ListView<ReadItemChoiceObj> list) {
readitemlistcell = new NamePriceCell();
return readitemlistcell;
}
}
);
readitemlist.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
ReadItemChoiceObj dataobj = (ReadItemChoiceObj) readitemlist.getSelectionModel().getSelectedItem();
System.out.println(String.valueOf(dataobj.getid()));
// showorderedlist(new ChosenItemsStat().sendchosenlist());
}
});
System.out.println(readitemlist.cellFactoryProperty().getClass().toString());
}
...
}
Since it's been almost 5 months without another contribution, i would like to post my solution as an answer. It's not mine but inspired (or copied) by the post mentioned in my question (or another one, now I don't remember, please excuse my possible misplacement of credits).
More precisely I created a static StringProperty in TableMain file (the main Controller class). Every time the plusbtn (NamePriceCell class that extends ListCell) is clicked a random number turned to string is forced in the static StringProperty of the TableMain controller class. Inside this class the StringProperty has a ChangeListener added which in its turn triggers (from inside of the main controller now - that was the clue) the refreshing of the orderedlist (the listview that had to be refreshed with the added items).
NamePriceCell.java
public class NamePriceCell extends ListCell<ReadItemChoiceObj> {
#FXML
Label namelbl;
#FXML
Label pricelbl;
#FXML
Button plusbtn;
#FXML
Region spacer;
#FXML
HBox cellHbox;
FXMLLoader mLLoader;
ReadItemChoiceObj readitem;
#Override
protected void updateItem(ReadItemChoiceObj readitem, boolean empty) {
super.updateItem(readitem, empty);
if(empty || readitem == null) {
setText(null);
setGraphic(null);
} else {
this.readitem = readitem;
if (mLLoader == null) {
mLLoader = new FXMLLoader(getClass().getResource("fxml/NamePriceCell.fxml"));
mLLoader.setController(this);
try {
mLLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
namelbl.setText(readitem.getname());
namelbl.setMaxWidth(500);
pricelbl.setText(String.format("%.2f", readitem.getprice()));
pricelbl.setStyle("-fx-font: 8pt \"Arial\";");
pricelbl.setMaxWidth(40);
spacer.setMaxWidth(10);
spacer.setMinWidth(10);
plusbtn.setMaxWidth(20);
cellHbox.setHgrow(namelbl, Priority.ALWAYS);
cellHbox.setAlignment(Pos.BASELINE_LEFT);
setText(null);
setGraphic(cellHbox);
plusbtn.setOnMouseClicked(whattodohandler);
}
}
EventHandler<MouseEvent> whattodohandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
OrderItem orderitem = new OrderItem();
orderitem.read_item_name = readitem.getname();
orderitem.read_item_price=readitem.getprice();
orderitem.read_itemID=readitem.getid();
orderitem.choice_name_list = new ArrayList<String>();
orderitem.choice_price_list = new ArrayList<Float>();
orderitem.choice_id_list = new ArrayList<Integer>();
ChosenItemsStat.getplusbtnclicked(orderitem);
Random rand = new Random();
TablesMain.stringProperty.setValue(String.valueOf(rand));
}
};
}
ChosenItemsStat.java (a static class receiving the additions to the list)
public class ChosenItemsStat {
static ArrayList<OrderItem> chosenorderitemsstat = new ArrayList<>();
static void getplusbtnclicked(OrderItem orderitem){
chosenorderitemsstat.add(orderitem);
}
}
I didn't use the VboxList.fxml or the VboxListController.java as I included this in the TablesMain.java (see below)
TablesMain.java (as in the question but with this addition)
stringProperty = new SimpleStringProperty();
stringProperty.setValue("");
tablelbl.setText(stringProperty.getValue());
stringProperty.addListener(new ChangeListener(){
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
showselectedlist();
}
});
while the showselectedlist() that is being called by the change of the StringProperty value is the one below (in this method the cell is being contructed as in the removed class (VboxListController)
private void showselectedlist(){
orderitems.addAll(ChosenItemsStat.chosenorderitemsstat);
ChosenItemsStat.chosenorderitemsstat.clear();
ListView<OrderItem> selectedlist = new ListView<>();
ObservableList<OrderItem> myObservableList = FXCollections.observableList(orderitems);
selectedlist.setItems(myObservableList);
selectedlist.setMaxWidth(220);
selectedlist.setCellFactory(new Callback<ListView<OrderItem>, ListCell<OrderItem>>() {
#Override
public ListCell<OrderItem> call(ListView<OrderItem> p) {
ListCell<OrderItem> cell = new ListCell<OrderItem>(){
OrderedCell ordcell= new OrderedCell();
#Override
protected void updateItem(OrderItem orderitem, boolean bln) {
super.updateItem(orderitem, bln);
if (orderitem != null) {
Float price = ordcell.getitemTempPrice(orderitem,orderitem.read_item_price);
HBox namepriceHbox = new HBox();
Label lblprice= new Label(String.format("%.2f",price));
Region betweenregion = new Region();
Label lblname = new Label();
lblname.setText(orderitem.read_item_name );
lblname.setStyle("-fx-font: 10pt \"Arial\";-fx-font-weight:bold");
namepriceHbox.setHgrow(betweenregion, Priority.ALWAYS);
namepriceHbox.getChildren().addAll(lblname,betweenregion,lblprice);
VBox allVbox = new VBox();
Text lblchoices = new Text();
String choices = ordcell.choicestostring(orderitem);
lblchoices.setText(choices);
lblchoices.setWrappingWidth(listpane.getLayoutBounds().getWidth());
if (choices.equals("")) allVbox.getChildren().addAll(namepriceHbox);
else allVbox.getChildren().addAll(namepriceHbox, lblchoices);
double namepricewidth = listpane.getLayoutBounds().getWidth();
System.out.println("namepricewidth is "+String.valueOf(namepricewidth));
//allVbox.setPadding(new Insets(10,0,10,0));
setGraphic(allVbox);
}
}
};
return cell;
}
});
listpane.getChildren().add(selectedlist);
}
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.