javafx: setRowFactory fails to detect the current state of the TableView - java

I have the following class for setRowFactory. The reason I am not writing this as an anonymous class is because I have multiple functions within this class and I want this class to be reusable. The functions are:
highlight the recently added Trade Object (entire row) in the TableView
Flash the "price" cell when the price property is greater than 0, where caution property becomes true.
public class Trade{
private ReadOnlyBooleanWrapper caution;
...
// with constructor there is
public Trade(){
...
this.caution = new ReadOnlyBooleanWrapper();
this.caution.bind(this.price.greaterThan(0));
}
...
}
This works fine. Problem occurs when I delete Trade objects from the TableView. The empty rows continue to flash, which clearly means that the code is not monitoring the current state of the TableView.
I am still new to javafx and I think I am missing the addListener code to track the current state of all the rows within tableView (Correct me if I am wrong.) And I don't know how to write it out.
Code:
public class AnimatedTransactionLogTableRow<T> extends TableRow<T> {
private static final PseudoClass PS_NEW = PseudoClass.getPseudoClass("new-row");
private static final PseudoClass PS_FLASH = PseudoClass.getPseudoClass("flash-row");
private static final PseudoClass PS_CF = PseudoClass.getPseudoClass("cell-positive");
private final ObjectExpression<T> recentItem;
private final InvalidationListener recentlyAddedListener = fObs -> recentItemChanged();
private final Function<T, BooleanExpression> flashExtractor;
private final ChangeListener<Boolean> flashListener = (fObs, fOld, fNew) -> flasherChanged(fNew);
private final Timeline flashTimeline;
public AnimatedTransactionLogTableRow(ObjectExpression<T> fRecentlyAddedProperty
,Function<T, BooleanExpression> fFlashExtractor
) {
recentItem = fRecentlyAddedProperty;
recentItem.addListener(new WeakInvalidationListener(recentlyAddedListener));
flashExtractor = fFlashExtractor;
flashTimeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> pseudoClassStateChanged(PS_FLASH, true)),
new KeyFrame(Duration.seconds(1.0), e -> pseudoClassStateChanged(PS_FLASH, false)));
flashTimeline.setCycleCount(Animation.INDEFINITE);
}
private void flasherChanged(boolean fNew) {
if (fNew) {
flashTimeline.play();
pseudoClassStateChanged(PS_CF,true);
} else {
flashTimeline.stop();
pseudoClassStateChanged(PS_FLASH, false);
pseudoClassStateChanged(PS_CF,false);
}
}
private void recentItemChanged() {
final T tmpRecentItem = recentItem.get();
pseudoClassStateChanged(PS_NEW, tmpRecentItem != null && tmpRecentItem == getItem());
}
#Override
protected void updateItem(T item, boolean empty) {
System.out.println("getItem(): " + getItem());
if (getItem() != null) {
final BooleanExpression be = flashExtractor.apply(getItem());
if (be != null) {
be.removeListener(flashListener);
}
}
super.updateItem(item, empty);
if (getItem() != null) {
final BooleanExpression be = flashExtractor.apply(getItem());
if (be != null) {
be.addListener(flashListener);
flasherChanged(be.get());
}
}
recentItemChanged();
}
}

I guess you need to call flasherChanged(false); if the TableRow is empty.
For example:
#Override
protected void updateItem(T item, boolean empty) {
System.out.println("getItem(): " + getItem());
if (getItem() != null) {
final BooleanExpression be = flashExtractor.apply(getItem());
if (be != null) {
be.removeListener(flashListener);
}
}
super.updateItem(item, empty);
if(empty) {
flasherChanged(false);
}
else if (getItem() != null) {
final BooleanExpression be = flashExtractor.apply(getItem());
if (be != null) {
be.addListener(flashListener);
flasherChanged(be.get());
}
}
recentItemChanged();
}

Related

JafaFX ListView change Color of single Cells/Items/Rows

Probably the answer to this question is very simple, so I'm going to try to keep my post so.
My Problem
I want the Text-Color of different Cells/Rows/Items to be customizable through a JavaFX ColorPicker. My Code does work, but not really like I want it to.. Everytime a new Item is added to the ListView, the whole ListView changes its Text-Color the Text-Color chosen for the latest Item.
Here's my code (Full class linked below, but I don't think it's needed)
#FXML
void handleAdd(){ //When add button is clicked
fontSize = 16.0;
listView.setCellFactory(cell -> {
ListCell<String> cel = new ListCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setTextFill(colorPicker.getValue());
setFont(Font.font(16));
setText(item);
} else {
setText("");
}
}
};
return cel;
});
listView.getItems().add(input.getText()); //input is my TextField
input.clear();
}
full class
Thanks in advance!
Use a item type for the ListView that contains a ObjectProperty<Color> in addition to a string
private static class ListItem {
public ListItem(String text) {
this.text = text;
}
private final String text;
private final ObjectProperty<Color> color = new SimpleObjectProperty(Color.BLACK);
}
ListView<ListItem> listView;
listView.setCellFactory(cell -> {
ListCell<ListItem> cel = new ListCell<ListItem>() {
#Override
protected void updateItem(ListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
textFillProperty().bind(item.color);
setFont(Font.font(16));
setText(item.text);
} else {
setText("");
textFillProperty().unbind();
setTextFill(Color.BLACK);
}
}
};
return cel;
});
This way you simply need to set the property to a different value, if you want to change the color, e.g:
ListItem selectedItem = listView.getSelectionModel().getSelectedItem();
if (item != null) {
item.color.set(Color.RED);
}

JavaFX - Put a control (like a button) in a TableCell?

I'm trying to build custom content into a JavaFX table based on the object in the table cell. My feeling is that I can use the setCellFactory to do this, but I'm at a loss....
for instance...if I have an interface for my data:
public interface dbBase { ... }
then, each of my datatypes implement that interface
public class dbType1 implements dbBase { ... }
public class dbType2 implements dbBase { ... }
public class dbType3 implements dbBase { ... }
then, when setting up my table, I have...
dataColumn.setCellFactory(data -> {
// what should I put here?
});
dataColumn is:
TableColumn<dbBase, object> dataColumn;
So, my question is:
How can I return a custom TableViewCell based on the type of the object in the column?
I couldn't find anyway to determine the type of the class at the time of the setCellFactory call, so I decided to subclass TreeTableCell instead. That way I could change up the GUI anytime an item was updated.
So, my setCellValueFactor and setCellFactory call now looks like this:
dataColumn.setCellValueFactory(p ->
new SimpleObjectProperty<object>(p.getValue().getValue()));
dataColumn.setCellFactory(p -> new CustomTreeTableCell());
and CustomTreeTableCell() looks like this:
public class CANESTreeTableCell extends TreeTableCell<dbBase, object> {
private final Button button;
private final TextField textField;
private final CheckBox checkBox;
private ObservableValue<object> observable;
public CustomTreeTableCell() {
this.button = new Button();
this.textField = new TextField();
this.checkBox = new CheckBox();
}
#Override
public void updateItem(object item, boolean empty) {
super.updateItem(item, empty);
if(empty) {
setGraphic(null);
} else {
final TreeTableColumn<dbBase, object> column = getTableColumn();
observable = column==null ? null :
column.getCellObservableValue(getIndex());
if(item instanceof dbType1) {
if(observable != null) {
button.textProperty().bind(
observable.getValue().getNameProperty());
} else if (item!=null) {
button.setText(item.getName());
}
setGraphic(button);
} else if (item instanceof dbType2) {
if(checkBox != null) {
checkBox.textProperty().bind(
observable.getValue().getNameProperty());
} else if (item!=null) {
checkBox.setText(item.getName());
}
setGraphic(checkBox);
} else if (item instanceof dbType3) {
if(observable != null) {
textField.textProperty().bind(
observable.getValue().getNameProperty());
} else if (item!=null) {
textField.setText(item.getName());
}
setGraphic(textField);
} else {
setGraphic(null);
}
}
}
}

JavaFX TreeView ChangeListener old value is always null

I have a TreeView of Objects. I want to add a ChangeListener that will draw my object on right pane and remove old one if present. But I can not do that, because of non obvious reasons my old value is always a null.
Here is my Listener:
treeView.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends TreeItem<Asset>> ov, TreeItem<Asset> t, TreeItem<Asset> t1) -> {
t.getValue().derepresent();
t1.getValue().represent();
});
This code makes my app to "represent" object well.
But when I`m selecting another item nothing happens.
And after switching back to my object(which I want to represent) app creates second instance of it(cause previous wasnt deleted).
So I`m asking, how can I solve that?
EDIT:
Here is useful code from my Controller class:
public class Controller implements Initializable {
#FXML
private VBox infoVBox;
#FXML
private TreeView<Asset> treeView;
private Stage stage;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void selectRootOnClick(ActionEvent actionEvent) {
try {
final DirectoryChooser directoryChooser = new DirectoryChooser();
final File selectedDirectory = directoryChooser.showDialog(stage);
TreeItem<Asset> treeItem = new TreeItem<>(new Asset(selectedDirectory.getAbsolutePath(), infoVBox));
createTree(treeItem);
treeItem.getChildren().sort(Comparator.comparing(t -> t.getValue().getAssetPath().toLowerCase()));
treeView.setRoot(treeItem);
treeView.setCellFactory(new Callback<>() {
public TreeCell<Asset> call(TreeView<Asset> tv) {
return new TreeCell<>() {
#Override
protected void updateItem(Asset item, boolean empty) {
super.updateItem(item, empty);
setText((empty || item == null) ? "" : item.getAssetPath());
}
};
}
});
treeView.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends TreeItem<Asset>> ov, TreeItem<Asset> t, TreeItem<Asset> t1) -> {
if (t != null) t.getValue().derepresent();
t1.getValue().represent();
});
} catch (IOException e) {
e.printStackTrace();
}
}
public void createTree(TreeItem<Asset> rootItem) throws IOException {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(rootItem.getValue().getAssetPath()))) {
for (Path path : directoryStream) {
TreeItem<Asset> newItem;
if (!Files.isDirectory(path)) {
String extension = "";
int i = path.toString().lastIndexOf('.');
if (i > 0) extension = path.toString().substring(i + 1);
switch (extension) {
case "mp3":
newItem = new TreeItem<>(new Audio(path.toString(), infoVBox));
rootItem.getChildren().add(newItem);
break;
default:
newItem = new TreeItem<>(new Asset(path.toString(), infoVBox));
rootItem.getChildren().add(newItem);
break;
}
} else {
newItem = new TreeItem<>(new Asset(path.toString(), infoVBox));
newItem.setExpanded(true);
rootItem.getChildren().add(newItem);
createTree(newItem);
}
}
}
}
}
I have an hierarchy of classes. In this hierarchy Audio class is inheriting Asset class.
Here is Asset class:
public class Asset {
protected final String assetPath;
public String getAssetPath() {
return assetPath;
}
protected VBox parent;
public Asset() {
assetPath = "";
parent = null;
}
public Asset(String path, VBox node) {
assetPath = path;
parent = node;
}
public void represent() {}
public void derepresent() {}
}
And my Audio Class:
public final class Audio extends Asset {
private Media media;
private MediaPlayer mediaPlayer;
private MediaControl mediaControl;
public Audio() {
super();
}
public Audio(String path, VBox parent) {
super(path, parent);
}
#Override
public void represent() {
media = new Media("file:///"+assetPath);
mediaPlayer = new MediaPlayer(media);
mediaControl = new MediaControl(mediaPlayer);
parent.getChildren().add(mediaControl);
}
#Override
public void derepresent() {
mediaControl = null;
mediaPlayer = null;
media = null;
parent.getChildren().remove(mediaControl);
}
}
Also I have MediaControl but I suppose it`s implementation is unnecessary for current thread.
You set mediaControl too early to null, so parent.getChildren().remove(mediaControl) is basically just parent.getChildren().remove(null) which always throws a NullPointerException.
Change derepresent() to:
#Override
public void derepresent() {
// you can drop this null-check when you're sure that mediaControl is set
if(mediaControl != null) {
parent.getChildren().remove(mediaControl);
}
mediaControl = null;
mediaPlayer = null;
media = null;
}
I also recommend to remove the the constructor Audio() because you probably get a NullPointerException in derepresent() and represent when parent is still null.

How to get a ComboBox to use an Object's toString method instead of setting it as a graphic? [duplicate]

I have a combobox which shows list of User objects. I have coded a custom cell factory for the combobox:
#FXML ComboBox<User> cmbUserIds;
cmbUserIds.setCellFactory(new Callback<ListView<User>,ListCell<User>>(){
#Override
public ListCell<User> call(ListView<User> l){
return new ListCell<User>(){
#Override
protected void updateItem(Useritem, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setText(item.getId()+" "+item.getName());
}
}
} ;
}
});
ListView is showing a string(id+name), but when I select an item from listview, Combobox is showing toString() method return value i.e address of object.
I can't override toString() method, because the User domain object should be same as the one at server.
How to display id in combobox? Please suggest
EDIT1
I tried below code. Now combo box shows id when I select a value from the listview.
cmbUserIds.setConverter(new StringConverter<User>() {
#Override
public String toString(User user) {
if (user== null){
return null;
} else {
return user.getId();
}
}
#Override
public User fromString(String id) {
return null;
}
});
The selected value in combo box is cleared when control focus is lost. How to fix this?
EDIT2:
#FXML AnchorPane root;
#FXML ComboBox<UserDTO> cmbUsers;
List<UserDTO> users;
public class GateInController implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
users = UserService.getListOfUsers();
cmbUsers.setItems(FXCollections.observableList(users));
cmbUsers.getSelectionModel().selectFirst();
// list of values showed in combo box drop down
cmbUsers.setCellFactory(new Callback<ListView<UserDTO>,ListCell<UserDTO>>(){
#Override
public ListCell<UserDTO> call(ListView<UserDTO> l){
return new ListCell<UserDTO>(){
#Override
protected void updateItem(UserDTO item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setText(item.getUserId()+" "+item.getUserNm());
}
}
} ;
}
});
//selected value showed in combo box
cmbUsers.setConverter(new StringConverter<UserDTO>() {
#Override
public String toString(UserDTO user) {
if (user == null){
return null;
} else {
return user.getUserId();
}
}
#Override
public UserDTO fromString(String userId) {
return null;
}
});
}
}
Just create and set a CallBack like follows:
#FXML ComboBox<User> cmbUserIds;
Callback<ListView<User>, ListCell<User>> cellFactory = new Callback<ListView<User>, ListCell<User>>() {
#Override
public ListCell<User> call(ListView<User> l) {
return new ListCell<User>() {
#Override
protected void updateItem(User item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setText(item.getId() + " " + item.getName());
}
}
} ;
}
}
// Just set the button cell here:
cmbUserIds.setButtonCell(cellFactory.call(null));
cmbUserIds.setCellFactory(cellFactory);
You need to provide a functional fromString() Method within the Converter!
I had the same problem as you have and as I implemented the fromString() with working code, the ComboBox behaves as expected.
This class provides a few of my objects, for dev-test purposes:
public class DevCatProvider {
public static final CategoryObject c1;
public static final CategoryObject c2;
public static final CategoryObject c3;
static {
// Init objects
}
public static CategoryObject getCatForName(final String name) {
switch (name) {
case "Kategorie 1":
return c1;
case "Cat 2":
return c2;
case "Steuer":
return c3;
default:
return c1;
}
}
}
The converter object:
public class CategoryChooserConverter<T> extends StringConverter<CategoryObject> {
#Override
public CategoryObject fromString(final String catName) {
//This is the important code!
return Dev_CatProvider.getCatForName(catName);
}
#Override
public String toString(final CategoryObject categoryObject) {
if (categoryObject == null) {
return null;
}
return categoryObject.getName();
}
}

How to make listener working in CellColumn getValue gwt?

My Requirement is to make the column as combination of text and hyperlink. Hence I can't use ClickableTextCell. I have done the following for it:-
private final Column < Trap, Widget > elementColumn = new Column < Trap, Widget > (new WidgetCell()) {
#Override
public Widget getValue(Trap trap) {
Label label = null;
Anchor anchor = null;
if (trap.getNode() != null) {
label = new InlineLabel(EmsEntryPoint.getMessagesInstance().device() + ": ");
String name = trap.getNode().getName();
if (name == null || name.isEmpty()) {
name = DataConverter.convertLongToHexId(trap.getNode().getId());
}
final Map < String, String > params = new HashMap < String, String > ();
params.put(NetworkActivity.PARAM_NODE_ID, String.valueOf(trap.getNode().getId()));
anchor = new Anchor(name);
anchor.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
NodeDetail nodeDetail = new NodeDetail(mInjector, UrlHelper.getPlaceToken(null, params));
bindNodeDetailButtons(nodeDetail);
nodeDetail.showNodeDetailPopup();
}
});
}
FlowPanel panel = new FlowPanel();
panel.add(label);
panel.add(anchor);
return panel;
}
}
Everything is correct in my code, but I don't know why Anchor's listener not working.
Kindly help.
You will have to override render method and add handlers to your cell.
I have used the following class:
abstract class CustomCell extends AbstractCell<String>{
private MyObject object;
private Set<String> set ;
#Override
public Set<String> getConsumedEvents() {
set = new HashSet<String>();
set.add("click");
return set;
}
public void setObject(MyObject object) {
this.object = object;
}
#Override
public void render(com.google.gwt.cell.client.Cell.Context context,
String value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<div><label>"+object.getLabel()+"</label><a id='idofthislelement'>"+object.getAnchor()+"</a></div>");
}
#Override
public void onBrowserEvent(Context context, Element parent,
String value, NativeEvent event,
ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("click".equals(event.getType())) {
EventTarget eventTarget = (EventTarget) event.getEventTarget();
Element targetElement = Element.as(eventTarget);
if (parent.getFirstChildElement().isOrHasChild(targetElement)&&targetElement.getId().equals("idofthislelement")) {
doAction(value, valueUpdater,object);
}
}
}
protected abstract void doAction(String value, ValueUpdater<String> valueUpdater,MyObject object);
}
Here's the implementation:
private void createTextAnchorCol() {
final CustomCell<MyObject> cell = new CustomCell<MyObject>() {
#Override
protected void doAction(String value,
ValueUpdater<String> valueUpdater, MyObject object) {
Window.alert("Clicked");
//do whatever required
}
};
Column<MyObject, String> abc = new Column<MyObject, String>(cell) {
#Override
public String getValue(MyObject object) {
cell.setObject(object);
return null;
}
};

Categories

Resources