JavaFX pass values from child to parent - java-8

I have one parent controller which contain one button. When I click on button it open new window and show some data into table. The code I have used for opening window is
Stage stage = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("../layout/SearchCustomer.fxml"));
Parent parent = (Parent) fxmlLoader.load();
Scene scene = new Scene(parent);
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(parent.getScene().getWindow());
stage.setScene(scene);
stage.resizableProperty().setValue(false);
stage.showAndWait();
It opens window properly. Now what I need is, when I double click on the row of table of the child window, It should set some value in parent controller textbox. How would we pass this value from child controller to parent controller?

Expose a property in your child controller and observe it from the "parent" controller. There isn't really enough information in your question to give a precise answer, but it would look something like:
public class ChildController {
#FXML
private TableView<Customer> customerTable ;
private final ReadOnlyObjectWrapper<Customer> currentCustomer = new ReadOnlyObjectWrapper<>();
public ReadOnlyObjectProperty<Customer> currentCustomerProperty() {
return currentCustomer.getReadOnlyProperty() ;
}
public Customer getCurrentCustomer() {
return currentCustomer.get();
}
public void initialize() {
// set up double click on table:
customerTable.setRowFactory(tv -> {
TableRow<Customer> row = new TableRow<>();
row.setOnMouseClicked(e -> {
if (row.getClickCount() == 2 && ! row.isEmpty()) {
currentCustomer.set(row.getItem());
}
}
});
}
}
and then you just do:
Stage stage = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("../layout/SearchCustomer.fxml"));
Parent parent = (Parent) fxmlLoader.load();
ChildController childController = fxmlLoader.getController();
childController.currentCustomerProperty().addListener((obs, oldCustomer, newCustomer) -> {
// do whatever you need with newCustomer....
});
Scene scene = new Scene(parent);
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(parent.getScene().getWindow());
stage.setScene(scene);
stage.resizableProperty().setValue(false);
stage.showAndWait();
An alternative approach is to use a Consumer as a callback in the child controller:
public class ChildController {
#FXML
private TableView<Customer> customerTable ;
private Consumer<Customer> customerSelectCallback ;
public void setCustomerSelectCallback(Consumer<Customer> callback) {
this.customerSelectCallback = callback ;
}
public void initialize() {
// set up double click on table:
customerTable.setRowFactory(tv -> {
TableRow<Customer> row = new TableRow<>();
row.setOnMouseClicked(e -> {
if (row.getClickCount() == 2 && ! row.isEmpty()) {
if (customerSelectCallback != null) {
customerSelectCallback.accept(row.getItem());
}
}
}
});
}
}
And in this version you do
Stage stage = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("../layout/SearchCustomer.fxml"));
Parent parent = (Parent) fxmlLoader.load();
ChildController childController = fxmlLoader.getController();
childController.setCustomerSelectCallback(customer -> {
// do whatever you need with customer....
});
Scene scene = new Scene(parent);
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(parent.getScene().getWindow());
stage.setScene(scene);
stage.resizableProperty().setValue(false);
stage.showAndWait();

In my example below, the account window (child/dialog) passes the newly selected account name back to the parent window when the save button is clicked. On the parent window ...
Stage nstage = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Accounts.fxml"));
Parent root = (Parent) fxmlLoader.load();
AccountsController accController = fxmlLoader.getController();
accController.dialogMode(); //Call a function in acconunt windoow to preset some fields
Scene scene = new Scene(root);
nstage.setTitle("Create Company Account");
nstage.setScene(scene);
Stage stage = (Stage) src.getScene().getWindow();
nstage.initOwner(stage);
nstage.initModality(Modality.APPLICATION_MODAL);
//nstage.initStyle(StageStyle.UNDECORATED);
nstage.show();
nstage.setOnCloseRequest(new EventHandler<WindowEvent>(){
public void handle(WindowEvent we) {
txtCompany.setText( accController.lbl1.getText() );
}
});

Related

Xamarin Prism cannot navigate with parameters

for my app i create my own buttons using a frame and adding a tapgesture to it. here i use the navigation of prism to go to a specific page with a parameter. however. the viewmodel i'm going to does not trigger the Navigated to method. here is some code.
during debugging it seems that the adding of the parameters is no problem. however the constructor for the viewmodel is called instead.
button
public class FolderButton : Frame
{
public FolderButton(Folder folder, INavigationService navigationService)
{
var navParams = new NavigationParameters();
navParams.Add("folder", folder);
GestureRecognizers.Add(new TapGestureRecognizer()
{
Command = new Command(async () => { await navigationService.NavigateAsync("FolderInventory", navParams); }),
});
BackgroundColor = Color.CornflowerBlue;
var thickness = new Thickness();
thickness.Bottom = 10;
thickness.Left = 10;
thickness.Right = 10;
thickness.Top = 10;
Margin = thickness;
CornerRadius = 5;
var completeStack = new StackLayout();
var imgStack = new StackLayout();
imgStack.Padding = thickness;
imgStack.Children.Add(new Image { Source = "folder.png" });
completeStack.Children.Add(imgStack);
var lblStack = new StackLayout();
lblStack.Padding = thickness;
lblStack.Children.Add(new Label
{
Text = folder.Name,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Start
});
completeStack.Children.Add(lblStack);
Content = completeStack;
}
}
called viewmodel
public class FolderInventoryViewModel : BindableBase, INavigatedAware
{
public Folder Folder => _folder;
private readonly INavigationService _navigationService;
private Folder _folder;
private readonly ISQLiteService _sqlService;
private List<Frame> _buttons;
public List<Frame> Buttons
{
get => _buttons;
set => _buttons = value;
}
public FolderInventoryViewModel(Folder folder, INavigationService navigationService, ISQLiteService sqlService)
{
_folder = folder;
_sqlService = sqlService;
_navigationService = navigationService;
GetItemsForFolder();
}
private void GetItemsForFolder()
{
var itemList = _sqlService.GetAllFolderItems(Folder.Name);
foreach (var item in itemList)
{
var itemButton = new ItemButton(_navigationService, item);
_buttons.Add(itemButton);
}
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
if (parameters["folder"] is Folder folder)
{
_folder = folder;
}
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters["folder"] is Folder folder)
{
_folder = folder;
}
}
}
This is not the essence of using the framework. To properly use the Prism with its NavigationParameters you first properly maintain the MVVM idea behind it.
E.g.
<Button Command="{Binding testCommand}" text="TestButton"/>
Your ViewModel (Pardon about this, you need to inject NavigationService to your ViewModel's constructor)
private DelegateCommand _testCommand;
public DelegateCommand testCommand =>
_testCommand?? (_testCommand= new DelegateCommand(ExecuteTest));
private void ExecuteTest()
{
NavigationParameters navigationParameters = new NavigationParameters();
navigationParameters.Add("yourParameterId", value);
NavigationService.NavigateAsync("YourPage", navigationParameters);
}
And then onto your next page
Inherit INavigationAware to your NextPage : YourNextPage: BaseViewModel, INavigationAware
INavigationAware has 3 methods NavigatingTo, NavigatedTo, NavigatedFrom
Inside OnNavigatedTo you can call the parameters you have passed
public void OnNavigatedTo(NavigationParameters parameters)
{
//You can check if parameters has value before passing it inside
if(parameters.ContainsKey("YourParameterId")
{
yourItem = (dataType)parameters[YourParameterId];
}
}
Also note: The constructor will always be called first before the Navigating methods

getting source scene from menuItem

I found related questions on the side, but no without using FXML, MenuItem doesn't have a super class that I can cast down to get the scene. I use the below approach when I found the same problem with a button instance.
(Scene) ((Node) event.getSource()).getScene();
I any ideas on how to solve this issue will be most appreciated.
Thanks In advance
here is the window's view
/**
* #author Jose Gonzalez
*
*/
public class Transaction extends TempletePattern{
private ImageView viewImage;
private Button button;
private TransactionController controller;
private TableView<Saving> table;
private TableColumn dateColum;
private TableColumn descriptionColum;
private TableColumn amountColum;
private TableColumn valanceColum;
/**
*
* #param controller
*/
public Transaction(TransactionController controller)
{
this.controller = controller;
}
/**
* main method all private methods in the class and set them up on the borderpane
* #return pane fully setup to be mount on the scene
*/
public BorderPane setScreen()
{
BorderPane trans = new BorderPane();
trans.setStyle("-fx-background: #FFFFFF;");
VBox topBox = new VBox ();
topBox.getChildren().addAll( setMenu(),setTop() );
trans.setTop(topBox );
trans.setBottom(processUpdate(process) );
trans.setCenter(setCenter() );
return trans;
}
/**
*
* #return vbox holding all note pertaining to the center of the borderpane
*/
private VBox setCenter()
{
VBox center = new VBox();
center.setPadding(new Insets(30, 20, 20, 20) );
table = new TableView<>();
table.setEditable(true);
dateColum = new TableColumn("Date");
dateColum.setCellValueFactory( new PropertyValueFactory<>("firstName"));
dateColum.prefWidthProperty().bind(table.widthProperty().divide(4));
dateColum.setResizable(false);
descriptionColum = new TableColumn("Description");
descriptionColum.prefWidthProperty().bind(table.widthProperty().divide(4));
descriptionColum.setResizable(false);
descriptionColum.setCellValueFactory(new PropertyValueFactory<>("lastName") );
amountColum = new TableColumn("Amount");
amountColum.prefWidthProperty().bind(table.widthProperty().divide(4));
amountColum.setResizable(false);
amountColum.setCellValueFactory( new PropertyValueFactory<>("transaction") );
valanceColum = new TableColumn("Availble Valance");
valanceColum.prefWidthProperty().bind(table.widthProperty().divide(4));
valanceColum.setResizable(false);
valanceColum.setCellValueFactory( new PropertyValueFactory<>("valance"));
table.getColumns().addAll(dateColum, descriptionColum, amountColum,valanceColum );
mockInfo();
center.getChildren().add(table);
return center;
}
/**
*
* #return the screen's menu fully set up
*/
private MenuBar setMenu()
{
MenuBar menubar = new MenuBar();
final Menu UserMenu = new Menu("User");
UserMenu.setId("user");
UserMenu.setOnAction(controller);
MenuItem itemLog = new MenuItem("log out");
itemLog.setId("logout");
itemLog.setOnAction(controller);
MenuItem itemAcount = new MenuItem("new acount");
itemAcount.setId("newAccount");
UserMenu.getItems().addAll(itemLog, itemAcount);
final Menu acctMenu = new Menu("Accounts");
MenuItem itemsavis = new MenuItem("Savings");
MenuItem itemCredit = new MenuItem("Credit");
MenuItem itemChecking = new MenuItem("Checking");
acctMenu.getItems().addAll(itemsavis, itemCredit, itemChecking);
final Menu aboutMenu = new Menu("about");
MenuItem itemHelp = new MenuItem("Help");
aboutMenu.getItems().addAll(itemHelp);
menubar.getMenus().addAll(UserMenu, acctMenu, aboutMenu);
return menubar;
}
/**
* #param receive constumer info from model through controller and set it on table.
*/
public void tableDataSetter(ObservableList<Saving> costumerInfo)
{
table.setItems(costumerInfo);
}
/**
* insert mock data to be displayed as a place holder
*/
private void mockInfo()
{
ObservableList<Saving> data =
FXCollections.observableArrayList(
new Saving("11/10/16", "Deposit", 123, "123" ),
new Saving("11/11/16", "Withdraw", 5, "123" ),
new Saving("11/12/16", "Deposit", 24, "123" ),
new Saving("11/13/16", "Withdraw", 62, "123" ),
new Saving("11/14/16", "Deposit", 134, "123" ),
new Saving("11/15/16", "Deposit", 134, "123" ),
new Saving("11/10/16", "Withdraw", 123, "123" ),
new Saving("11/11/16", "Deposit", 5, "123" ),
new Saving("11/12/16", "Withdraw", 24, "123" ),
new Saving("11/13/16", "Deposit", 62, "123" ),
new Saving("11/14/16", "Withdraw", 134, "123" ),
new Saving("11/15/16", "Deposit", 134, "123" )
);
table.setItems( data );
}
}
and here is the controller. ps I just started working on it.
public class TransactionController implements EventHandler{
//private LoginModel model ;
private Transaction view;
public TransactionController()
{
// model = new LoginModel();
view = new Transaction(this);
}
#Override
public void handle(Event event) {
if( ( event.getSource() instanceof MenuItem))
{
System.out.println( "afe " + (((Object)event.getTarget())) );
if( ( (MenuItem) (event.getSource()) ).getId().equals("logout") )
{
System.err.println("from inside logout");
/// this.goTologInt(event);
}else if( ( (MenuItem) (event.getSource()) ).getId().equals("newAccount") )
{
// this.goToNewAct(event);
}
}
}
private void goToNewAct(Event event)
{
Scene scene = (Scene) ( (Control) event.getSource()).getScene();
// Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
NewCustomerView newAcct = new NewCustomerView();
scene.setRoot( newAcct.newCustomerScreen());
}
private void goTologInt(Event event)
{
Scene scene = (Scene) ((Node)event.getSource()).getScene() ;
//Scene scene = (Scene) ((Control) event.getSource()).getScene();
// Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
LoginView newAcct = new LoginView( new LoginController());
scene.setRoot( newAcct.loginScreen());
}
}
You could go up through the menu hierarchy until you reach a menu with a popup and get the owner window of this popup, which is the window the context menu was opened from.
#Override
public void start(Stage primaryStage) {
MenuItem menuItem = new MenuItem("Something");
Menu menuInner = new Menu("menu", null, menuItem);
Menu menuOuter = new Menu("menu", null, menuInner);
MenuBar menuBar = new MenuBar(menuOuter);
StackPane root = new StackPane();
root.setAlignment(Pos.TOP_LEFT);
root.getChildren().add(menuBar);
Scene scene = new Scene(root);
menuItem.setOnAction(evt -> {
MenuItem m = (MenuItem) evt.getSource();
while (m.getParentPopup() == null) {
m = m.getParentMenu();
}
Scene s = m.getParentPopup().getOwnerWindow().getScene();
System.out.println(s == scene);
});
primaryStage.setScene(scene);
primaryStage.show();
}
In general I'd consider it a better idea though to simply make the information about the scene in question available to the EventHandler of the MenuItem:
final Scene scene = ...
menuItem.setOnAction(evt -> {
// do something with scene
});
The alternative to a (effectively) final local variable would be using a field...

How to use a modal pop up to update data in a grid with a custom cell

I would like to be able to use a modal window to present the contents of a column to the user for editing. I am not able to make this work, and I am not sure where I am going wrong.
I have provided a button in the table which will indicate if there are additional details (in this case comments). When the user selects the button, I want to open a modal dialog to enter the data and when it closes, update the field.
I have gotten the majority of this wired up, but the data is not making it back to my model. I have tried several things, and all without results. It appears that the commit edit call I am making is not seeing the field as in "edit mode" and just skips.
This is my code for my custom table cell:
public class CommentTableCell<T> extends TableCell<T, String> {
private Button actionBtn;
private TextArea textArea;
public CommentTableCell(TableColumn<T, String> column) {
super();
actionBtn = new Button("my action");
actionBtn.setTooltip(new Tooltip("Select to add/edit comments..."));
actionBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
startEdit();
System.out.println("Action: "+getItem());
Stage commentStage = new Stage();
AnchorPane ap = new AnchorPane();
textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 5.0);
AnchorPane.setBottomAnchor(textArea, 5.0);
AnchorPane.setLeftAnchor(textArea, 5.0);
AnchorPane.setRightAnchor(textArea, 5.0);
ap.getChildren().add(textArea);
Scene commentScene = new Scene (ap, 200, 200);
commentStage.setScene(commentScene);
commentStage.show();
commentStage.setOnCloseRequest(a -> {
commitEdit(textArea.getText());
});
// I have tried with an column.setOnEditCommit() as well as what is noted below which I found here, passing in the column.
final TableView<T> tableView = getTableView();
tableView.getSelectionModel().select(getTableRow().getIndex());
tableView.edit(tableView.getSelectionModel().getSelectedIndex(), column);
}
});
setText(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
actionBtn.getStyleClass().clear();
setEditable(false);
if (item != null && item.length() > 0) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_EDIT_COMMNET);
setGraphic(actionBtn);
} else if (!empty) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_ADD_COMMNET);
setGraphic(actionBtn);
} else {
setGraphic(null);
}
}
}
During the execution it hits the commitEdit() call and the following has isEditing in the TableCell as null:
#Override public void commitEdit(T newValue) {
if (! isEditing()) return;
My table looks basically like this:
TableView<SomeDTO> addressTableView = new TableView()
addressTableView.setItems(sortedItems);
addressTableView.setEditable(true);
commentsColumn.setCellValueFactory(cellValue -> cellValue.getValue().commentsProperty());
commentsColumn.setCellFactory(tc -> new CommentTableCell<SomeDTO>(commentsColumn));
I have found a solution to my issue - though I am not sure it is the best way or not.
I have changed my CommentTableCell as follows and it seems to work like a charm..
public class CommentTableCell<T> extends TableCell<T, String> {
private Button actionBtn;
public CommentTableCell() {
super();
actionBtn = new Button("my action");
actionBtn.setTooltip(new Tooltip("Select to add/edit comments..."));
actionBtn.setOnAction(event ->
{
Stage commentStage = new Stage();
AnchorPane ap = new AnchorPane();
TextArea textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 5.0);
AnchorPane.setBottomAnchor(textArea, 5.0);
AnchorPane.setLeftAnchor(textArea, 5.0);
AnchorPane.setRightAnchor(textArea, 5.0);
ap.getChildren().add(textArea);
Scene commentScene = new Scene (ap, 200, 200);
commentStage.setScene(commentScene);
if(getItem() != null) {
String myValue = getItem();
textArea.setText(myValue);
textArea.selectAll();
}
commentStage.show();
commentStage.setOnCloseRequest(a -> {
commitEdit(textArea.getText());
});
});
}
#Override
#SuppressWarnings({"unchecked", "rawtypes"})
public void commitEdit(String item) {
if (isEditing()) {
super.commitEdit(item);
} else {
final TableView table = getTableView();
if (table != null) {
TablePosition position = new TablePosition(getTableView(),
getTableRow().getIndex(), getTableColumn());
CellEditEvent editEvent = new CellEditEvent(table, position,
TableColumn.editCommitEvent(), item);
Event.fireEvent(getTableColumn(), editEvent);
}
updateItem(item, false);
if (table != null) {
table.edit(-1, null);
}
}
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
actionBtn.getStyleClass().clear();
setEditable(false);
if (item != null && item.length() > 0) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_EDIT_COMMNET);
setGraphic(actionBtn);
} else if (!empty) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_ADD_COMMNET);
setGraphic(actionBtn);
} else {
setGraphic(null);
}
}
}

Add buttons in a loop dynamically in HBox which is already present in javafx UI

I am fetching the count of the rows from the db table on click of one button and depending upon the count ,I am adding the buttons in HBOX which is already present in the UI. That loop is running fine but My problem here is only one button with the last entry in the loop is being added to the ui .
On click of the button Called Function is:
#FXML
public void goToProjectUpdateScreen(ActionEvent event) {
Session session = NewHibernateUtil.opensession();
session.beginTransaction();
Query queryResult = session.createQuery("from Project where RegistrationId= 3");
java.util.List allUsers;
allUsers = queryResult.list();
for (int i = 0; i < allUsers.size(); i++) {
try {
Project project = (Project) allUsers.get(i);
Button button = new Button();
button.setOnMouseClicked((MouseEvent t) ->
{
System.out.println(button.getId());
});
button.setText(project.getProjectname());
button.setId(Integer.toString(project.getProjectid()));
showProjectUpdateDialog(button,project.getProjectname());
} catch (IOException ex) {
Logger.getLogger(NewProjectScreenController.class.getName()).log(Level.SEVERE, null, ex);
}
}
session.getTransaction().commit();
session.close();
}
public void showProjectUpdateDialog(Button button,String buttonname) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ProjectUpdateScreen.fxml"));
Parent root = (Parent) fxmlLoader.load();
ProjectUpdateScreenController controller = fxmlLoader.<ProjectUpdateScreenController>getController();
controller.setButton(button,buttonname);
fxmlLoader.setController(button);
MainController.parentWindow.getScene().setRoot(root);
}
SetButton Function in Project Update Screen:
#FXML
public HBox projectlist;
public void setButton(Button button ,String buttonname) {
projectlist.setSpacing(10);
projectlist.getChildren().add(button);
}
Can anyone explain me about this???
You must load the fxml only once. Currently you load it every time you add a button and set it as root, so the last one is the remaining one.

How do I add components to a layout after it has become part of a scene?

The root layout in this case which has content...
object SomeApp extends JFXApp {
stage = new PrimaryStage {
title = "SomeApp"
width = 800
height = 600
val TheButton = new Button("Add a Button") {
onAction = {
e: ActionEvent => {
root.dd(new Button("Some Button"))
}
}
}
val SomeLayout = new AnchorPane {
styleClass += "someStyle"
content = TheButton
}
scene = new Scene {
stylesheets = List(getClass.getResource("Extend.css").toExternalForm)
root = new AnchorPane {
content = SomeLayout
}
}
}
}
I am trying to get it to add a button to the root layout when TheButton is clicked...
This works:
object SFXAddElemAfterStart extends JFXApp {
stage = new PrimaryStage {
title = "SFXAddElemAfterStart"
width = 800
height = 600
scene = new Scene {
val paneNeedingButton = new AnchorPane {
content = layout
}
root = paneNeedingButton
def layout: AnchorPane = new AnchorPane {
styleClass += "someStyle"
content = button
}
def button = new Button("Add a Button") {
onAction = {
e: ActionEvent => {
paneNeedingButton.children += new Button("Some Button")
}
}
}
}
}
}
You just need to put the button in a reasonable place.
(BTW sorry for the long wait, scalafx is a low traffic tag)

Resources