I'm trying to set different colors to shape according by user status on ListView. But when I scroll the ListView some another cells change its shapes too as an example:
I need my shapes like this (each rectangle is inside a cell) :
But after some scrolls, they stay like this:
This is part of my code:
userListView.setCellFactory(userCell -> new UserCellController());
Class UserCellController:
public class UserCellController extends ListCell<UserPopulate> {
#FXML
private Rectangle statusShape;
private FXMLLoader mLLoader;
#Override
protected void updateItem(UserPopulate user, boolean empty) {
super.updateItem(user, empty);
if(empty || user == null) {
setText(null);
setGraphic(null);
} else {
if (mLLoader == null) {
mLLoader = new FXMLLoader(getClass().getResource("/view/userCell.fxml"));
mLLoader.setController(this);
try {
mLLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
if ( user.getUserStatus().toString().equals("3") ) {
statusShape.setFill(Color.RED);
}
setText(null);
setGraphic(cellPane);
}
}
}
I need that only the second cell (with user status == 3) with RED shape.
You need an else to go with your if:
// surely you shouldn't actually be testing for string equality here?
if ( user.getUserStatus().toString().equals("3") ) {
statusShape.setFill(Color.RED);
} else {
// or whatever the default color is...
statusShape.setFill(Color.BLUE);
}
The issue is that if a cell that held a user with status 3, and is then reused (e.g. during the user scrolling) to hold a user with a different status, then the color will not be reset unless you explicitly code for it.
The basic rule is that the code in updateItem() needs to cover all cases.
Related
I have a TableView that represents a calendar. Each cell is one day. and I want to add an event to the cells. When the cell is clicked, the background must be changed to red ... it should be possible to select more than one cell
If it's just selection that you need you can use a selection model with multiple selection an cell selection mode.
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.getSelectionModel().setCellSelectionEnabled(true);
CSS stylesheet
.table-cell {
-fx-selection-bar: red;
-fx-selection-bar-non-focused: red;
}
If you don't want to use the selection model, you need to store the data in your items and use a custom TableCell
public class DayCell extends TableCell<Week, Boolean> {
{
setOnMouseClicked(evt -> {
if (!isEmpty() && getItem() != null && evt.getButton() == MouseButton.PRIMARY) {
WritableValue<Boolean> property = (WritableValue<Boolean>) getTableColumn().getCellObservableValue((Week) getTableRow().getItem());
property.setValue(!getItem());
}
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setStyle(null);
} else {
setStyle(item ? "-fx-background-color: red;" : null);
}
}
}
column.setCellFactory(c -> new DayCell());
This requires the ObservableValues returned by the cellValueFactory to implement WritableValue<Boolean> in a way that stores the data in table item class. Using a BooleanProperty stored in this class would do the trick.
I don't think it's a good idea to use TableView in this case though. It's not designed to display a fixed size non-resizable grid.
I have to display 5000 nodes using ListView. Every node contains complex controls but only some text part is different in nodes. How can i reuse existing nodes controls to recreate my cells while scrolling
The answer of James_D points into the right direction. Normally, in JavaFX you shouldn't worry about reusing existing nodes - the JavaFX framework does exactly this, out-of-the-box. If you want to implement some custom cell rendering, you need to set a cell factory, and that usually looks like this:
listView.setCellFactory(new Callback() {
#Override
public Object call(Object param) {
return new ListCell<String>() {
// you may declare fields for some more nodes here
// and initialize them in an anonymous constructor
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty); // Default behaviour: zebra pattern etc.
if (empty || item == null) { // Default technique: take care of empty rows!!!
this.setText(null);
} else {
this.setText("this is the content: " + item);
// ... do your custom rendering!
}
}
};
}
});
Please note: this should work, but is merely illustrative - we Java Devs know that e.g., we would use a StringBuilder for String concatenation, especially in such cases where the code will execute very often.
If you want some complex rendering, you may build that graphic with additional nodes and set them as graphics property with setGraphic(). This works similar to the Label control:
// another illustrative cell renderer:
listView.setCellFactory(new Callback() {
#Override
public Object call(Object param) {
return new ListCell<Integer>() {
Label l = new Label("X");
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
this.setGraphic(null);
} else {
this.setGraphic(l);
l.setBackground(
new Background(
new BackgroundFill(
Color.rgb(3 * item, 2 * item, item),
CornerRadii.EMPTY,
Insets.EMPTY)));
l.setPrefHeight(item);
l.setMinHeight(item);
}
}
};
}
});
I'm currently trying to add some images from a decoded video to a TableView row and they are not appearing. Only empty TableColumns. The TableView has been designed in JavaFx Scene Builder along with the Label.
Here's what I got so far:
public class MainScreenController implements Initializable {
#FXML
private Label previewBoxLabel;
#FXML
private TableView tableView;
private ObservableList<ImageView> imageList = FXCollections.observableArrayList();
#FXML
public void AddClipBeta(){
//Code which uses an external class in order to decode video (Variables Frames, width and height are not shown but are present in the actual code)
VideoSegment clip = new VideoSegment(0, file.getPath(), 0, Frames, width, height);
//Opens the file in decoding class - ready to output frames
try{clip.openFile();} catch(Exception e){}
//First frame is updated on the preview box
previewBoxLabel.setGraphic(new ImageView(convertToFxImage(clip.getThumbnail())));
System.out.println(file.getPath());
int i =0;
//While loop in test phase to see whether or not 10 frames will be visible in the table
while(i != 10){
//Creates and sets columns to tableView
TableColumn<ImageView, ImageView> col = new TableColumn<ImageView, ImageView>();
col.setPrefWidth(100); //Set width of column
tableView.getColumns().add(col);
col.setCellFactory(new Callback<TableColumn<ImageView, ImageView>, TableCell<ImageView, ImageView>>() {
#Override
public TableCell<ImageView, ImageView> call(TableColumn<ImageView, ImageView> p) {
TableCell<ImageView, ImageView> cell = new TableCell<ImageView, ImageView>(){
};
return cell;
}
});
//Adds current frame to list
imageList.add(new ImageView(convertToFxImage(clip.getThumbnail())));
//Gets next video frame
try{clip.getNextFrame();} catch(Exception e){}
//Updates counter
i++;
}
//Sets list of frames on the table
tableView.setItems(imageList);
}
// There is a problem with this implementation: transparent pixels on the BufferedImage aren't converted to transparent pixels on the fxImage.
public static javafx.scene.image.Image convertToFxImage(java.awt.image.BufferedImage awtImage) {
if (Image.impl_isExternalFormatSupported(BufferedImage.class)) {
return javafx.scene.image.Image.impl_fromExternalImage(awtImage);
} else {
return null;
}
}
I've been struggling understanding how the TableView works the last couple of days and it would be a real breakthrough if we could get to the bottom of this.
Thanks for reading and any help in advance!
When setting a CellFactory, you need to take in to account that it will override some default bevaiours such as setting text and images.
For example. I had to create a ListView of Applications that launched on double click. I had to set a CellFactory in order to add a listener to the mouse click of each individual cell.
applications.setCellFactory(new Callback<TreeView<Application>, TreeCell<Application>>() {
#Override
public TreeCell<Application> call(TreeView<Application> param) {
return new TreeCell<Application>() {
#Override
protected void updateItem(Application item, boolean empty) {
//call the origional update first
super.updateItem(item, empty);
//the root item in my list is null, this check is required to keep a null pointer from happening
if (item != null) {
// text and graphic are stored in the Application object and set.
this.setText(item.getApplicationListName());
this.setGraphic(item.getGraphic());
// registers the mouse event to the cell.
this.setOnMouseClicked((MouseEvent e) -> {
if (e.getClickCount() == 2) {
try {
this.getItem().launch(tabBar);
} catch (UnsupportedOperationException ex) {
Dialogs.create().nativeTitleBar().masthead("Comming Soon™").message("Application is still in development and will be available Soon™").nativeTitleBar().title("Unavailable").showInformation();
}
} else {
e.consume();
}
});
}else if(empty){
this.setText(null);
this.setGraphic(null);
this.setOnMouseClicked(null);
}
}
};
}
});
This was pieced together from some other code so if there is anything else you would like explained, let me know!
I managed to sort this out with the help of you guys. Basically, what I did was make a class with a bunch of setters and getters and a constructor that takes in ImageViews and sets it to a variable in the class via it's constructors. Then I went back to my code and added the following:
Class with Getters and Setters:
import javafx.scene.image.ImageView;
public class tableDataModel {
private ImageView image;
public tableDataModel(ImageView image){
this.image = image;
}
public ImageView getImage(){
return image;
}
public void setImage(ImageView image){
this.image = image;
}
}
Code from MainScreenController:
TableColumn<tableDataModel, ImageView> col = new TableColumn<>();
tableView.getColumns().add(col);
imageList.add(new tableDataModel(new ImageView(convertToFxImage(clip.getThumbnail()))));
col.setPrefWidth(50);
col.setCellValueFactory(new PropertyValueFactory<tableDataModel, ImageView>("image"));
int i = 0;
while (i != 10) {
try {
imageList.add(new tableDataModel(new ImageView(convertToFxImage(clip.getNextFrame()))));
} catch (Exception e) {
}
i++;
}
tableView.setItems(imageList);
I have the following problem creating custom cell factory of a ComboBox from an FXML file created with Scene Builder in JavaFX:
I created a custom cell factory of Labels. It works fine when the user clicks on the items. The y are displayed in the "button" area. But when the user wants to click on another items the previously clicked item is gone.
Here is the code of the combobox cell factory:
idCardOnlineStatusComboBox.setCellFactory(new Callback<ListView<Label>, ListCell<Label>>() {
#Override public ListCell<Label> call(ListView<Label> param) {
final ListCell<Label> cell = new ListCell<Label>() {
#Override public void updateItem(Label item,
boolean empty) {
super.updateItem(item, empty);
if(item != null || !empty) {
setGraphic(item);
}
}
};
return cell;
}
});
I suppose there is a problem in the cell factory but i can't figure out where it is.
I extract the combobox from the fxml with this code:
#FXML private ComboBox idCardOnlineStatusComboBox;
then i fill the combobox with this:
idCardOnlineStatusComboBox.getItems().addAll(
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Online.Title"), new ImageView(onlineImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Away.Title"), new ImageView(awayImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.DoNotDisturb.Title"), new ImageView(doNotDisturbImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Invisible.Title"), new ImageView(offlineImg)),
new Label(Resource.getStringFor("MainForm.Pane.MenuBar.Vortex.OnlineStatus.Offline.Title"), new ImageView(offlineImg))
);
The disappearing behavior may be a bug. You can file it to JavaFX Jira, and let the Oracle guys decide it further. Additionally you can investigate the ComboBox.setCellFactory(...) source code for the reason of this behavior and find workaround. But my suggestion is to use the ComboBox Cell's (ListCell) internal Labelled component, instead of yours:
#Override
public void updateItem(Label item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getText());
setGraphic(item.getGraphic());
} else {
setText(null);
setGraphic(null);
}
}
Note the else part of the code, cover all use cases when writing an if-statement.
I am creating TableView in JavaFX. In which I want to show Context Menu in right click of mouse in tableView. So I am adding an EventHandler on table as given below :
TableView tableView=new TableView();
EventHandler event = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
if (me.getButton() == MouseButton.SECONDARY) {
tableView.getContextMenu().show(tableView, me.getSceneX(), me.getSceneY());
}
}
};
tableView.addEventHandler(MouseEvent.MOUSE_CLICKED, event);
But my problem is that Context Menu is visible wherever I right click on any part of table.
I want to do that Context Menu should be only visible if I clicked on any rows in TableView.
i.e. How would I get row number in TableView at specific point, So that my Context Menu should be only visible, if I clicked on any row of TableView.
The best solution I found was to check if the y coordinate is outside of the bounds of the column header and then to explicitly show the menu.
ContextMenu visibleMenu = null;
tableView.setOnMouseClicked((MouseEvent e) -> {
if (visibleMenu !=null) {
visibleMenu.hide();
visibleMenu = null;
}
if (e.getButton()==MouseButton.SECONDARY) {
double columnHeaderHeight = tableView.lookup(".column-header-background").getBoundsInLocal().getHeight();
if (e.getY()>columnHeaderHeight) {
visibleMenu = getContextMenu(); // build on the fly or use a prebuild menu
visibleMenu.show(tableView, e.getScreenX(), e.getScreenY());
} else {
// you could show a header specific context menu here
}
}
});
The added benefit is that you can build the context menu on the fly with context sensitive items (that for example only appear if a certain type of cell is selected), or just reuse a prebuild contextmenu as setContextMenu does, up to you.
Add context menu to the specific cells using CellFactory not to the whole table.
E.g. using Table from Oracle tutorial:
TableColumn firstNameCol = new TableColumn();
firstNameCol.setText("First");
firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
firstNameCol.setCellFactory(new Callback<TableColumn, TableCell>() {
#Override
public TableCell call(final TableColumn param) {
final TableCell cell = new TableCell() {
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
if (isEditing()) {
setText(null);
} else {
setText(getItem().toString());
setGraphic(null);
}
}
}
};
// This way I will have context menu only for specific column
cell.setContextMenu(ContextMenuBuilder.create().items(MenuItemBuilder.create().text("menu").build()).build());
return cell;
}
});
may be the older question. There is a solution, like getting the target of the mouse event of the table and check for instance for class TableCellSkin and display the context menu as,
table.addEventHandler(MouseEvent.MOUSE_CLICKED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY
&& !isRowEmpty) {
EventTarget target = e.getTarget();
if (target instanceof TableCellSkin
|| ((Node) target).getParent() instanceof TableCellSkin) {
// do your stuff. Context menu will be displayed by default
} else {
// hide the context menu when click event is outside table row
table.getContextMenu().hide();
}
}
}
});
#FXML
void tableContextMenuRequested(ContextMenuEvent event) {
if (tableview.getSelectionModel().getSelectedItems().size() == 0) {
tableContextMenu.hide();
}
}