I am trying to implement a TreeTableView in JavaFX, containing 'MyData' objects, and having two columns. First column should contain a string; this was easy:
column1.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, String> entry)
-> new ReadOnlyStringWrapper(entry.getValue().getValue().toString()));
For the second column, I need to use some more complex data within the MyData object, and I want to render basically a sequence of icons that depict that data. So, I tried to create a custom cell renderer:
MyCellRenderer extends TreeTableCell<MyData, MyData> {
#Override
protected void updateItem(MyData item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
setText(null);
} else {
// building some ContentPane with an HBox of Images here..
setGraphic(contentPane);
}
}
}
and then set the column CellFactory and CellValueFactory as follows:
column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry)
-> new ReadOnlyObjectWrapper(entry));
column2.setCellFactory(param -> new MyCellRenderer());
But I get this exception at runtime:
Exception in thread "JavaFX Application Thread"
java.lang.ClassCastException:
javafx.scene.control.TreeTableColumn$CellDataFeatures cannot be cast
to MyData
I am afraid I don't really understand the meaning of the different generic types for all these classes, and also I am not sure about the "ReadOnlyObjectWrapper". I just tried to copy/paste and tweak it from the setup of the first column.
I would be very thankful if someone could shine some light on me. Unfortunately the oracle docs about TreeTableView don't go into that much detail, they just show simple examples.
Thank you
You're passing entry, which is of the type TreeTableColumn.CellDataFeatures<MyData, MyData>, as the initial value to a new ReadOnlyObjectWraper - a raw type - which is expecting a type of MyData at runtime and not TreeTableColumn.CellDataFeatures<MyData, MyData>. As you can see, there is a mismatch of generic types.
Try changing
new ReadOnlyObjectWrapper(entry)
to
new ReadOnlyObjectWrapper<>(entry.getValue().getValue())
The reason for two getValue() calls is because the first entry.getValue() returns a TreeItem<MyData> and the second getValue() returns the actual MyData instance.
This is all assuming that your table is declared TreeTableView<MyData> and your column is declared TreeTableColumn<MyData, MyData>.
Edit: Since you said you don't really understand all the generic signatures here's a brief explanation.
TreeTableView<S>
Here the S is the type of object the TreeTableView displays. AKA, the model class. An example would be a Person class which would make S a Person.
TreeTableColumn<S, T>
The S here is the same as the S in the TreeTableView that the column is destined to be a part of. The T is the type of object that a TreeTableCell in the column will be displaying. This is normally a value contained within a property of the type S. Such as a StringProperty for a name of a Person which would make T a String.
TreeTableCell<S, T>
The S and T will be the same as the TreeTableColumn which the cell will be a part of.
Now, for the value callback:
Callback<TreeTableColumn.CellDataFeatures<S, T>, ObservableValue<T>>
Again, the S and T represent the same types of the TreeTableColumn for which the Callback will belong to. This Callback returns an ObservableValue that contains the type T so that the TreeTableCell can observe the value for changes and update the UI accordingly. In your case, since the type you want to display is not held in a property you return a new ReadOnlyObjectWrapper to satisfy the API requirements. If I continue the name StringProperty example I gave above you could end up with something like:
TreeTableView<Person> table = ..;
TreeTableColumn<Person, String> column = ...;
column.setCellValueFactory(dataFeatures -> {
// This could all be done in one line but I figured I'd
// make it explicit to show all the types used.
TreeItem<Person> item = dataFeatures.getValue();
Person person = item.getValue();
return person.nameProperty(); // returns StringProperty which is an
// ObservableStringValue which in turn
// is an ObservableValue<String>
});
You need
column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry)
-> new ReadOnlyObjectWrapper<>(entry.getValue().getValue()));
Note that if you don't use raw types (i.e. use ReadOnlyObjectWrapper<> or ReadOnlyObjectWrapper<MyData>, instead of just ReadOnlyObjectWrapper), the compiler will inform you of the error, which is much better than trying to decipher what went wrong at runtime.
As you can see, the parameter type for the cell value factory is a TreeTableColumn.CellDataFeatures (see docs). This is simply a wrapper for the row value from which you're going to extract the data that are shown in the cell; this wrapper just contains the tree item for the row itself (which you get with getValue()), as well as the column to which the cell value factory is attached (getTreeTableColumn()) and the table to which that column belongs (getTreeTableView()).
The latter two, I believe, are designed to enable you to write general, reusable, cell value factories, which you might want to customize on the basis of the column or table to which they're attached. (Use cases for this are hard for me to envisage, but nevertheless I suspect there is some occasion for them...)
The TreeItem containing the row (which you get with entry.getValue()), of course contains the row value itself (you get this with getValue(), which is why you end up with entry.getValue().getValue()), as well as other TreeItem-specific information (is it expanded, selected, etc etc).
Related
I have a simple JavaFX Table with a column like this:
#FXML
private TableColumn<PropertyModel, String> columnPropertyProdValue;
Within the initialize method I used this
columnPropertyProdValue.setCellFactory(TextFieldTableCell.forTableColumn());
in order to get an textarea at a double click event on this column. This is working fine.
The column should store different property values but the datatype depends on a different column in the table. The first column "datatype" defines the datatype like boolean, string or integer and the property column should store its value. But in the moment it is always a String.
Its fine for me if I store the property value as a String in the database but the application should check for correctness of the datatype at runtime.
Does anyone has a good idea how to realize that?
Thanks a lot
Hauke
For TextInputControl and subclasses you can apply TextFormatter. This class allows you to control input. For example (for float number):
setTextFormatter(new TextFormatter<String>(
s -> {
if (s.getControlNewText().isEmpty())
return s;
try{
Float.parseFloat(s.getControlNewText());
return s;
} catch (NumberFormatException e) {
return null;
}
}
I've created a lookup with two columns, first one containing and integer which works just fine but the second one has a long name and this is where the problem arises. Users should horizontally scroll in order to check the entire string and even in that case, the column's width is not big enough to display the whole data.
I've found this :
Adjusting column width on form control lookup
But i don't understand exactly where and what to add.
I am not sure but maybe I have to add the fact that this lookup is used on a menu item which points to an SSRS report, in the parameters section.
Update 1:
I got it working with a lookup form called like this :
Args args;
FormRun formRun;
;
args = new Args();
args.name(formstr(LookupOMOperatingUnit));
args.caller(_control);
formRun = classfactory.formRunClass(args);
formRun.init();
_control.performFormLookup(formRun);
and in the init method of this form i added:
public void init()
{
super();
element.selectMode(OMOperatingUnit_OMOperatingUnitNumber);
}
meaning the field i really need.
I am not sure i understand the mechanism completely but it seems it knows how to return this exact field to the DialogField from where it really started.
In order to make it look like a lookup, i have kept the style of the Design as Auto but changed the WindowType to Popup, HideToolBar to Yes and Frame to Border.
Probably the best route is do a custom lookup and change the extended data type of the key field to reflect that. In this way the change is reflected in all places. See form FiscalCalendarYearLookup and EDT FiscalYearName as an example of that.
If you only need to change a single place, the easy option is to override performFormLookup on the calling form. You should also override the DisplayLength property of the extended data type of the long field.
public void performFormLookup(FormRun _form, FormStringControl _formControl)
{
FormGridControl grid = _form.control(_form.controlId('grid'));
grid.autoSizeColumns(false);
super(_form,_formControl);
}
This will not help you unless you have a form, which may not be the case in this report scenario.
Starting in AX 2009 the kernel by default auto-updates the control sizes based on actual record content. This was a cause of much frustration as the sizes was small when there was no records and these sizes were saved! Also the performance of the auto-update was initially bad in some situations. As an afterthought the grid control autoSizeColumns method was provided but it was unfortunately never exposed as a property.
you can extends the sysTableLookup class and override the buildFromGridDesign method to set the grid control width.
protected void buildFormGridDesign(FormBuildGridControl _formBuildGridControl)
{
if (gridWidth > 0)
{
_formBuildGridControl.allowEdit(true);
_formBuildGridControl.showRowLabels(false);
_formBuildGridControl.widthMode(2);
_formBuildGridControl.width(gridWidth);
}
else
{
super(_formBuildGridControl);
}
}
I am looking for a way to get the selected cell of a TableView control. Note that I don't just want the cell value, I want an actual TableCell object. One would expect that the method:
tableView.getSelectionModel().getSelectedCells().get(0)
does just that, but it returns a TablePosition object, which gives you row and column information, but I don't see a way to get TableCell object from that.
The reason I need this is because I want to respond to a key press, but attaching an event filter to TableCell does not work (probably because it is not editable). So I attach it to TableView, but then I need to get the currently selected cell.
EDIT: For future readers: DO NOT mess with TableCell objects, except in cell factory. Use the TableView the way designers intended, or you will be in lot of trouble. If you need data from multiple sources in single table, it is better to make a new class that aggregates all the data and use that as a TableView source.
I just posted an answer that uses this code to edit a Cell. I don't think you can get a reference to the actual table cell as that's internal to the table view.
tp = tv.getFocusModel().getFocusedCell();
tv.edit(tp.getRow(), tp.getTableColumn());
Your method also returns a TablePosition so you can use that as well.
Here's the link https://stackoverflow.com/a/21988562/2855515
This will probably get downvoted because the OP asked about returning the cell itself, rather than what I'll describe, but a Google search led me here for my issue.
I personally ran into issues trying to retrieve data from an individual cell.
java.is.for.desktop offered buggy code related to this matter, that throws an ArrayIndexOutOfBoundsException, but is on the right track. My goal is to offer a better example of that using a lambda.
To access data from a single TableCell:
tableView.getFocusModel().focusedCellProperty().addListener((ObservableValue<? extends TablePosition> observable, TablePosition oldPos, TablePosition pos) -> {
int row = pos.getRow();
int column = pos.getColumn();
String selectedValue = "";
/* pos.getColumn() can return -1 if the TableView or
* TableColumn instances are null. The JavaDocs state
* this clearly. Failing to check will produce an
* ArrayIndexOutOfBoundsException when underlying data is changed.
*/
if ((pos.getRow() != -1) && (pos.getColumn() != -1))
{
selectedValue = tableView.getItems()
.get(row)
.get(column);
if ((selectedValue != null) && (!selectedValue.isEmpty()))
{
// handling if contains data
}
else
{
// handling if doesn't contain data
}
}
});
Edit:
I meant to say ArrayIndexOutOfBoundsException, rather than NullPointerException, I updated this answer to reflect that. I also cleaned up spelling and grammar.
You want to respond to key press? Better don't.
Instead, you could register a listener for focusing of table cells, which would work with arrow keys and mouse clicks on table cells (and even with touch events, oh my, the future is already there).
table.getFocusModel().focusedCellProperty().addListener(
new ChangeListener<TablePosition>() {
#Override
public void changed(ObservableValue<? extends TablePosition> observable,
TablePosition oldPos, TablePosition pos) {
int row = pos.getRow();
int column = pos.getColumn();
String selectedValue = "";
if (table.getItems().size() > row
&& table.getItems().get(row).size() > column) {
selectedValue = table.getItems().get(row).get(column);
}
label.setText(selectedValue);
}
});
In this example, I am using a "classic" TableView with List<String> as column model. (So, your data type could be different than String.) And, of course, that label is just an example from my code.
I have a plain textfield in Tridion that can have multiple values. The itemtype is a SingleLineTextField.
In the TBB code I have the following (removed the non-essential parts):
ItemFields itemFields = new ItemFields(folder.Metadata, folder.MetadataSchema);
foreach (ItemField itemField in itemFields)
{
string itemFieldValue = string.Empty;
switch (Utilities.GetFieldType(itemField))
{
case FieldType.SingleLineTextField:
itemFieldValue = itemField.ToString();
break;
}
}
Now the result in case of two entries is just two strings with a character line break in it.
String A
String B
The method used is a generic one, which also works on other fields, so I was looking for some way to find out if a SingleLineTextField has more values in it.
You can cast the field to a SingleLineTextField type, then iterate through the Values collection, something along these lines:
SingleLineTextField field = (SingleLineTextField)itemField;
foreach(string value in field.Values)
{
// do something with value
}
// or if all you want is the count of values
int i = field.Values.Count;
Firstly, I would advise against relying on the ToString() method on objects unless it is specifically documented. In this case it works with the abstract class ItemField, but this may not always be the case.
The TOM.Net API only defines Definition and Name properties for ItemField, so you need to cast your ItemField object to something more specific.
the TextField abstract class, which SingleLineTextField inherits from, defines a ToString() method, but also Value and Values properties, which are much better suited to what you're trying to do. Looking at the documentation, we can see that Values will give us an IList<String> of the values, even if your field is not multi-valued. Perfect!
So, to answer your question, "I was looking for some way to find out if a SingleLineTextField has more values in it", you need to cast your ItemField as a TextField and check the number of Values it provides, thus:
TextField textField = (TextField)itemField;
// If you need to deal with multi-valued fields separately
if (textField.Values.Count > 1)
{
//Logic to deal with multiple values goes here
}
else
{
//Logic to deal with single valued goes here
}
// Much better... If you can deal with any number of values in a generic fashion
foreach (string value in textField.Values)
{
// Generic code goes here
}
Is there a way to bind data to a list and/or a table using the groovy swing builder bind syntax? I could only find simple examples that bind simple properties like strings and numbers to a text field, label, or button text.
Had a look round, and the best I could see was using GlazedLists rather than standard Swing lists
http://www.jroller.com/aalmiray/entry/glazedlists_groovy_not_your_regular
There is a GlazedList plugin. And this article is very helpful. The Griffon guys swear by GlazedLists.
I just did something like this--it's really not that hard to do manually. It's still a work in progress, but if it helps anyone I can give what I have. So far it binds the data in both directions (Updating the data updates the component, editing the table updates the data and sends a notification to any propertyChangeListeners of the "Row")
I used a class to define one row of a table. You create this class to define the nature of your table. It looks something like this:
class Row
{
// allows the table to listen for changes and user code to see when the table is edited
#Bindable
// The name at the top of the column
#PropName("Col 1")
String c1
#Bindable
// In the annotation I set the default editable to "false", here I'll make c2 editable.
// This annotation can (and should) be expanded to define more column properties.
#PropName(value="Col 2", editable=true)
String c2
}
Note that once the rest of the code is packaged up in a class, this "Row" class is the ONLY thing that needs to be created to create a new table. You create instances of this class for each row, add them to the table and you are completely done--no other gui work aside from getting the table into a frame.
This class could include quite a bit more code--I intend to have it contain a thread that polls a database and updates the bound properties, then the table should pick up the changes instantly.
In order to provide custom column properties I defined an annotation that looks like this (I plan to add more):
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface PropName {
String value();
boolean editable() default false
}
The rest is a class that builds the table. I keep it in a separate class so it can be reused (by instantiating it with a different "Row" class)
NOTE: I butchered this as I pasted it in so it may not run without a little work (braces probably). It used to include a frame which I removed to just include the table. You need to wrap the table returned from getTable() in a frame..
public class AutoTable<T>
{
SwingBuilder builder // This way external code can access the table
def values=[] // holds the "Row" objects
PropertyChangeListener listener={repaint()} as PropertyChangeListener
def AutoTable(Class<T> clazz)
{
builder = new SwingBuilder()
builder.build{
table(id:'table') {
tableModel(id:'tableModel') {
clazz.declaredFields.findAll{
it.declaredAnnotations*.annotationType().contains(PropName.class)}.each {
def annotation=it.declaredAnnotations.find{it.annotationType()==PropName.class
}
String n=annotation.value()
propertyColumn(header:n, propertyName:it.name, editable:annotation.editable())
}
}
tableModel.rowsModel.value=values
}
}
// Use this to get the table so it can be inserted into a container
def getTable() {
return builder.table
}
def add(T o) {
values.add(o)
o.addPropertyChangeListener(listener)
}
def remove(T o) {
o.removePropertyChangeListener(listener)
values.remove(o)
}
def repaint() {
builder.doLater{
builder.table.repaint();
}
}
}
There is probably a way to do this without the add/remove by exposing a bindable list but it seemed like more work without a lot of benifit.
At some point I'll probably put the finished class up somewhere--if you have read this far and are still interested, reply in a comment and I'll be sure to do it sooner rather than later.