add property to Cell (in code) - java

I'm using GWT primarily in code. I currently have a column with a TextInputCell:
public class EditPanel extends Composite{
#UiField
protected DataGrid<MyObject> dataGrid;
public EditPanel(final ActionHandler actionHandler){
...
Column<MyObject, String> inputColumn = new Column<MyObject, String>(new TextInputCell()){
... // Override of the getValue
};
inputColumn.setFieldUpdater(...);
this.dataGrid.addColumn(inputColumn, "Column title");
...
}
...
}
Based on this SO answer I know I can add a placeholder-text (or any other property) to a regular TextField in GWT like this:
TextField myInputField = new TextField();
myInputField.getElement().setPropertyString("placeholder", "some placeholder text");
However, on the TextInputCell there isn't really a getElement() method to retrieve the input-field.
When looking through the TextInputCell-class code I came across the protected getInputElement(parent) method, so I did managed to get a placeholder with the following work-around:
final TextInputCell myInputCell = new TextInputCell(){
#Override
protected InputElement getInputElement(final Element parent){
final InputElement inputElement = super.getInputElement(parent);
inputElement.setPropertyString("placeholder", "my placeholder text");
return inputElement;
}
};
Column<MyObject, String> inputColumn = new Column<MyObject, String>(myInputCell){
...
};
It works, but I have two concerns:
Obviously it is very ugly to set it like this..
The getInputElement(parent)-method isn't called initially. I do get the placeholder when I focus one of the input-field, but the property is not always added by default..
Does anyone have an actual solution of how to add a Property to the (TextInput)Cell of a Column, instead of this ugly maybe-working work-around?
EDIT: Some things I've tried:
1) Trying to retrieve the element with the getRowElement method, like in this SO question & answer:
this.dataGrid.addColumn(inputColumn, "Column title");
if (this.dataGrid.getRowCount() > 0){
final Element element = this.dataGrid.getRowElement(0);
element.setProperty("placeholder", "some placeholder text");
}
This doesn't work because getRowCount() always returns 0. I also tried a this.dataGrid.redraw(); right before it, but it still returns 0.
2) Overriding the TextInputCell as geert3 suggested:
public class MyTextInputCell extends TextInputCell{
#Override
public InputElement getInputElement(Element parent){
return super.getInputElement(parent);
}
}
The problem? I don't know what to put in for the parent-parameter. I did try this.getElement():
MyTextInputCell textInputCell = new MyTextInputCell();
Column<MyObject, String> inputColumn = new Column<MyObject, String>(textInputCell){
... // Override of the getValue
};
inputColumn.setFieldUpdater(...);
this.dataGrid.addColumn(inputColumn, "Column title");
textInputCell.getInputElement(this.getElement()).setPropertyString("placeholder", "some placeholder text");
but that doesn't seem to work. And when I try this, this.dataGrid or inputColumn it gives an error because they aren't considered as Elements (and I also can't cast them).
3) Using my initial work-around.
Problem: I can't really find a way to enforce the getInputElement(parent) method call when the page loads, instead of when an input-field is focused.
Pretty annoying that there is no way to directly access the InputFields of a TextInputCell in GWT...

I've been reading up a bit on Cells. These are in fact reused, a single Cell can render multiple DOM instances of itself, and it can handle events from multiple rendered instances. This means that a Cell doesn't have a one-to-one associated Element, but receives the "current" element as it is being reused, passed as the "parent" argument to several methods.
So in conclusion I think this means that the original solution you've described in your question appears to be a valid solution, not "ugly" at all.

Related

JavaFX complex string binding

I'm new to JavaFX and was wondering if the Bindings API allowed an easier way to achieve the following. Consider a model that contains a database that may be null (because the database loads asynchronously) and a view that displays a label status reflecting the state of the database. If it is null it should say something like "Loading..." and if it isn't it should display how many items are in the database. It also would be great if the status could reflect the size of the database as it grows or shrinks.
So far, I understand that I could bind an integer property (size of the database) to the text property of the label by using a converter. This is fine, but I want the label to display more than the number. A localized string like "Loaded {0} items" precisely. And let's not forget that the database may still be null.
This is the solution I have in place
#Override
public void initialize(URL url, ResourceBundle bundle) {
// Initialize label with default value
status();
model.databaseProperty().addListener((obs, old, neu) -> {
// Update label when database is no longer null
status();
// Update label when size of database changes
neu.sizeProperty().addListener(x -> status());
});
}
public void status() {
if (model.database() == null) {
status.setText(bundle.getString("status.loading"));
} else {
String text = bundle.getString("status.ready");
int size = model.database().size();
text = new MessageFormat(text).format(size);
status.setText(text);
}
}
It works, but is there a way to do it with a chain of bindings, or at least part of it? I've seen how powerful (and lenghty) boolean bindings can be but I'm not sure something as flexible is possible with string bindings.
You can use Bindings.when, which is essentially a dynamic if/then binding:*
status.textProperty().bind(
Bindings.when(model.databaseProperty().isNull())
.then(bundle.getString("status.loading"))
.otherwise(
Bindings.selectInteger(model.databaseProperty(), "size").asString(
bundle.getString("status.ready")))
);
However, the above assumes bundle.getString("status.ready") returns a java.util.Formatter string, not a MessageFormat string. In other words, it would need to be "Loaded %,d items" rather than "Loaded {0,number,integer} items".
Bindings doesn’t have built-in support for MessageFormat, but if you really want to stick with MessageFormat (which is a legitimate requirement, as there are things MessageFormat can do which Formatter cannot), you can create a custom binding with Bindings.createStringBinding:
MessageFormat statusFormat = new MessageFormat(bundle.getString("status.ready"));
status.textProperty().bind(
Bindings.when(model.databaseProperty().isNull())
.then(bundle.getString("status.loading"))
.otherwise(
Bindings.createStringBinding(
() -> statusFormat.format(new Object[] { model.getDatabase().getSize() }),
model.databaseProperty(),
Bindings.selectInteger(model.databaseProperty(), "size")))
);
* Actually, it’s more like the ternary ?…: operator.

Hiding Multiple Elements in JTable via ButtonClick

I am currently working on a tool which edits data dynamically in a JTable. I want to hide the targeted row whenever a button is clicked. Right now I am using RowFilter. Whenever the button isClicked, a new filter is created:
RowFilter<MyTableModel, Object> rowFilter = null;
try {
rowFilter = RowFilter.notFilter(RowFilter.regexFilter(((String)dataTable.getValueAt(dataTable.getSelectedRow(), 0)),0));
} catch (java.util.regex.PatternSyntaxException e) {
return;
}
sorter.setRowFilter(rowFilter);
This only works for one element each time the button is clicked. I want to stay them hidden, so you can continously hide elemtens in the table. It is important to mention that I do not want to delete the rows, just hide them.
I hope someone has an easy answer for this, looking for quite a while now.
This method sorter.setRowFilter(rowFilter); is replacing the filter every time you "add" a new filter. So, it's "forgetting" the old rules. What you have to do is edit the existing filter to include the new rules for filtering.
Check out the documentation for more details.
In any case, I extracted a part of the documentation which you should try to implement.
From RowFilter Javadoc:
Subclasses must override the include method to indicate whether the
entry should be shown in the view. The Entry argument can be used to
obtain the values in each of the columns in that entry. The following
example shows an include method that allows only entries containing
one or more values starting with the string "a":
RowFilter<Object,Object> startsWithAFilter = new RowFilter<Object,Object>() {
public boolean include(Entry<? extends Object, ? extends Object> entry) {
for (int i = entry.getValueCount() - 1; i >= 0; i--) {
if (entry.getStringValue(i).startsWith("a")) {
// The value starts with "a", include it
return true;
}
}
// None of the columns start with "a"; return false so that this
// entry is not shown
return false;
}
};
This means that the include() method is going to return true or false depending if an item should be shown.
Therefore, you should only set the RowFilter once, and reimplment the include() method to match all the rules you currently have set upon your view.

How to hide TableView column header in JavaFX 8?

I need to have an observable list of a type that will be displayed in a TableView with one single column, that when selected will display the rest of its information on the right. The TableView is wrapped in a TitledPane, which is wrapped in an Accordion. See image below:
As you can see in this scenario I don't want to show the Column Header.
I tried following the instruction here, which leads to here:
Pane header = (Pane) list.lookup("TableHeaderRow");
header.setMaxHeight(0);
header.setMinHeight(0);
header.setPrefHeight(0);
header.setVisible(false);
However, it appears to not be working for JavaFX 8. The lookup("TableHeaderRow") method returns null which makes me think that the "TableHeaderRow" selector no longer exist.
Is there an updated workaround for removing/hiding the table header in JavaFX 8?
I faced the problem of hiding column headers recently and could solve it using css.
I created a styleclass:
.noheader .column-header-background {
-fx-max-height: 0;
-fx-pref-height: 0;
-fx-min-height: 0;
}
and added it to the TableView:
tableView.getStyleClass().add("noheader");
Just in case someone needs an alternative approach. It also gives the flexibility of toggling column headers.
As observed in the comments, lookups do not work until after CSS has been applied to a node, which is typically on the first frame rendering that displays the node. Your suggested solution works fine as long as you execute the code you have posted after the table has been displayed.
For a better approach in this case, a single-column "table" without a header is just a ListView. The ListView has a cell rendering mechanism that is similar to that used for TableColumns (but is simpler as you don't have to worry about multiple columns). I would use a ListView in your scenario, instead of hacking the css to make the header disappear:
ListView<Album> albumList = new ListView<>();
albumList.setCellFactory((ListView<Album> lv) ->
new ListCell<Album>() {
#Override
public void updateItem(Album album, boolean empty) {
super.updateItem(album, empty);
if (empty) {
setText(null);
} else {
// use whatever data you need from the album
// object to get the correct displayed value:
setText(album.getTitle());
}
}
}
);
albumList.getSelectionModel().selectedItemProperty()
.addListener((ObservableValue<? extends Album> obs, Album oldAlbum, Album selectedAlbum) -> {
if (selectedAlbum != null) {
// do something with selectedAlbum
}
);
There's no need for CSS or style or skin manipulation. Simply make a subclass of TableView and override resize, like this
class XTableView extends TableView {
#Override
public void resize(double width, double height) {
super.resize(width, height);
Pane header = (Pane) lookup("TableHeaderRow");
header.setMinHeight(0);
header.setPrefHeight(0);
header.setMaxHeight(0);
header.setVisible(false);
}
}
This works fine as of June 2017 in Java 8.
Also, I would recommend using this nowadays.
tableView.skinProperty().addListener((a, b, newSkin) -> {
TableHeaderRow headerRow = ((TableViewSkinBase)
newSkin).getTableHeaderRow();
...
});
This can be executed during initialization, the other method as mention above, will return null, if run during initialization.
Combining the last two answers for a more generic solution without the need to override methods because getTableHeaderRow is no longer visible to be accessed. Tested with Java 11:
private void hideHeaders() {
table.skinProperty().addListener((a, b, newSkin) ->
{
Pane header = (Pane) table.lookup("TableHeaderRow");
header.setMinHeight(0);
header.setPrefHeight(0);
header.setMaxHeight(0);
header.setVisible(false);
});
}

Is it possible to bind the non-empty state of an ObservableList inside an ObjectProperty with the Bindings API?

I have a situation where I want to bind a BooleanProperty to the non-empty state of an ObservableList wrapped inside an ObjectProperty.
Here's a basic synopsis of the behavior I'm looking for:
ObjectProperty<ObservableList<String>> obp = new SimpleObjectProperty<ObservableList<String>>();
BooleanProperty hasStuff = new SimpleBooleanProperty();
hasStuff.bind(/* What goes here?? */);
// ObservableProperty has null value
assertFalse(hasStuff.getValue());
obp.set(FXCollections.<String>observableArrayList());
// ObservableProperty is no longer null, but the list has not contents.
assertFalse(hasStuff.getValue());
obp.get().add("Thing");
// List now has something in it, so hasStuff should be true
assertTrue(hasStuff.getValue());
obp.get().clear();
// List is now empty.
assertFalse(hasStuff.getValue());
I'd like to use the builders in the Bindings class rather than implementing a chain of custom bindings.
The Bindings.select(...) method theoretically does what I want, except that there's no Bindings.selectObservableCollection(...) and casting the return value from the generic select(...) and passing it to Bindings.isEmpty(...) doesn't work. That is, the result of this:
hasStuff.bind(Bindings.isEmpty((ObservableList<String>) Bindings.select(obp, "value")));
causes a ClassCastException:
java.lang.ClassCastException: com.sun.javafx.binding.SelectBinding$AsObject cannot be cast to javafx.collections.ObservableList
Is this use case possible using just the Bindings API?
Solution
Based on answer from #fabian, here's the solution that worked:
ObjectProperty<ObservableList<String>> obp = new SimpleObjectProperty<ObservableList<String>>();
ListProperty<String> lstProp = new SimpleListProperty<>();
lstProp.bind(obp);
BooleanProperty hasStuff = new SimpleBooleanProperty();
hasStuff.bind(not(lstProp.emptyProperty()));
assertFalse(hasStuff.getValue());
obp.set(FXCollections.<String>observableArrayList());
assertFalse(hasStuff.getValue());
obp.get().add("Thing");
assertTrue(hasStuff.getValue());
obp.get().clear();
assertFalse(hasStuff.getValue());
I don't see a way to do this using Bindings API only. ObservableList doesn't have a property empty, so you can't use
Bindings.select(obp, "empty").isEqualTo(true)
and
ObjectBinding<ObservableList<String>> lstBinding = Bindings.select(obp);
hasStuff.bind(lstBinding.isNotNull().and(lstBinding.isNotEqualTo(Collections.EMPTY_LIST)));
doesn't work since it only updates when the list changes, but not when it's contents change (i.e. the third assertion fails).
But the custom chain of bindings you have to create is very simple:
SimpleListProperty lstProp = new SimpleListProperty();
lstProp.bind(obp);
hasStuff.bind(lstProp.emptyProperty());
It could be done with fewer variables:
SimpleListProperty<String> listProperty = new SimpleListProperty<>(myObservableList);
BooleanProperty hasStuff = new SimpleBooleanProperty();
hasStuff.bind(not(listProperty.emptyProperty()));
Does it really have to be an ObjectProperty<ObservableList<String>>? If so, this answer does not solve your problem...
But, I think that if you change the type of obp like this:
Property<ObservableList<String>> obp = new SimpleListProperty<>();
You should be able to use:
hasStuff.bind(Bindings.isEmpty((ListProperty<String>) obp));

How do bind indexed property to jface viewer

I want to bind an indexed property to JFace ComboViewer.
Lets say that I have a DataModel class like this:
class DataModel {
private String[] props = {"A","B","C"};
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public String getProperties( int idx ){
return props[idx];
}
public void setProperties( int idx, String value ){
String oldVal = props[idx];
props[idx] = value;
pcs.fireIndexedPropertyChange( "properties", idx, oldVal, value );
}
// code to add/remove PropertyChangeListener
// ...
}
The data binding code for simple property would look like this:
DataModel dataModel = ...
ComboViewer propertyChoice = ...
DataBindingContext ctx = new DataBindingContext();
IObservableValue target = ViewerProperties.singleSelection().observe( propertyChoice );
IObservableValue model = BeanProperties.value( DataModel.class, "properties" ).observe(dataModel);
ctx.bindValue( target, model );
but with an indexed property I have to inform the ctx at which index is the value that I want to bind. I have tried
IObservableValue model = BeanProperties.value( DataModel.class, "properties[0]" ).observe(dataModel);
but it doesn't work.
Is it possible to bind indexed property instead of simple property? How?
Unfortunately this seems to be unsupported. I was looking for exactly the same functionality. There is no documentation in BeanProperties that says it is supported.
When looking into the implementation of BeanProperties.value, you find that it delegates to BeanPropertyHelper for reading and writing a property. The method Object readProperty(Object source, PropertyDescriptor propertyDescriptor) does not know about the subclass IndexedPropertyDescriptor. When it is invoked for an indexed property, readProperty tries to use a read method that reads the entire array. I think this method is optional for indexed properties. For indexed properties it should use the IndexedPropertyDescriptor.getIndexedReadMethod().
Depending on your use case you may be able to workaround the problem by using BeanProperties.list. However you cannot use this in combination with indexed properties. I tried this by adding a method that returns the entire array but still keeping the method that does a "fireIndexedPropertyChange". Unfortunately this gives a ClassCastException: Eclipse's BeanListProperty seems to suppose that the value in the change event is an array or list. However for an indexed property it is a single element of the array.
Or perhaps you can use an observable map instead?

Categories