Been learning JavaFX in my freetime, and thought a simple schedule application would be a good practice. My current goal is output all members of an ArrayList, and have buttons for each, allowing to mark task as completed, or edit a task. However I'm struggling with button placement, I have an HBox containing a label and two buttons, I can get them spaced, but since the task are different sized strings, I cant get the buttons to line up with each other. Is there any way to have them base their position on the left side, and not on the right after the string?
Here is my current code:
@FXML
public Node createPage(int pageIndex){
ListView<Event> eventView = new ListView<>();
int fromIndex = pageIndex * maxPerPage;
int toIndex = Math.min(fromIndex + maxPerPage, schedule.size());
List<Event> eventString = schedule.subList(fromIndex, toIndex);
//testing buttons
eventView.setCellFactory(param -> new ListCell<Event>(){
@Override
protected void updateItem(Event item, boolean empty){
super.updateItem(item,empty);
if(empty || item == null){
setText(null);
setGraphic(null);
} else{
Label label = new Label(item.display());
Button editButton = new Button ("Edit");
Button completedButton = new Button ("Completed");
editButton.setOnAction(event ->{
System.out.println("done");
});
completedButton.setOnAction(event ->{
System.out.println("done");
});
HBox eventHbox = new HBox(label);
HBox buttonHbox = new HBox(editButton,completedButton);
buttonHbox.setSpacing(50);
buttonHbox.setAlignment(Pos.CENTER_RIGHT);
HBox.setMargin(buttonHbox,new Insets(0,0,0,200));
HBox hbox = new HBox(eventHbox,buttonHbox);
setGraphic(hbox);
}
}
});
}
also eventually I will need to figure out how to send the element the button is aligned with into the function for when the button is pressed if anyone has tips for that
I have tried using BASELINE_RIGHT
instead of CENTER_RIGHT
and if I change the passing in setMargin()
anymore or to like max or something it'll make me scroll over to see it
EDIT:: trying to figure out TableView but not getting it, for person who wanted photos, (the want to have is created with data that is all same size, ideally button placemen does not depend on data length)
Region
as a spacer-filler in a single HBox
[Caveat: I am not an expert on JavaFX.]
I asked a similar Question, more narrowly focused on a way to accomplish this label-button-button layout with just one HBox
rather than nesting multiple HBox
objects.
The Answer there by makki saud alghamdi provides at least part of a possible solution for you. That Answer involves using a Region
object as a space between the label and the buttons, to fill the void, effectively pushing the buttons to the right.
The key lines of code are:
// Mind the gap between label and buttons.
Region spacer = new Region ( );
HBox.setHgrow ( spacer , Priority.ALWAYS );
HBox rowBox = new HBox ( label , spacer , editButton , completedButton );
Here is an entire example app.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
public class HelloApplication extends Application
{
@Override
public void start ( Stage stage ) throws IOException
{
record Event( String title , LocalDate when ) { }
ObservableList < Event > events =
FXCollections.observableList (
List.of (
new Event ( "A bit of text" , LocalDate.now ( ) ) ,
new Event ( "Even more text than that." , LocalDate.now ( ).plusDays ( 1 ) ) ,
new Event ( "Lots and lots and lots of text for a very long line." , LocalDate.now ( ).plusDays ( 2 ) )
)
);
ListView < Event > eventView = new ListView <> ( events );
eventView.setCellFactory ( ( ListView < Event > listView ) -> new ListCell < Event > ( )
{
Button editButton = new Button ( "Edit" );
Button completedButton = new Button ( "Completed" );
Label label = new Label ( );
Label labelTwo = new Label( );
Region spacer = new Region ( );
VBox vBox = new VBox(label, labelTwo);
HBox rowBox = new HBox ( vBox , spacer , editButton , completedButton );
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd");
{
// Mind the gap between label and buttons.
HBox.setHgrow ( spacer , Priority.ALWAYS );
rowBox.setAlignment ( Pos.CENTER );
rowBox.setSpacing ( 7 );
}
@Override
protected void updateItem ( Event item , boolean empty )
{
super.updateItem ( item , empty );
if ( empty || item == null )
{
setText ( null );
setGraphic ( null );
}
else
{
label.setText(item.title());
labelTwo.setText("\tDue: " + item.when().format(dateTimeFormatter));
editButton.setOnAction ( actionEvent ->
{
System.out.println ( "DEBUG - Edit the Event: " + item.title() );
});
completedButton.setOnAction ( actionEvent ->
{
System.out.println ( "DEBUG - Completed the Event: " + item.title());
});
setGraphic ( rowBox );
}
}
} );
BorderPane pane = new BorderPane ( );
pane.setCenter ( eventView );
Scene scene = new Scene ( pane , 700 , 300 );
stage.setScene ( scene );
stage.setTitle ( "Schedule Example" );
stage.show ( );
}
public static void main ( String[] args )
{
launch ( );
}
}
And a screenshot.
I might not consider this solution complete. If the user narrows the width of the window, the buttons disappear off the right edge of the window. Ideally the buttons would always stay visible within the window, and the too-long label would truncate. I do not know the solution to that issue.
also eventually I will need to figure out how to send the element the button is aligned with into the function for when the button is pressed if anyone has tips for that
Binding each domain object to your buttons is an orthogonal issue from layout of widgets. So you should compose a separate Question on that specific issue.
But briefly…
Notice that your cell factory is handed a reference to the item, an object of your Event
class, which is to be displayed by the ListView
. In this line:
protected void updateItem ( Event item , boolean empty ) …
… that first parameter Event item
is the key. Notice how I used that reference in my call to item.toString
while instantiating each Label
.
Label label = new Label ( item.toString ( ) );
We can add similar calls in your button action handlers as well.
Button editButton = new Button ( "Edit" );
Button completedButton = new Button ( "Completed" );
editButton.setOnAction ( ( ActionEvent actionEvent ) ->
{
System.out.println ( "DEBUG - Edit the Event: " + item.toString ( ) );
} );
completedButton.setOnAction ( ( ActionEvent actionEvent ) ->
{
System.out.println ( "DEBUG - Completed the Event: " + item.toString ( ) );
} );
By the way, especially while learning, and often more generally, you may find it handy to expand the parameter types in your lambdas. Doing so helps keep it straight in you head as to exactly what objects are in play at every point.
So this:
editButton.setOnAction ( ( ActionEvent actionEvent ) -> …
… instead of this:
editButton.setOnAction ( actionEvent -> …