This question already has an answer here:
JavaBean wrapping with JavaFX Properties
(1 answer)
Closed 6 years ago.
I write my application that uses Tableview in which I want to represent and edit list of data.
I have data model. Something like
public class CModel
{
private List<CItem> m_lstItems;
public List<CItem> getList()
{
return m_lstItems;
}
}
public class CItem
{
private String m_sName;
private String m_sType;
public void setName(String s)
{
m_sName = s;
}
public String getName()
{
return new String(m_sName);
}
}
If I need to bind my data model I can create observableList(). But this doesn’t allow me to observe items editing. To make editing possible I need to inherit CItem members from Observable. If I declare it as Property TableView observes items changes.
The problem is that if CModel is pure data model I shouldn’t inherit it from Observable (because data and its view should be separated).
How can I wrap every list item with Observable or what is best approach?
For each column, create a CellValueFactory that wraps your POJO properties in a JavaFX Property. The example below does that on a fictive Person object that has a String property called name:
TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getName()));
Related
How can I prevent column width resizing? I have a table, and I'm wondering if I can simply disable the option to drag to resize width of the columns. Is there any way to do this, or will I have to manually set the max and min width of each column just so that it can't be manipulated by the user? The problem with this is that the text in the title/header of my table is smaller than what's in the row underneath it. Setting the width to the column width forces it smaller, and then you can't see the info in the cell underneath.
Also, if there's a way to disable rearranging the columns, that'd be nice too. I basically want what I have set to not be changed at all. If this can't be done, I might just replace each table with an image of itself, so that it can't be manipulated at all.
Prevent Resizing Columns
From here, we can know that setResizable(boolean) allows you to choose whether the user can resize a column. Setting the max and min width to the same value does prevents the user from resizing the column, but not a preferred method. Also, the user will see the resizing cursor but not the default cursor when attempting to resize the column.
Prevent Reordering Columns
JavaFX 9
For preventing the user from reordering the columns, there isn't a straight-forward solution until JavaFX 9, which introduces setReorderable(boolean), isReorderable(), reorderableProperty methods, and the reorderable field in the TableColumnBase class. Here is a snippet of the source code:
package javafx.scene.control;
//some imports and JavaDoc comments
#IDProperty("id")
public abstract class TableColumnBase<S,T> implements EventTarget, Styleable {
//some code
// --- Reorderable
/**
* A boolean property to toggle on and off the 'reorderability' of this column
* (with drag and drop - reordering by modifying the appropriate <code>columns</code>
* list is always allowed). When this property is true, this column can be reordered by
* users simply by dragging and dropping the columns into their desired positions.
* When this property is false, this ability to drag and drop columns is not available.
*
* #since 9
*/
private BooleanProperty reorderable;
public final BooleanProperty reorderableProperty() {
if (reorderable == null) {
reorderable = new SimpleBooleanProperty(this, "reorderable", true);
}
return reorderable;
}
public final void setReorderable(boolean value) {
reorderableProperty().set(value);
}
public final boolean isReorderable() {
return reorderable == null ? true : reorderable.get();
}
//some code
}
If your application bases on JavaFX 9, then you are lucky. Simply invoke setReorderable(false) on your desired table column and there you go.
JavaFX 8
If your application bases on JavaFX 8 or older versions, you can use com.sun.javafx.scene.control.skin.TableHeaderRow, which has isReordering, setReordering, reorderingProperty methods, and reorderingProperty field. Here is a snippet of the source code:
package com.sun.javafx.scene.control.skin;
//some imports and JavaDoc comments
public class TableHeaderRow extends StackPane {
//some code
private BooleanProperty reorderingProperty = new BooleanPropertyBase() {
#Override protected void invalidated() {
TableColumnHeader r = getReorderingRegion();
if (r != null) {
double dragHeaderHeight = r.getNestedColumnHeader() != null ?
r.getNestedColumnHeader().getHeight() :
getReorderingRegion().getHeight();
dragHeader.resize(dragHeader.getWidth(), dragHeaderHeight);
dragHeader.setTranslateY(getHeight() - dragHeaderHeight);
}
dragHeader.setVisible(isReordering());
}
#Override
public Object getBean() {
return TableHeaderRow.this;
}
#Override
public String getName() {
return "reordering";
}
};
public final void setReordering(boolean value) { reorderingProperty().set(value); }
public final boolean isReordering() { return reorderingProperty.get(); }
public final BooleanProperty reorderingProperty() { return reorderingProperty; }
//some code
}
The methods and fields work the same as the one in TableColumnBase in JavaFX 9, just with different names.
You want to obtain the TableHeaderRow object as a children of the skin of the TableView:
TableView<MyType> table = new TableView<MyType>();
//some code
//DISPLAY THE TABLE OR GETSKIN WILL RETURN NULL
com.sun.javafx.scene.control.skin.TableHeaderRow header = null;
for (Node node : table.getSkin().getChildren())
if (node instanceof com.sun.javafx.scene.control.skin.TableHeaderRow)
header = (com.sun.javafx.scene.control.skin.TableHeaderRow) node;
if (header == null); //Table not rendered
header.setReorderable(false);
You must have the TableView rendered before accessing the skin, because the TableViewSkin obtained from TableView.getSkin() is a visual representation of user interface controls. As the official JavaFx JavaDoc says, Skin is a
Base class for defining the visual representation of user interface
controls by defining a scene graph of nodes to represent the skin. A
user interface control is abstracted behind the Skinnable interface.
Therefore, the Skin will be null if the TableView is not rendered because there is nothing visual to represent.
Note that the second method cannot be used in Java 9 or later due to the modules in Java API block your access towards any sun packages.
EDIT:
In JavaFX 8 or older, there is a method called impl_setReorderable(boolean) which is deprecated, but works flawlessly, pretty much same as setReorderable(boolean) in JavaFX 9.
I have a TableView whose items contain checkboxes. As soon as 2 checkboxes are selected, I need to "unhide" a button.
I have no idea how to check that. Do you have an approach?
The items don't know each other.
The TableView-Controller holds the TableView and the TableColumns.
As far as I know you cannot use bindings here, since you cannot bind yourself to multiple properties. I'm glad for every kind of help. :)
EDIT: To clarify myself: tableView.getItems().addListener() won't work since this can only listen to modifications to the list and not to the outer elements. It can notice if "add()" or "remove" was called, but that's basically it as far as I know.
PS: Busy waiting in a seperate thread is no solution of course.
Assuming you have a TableView<Item> for some Item class with a BooleanProperty:
public class Item {
private final BooleanProperty checked = new SimpleBooleanProperty();
public BooleanProperty checkedProperty() {
return checked ;
}
public final boolean isChecked() {
return checkedProperty().get();
}
public final void setChecked(boolean checked) {
checkedProperty().set(checked);
}
// other properties, etc...
}
and your checkboxes are bound to this property, then you can create your items list using an extractor:
ObservableList<Item> items = FXCollections.observableArrayList(item ->
new Observable[] { item.checkedProperty() });
table.setItems(items);
This ensures that the list fires update notifications when the checkedProperty changes on any of its elements.
So now you can just do normal binding stuff like:
IntegerBinding numberChecked = Bindings.createIntegerBinding(() ->
(int) items.stream().filter(Item::isChecked).count(),
items);
button.visibleProperty().bind(numberChecked.greaterThanOrEqualTo(2));
If you want to be super-efficient:
int requiredNumberChecked = 2 ;
button.visibleProperty().bind(Bindings.createBooleanBinding(() ->
items.stream()
.filter(Item::isSelected)
.skip(requiredNumberChecked-1)
.findAny().isPresent(),
items));
(the binding will return true as soon as it finds two checked items, instead of scanning the entire list).
I have the following controller that is instantiated many times on my gui. The reason is beacause it has a tableview that gets filled with different kind of data. Looks like this
class Controller {
#FXML
TableView<Map<String, String> myTable;
private Manager manager;
//Each TableView has different ammount of columns with different names that get dynamically added to the table view using this function
public void setUpColumns(List<TableColumn<Map<String, String>, String>> columns){
myTable.getColumns().addAll(columns);
addContextMenuToColumnHeaders();
}
private addContextMenuToColumnHeaders(){
for (TableColumn<Map<String, String>, ?> tc : myTable.getColumns()){
ContextMenu addToGraphContextMenu = createAddToGraphContextMenu(tc);
tc.setContextMenu(addToGraphContextMenu);
}
}
private ContextMenu createAddToGraphContextMenu(TableColumn<Map<String, String> String> tc){
for (MangerHandledObject mHO : manager.getHandledObjects()){
MenuItem menuItem = new MenuItem(mHO.getName());
menuItem.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event){
//I want each menu item to have access to the column that is added to get the name of the column. Even after dynamically adding new menuItems
manager.callMethod(tc.getName());
}
});
}
}
}
The manager handled objects are not static. So the are added and deleted from the list that the manager keeps. I tried this
contextMenu.setOnShowing(......)
and before showing it will always check for the list from the manager and re-make the context menu items. But the problems is that when this executes I don't have access to the columns anymore. Is there any way to bypass this? Should I implement my own context menu to have a field of the column Name?
It worked. But I had to add at least one dummy MenuItem on my context menu in order for it to appear.
My TableView is not updating, do i need a listener ?
(m is my Model)
#FXML
private TableView<Mitarbeiter> mitarbeiter;
ObservableList<Mitarbeiter> data =
FXCollections.observableArrayList(m.getMitarbeiterListe()
);
mitarbeiter.setItems(data);
public ArrayList getMitarbeiterListe(){
return mitarbeiterliste;
}
In a new Stage i add some Mitarbeiter to the List in my Model
m.addMitarbeiterToList(mitarbeiter)
public void addMitarbeiterToList(Mitarbeiter mitarbeiter){
mitarbeiterliste.add(mitarbeiter);
}
But the TableView in the other Stage is not updating the new data.
In the end, is the ObservableList not pointed to the ArrayList from the Model ?
Instead of adding items to mitarbeiterliste (which is an ArrayList, and is not observable), add them to data, which is the ObservableList holding the items for the table. The TableView observes this list and automatically updates the view when the list contents changes.
The context of your code snippets is not very clear, but you would do something like
public ArrayList getMitarbeiterListe(){
return data;
}
or instead of
mitarbeiterliste.add(mitarbeiter);
do
data.add(mitarbeiter);
This is all I need to finish answering my last question.
If you look at my last question, you will see my class, containing four fields, corresponding to the four columns of my table
public static class ContactOptions {
private final StringProperty one;
private final StringProperty two;
private final StringProperty three;
private final StringProperty four;
ContactOptions(String col1, String col2, String col3, String col4) {
this.one = new StringProperty(col1);
this.two = new StringProperty(col2);
this.three = new StringProperty(col3);
this.four = new StringProperty(col4);
}
public String getOne() {
return one.get();
}
public String getTwo() {
return two.get();
}
public String getThree() {
return three.get();
}
public String getFour() {
return four.get();
}
}
How do I get the GUI to update after I run ContactOptions.one.set?
When I scroll down and back up, it updates. How can I get it to update without scrolling.
I also asked this at https://forums.oracle.com/forums/thread.jspa?threadID=2275725
Maybe you should use one of the methods below:
updateTableColumn(TableColumn col)
Updates the TableColumn associated with this TableCell.
updateTableRow(TableRow tableRow)
Updates the TableRow associated with this TableCell.
updateTableView(TableView tv)
Updates the TableView associated with this TableCell.
(From http://docs.oracle.com/javafx/2.0/api/javafx/scene/control/TableCell.html)
But I think you had already handled this problem)
For updates to work seamlessly, you should initialize each property and add the xxxProperty method:
private final StringProperty one = new SimpleIntegerProperty();
public StringProperty oneProperty() {
return one;
}
From my own experience, do not use property names using upperCase letters, like "myOne" or "myONE", as the xxxProperty method won't run.
Which build of JavaFX 2.0 are you using? The reason I ask is that tables changed recently and I want to make sure you are using the latest version.