Here is my problem. I have table with normal text columns and 2 columns with dropdowns and one with checkboxes. This is my callback for cell factory for dropdown columns :
Callback<TableColumn<Person, String>, TableCell<Person, String>> dropdownConditionCellFactory =
new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
#Override
public TableCell call(TableColumn p) {
Tools.Tables.ComboBoxCell<partCondition> cell = new Tools.Tables.ComboBoxCell<partCondition>(partConditionList)
return cell;
}
};
And class for this cell factory:
public static class ComboBoxCell extends TableCell {
private ComboBox combo;
public ComboBoxCell() {
combo = new ComboBox();
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
public ComboBoxCell(ObservableList items) {
combo = new ComboBox();
combo.setItems(items);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
combo.getSelectionModel().selectFirst();
}
public T getSelectedItem()
{
return (T) combo.getSelectionModel().getSelectedItem();
}
public void setSelectedItem(T t)
{
combo.getSelectionModel().select(t);
}
}
My problem is that when Table is quite big and there is only 2 rows in it, dropdowns are produced anyway and it looks like this:
Is there a way to produce only as many dropdowns and checkboxes as many items there is in observable list that feeds this table?
While working with cells, please read the Cell API beforehand, to understand how they are handled under the hood. In short the cells are reused in different rows to render different items/records. Each time when the cell is reused its updateItem() method will be called to refresh the item the cell is rendering. Thus you need to override this method and control the graphic in there, instead of in constructor:
private ComboBox combo;
public ComboBoxCell() {
combo = new ComboBox();
}
public ComboBoxCell(ObservableList items) {
combo = new ComboBox();
combo.setItems(items);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
combo.getSelectionModel().select(item);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
Related
I am trying to change the rendering of a TextFieldTreeTableCell to show a string as a Hyperlink as opposed to plaintext, to no avail. It seems as though it should be doable using setSkin, but something like
setSkin((new HyperLink()).getSkin());
or
setSkin((new HyperLink(getItem())).getSkin());
does not help. Any insight on how this could be done?
What you are doing wrong
You are not using the right function to customize your cell: setSkin is is used for creating custom control skins and is generic to all kinds of controls not just cells, you should a use a cell factory instead.
You are not using the right superclass: TextFieldTreeTableCell is for creating a cell which contains a label that can be made into an editable TextField when you click on it. Such functionality is not useful when you want to "display a non-editable, clickable URL".
Approach you should use
Cells have a specific method for controlling their rendering which is preferred to the skin mechanism when working with cells. This cell specific mechanism is called a cell factory and is documented with an example in the Cell documentation.
TreeTableColumns allow you to set a cell factory on the column to control the rendering of the column cells. The relevant code for rendering a Hyperlink in a cell is below:
emailColumn.setCellFactory(param -> new TreeTableCell<Employee, String>() {
private Hyperlink link = new Hyperlink();
{
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
return;
}
link.setText(item);
link.setVisited(getTreeTableRow().getItem().isVisited());
link.setOnAction(event -> {
getTreeTableRow().getItem().setVisited(true);
sendLabel.setText("Send mail to: " + item);
});
setGraphic(link);
}
});
Sample Application
In the screen shot below, the user has just linked on the hyperlink for anna.black#example.com.
The sample code is a modified version of the code from the Oracle TreeTableView tutorial. The addition of a visited property to the Employee class is necessary to keep track of which items in the TreeTableView have been clicked on, so that the Hyperlink visited property can be appropriately set when the cell is updated.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.*;
public class TreeTableViewSample extends Application {
private List<Employee> employees = Arrays.asList(
new Employee("Ethan Williams", "ethan.williams#example.com"),
new Employee("Emma Jones", "emma.jones#example.com"),
new Employee("Michael Brown", "michael.brown#example.com"),
new Employee("Anna Black", "anna.black#example.com"),
new Employee("Rodger York", "roger.york#example.com"),
new Employee("Susan Collins", "susan.collins#example.com"));
private final ImageView depIcon = new ImageView (
new Image("http://icons.iconarchive.com/icons/custom-icon-design/flatastic-10/16/Bear-icon.png")
);
final TreeItem<Employee> root =
new TreeItem<>(new Employee("Sales Department", ""), depIcon);
public static void main(String[] args) {
Application.launch(TreeTableViewSample.class, args);
}
final Label sendLabel = new Label();
#Override
public void start(Stage stage) {
root.setExpanded(true);
employees.forEach((employee) -> root.getChildren().add(new TreeItem<>(employee)));
stage.setTitle("Tree Table View Sample");
final Scene scene = new Scene(new VBox(), 400, 400);
scene.setFill(Color.LIGHTGRAY);
VBox sceneRoot = (VBox) scene.getRoot();
TreeTableColumn<Employee, String> empColumn =
new TreeTableColumn<>("Employee");
empColumn.setPrefWidth(150);
empColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Employee, String> param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getName())
);
TreeTableColumn<Employee, String> emailColumn =
new TreeTableColumn<>("Email");
emailColumn.setPrefWidth(190);
emailColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Employee, String> param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getEmail())
);
emailColumn.setCellFactory(param -> new TreeTableCell<Employee, String>() {
private Hyperlink link = new Hyperlink();
{
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
return;
}
link.setText(item);
link.setVisited(getTreeTableRow().getItem().isVisited());
link.setOnAction(event -> {
getTreeTableRow().getItem().setVisited(true);
sendLabel.setText("Send mail to: " + item);
});
setGraphic(link);
}
});
TreeTableView<Employee> treeTableView = new TreeTableView<>(root);
treeTableView.getColumns().setAll(empColumn, emailColumn);
sceneRoot.getChildren().addAll(treeTableView, sendLabel);
stage.setScene(scene);
stage.show();
}
public class Employee {
private SimpleStringProperty name;
private SimpleStringProperty email;
private SimpleBooleanProperty visited;
public SimpleStringProperty nameProperty() {
if (name == null) {
name = new SimpleStringProperty(this, "name");
}
return name;
}
public SimpleStringProperty emailProperty() {
if (email == null) {
email = new SimpleStringProperty(this, "email");
}
return email;
}
private Employee(String name, String email) {
this.name = new SimpleStringProperty(name);
this.email = new SimpleStringProperty(email);
this.visited = new SimpleBooleanProperty(false);
}
public String getName() {
return name.get();
}
public void setName(String fName) {
name.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
public void setVisited(boolean visited) {
this.visited.set(visited);
}
public boolean isVisited() {
return visited.get();
}
}
}
I have a combo Box and a table-view. ComboBox items are filled with tables column-names. I want to bind comboBox item selection and the table column sort.
Example: If I select item say "Name" from comboBox which is at index 0 of comboBox, the 0th column of table is sorted.
Again if I sort a column in the table, comboBox selected item should update with the corresponding column name.
Right now i am achieving the table column sort based on comboBox item slection with below code.
private void bindComboBoxAndTableColumnSort() {
ComboBox combo = topComboBarController.getSortCombo();
combo.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0,
Number oldVal, Number newVal) {
System.out.println("oldVal = "+ oldVal + " and newVal = "+ newVal);
TableColumn sortColumn = null;
SortType st = null ;
sortColumn = table.getColumns().get( newVal.intValue() ) ;
st = table.getColumns().get( newVal.intValue() ).getSortType() ;
table.getSortOrder().clear();
if(sortColumn != null){
table.getSortOrder().add(sortColumn);
sortColumn.setSortType(SortType.ASCENDING);
}
}
});
}
If someone can share some demo code, it will be helpful.
You need a second Listener which listens to the change to the changeorder of your TableView. Notice the need of the while loop to listen to the paramChange. Replace the ... with your binding to your ComboBox
tableView.getSortOrder().addListener(new ListChangeListener<TableColumn<ColumnClass, ?>>() {
#Override public void onChanged(Change<? extends TableColumn<ColumnClass, ?>> paramChange) {
while(paramChange.next()) {
if (paramChange.wasPermutated()) {
final TableColumn<ColumnClass, ?> first = paramChange.getList().get(0);
final String tableColumnName = first.getText();
...
}
}
}
});
Edit
According to the request some other approach
final ComboBox<String> box = new ComboBox<>();
table.getSortOrder().get(0).textProperty().bindBidirectional(box.valueProperty());
With below code, i am able to achieve what #thatslch suggested.
table.getSortOrder().addListener(new ListChangeListener<TableColumn<Person, ?>>(){
#Override
//public void onChanged( javafx.collections.ListChangeListener.Change<? extends TableColumn<Person, ?>> paramChange) {
public void onChanged( Change<? extends TableColumn<Person, ?>> paramChange) {
// TODO Auto-generated method stub
while(paramChange.next()) {
if (paramChange.wasAdded()) {
System.out.println("paramChanged.wasAdded() ");
ComboBox combo = topComboBarController.getSortCombo();
combo.valueProperty().bind( paramChange.getList().get(0).textProperty() );
}
}
}
I have a UIPickerView that is using a custom UIPickerViewModel to overide the view that is shown in the UIPicker View.
When I First open the UIPickerView, all goes well, I am able to select an item and what not.
If the Item selected is the first or 2nd item in the UIPicker, I don't run into any issues. However, if I select say the 3rd item, dismiss the Picker then try to select another option again, the UIPickerView never shows on the screen.
What I found out is that the GetView method on my PickerViewModel is getting called over and over again for the selected row as well as the row after. Is there anything I have done that might cause this behavior? Below is my PickerViewModel
public class AddressPickerViewModel : UIPickerViewModel
{
private ShippingView view;
public AddressPickerViewModel(ShippingView view)
{
this.view = view;
}
private AddressPickerCell currentSelectedCell;
public override UIView GetView(UIPickerView picker, int row, int component, UIView view)
{
if (view == null)
{
view = new AddressPickerCell();
var views = NSBundle.MainBundle.LoadNib(AddressPickerCell.Key, view, null);
view = Runtime.GetNSObject(views.ValueAt(0)) as AddressPickerCell;
}
var pickerCell = (AddressPickerCell)view;
pickerCell.Selected = false;
pickerCell.ShippingAddress = this.view.ViewModel.Addresses.ToArray()[row].Address;
return view;
}
public override int GetComponentCount(UIPickerView v)
{
return 1;
}
public override int GetRowsInComponent(UIPickerView pickerView, int component)
{
return this.view.ViewModel.Addresses.Count;
}
public override void Selected(UIPickerView picker, int row, int component)
{
if (this.currentSelectedCell != null)
{
this.currentSelectedCell.Selected = false;
}
view.ViewModel.SelectedAddress=
this.view.ViewModel.Addresses.ElementAt(picker.SelectedRowInComponent(0));
var cell = picker.ViewFor(row, component) as AddressPickerCell;
if (cell != null)
{
this.currentSelectedCell = cell;
this.currentSelectedCell.Selected = true;
}
}
public override float GetComponentWidth(UIPickerView picker, int component)
{
if (component == 0)
return 300f;
else
return 40f;
}
public override float GetRowHeight(UIPickerView picker, int component)
{
return 140f;
}
}
im stuck into some problem, need guidance !
i have a TableView that has 2 ComboBoxTableCells, my requirement is to update the list in combobox of 2nd cell on change of the first.
i have tried it the following way,no luck so far.
public class Test{
private StringProperty name;
private StringProperty city;
public Test(String name, String city){
this.name = new SimpleStringProperty(name);
this.city = new SimpleStringProperty(city);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.setValue(name);
}
public String getCity() {
return city.get();
}
public void setCity(String city) {
this.city.setValue(city);
}
public StringProperty nameProperty() {return name;}
public StringProperty cityProperty() {return city;}
}
TableView _table= new TableView();
final ObservableList list = FXCollections.observableArrayList();
list.add("name 1");
list.add("name 2");
list.add("name 3");
list.add("name 4");
final ObservableList list2 = FXCollections.observableArrayList();
list2.add("city 1");
list2.add("city 2");
list2.add("city 3");
list2.add("city 4");
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Test, String>("name"));
firstNameCol.setCellFactory(ComboBoxTableCell.forTableColumn(list));
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Test, String>>() {
#Override
public void handle(CellEditEvent<Test, String> t) {
((Test) t.getTableView().getItems().get(t.getTablePosition().getRow())).setName(t.getNewValue());
System.out.println(t.getTableColumn().getCellData(t.getTablePosition().getRow()));
i guess have to do something here, tried the following line to see the impact on the respective cell
list2.clear();
it updated data for the whole column i just want it to be updated for the respective cell only.
}
}
);
TableColumn lastNameCol = new TableColumn("City");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Test, String>("city"));
lastNameCol.setCellFactory(ComboBoxTableCell.forTableColumn(list2));
lastNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Test, String>>() {
#Override
public void handle(CellEditEvent<Test, String> t) {
((Test) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setName(t.getNewValue());
}
}
);
_table.setEditable(true);
_table.getColumns().addAll(firstNameCol,lastNameCol);
ObservableList listItems = FXCollections.observableArrayList();
listItems.add(new Test("name 4", "city 2"));
listItems.add(new Test("name 2", "city 3"));
table.getTableView().setItems(listItems);
_table.setItems(listItems);
any help will be highly appreciated. thanks
Here's a hacky approach that I haven't tested:
Add a dummy (boolean?) property on the data items that you will use to communicate between firstNameCol and lastNameCol
In the onEditCommit handler for firstNameCol, change the value of the dummy property. Be sure it changes.
Have lastNameCol be a column for the dummy property. Register a cell factory for lastNameCol that returns a TableCell with an overriden updateItem() method (pseudo-code below)
lastNameCol.setCellFactory(new Callback() {
#Override
public TableCell call(TableColumn col) {
return new TableCell() {
#Override
public void updateItem(Boolean item, boolean empty) {
if (!empty) {
// Don't care about the value of item
// Just look up the value of firstNameCol using
// getTablePosition(), then create and populate
// a ComboBox with the appropriate items and set
// it as the graphic for this cell via this.setGraphic()
// Add handler to ComboBox control to update data item when
// selection changes
}
}
};
}
});
Situation:-
In my code I have to use the LWUIT Component object for the listview controls. The controls are dynamic and hence can be in any number.
Right now I am creating Component objects according to the controls(in numbers) i.e.- for every control to be created first the Component object is creating.
This process slows down the rendering of the listview when the controls are increasing.
Solution:-
If I create the Component object and use it in a loop for all the controls it is taking the reference of the object and hence displays all the listview items(controls) with the same data.
Now I am able to think of one last option of Cloning my object and using it to create the controls.
Problem:-
But I can't find any way in LWUIT by which I can achieve the copying of object.
What can be the alternatives in LWUIT to solve this problem?
P.S.-The listview items are of same type, but with different data.
Use a List component and the Renderer design pattern to create a "rubber stamp" component where you can display a large number of elements easily. See an explanation of this in the Codename One blog.
Create these classes first :
public class ListUtil {
private Vector data = new Vector();
private Content[] contents;
public ListUtil(Vector vData)
{
data = vData;
contents = new Content[vData.size()];
}
public List createList(Display display, CListCell renderer, ActionListener listener)
{
CList theList;
for(int i = 0; i < data.size(); i++)
{
contents[i] = new Content(String.valueOf(data.elementAt(i)));
}
theList = new CList(display, contents, renderer, listener);
return theList;
}
}
public class Content
{
private String row;
public Content(String row)
{
this.row = row;
}
public String getRow()
{
return (row);
}
}
public class CListCell extends Container implements ListCellRenderer {
private Label focus = new Label("");
public CListCell()
{
super();
// create and add the components here among the components which will display data
}
public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected)
{
Content entry = null;
if (value instanceof Content)
entry = (Content)value;
componentDisplayingDataAddedIntoThisListCellRenderer.setText(entry.getRow());
return this;
}
public Component getListFocusComponent(List arg0)
{
return focus;
}
}
public class CList extends List {
private Display disp;
public CList(Display display, Object[] data, CListCell renderer, ActionListener actionListener)
{
super(data);
setListCellRenderer(renderer);
setIgnoreFocusComponentWhenUnfocused(true);
addActionListener(actionListener);
setScrollAnimationSpeed(getScrollAnimationSpeed()/4);
disp = display;
}
public void pointerReleased(int x,int y)
{
if (isEnabled() && hasFocus())
super.pointerReleased(x, y);
}
public void keyReleased(int keyCode)
{
if (isEnabled() && hasFocus())
{
if (disp.getGameAction(keyCode) == Display.GAME_FIRE)
pointerReleased(getX(),getY());
else
super.keyReleased(keyCode);
}
}
}
To create your List and add it to a Form :
public class myForm extends Form implements ActionListener
{
private Vector listSource = // your vector of data
private CListCell renderer = new CListCell();
private List theList = (new ListUtil(listSource)).createList(Display.getInstance(),renderer, this);
...
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == theList)
doSomething();
}
}