JavaFX TableView TableRow focus behaves strangely - focus

I'm using JDK 1.7.0_51 if that matters. I have the following adapted example code to demonstrate the unusual behaviour that I'm seeing.
I've programmatically selected row 0 in the table, then insert added rows at the 0 position. I'd expect the selected row to shift down by one, but instead it is dropping by two. In addition, when I monitor the focus property on the table row, I see that it changes quite a bit during this addition of a single row. And when it is finished inserting, row 2 has the dark blue background indicating selection, but row 3 has a blue highlighted border. I'm not sure what that is indicating.
I'm currently doing data validation when a table row focus is lost so that I can prevent users from leaving the current entry until it is correctly formed. But this selection and focus behaviour is causing my application to misbehave. This is the simplest test case I was able to create that shows what's happening.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableViewTest extends Application
{
private final TableView< Person > table = new TableView< Person >();
private final ObservableList< Person > data =
FXCollections.observableArrayList(
new Person( "Jacob", "Smith", "jacob.smith#example.com" ),
new Person( "Isabella", "Johnson", "isabella.johnson#example.com" ),
new Person( "Ethan", "Williams", "ethan.williams#example.com" ),
new Person( "Emma", "Jones", "emma.jones#example.com" ),
new Person( "Michael", "Brown", "michael.brown#example.com" ) );
final HBox hb = new HBox();
public static void main( String[] args )
{
launch( args );
}
#Override
public void start( Stage stage )
{
Scene scene = new Scene( new Group() );
stage.setTitle( "Table View Sample" );
stage.setWidth( 450 );
stage.setHeight( 550 );
final Label label = new Label( "Address Book" );
label.setFont( new Font( "Arial", 20 ) );
table.setEditable( true );
table.setRowFactory( getRowFactory() );
TableColumn firstNameCol = new TableColumn( "First Name" );
firstNameCol.setMinWidth( 100 );
firstNameCol.setCellValueFactory(
new PropertyValueFactory< Person, String >( "firstName" ) );
TableColumn lastNameCol = new TableColumn( "Last Name" );
lastNameCol.setMinWidth( 100 );
lastNameCol.setCellValueFactory(
new PropertyValueFactory< Person, String >( "lastName" ) );
TableColumn emailCol = new TableColumn( "Email" );
emailCol.setMinWidth( 200 );
emailCol.setCellValueFactory(
new PropertyValueFactory< Person, String >( "email" ) );
table.setItems( data );
table.getColumns().addAll( firstNameCol, lastNameCol, emailCol );
table.getSelectionModel().select( 0 );
table.getSelectionModel().getSelectedCells().addListener( new ListChangeListener< TablePosition >()
{
#Override
public void onChanged( final ListChangeListener.Change< ? extends TablePosition > c )
{
if ( !c.getList().isEmpty() )
{
for ( TablePosition pos : c.getList() )
{
System.out.println( "Row " + String.valueOf( pos.getRow() ) + " selected" );
}
}
else
{
System.out.println( "Row unselected" );
}
}
} );
final TextField addFirstName = new TextField();
addFirstName.setPromptText( "First Name" );
addFirstName.setMaxWidth( firstNameCol.getPrefWidth() );
final TextField addLastName = new TextField();
addLastName.setMaxWidth( lastNameCol.getPrefWidth() );
addLastName.setPromptText( "Last Name" );
final TextField addEmail = new TextField();
addEmail.setMaxWidth( emailCol.getPrefWidth() );
addEmail.setPromptText( "Email" );
final Button addButton = new Button( "Add" );
addButton.setOnAction( new EventHandler< ActionEvent >()
{
#Override
public void handle( ActionEvent e )
{
data.add( 0, new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText() ) );
addFirstName.clear();
addLastName.clear();
addEmail.clear();
}
} );
hb.getChildren().addAll( addFirstName, addLastName, addEmail, addButton );
hb.setSpacing( 3 );
final VBox vbox = new VBox();
vbox.setSpacing( 5 );
vbox.setPadding( new Insets( 10, 0, 0, 10 ) );
vbox.getChildren().addAll( label, table, hb );
( ( Group ) scene.getRoot() ).getChildren().addAll( vbox );
stage.setScene( scene );
stage.show();
}
private Callback< TableView< Person >, TableRow< Person >> getRowFactory()
{
return new Callback< TableView< Person >, TableRow< Person >>()
{
#Override
public TableRow< Person > call( final TableView< Person > arg0 )
{
return new DataTableRow();
}
};
}
public class DataTableRow extends TableRow< Person >
{
private final ChangeListener< Boolean > focusListener = new ChangeListener< Boolean >()
{
#Override
public void changed( final ObservableValue< ? extends Boolean > unused, final Boolean arg1,
final Boolean focused )
{
System.out.println( "Row " + getIndex() + " Focus: " + focused );
}
};
public DataTableRow()
{
focusedProperty().addListener( this.focusListener );
}
}
public static class Person
{
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person( String fName, String lName, String email )
{
this.firstName = new SimpleStringProperty( fName );
this.lastName = new SimpleStringProperty( lName );
this.email = new SimpleStringProperty( email );
}
public String getFirstName()
{
return firstName.get();
}
public void setFirstName( String fName )
{
firstName.set( fName );
}
public String getLastName()
{
return lastName.get();
}
public void setLastName( String fName )
{
lastName.set( fName );
}
public String getEmail()
{
return email.get();
}
public void setEmail( String fName )
{
email.set( fName );
}
}
}
The output I get from running this and adding a single row is:
Row unselected
Row 1 selected
Row 0 Focus: false
Row 1 Focus: true
Row 1 Focus: false
Row 2 Focus: true
Row unselected
Row unselected
Row 2 selected
Row 2 Focus: false
Row 3 Focus: true
Row 3 Focus: true
Can anyone help me understand what is going on here and whether this is correct behaviour?

What IDE are you using? Have you tried it on IntelliJ? I don't have the focus problem you reported when I run your Class in IntelliJ at all. I'm using jdk 8u20. When I go to a new row, the previous row get de-focused and the new row gets focused. See below:
Row 0 Focus: true
Row 0 Focus: false
Row 1 Focus: true
Row 1 selected
Row 1 Focus: false
Row 2 Focus: true
Row 2 selected
Row unselected
Row 3 selected
Row 2 Focus: false
Row 3 Focus: true
Row 3 Focus: false
Row 4 Focus: true
Row 1 Focus: true
Row 4 Focus: false
Row 1 selected
Row 1 Focus: false
Row 5 Focus: true
Row 5 selected
Row 3 Focus: true
Row 5 Focus: false
Row 3 selected
Row 0 Focus: true
Row 3 Focus: false
Row 0 selected
Row 0 Focus: false
Row 1 Focus: true
Row 1 selected
Row 1 Focus: false
Row 2 Focus: true
Row 2 selected
Row 2 Focus: false
Row 3 Focus: true
Row 3 selected
Row 0 Focus: true
Row 3 Focus: false
Row 0 selected

Related

Vaadin TreeGrid has no expand or collapse icon

I use Vaadin to create a simple Web Application that contains a single TreeGrid.
The TreeGrid shows up, but there is no handle to expand or collapse a root element ("Year 2010" or "Year 2011") although there are children.
When I use expand(..) Method, the tree is expanded (as shown in the screenshot), but there is no icon to collapse it. In the screenshot below "Year 2011" is the expanded root node where "Customer Project1" and "Customer Project 2" are children.
The project is build with Maven, I use vaadin-bom version 13.0.2 with vaadin-grid-flow-3.0.3.jar.
Below the screenshot there is the code I use. I think the problem must be somewhere in method createTreeGrid().
The working example from Vaadin can be found here
Vaadin example
So anyone any ideas on how to solve this? any help appreciated... :-)
Thorsten
package hello;
#Route
public class MainView extends VerticalLayout
{
public MainView()
{
add( createTreeGrid() );
}
private TreeGrid<Project> createTreeGrid()
{
TreeGrid<Project> treeGrid = new TreeGrid<>();
final List<Project> generateProjectsForYears = generateProjectsForYears( 2010, 2016 );
treeGrid.setItems( generateProjectsForYears, Project::getSubProjects );
treeGrid.addColumn( Project::getName ).setHeader( "Project Name" ).setId( "name-column" );
treeGrid.addColumn( Project::getHoursDone ).setHeader( "Hours Done" );
treeGrid.addColumn( Project::getLastModified ).setHeader( "Last Modified" );
treeGrid.expand( generateProjectsForYears.get( 1 ) ); // works!
return treeGrid;
}
private List<Project> generateProjectsForYears( int startYear, int endYear )
{
List<Project> projects = new ArrayList<>();
for ( int year = startYear; year <= endYear; year++ )
{
Project yearProject = new Project( "Year " + year );
Random random = new Random();
for ( int i = 1; i < 2 + random.nextInt( 5 ); i++ )
{
Project customerProject = new Project( "Customer Project " + i );
customerProject.setSubProjects( Arrays.asList(
new LeafProject( "Implementation", random.nextInt( 100 ), year ),
new LeafProject( "Planning", random.nextInt( 10 ), year ),
new LeafProject( "Prototyping", random.nextInt( 20 ), year ) ) );
yearProject.addSubProject( customerProject );
}
projects.add( yearProject );
}
return projects;
}
private class Project
{
private List<Project> subProjects = new ArrayList<>();
private String name;
public Project( String name )
{
this.name = name;
}
public String getName()
{
return name;
}
public List<Project> getSubProjects()
{
return subProjects;
}
public void setSubProjects( List<Project> subProjects )
{
this.subProjects = subProjects;
}
public void addSubProject( Project subProject )
{
subProjects.add( subProject );
}
public int getHoursDone()
{
return getSubProjects().stream()
.map( project -> project.getHoursDone() )
.reduce( 0, Integer::sum );
}
public Date getLastModified()
{
return getSubProjects().stream()
.map( project -> project.getLastModified() )
.max( Date::compareTo ).orElse( null );
}
}
private class LeafProject extends Project
{
private int hoursDone;
private Date lastModified;
public LeafProject( String name, int hoursDone, int year )
{
super( name );
this.hoursDone = hoursDone;
Random random = new Random();
lastModified = new Date( year - 1900, random.nextInt( 12 ),
random.nextInt( 10 ) );
}
#Override
public int getHoursDone()
{
return hoursDone;
}
#Override
public Date getLastModified()
{
return lastModified;
}
}
}
You have to define a hierarchy column.
So simply replace
treeGrid.addColumn( Project::getName ).setHeader( "Project Name" ).setId( "name-column" );
with
treeGrid.addHierarchyColumn( Project::getName ).setHeader( "Project Name" ).setId( "name-column" );

Dropdown select (Dart Angular Components) discards the second selection

I have the following example class:
import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'package:angular_components/angular_components.dart'
show
SelectionModel,
HasUIDisplayName,
Selectable,
SelectableOption,
StringSelectionOptions,
MaterialDropdownSelectComponent,
MaterialSelectSearchboxComponent,
SelectionChangeRecord,
ItemRenderer,
CachingItemRenderer;
#Component(
selector: 'example-select',
templateUrl: 'example.html',
styleUrls: const [
'example.css'
],
directives: const [
CORE_DIRECTIVES,
formDirectives,
MaterialDropdownSelectComponent,
MaterialSelectSearchboxComponent,
])
class ExampleSelect {
int width = 0;
List<SelectElement> valuesList;
SelectionOptions<SelectElement> _elementListOptions;
StringSelectionOptions<SelectElement> get elementOptions =>
_elementListOptions;
ItemRenderer<SelectElement> get itemRenderer => _itemRenderer;
// Single Selection Model.
final SelectionModel<SelectElement> singleSelectModel =
new SelectionModel.withList(selectedValues: []);
// Label for the button for single selection.
String get singleSelectLabel => singleSelectModel.selectedValues.isNotEmpty
? itemRenderer(singleSelectModel.selectedValues.first)
: 'No Selection';
dynamic get singleSelectedValue => singleSelectModel.selectedValues.isNotEmpty
? singleSelectModel.selectedValues.first.value
: null;
ExampleSelect() {
singleSelectModel.selectionChanges.listen(updateModel);
valuesList = <SelectElement>[
new SelectElement(1, "First"),
new SelectElement(2, "Second"),
new SelectElement(3, 'Third')
];
_elementListOptions = new SelectionOptions<SelectElement>(valuesList);
}
void updateModel(List<SelectionChangeRecord> record) {
print(record);
}
static final ItemRenderer<SelectElement> _itemRenderer =
new CachingItemRenderer<SelectElement>(
(selectElement) => "$selectElement");
}
class SelectElement implements HasUIDisplayName {
final value;
final String label;
const SelectElement(this.value, this.label);
#override
String get uiDisplayName => label;
#override
bool operator ==(Object other) => other is SelectElement && label == label;
#override
int get hashCode => label.hashCode;
#override
String toString() => uiDisplayName;
}
class SelectionOptions<T> extends StringSelectionOptions<T>
implements Selectable {
SelectionOptions(List<T> options)
: super(options, toFilterableString: (T option) => option.toString());
#override
SelectableOption getSelectable(selectElement) =>
selectElement is SelectElement
? SelectableOption.Selectable
: SelectableOption.Disabled;
}
The html is as follows:
<material-dropdown-select
[buttonText]="singleSelectLabel"
[selection]="singleSelectModel"
[options]="elementOptions"
[width]="width"
[itemRenderer]="itemRenderer">
</material-dropdown-select>
<br>
Selected: {{singleSelectLabel}}
Now, if I run this example, and I select the first element, everything works fine, but when I select the "Second" element, the dropdown return to not selected.
This is resumed by the print function that shows the following:
[SelectionChangeRecord{added: [First], removed: []}]
[SelectionChangeRecord{added: [], removed: [First]}]
What I'm doing wrong?
There is an error in your equals method:
#override
bool operator ==(Object other) => other is SelectElement && label == label;
Do you see it??
Should be
other.label == label
I'd probably suggest not overriding the equals method, but that is what is happening here. So when you select/unselect the widget is selecting/unselecting everything because you said they were equal.

How to give a Contextmenu to same tableview according to Action in javafx?

I have a combobox and a table. Combobox have two items they are type1 and type2. When i select type1 from combo then table contextmenu shows "type1 menu" and When i select type2 from combo then table contextmenu shows "type2 menu". How to do this figure out?
Here is my experiment Code.... But its not work correctly..!
import java.util.Arrays;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewSample extends Application {
private final TableView<Person> table = new TableView<>();
private final ComboBox<String> combo = new ComboBox<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Right Click a table Row");
label.setFont(new Font("Arial", 20));
combo.getItems().setAll("Type1","Type2");
combo.getSelectionModel().select(0);
table.setEditable(true);
//----------------------- Add table column ------------------------------//
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
table.setItems(data);
table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));
setTableMenu(0); // You Can Comment This //
combo.setOnAction(event -> {
setTableMenu(combo.getSelectionModel().getSelectedIndex());
});
//-------------------------- Final Works ------------------------------//
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, combo, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
//---------- Set What ContextMenu according to the combobox -----------//
private void setTableMenu(int selectedIndex) {
table.setRowFactory((TableView<Person> tableView) -> {
final TableRow<Person> row = new TableRow<>();
switch(selectedIndex){
case 0:
final ContextMenu contextMenu1 = new ContextMenu();
final MenuItem item1 = new MenuItem("Type1 menu");
item1.setOnAction((ActionEvent event) -> {
System.out.println("Type 1 menu selected");
});
contextMenu1.getItems().add(item1);
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu1)
);
break;
case 1:
final ContextMenu contextMenu2 = new ContextMenu();
final MenuItem item2 = new MenuItem("Type2 menu");
item2.setOnAction((ActionEvent event) -> {
System.out.println("Type 2 menu selected");
});
contextMenu2.getItems().add(item2);
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu2)
);
}
return row ;
});
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
}
Changing the rowFactory won't update the TableRows. The rows will still be the ones created by the initial rowFactory and the type of context menu is decided when the row is created and is never changed.
You could however prepare the ContextMenu just before it's shown and modify it according to the state of the ComboBox:
// in start method
table.setRowFactory((TableView<Person> tableView) -> {
final TableRow<Person> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem item1 = new MenuItem("Type1 menu");
item1.setOnAction((ActionEvent event) -> {
System.out.println("Type 1 menu selected");
});
contextMenu.getItems().add(item1);
final MenuItem item2 = new MenuItem("Type2 menu");
item2.setOnAction((ActionEvent event) -> {
System.out.println("Type 2 menu selected");
});
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu)
);
row.setOnContextMenuRequested(evt -> {
// update menu when requested
switch (combo.getSelectionModel().getSelectedIndex()) {
case 0:
contextMenu.getItems().setAll(item1);
break;
case 1:
contextMenu.getItems().setAll(item2);
break;
}
});
return row;
});
/*setTableMenu(0);
combo.setOnAction(event -> {
setTableMenu(combo.getSelectionModel().getSelectedIndex());
});*/
Alternatively you could also use the same ContextMenu for all rows and just modify that on a change in the ComboBox:
// in start method
final MenuItem item1 = new MenuItem("Type1 menu");
item1.setOnAction((ActionEvent event) -> {
System.out.println("Type 1 menu selected");
});
final ContextMenu contextMenu = new ContextMenu(item1);
final MenuItem item2 = new MenuItem("Type2 menu");
item2.setOnAction((ActionEvent event) -> {
System.out.println("Type 2 menu selected");
});
combo.setOnAction(evt -> {
switch (combo.getSelectionModel().getSelectedIndex()) {
case 0:
contextMenu.getItems().setAll(item1);
break;
case 1:
contextMenu.getItems().setAll(item2);
break;
}
});
table.setRowFactory((TableView<Person> tableView) -> {
final TableRow<Person> row = new TableRow<>();
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu) null)
.otherwise(contextMenu)
);
return row;
});
/*setTableMenu(0);
combo.setOnAction(event -> {
setTableMenu(combo.getSelectionModel().getSelectedIndex());
});*/

Recursive method not incrementing string analysis counter in TableView GUI (JavaFX)

EDIT Recursion is required for the counter increment.
I have a GUI that adds entered fields to a ListView, but also adds the name of the object to a TableView beside it and displays four substring counts. They are Plant objects, and it's supposed to display in the ListView (which it does), and then display the name with each occurrence of a certain substring:
See here:
I don't understand what I'm missing, because in my addButtonClick method, I call my recursive method. The logic seems correct to me in the method, so I must be missing something in my Integer properties, or a constructor.
Here is the method in the Controller for the add button click. There is a combo box where a user can choose Flower, Fungus, Weed, or Herb. And when the user clicks a respective plant type, respective radio buttons appear that are related to certain traits. All of that works. I will show the Flower section of code:
/*
Adds respective plant type and resets controls
*/
public void handleAddButtonClick(ActionEvent event) {
if (idInput != null && nameInput != null & colorInput != null) {
if (plantType.getValue().equals("Flower")) {
Flower flower = new Flower(ID, idNum, name, color, smell, thorns, edible, poisonous, flavor, medicine, seasonal);
flower.setID(idInput.getText());
flower.setName(nameInput.getText());
flower.setColor(colorInput.getText());
flower.setSmell(scentedRadio.isSelected());
flower.setThorns(thornyRadio.isSelected());
observablePlantList.add(flower);
//this is where the table information gets added.
//it adds it, just displays 0's.
flower.setPlantName(nameInput.getText());
Plant.substringCounter(name, "e"); //tried flower instead
Plant.substringCounter(name, "ar");//of Plant. still nothing.
Plant.substringCounter(name, "er");
Plant.substringCounter(name, "o");
observableAnalysisList.add(flower);
//just doing some debug printing. this prints 0
System.out.println(Plant.substringCounter(name, "e"));
//more debugging. this prints fine because of toString() method
System.out.println(flower);
Here is the relevant code in the main class and the Flower class:
//Plant Table properties ("e", "o", "ar", "er")
public StringProperty plantName = new SimpleStringProperty(this, "plantName", "");
public String getPlantName() {return plantName.get(); }
public StringProperty plantNameProperty() {return plantName; }
public void setPlantName(String plantName) {this.plantName.set(plantName); }
public IntegerProperty countLetterE = new SimpleIntegerProperty(this, "countLetterE", 0);
public int getLetterE() {return countLetterE.get();}
public IntegerProperty eProperty() {return countLetterE; }
public void setCountLetterE(int countLetterE) {this.countLetterE.set(countLetterE);}
public IntegerProperty countLetterO = new SimpleIntegerProperty(this, "countLetterO", 0);
public int getLetterO() {return countLetterO.get(); }
public IntegerProperty oProperty() {return countLetterO; }
public void setCountLetterO(int countLetterO) {this.countLetterO.set(countLetterO);}
public IntegerProperty countLetterER = new SimpleIntegerProperty(this, "countLetterER", 0);
public int getLetterER() {return countLetterER.get(); }
public IntegerProperty erProperty() {return countLetterER; }
public void setCountLetterER(int countLetterER) {this.countLetterER.set(countLetterER);}
public IntegerProperty countLetterAR = new SimpleIntegerProperty(this, "countLetterAR", 0);
public int getLetterAR() {return countLetterAR.get(); }
public IntegerProperty arProperty() {return countLetterAR; }
public void setCountLetterAR(int countLetterAR) {this.countLetterAR.set(countLetterAR);}
Recursive method:
public static int substringCounter(String plantName, String letters) {
plantName = plantName.toLowerCase();
if(plantName.isEmpty()) {
return 0;
}
if(plantName.indexOf(letters) == -1) {
return 0;
}
return 1 + substringCounter(plantName.substring(plantName.indexOf(letters) + 1), letters);
}
//toString method for Plant class to display in ListView. Works fine.
public String toString() {
return "ID: " + this.ID + "-" + this.idNum + ", Name: " + this.name + ", Color: " + this.color;
}
}
Flower class
public class Flower extends Plant {
public Flower(String ID, int idNum, String name, String color, boolean smell, boolean thorns, boolean edible, boolean poisonous, boolean flavor, boolean medicine, boolean seasonal) {
super(ID, idNum, name, color, smell, thorns, edible, poisonous, flavor, medicine, seasonal);
}
public void setSmell(boolean smell) {
this.smell = smell;
}
public void setThorns(boolean thorns) {
this.thorns = thorns;
}
//toString method for the ListView only. All works fine here
public String toString() {
return super.toString() + ", Scent? " + this.smell + ", Thorns? " + this.thorns;
}
}
I sincerely hope I either 1) gave you enough information, or 2) didn't give you too much information. Thank you all for any help offered.
You are invoking a static method on a string and return a number which effectively results in absolutely nothing when you call it like that:
Plant.substringCounter(name, "e");
You need to modify a property if you want some "action" or at least process the result that's being returned by substringCounter.
Besides, I have no idea why you use recursion. This does the same:
String text = "dandelion";
String search = "d";
int count = 0;
int pos = 0;
if (!search.isEmpty()) {
while ((pos = text.indexOf(search, pos)) != -1) {
count++;
pos++;
}
}
System.out.println(count);
Basically something like this:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
HBox box = new HBox();
box.setSpacing(10);
TextField sourceTextField = new TextField( "Dandelion");
Label result = new Label();
TextField searchTextField = new TextField();
// add listener for counting the number of substrings
searchTextField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String sourceText = sourceTextField.getText().toLowerCase();
String searchText = newValue.toLowerCase();
int count = 0;
int pos = 0;
if( !searchText.isEmpty()) {
while( (pos = sourceText.indexOf( searchText, pos)) != -1) {
count++;
pos++;
}
}
result.setText( String.valueOf(count));
}
});
box.getChildren().addAll( new Label( "Search:"), searchTextField, new Label( "Text:"), sourceTextField, new Label( "Count:"), result);
Scene scene = new Scene(box, 600, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Or using your recursive method (you also need to check letters for empty string by the way):
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
HBox box = new HBox();
box.setSpacing(10);
TextField sourceTextField = new TextField( "Dandelion");
Label result = new Label();
TextField searchTextField = new TextField();
searchTextField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String sourceText = sourceTextField.getText().toLowerCase();
String searchText = newValue.toLowerCase();
result.setText( String.valueOf( substringCounter( sourceText, searchText)));
}
});
box.getChildren().addAll( new Label( "Search:"), searchTextField, new Label( "Text:"), sourceTextField, new Label( "Count:"), result);
Scene scene = new Scene(box, 600, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static int substringCounter(String plantName, String letters) {
plantName = plantName.toLowerCase();
if(letters.isEmpty()) {
return 0;
}
if(plantName.isEmpty()) {
return 0;
}
if(plantName.indexOf(letters) == -1) {
return 0;
}
return 1 + substringCounter(plantName.substring(plantName.indexOf(letters) + 1), letters);
}
public static void main(String[] args) {
launch(args);
}
}
Instead of setting the label like I did in the example you of course have to change your integer properties.

JavaFX2: Add rows to a TableView that uses Table Cell Binding

I am attempting to modify the solution outlined in UITableView - Better Editing through Binding? (Thank you, jKaufmann, for that excellent example) to allow adding rows to the table.
I have introduced a button that, when clicked, invokes code to add an additional TableData row to the ObservableList backing the table.
The new row shows up without incident. However, after a few rows are added and you scroll through the table using the Up/Down keys a couple of times, random rows in the table start getting replaced with a blank row.
It usually takes adding a couple of rows, selecting a row, traversing down the list (using the keyboard) to the new row, adding a couple more rows, traversing down to the new row and all the way to the top again to reproduce the issue. To select a row in the table, I click on the edge of a row, so that the row is selected rather than an individual cell.
The source code (essentially jkaufmann's example modified to include an 'Add Row' button) is here.
package tablevieweditingwithbinding;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableViewEditingExample2 extends Application {
public static class TableData {
private SimpleStringProperty firstName, lastName, phone, email;
private ObjectProperty<SimpleStringProperty> firstNameObject;
public TableData(String firstName, String lastName, String phone, String email) {
this.firstName = new SimpleStringProperty(firstName);
this.firstNameObject = new SimpleObjectProperty(firstNameObject);
this.lastName = new SimpleStringProperty(lastName);
this.phone = new SimpleStringProperty(phone);
this.email = new SimpleStringProperty(email);
}
public String getEmail() {
return email.get();
}
public void setEmail(String email) {
this.email.set(email);
}
public SimpleStringProperty emailProperty() {
return email;
}
public String getFirstName() {
return firstName.get();
}
public SimpleStringProperty getFirstNameObject() {
return firstNameObject.get();
}
public void setFirstNameObject(SimpleStringProperty firstNameObject) {
this.firstNameObject.set(firstNameObject);
}
public ObjectProperty<SimpleStringProperty> firstNameObjectProperty() {
return firstNameObject;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public SimpleStringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public SimpleStringProperty lastNameProperty() {
return lastName;
}
public String getPhone() {
return phone.get();
}
public void setPhone(String phone) {
this.phone.set(phone);
}
public SimpleStringProperty phoneProperty() {
return phone;
}
}
public static class TextFieldCellFactory
implements Callback<TableColumn<TableData, String>, TableCell<TableData, String>> {
#Override
public TableCell<TableData, String> call(TableColumn<TableData, String> param) {
TextFieldCell textFieldCell = new TextFieldCell();
return textFieldCell;
}
public static class TextFieldCell extends TableCell<TableData, String> {
private TextField textField;
private StringProperty boundToCurrently = null;
public TextFieldCell() {
String strCss;
// Padding in Text field cell is not wanted - we want the Textfield itself to "be"
// The cell. Though, this is aesthetic only. to each his own. comment out
// to revert back.
strCss = "-fx-padding: 0;";
textField = new TextField();
//
// Default style pulled from caspian.css. Used to play around with the inset background colors
// ---trying to produce a text box without borders
strCss = ""
+ //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
"-fx-background-color: transparent;"
+ //"-fx-background-insets: 0, 1, 2;" +
//"-fx-background-insets: 0;" +
//"-fx-background-radius: 3, 2, 2;" +
//"-fx-background-radius: 0;" +
//"-fx-padding: 3 5 3 5;" + /*Play with this value to center the text depending on cell height??*/
//"-fx-padding: 0 0 0 0;" +
//"-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);" +
//"-fx-accent: derive(-fx-control-inner-background, -40%);" +
"-fx-cell-hover-color: derive(-fx-control-inner-background, -20%);"
+ "-fx-cursor: text;"
+ "";
//
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
TextField tf = (TextField) getGraphic();
String strStyleGotFocus = "-fx-background-color: purple, -fx-text-box-border, -fx-control-inner-background;"
+ "-fx-background-insets: -0.4, 1, 2;"
+ "-fx-background-radius: 3.4, 2, 2;";
String strStyleLostFocus = //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
"-fx-background-color: transparent;"
+ //"-fx-background-insets: 0, 1, 2;" +
"-fx-background-insets: 0;"
+ //"-fx-background-radius: 3, 2, 2;" +
"-fx-background-radius: 0;"
+ "-fx-padding: 3 5 3 5;"
+ /**/ //"-fx-background-fill: green;" + /**/
//"-fx-background-color: green;" +
"-fx-background-opacity: 0;"
+ //"-fx-opacity: 0;" +
//"-fx-padding: 0 0 0 0;" +
"-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);"
+ "-fx-cursor: text;"
+ "";
if (newValue.booleanValue()) {
tf.setStyle(strStyleGotFocus);
} else {
tf.setStyle(strStyleLostFocus);
}
}
});
textField.setStyle(strCss);
this.setGraphic(textField);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// Retrieve the actual String Property that should be bound to the TextField
// If the TextField is currently bound to a different StringProperty
// Unbind the old property and rebind to the new one
ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
SimpleStringProperty sp = (SimpleStringProperty) ov;
if (this.boundToCurrently == null) {
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(sp);
} else {
if (this.boundToCurrently != sp) {
this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(this.boundToCurrently);
}
}
System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
} else {
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
}
private final TableView<TableData> table = new TableView<TableData>();
final ObservableList<TableData> ol =
FXCollections.observableArrayList(
new TableData("Wilma", "Flintstone", "555-123-4567", "WFlintstone#gmail.com"),
new TableData("Fred", "Flintstone", "555-123-4567", "FFlintstone#gmail.com"),
new TableData("Barney", "Flintstone", "555-123-4567", "Barney#gmail.com"),
new TableData("Bugs", "Bunny", "555-123-4567", "BugsB#gmail.com"),
new TableData("Yo", "Sam", "555-123-4567", "ysam#gmail.com"),
new TableData("Tom", "", "555-123-4567", "tom#gmail.com"),
new TableData("Jerry", "", "555-123-4567", "Jerry#gmail.com"),
new TableData("Peter", "Pan", "555-123-4567", "Ppan#gmail.com"),
new TableData("Daffy", "Duck", "555-123-4567", "dduck#gmail.com"),
new TableData("Tazmanian", "Devil", "555-123-4567", "tdevil#gmail.com"),
new TableData("Mickey", "Mouse", "555-123-4567", "mmouse#gmail.com"),
new TableData("Mighty", "Mouse", "555-123-4567", "mimouse#gmail.com"));
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}
static int counter = 1;
#Override
public void start(Stage Stage) {
Stage.setTitle("Editable Table");
BorderPane borderPane = new BorderPane();
Scene scene = new Scene(borderPane, 800, 600);
// top of border pane
Button b1 = new Button("Change value in table list");
Button b2 = new Button("Add row");
HBox hbox = new HBox(10);
hbox.setStyle("-fx-background-color: #336699");
hbox.setAlignment(Pos.BOTTOM_CENTER);
HBox.setMargin(b2, new Insets(10, 0, 10, 0));
HBox.setMargin(b1, new Insets(10, 0, 10, 0));
hbox.getChildren().addAll(b1, b2);
borderPane.setTop(hbox);
BorderPane.setAlignment(hbox, Pos.CENTER);
// Button Events
b1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
String curFirstName = ol.get(0).getFirstName();
if (curFirstName.contentEquals("Jason")) {
ol.get(0).setFirstName("Paul");
} else {
ol.get(0).setFirstName("Jason");
}
}
});
b2.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Platform.runLater(new Runnable() {
#Override
public void run() {
int dataListSize = 0;
dataListSize = ol.size();
System.out.println("Table size = " + dataListSize);
ol.add(new TableData("firstName" + counter,
"lastName" + counter,
"phone" + counter,
"email" + counter++));
dataListSize = ol.size();
System.out.println("Table size = " + dataListSize);
table.getColumns().get(0).setVisible(false);
table.getColumns().get(0).setVisible(true);
}
});
}
});
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setItems(ol);
borderPane.setCenter(table);
BorderPane.setAlignment(table, Pos.CENTER);
BorderPane.setMargin(table, new Insets(25));
// Add columns
TableColumn<TableData, String> c1 = new TableColumn<TableData, String>("FirstName");
c1.setCellValueFactory(new PropertyValueFactory<TableData, String>("firstName"));
c1.setCellFactory(new TextFieldCellFactory());
TableColumn<TableData, String> c2 = new TableColumn<TableData, String>("LastName");
c2.setCellValueFactory(new PropertyValueFactory<TableData, String>("lastName"));
c2.setCellFactory(new TextFieldCellFactory());
TableColumn<TableData, String> c3 = new TableColumn<TableData, String>("Phone");
c3.setCellValueFactory(new PropertyValueFactory<TableData, String>("phone"));
c3.setCellFactory(new TextFieldCellFactory());
TableColumn<TableData, String> c4 = new TableColumn<TableData, String>("Email");
c4.setCellValueFactory(new PropertyValueFactory<TableData, String>("email"));
c4.setCellFactory(new TextFieldCellFactory());
table.getColumns().addAll(c1, c2, c3, c4);
scene.getStylesheets().add(TableViewEditingWithBinding.class.getResource("styles.css").toExternalForm());
Stage.setScene(scene);
Stage.show();
}
}
I tried adding the following code
table.getColumns().get(0).setVisible(false);
table.getColumns().get(0).setVisible(true);
in the handler for the addRow button after the row was added, but that didn't help any. I also tried clearing the backing observable list and resetting the value to the list + the new row after each row was added; that did not solve the issue either.
Any help would be most appreciated.
I had the same problem. After deleting one row some other rows are blanked out. If I scrolled the blank rows changed.
My solution was to move the line
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
to the constructor of the cell and to remove the line
setContentDisplay(ContentDisplay.TEXT_ONLY);
completely.
The API doc of Cell.updateItem states
empty - whether or not this cell represents data from the list. If it is empty, then it does not represent any domain data, but is a cell being used to render an "empty" row.
For me this looks like a bug in JavaFX because all my rows are backed with domain data but still some of them (changing when scrolling) receive an updateItem() with empty=true.

Resources