I'm trying to do something pretty simple. I want to place an icon in a column for a particular row in a table. If it's a folder, display a folder icon. If it's a file, display a file icon.
Does anyone know how to do this in JavaFX 2?
I've tried so many things and this seems like it should be pretty simple or at least an example somewhere.
Okay so I had a huge dummy moment. Turns out that I had my image url path wrong.
I did find a site that provides a great example for adding elements for table. This helped me understand everything.
Now if the 4 different ways I tried before would've worked, I don't know because my image url path was wrong. But anyway here is the link and a code snippet.
Bottom line was that you need to have the CellValueFactory and the CellFactory. I was attempting to use either or. The updateItem template method in TableCell relies on the value dervied from CellValueFactory.
http://blog.ngopal.com.np/2011/10/01/tableview-cell-modifiy-in-javafx/
TableColumn albumArt = new TableColumn("Album Art");
albumArt.setCellValueFactory(new PropertyValueFactory("album"));
albumArt.setPrefWidth(200);
// SETTING THE CELL FACTORY FOR THE ALBUM ART
albumArt.setCellFactory(new Callback<TableColumn<Music,Album>,TableCell<Music,Album>>(){
#Override
public TableCell<Music, Album> call(TableColumn<Music, Album> param) {
TableCell<Music, Album> cell = new TableCell<Music, Album>(){
#Override
public void updateItem(Album item, boolean empty) {
if(item!=null){
HBox box= new HBox();
box.setSpacing(10) ;
VBox vbox = new VBox();
vbox.getChildren().add(new Label(item.getArtist()));
vbox.getChildren().add(new Label(item.getAlbum()));
ImageView imageview = new ImageView();
imageview.setFitHeight(50);
imageview.setFitWidth(50);
imageview.setImage(new Image(MusicTable.class.getResource("img").toString()+"/"+item.getFilename()));
box.getChildren().addAll(imageview,vbox);
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(box);
}
}
};
System.out.println(cell.getIndex());
return cell;
}
});
In case the provided answers did not work for you (like it didn't for me), this was the solution I found (Of course you still needs to create the tableView and add the columns to it):
//Create your column that will hold the image
private final TreeTableColumn<YourObjectClass,ImageView> columnImage= new TreeTableColumn<YourObjectClass,ImageView>("Image");
public void start() {
//Set your cellValueFactory to a SimpleObjectProperty
//Provided that your class has a method "getImage()" this will work beautifully!
columnImage.setCellValueFactory(c-> new SimpleObjectProperty<ImageView>(new ImageView(c.getValue().getValue().getImage())));
}
Related
I have a single column TableView display Labels with an icon. What I'd like to do is add an animated icon to the label or cell so users know it's loading. I'm not sure how to go about this. I've looked around the internet and haven't come up with anything. I haven't had any luck trying to extend the label and customize the drawing, it's not quite as straight forward as overriding a paint method. So if anyone could point me in the right direction on how I could achieve this effect.
I think you cannot do that since the cell renderer will travel across cells and invoke paint for each cell. So I don't think you can do that and show animated icons. What you can do is to change some property of the row that is loading and invoke transition.
You can get some ideas from this code and get some similar effect:
https://github.com/james-d/Animated-Table-Row/blob/master/src/animatedtablerow/AnimatedTableRow.java
Thought I already posted this but in case this comes up for anyone else looking to do something similar, here was my solution:
sourceColumn.setCellFactory(new Callback<TableColumn<SourceItem, SourceItem>,TableCell<SourceItem, SourceItem>>(){
public TableCell<SourceItem, SourceItem> call(TableColumn<SourceItem, SourceItem> param){
TableCell<SourceItem, SourceItem> cell = new TableCell<SourceItem, SourceItem>(){
#Override
public void updateItem(SourceItem item, boolean empty) {
if (!empty) {
HBox box = new HBox();
box.setAlignment(Pos.CENTER_LEFT);
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
box.getChildren().addAll(item.getLabel(), spacer);
if (item instanceof ShareSourceItem) {
if (((ShareSourceItem)item).isResolving()) {
box.getChildren().addAll(loadImage(mediaPath+"loader2.gif"));
}
}
setGraphic(box);
}
}
};
return cell;
}
});
I am simply trying to create a Grid plugin that can have each cell be edited by a user. I found Eclipse Nebula and it seems pretty close to what I want to do except I can't figure out a way to make the cells editable. So far I have something simple like this:
public class SampleView2 extends ViewPart {
public SampleView2() {
}
public void createPartControl(Composite parent) {
// create Grid
Grid grid = new Grid(parent,SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
grid.setHeaderVisible(true);
// create column
GridColumn col = new GridColumn(grid, 0);
col.setText("First Column");
col.setWidth(140);
// write text in row
GridItem item = new GridItem(grid, 0);
item.setText("This is my first cell"); // <--- I want the user to be able to edit this
}
This code produces this:
As you can see, I can manually set the text in the cell but I want the user to be able to edit it.
In Eclipse Nebula Grid the GridEditor is for editable cells.
This code snippet gives an example of how to use the GridEditor.
The last column of my Table Viewer contains a check box only. The check box appears in the left side of the cell, and because the column name is pretty long it looks ugly as hell. How can I center the check box in the middle of the cell ? Is it possible without using images ? Here is how I create the column:
// third column - check box. temporary
TableColumn column = new TableColumn(viewer.getTable(), SWT.NONE);
column.setText("PrettyLongColumnName");
column.setWidth(100);
TableViewerColumn checkColumn = new TableViewerColumn(viewer, column);
checkColumn.setLabelProvider(new ColumnLabelProvider() {
// the checkboxes should be disposed and rebuilt when input changes
#Override
public void update(ViewerCell cell) {
MyObject system = (MyObject) cell.getElement();
TableItem item = (TableItem) cell.getItem();
Button button;
if (buttonsMap.containsKey(cell.getElement())) {
button = rightTableButtons.get(cell.getElement());
} else {
button = new Button((Composite) cell.getViewerRow().getControl(), SWT.CHECK);
button.setEnabled(true);
buttonsMap.put(cell.getElement(), button);
TableEditor editor = new TableEditor(item.getParent());
editor.grabHorizontal = true;
editor.grabVertical = true;
editor.setEditor(button, item, cell.getColumnIndex());
editor.layout();
}
}
}
});
TL;DR: Not available nativley, but can be implemented via epic hackery.
Checkboxes are actually an OS feature. As SWT is cross-platform, we rely on it being provided by OS.
AFIK the only thing provided by all OS's (Gtk/Win32/Cocoa) is a single checkbox on the first column.
Other
fancy functionality has to be implemented manually.
One way I've seen people do it is to draw custom icons and then update the icon when you click on it with event listeners.
One example on how to draw icons on the right is in this snippet. You'd have to add click listener to change the icon when clicked into checked/unchecked.
Note, this may cause your application to look inconsistent across platforms and themes (e.g dark theme).
To get around this, we have had some people that actually generate a native checkbox, then programatically take a screen shot, then draw it in the right side of a column. I think this is hackery at it's finest.
Let me know if you have questions.
My layout issue
I have a little issue with ListView and I'm not sure if it's because of some knowledge I missing or if my approach is flawed. Have to admit I'm not yet clear with how JavaFX handle the layout in the many possible cases.
The above screenshot shows the result I get twice with the exact same code, except that on the second one an invisible shape I use for coherent layout is made visible for debug.
The various classes involved by the CellFactory extend Group, I tried with some other Parent without much success so far.
How to reproduce
Rather than sharing my StarShape, StarRow and some other misc classes (I'd be happy to if requested) I wrote a sample reproducing the issue. The class extends Application and overrides the start(...) method as such:
#Override
public void start(Stage primaryStage) throws Exception {
final StackPane root = new StackPane();
final Scene scene = new Scene(root, 400, 600);
final ListView<Boolean> listView = new ListView<>();
listView.setCellFactory(this::cellFactory);
for (int i = 0; i < 5 ; i++) {
listView.getItems().add(true);
listView.getItems().add(false);
}
root.getChildren().add(listView);
primaryStage.setScene(scene);
primaryStage.setTitle("ListView trims the invisible");
primaryStage.show();
}
where this::cellFactory is
private ListCell<Boolean> cellFactory(ListView<Boolean> listView) {
return new ListCell<Boolean>() {
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
final Rectangle tabShape = new Rectangle();
tabShape.setHeight(20);
tabShape.setWidth(40);
tabShape.setVisible(item);
final Label label = new Label(item.toString());
label.setLayoutX(40);
final Group cellRoot = new Group();
cellRoot.getChildren().add(tabShape);
cellRoot.getChildren().add(label);
setGraphic(cellRoot);
}
}
};
}
The above will display a ListView<Boolean> with black shapes in front of true items (because of the tabShape.setVisible(item); bit). The false items are looking like regular Label objects as if the invisible shape in their Group wasn't there (but it is).
Closing comments
Debugging this, it turns out groups with the invisible shapes are given negative layoutX property values. Thus Label controls aren't aligned as I'd like them to be. It doesn't happen when I call setLayoutX and setLayoutY outside of a ListView (the invisible shapes do force offsets), but it's probably not the only place where it would happen.
What's happening and how to avoid it? Alternatively, as I'm guessing I'm approaching this wrong, what'd be the right way? In other words, what is the question I should be asking instead of this?
Taking from #dlatikay's comment, instead of setting the placeholder items to invisible, you can render them transparent by setting their opacity to 0.0.
Applied to the MCVE from your question, this would be done by replacing:
tabShape.setVisible(item);
with:
tabShape.setOpacity(item ? 1.0 : 0.0);
In terms of user experience, you could take this one step further. Instead of setting the "inactive" stars to fully transparent, you could set them to be near-transparent, as in this mockup (with opacity set to 0.1):
The benefits that I see are:
It indicates not only the rating of an item in the list, but also the maximum rating.
It avoids awkward empty spaces for list items with zero stars.
I'm guessing I'm approaching this wrong
No, you're not. As with all layouts, there's often multiple ways to approach the same problem. Your approach is actually correct, and you're very close to a working solution.
You can achieve what you're after with a mere 1 line change. That is, changing the Group to an HBox.
An HBox ensures that elements are ordered horizontally, one after another. They also allow invisible elements to still take up space.
I also commented out one line: label.setLayoutX(40). I did this because HBox will not respect this setting, and actually you don't need it to. It will automatically shift the elements horizontally by as much is required.
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
}
else {
final Rectangle tabShape = new Rectangle();
tabShape.setHeight(20);
tabShape.setWidth(40);
tabShape.setVisible(item);
final Label label = new Label(item.toString());
//label.setLayoutX(40);
final HBox cellRoot = new HBox();
cellRoot.getChildren().add(tabShape);
cellRoot.getChildren().add(label);
setGraphic(cellRoot);
}
}
When I make those changes, your layout will render like so:
Important: Your example and your screenshots are slightly different. You may want to use a VBox for your star example (V for 'vertical', H for 'horizontal').
There is a way to set tooltip like that
if (tooltipText != null) {
Label lb = new Label(column.getText());
lb.setTooltip(new Tooltip(tooltipText));
column.setText(null);
column.setGraphic(lb);
}
Unfortunately then will be exists ugly side-effect.
We set null to text of column but menuItems invoke column.getText(). If we don't do this, there will be double name in header.How to solve it? Suppose by means of css..
This answer doesn't erase side-effect
How to add a tooltip to a TableView header cell in JavaFX 8
Note: The following code relies on internals of the TableView skin. These could be subject to change, since they reside in the com.sun packages.
CSS lookup can be used to get access to the TableColumnHeader nodes after the first layout pass on the TableView. Those can be used to retrieve the Label that is used to display the TableColumn's text property.
#Override
public void start(Stage primaryStage) {
TableView tv = createTableView();
Scene scene = new Scene(tv);
// generate layout pass
tv.applyCss();
tv.layout();
// assign tooltips to headers
tv.lookupAll(".column-header").stream().forEach(n -> {
TableColumnHeader header = (TableColumnHeader) n;
Tooltip tooltip = new Tooltip("Tooltip: " + header.getTableColumn().getText());
((Control) header.lookup(".label")).setTooltip(tooltip);
});
primaryStage.setScene(scene);
primaryStage.show();
}