JavaFX TextField: How to "auto-scroll" to right when text overflows - string

I'm using a TextField to display the path of a directory the user has opened in my application.
Currently, if the path can't fit inside the TextField, upon focusing away/clicking away from this control, it looks like as if the path has become truncated:
I want the behaviour of TextField set such that when I focus away from it, the path shown inside automatically scrolls to the right and the user is able to see the directory they've opened. I.e. something like this:
How can I achieve this? I've tried adapting the answer given from here
as follows in initialize() method in my FXML Controller class:
// Controller class fields
#FXML TextField txtMoisParentDirectory;
private String moisParentDirectory;
// ...
txtMoisParentDirectory.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldStr, String newStr) {
moisParentDirectory = newStr;
txtMoisParentDirectory.selectPositionCaret(moisParentDirectory.length());
txtMoisParentDirectory.deselect();
}
});
However it doesn't work.

Your problem is based on two events, the length of the text entered and the loss of focus, so to solve it I used the properties textProperty() and focusedProperty() and here is the result :
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Launcher extends Application{
private Pane root = new Pane();
private Scene scene;
private TextField tf = new TextField();
private TextField tft = new TextField();
private int location = 0;
#Override
public void start(Stage stage) throws Exception {
scrollChange();
tft.setLayoutX(300);
root.getChildren().addAll(tft,tf);
scene = new Scene(root,400,400);
stage.setScene(scene);
stage.show();
}
private void scrollChange(){
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
location = tf.getText().length();
}
});
tf.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(!newValue){
Platform.runLater( new Runnable() {
#Override
public void run() {
tf.positionCaret(location);
}
});
}
}
});
}
public static void main(String[] args) {
launch(args);
}
}
And concerning the Platform.runLater I added it following this answer Here I don't know why it does not work without it, good luck !

tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
int location = tf.getText().length();
Platform.runLater(() -> {
tf.positionCaret(location);
});
}
});
this is also work

Since the other answers didn't work for me here is a solution that should do the trick:
private TextField txtField;
// Both ChangeListeners just call moveCaretToEnd(), we need them both because of differing data types we are listening to
private final ChangeListener<Number> caretChangeListener = (observable, oldValue, newValue) -> moveCaretToEnd();
private final ChangeListener<String> textChangeListener = (observable, oldValue, newValue) -> moveCaretToEnd();
// This method moves the caret to the end of the text
private void moveCaretToEnd() {
Platform.runLater(() -> {
txtField.deselect();
txtField.end();
});
}
public void initialize() {
// Immediatly add the listeners on initialization (or once you created the TextField if you are not using FXML)
txtField.caretPositionProperty().addListener(caretChangeListener);
txtField.textProperty().addListener(textChangeListener);
txtField.focusedProperty().addListener((observable, oldValue, isFocused) -> {
if (isFocused) {
// once the TextField has been focused remove the listeners to enable normal editing of the text
txtField.caretPositionProperty().removeListener(caretChangeListener);
txtField.textProperty().removeListener(textChangeListener);
} else {
// when the focus is lost apply the listeners again
moveCaretToEnd();
txtField.caretPositionProperty().addListener(caretChangeListener);
txtField.textProperty().addListener(textChangeListener);
}
});
}

Related

Running an infinite loop in a JavaFX(Embedded in javax.swing.JFrame) App

I am trying to run an Infinite loop in my JavaFX app.
An infinite while loop is present in my code in the Kulta.java file.
This loop actually freezes my app.
While the same thing works when I port the app to normal javax.swing.
Now since java.lang.Thread doesn't work for javafx, I came accross javafx.concurrent.Task,
which is not working as intended. As one of the main features of multithreading, i.e. running an infinite loop in a GUI app, is not served properly, please help me with the solution.
This is my code:
Urania.java
import java.awt.Toolkit;
import java.awt.Dimension;
import javax.swing.SwingUtilities;
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
public class Urania {
public static final Dimension DIMENSION = Toolkit.getDefaultToolkit().getScreenSize();
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
#Override
public void run() {
Kulta kulta = new Kulta();
kulta.setTitle("Abha K Pauri");
kulta.setSize(DIMENSION.width/2, DIMENSION.height/2);
kulta.setLocationRelativeTo(null);
kulta.setDefaultCloseOperation(EXIT_ON_CLOSE);
kulta.setVisible(true);
}
}
);
}
}
And here is my JFrame in which I have embedded my JavaFX app.
Kulta.java
import javax.swing.JFrame
import javafx.embed.swing.JFXPanel;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.control.Button;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.concurrent.Task;
public class Kulta extends JFrame {
private JFXPanel fxpanel;
private Scene scene;
private BorderPane borderpane;
private Button button;
public static final String INVOKE = "INVOKE";
public static final String INTERRUPT = "INTERRUPT";
public static final String[] COLORS = new String[]{"yellow", "pink", "green", "blue", "orange"};
public Kulta() {
fxpanel = new JFXPanel();
add(fxpanel);
Platform.runLater(
new Runnable() {
#Override
public void run() {
Kulta.this.setScene();
Kulta.this.setButton();
Kulta.this.setListener();
}
}
);
}
private void setScene() {
borderpane = new BorderPane();
scene = new Scene(borderpane);
fxpanel.setScene(scene);
}
private void setButton() {
button = new Button(INVOKE);
borderpane.setTop(button);
}
private void setListener() {
Event event = new Event();
button.setOnAction(event);
}
private class Event implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent event) {
boolean flag = true;
Task<Void> onInvoke = new Task<Void>() {
#Override
public Void call() {
int count = 0;
flag = true;
button.setText(INTERRUPT);
/* This loop freezes the app. */
while(flag) {
borderpane.setStyle("-fx-color: "+COLORS[count]+";");
count++;
if(count == COLORS.length)
count = 0;
}
return null;
}
};
Task<Void> onInterrupt = new Task<Void>() {
#Override
public Void call() {
button.setText(INVOKE);
if(flag)
flag = false; // This will stop the onInvoke thread
return null;
}
};
Task<Void> change = new Task<Void>() {
#Override
public Void call() {
if(button.getText().equals(INVOKE))
onInvoke().run();
else if(button.getText().equals(INTERRUPT))
onInterrupt().run();
}
};
change.run();
}
}
}
How should I write the loop in order to not let the app freeze.
Any code, solution, link or any help in any form will help a lot.
Thanks in advance.

Using RowEditing and CheckBoxSelectionModel in a Grid Fails

In a GXT Grid I am attempting to use RowEditing and the CheckBoxSelectionModel. The Sencha Explorer Demo has examples of these in the Row Editable Grid and CheckBox Grid samples, but they don't show an example that includes a combination of these features. When I use both features on the same grid I am not getting the behavior that I had expected. If I click on the "selection" checkbox the row is placed into edit mode, where I would have expected the checkbox to just change from checked to unchecked or vice versa. In addition, when the row is placed into edit mode there is corruption on the line. Here is an example of a row from the grid prior to clicking on any of the values in that row:
and here is that row after clicking on one of the values:
Does anyone have any experience with this?
Update
Here's a sample class which demonstrates the issue:
package org.greatlogic.gxtgrid.client;
import java.util.ArrayList;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sencha.gxt.core.client.IdentityValueProvider;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.CheckBoxSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.editing.GridRowEditing;
public class GXTGrid implements EntryPoint {
//-------------------------------------------------------------------------
#Override
public void onModuleLoad() {
ListStore<Pet> listStore = new ListStore<>(new ModelKeyProvider<Pet>() {
#Override
public String getKey(Pet pet) {
return Integer.toString(pet.getPetId());
}
});
IdentityValueProvider<Pet> ivp = new IdentityValueProvider<>();
CheckBoxSelectionModel<Pet> sm = new CheckBoxSelectionModel<>(ivp);
ArrayList<ColumnConfig<Pet, ?>> ccList = new ArrayList<>();
ccList.add(sm.getColumn());
ColumnConfig<Pet, String> cc1;
cc1 = new ColumnConfig<>(Pet.getPetNameValueProvider(), 100, "Name");
ccList.add(cc1);
ColumnModel<Pet> columnModel = new ColumnModel<>(ccList);
Grid<Pet> grid = new Grid<>(listStore, columnModel);
grid.setSelectionModel(sm);
grid.setView(new GridView<Pet>());
GridRowEditing<Pet> gre = new GridRowEditing<>(grid);
gre.addEditor(cc1, new TextField());
listStore.add(new Pet(1, "Lassie"));
listStore.add(new Pet(2, "Scooby"));
listStore.add(new Pet(3, "Snoopy"));
ContentPanel contentPanel = new ContentPanel();
contentPanel.add(grid);
RootLayoutPanel.get().add(contentPanel);
}
//-------------------------------------------------------------------------
private static class Pet {
private int _petId;
private String _petName;
public static ValueProvider<Pet, String> getPetNameValueProvider() {
return new ValueProvider<Pet, String>() {
#Override
public String getPath() {
return "Pet.PetName";
}
#Override
public String getValue(Pet pet) {
return pet._petName;
}
#Override
public void setValue(Pet pet, final String value) {
pet._petName = value;
}
};
}
public Pet(int petId, final String petName) {
_petId = petId;
_petName = petName;
}
public int getPetId() {
return _petId;
}
}
//-------------------------------------------------------------------------
}
This behavior of GridRowEditing with CheckBoxSelectionModel is completely normal.
I have used you code to try some things. I think the best way to use GridRowEditing and CheckBoxSelectionModel is, as I guessed, to start editing on double click, as nothing is provided to do it with just one click yet. To do so just add
gre.setClicksToEdit(ClicksToEdit.TWO);
Otherwise, if you really do not want to use two clicks to start editing, you can also use InlineRowEditing, which will enable you to use CheckBoxSelectionModel as you want.
Eventually, you might be able to override the whole behavior of GridRowEditing to handle CheckBoxSelectionModel properly on one click only, but it would be more complicated and require more specific knowledge of GXT framework that I don't have.
I haven't found a solution to this problem using the CheckBoxSelectionModel, and so I decided to try another approach, namely, adding a column to the grid that contains a checkbox, and handling the state of the selections manually. To do this I found that I needed to respond to a few events, which wasn't too bad. Here's a new version of the sample code, which should provide a starting point for a real implementation:
import java.util.ArrayList;
import java.util.TreeSet;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sencha.gxt.cell.core.client.form.CheckBoxCell;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.form.CheckBox;
import com.sencha.gxt.widget.core.client.form.Field;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.CellSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.editing.GridRowEditing;
public class GXTGrid implements EntryPoint {
//-------------------------------------------------------------------------
private ListStore<Pet> _listStore;
private TreeSet<Integer> _selectedPetIdSet;
//-------------------------------------------------------------------------
#Override
public void onModuleLoad() {
_selectedPetIdSet = new TreeSet<>();
_listStore = new ListStore<>(new ModelKeyProvider<Pet>() {
#Override
public String getKey(Pet pet) {
return Integer.toString(pet.getPetId());
}
});
final CellSelectionModel<Pet> sm = new CellSelectionModel<>();
ArrayList<ColumnConfig<Pet, ?>> ccList = new ArrayList<>();
ValueProvider<Pet, Boolean> selectValueProvider;
selectValueProvider = new ValueProvider<GXTGrid.Pet, Boolean>() {
#Override
public String getPath() {
return "SelectCheckBox";
}
#Override
public Boolean getValue(Pet pet) {
return _selectedPetIdSet.contains(pet.getPetId());
}
#Override
public void setValue(Pet pet, final Boolean selected) { //
}
};
ColumnConfig<Pet, Boolean> cc0 = new ColumnConfig<>(selectValueProvider, 23, "");
CheckBoxCell checkBoxCell = new CheckBoxCell() {
#Override
protected void onClick(XElement parent, final NativeEvent event) {
super.onClick(parent, event);
Pet pet = sm.getSelectedItem();
if (!_selectedPetIdSet.remove(pet.getPetId())) {
_selectedPetIdSet.add(pet.getPetId());
}
}
};
cc0.setCell(checkBoxCell);
cc0.setFixed(true);
cc0.setHideable(false);
cc0.setMenuDisabled(true);
cc0.setResizable(false);
cc0.setSortable(false);
ccList.add(cc0);
ColumnConfig<Pet, String> cc1;
cc1 = new ColumnConfig<>(Pet.getPetNameValueProvider(), 100, "Name");
ccList.add(cc1);
ColumnModel<Pet> columnModel = new ColumnModel<>(ccList);
Grid<Pet> grid = new Grid<>(_listStore, columnModel);
grid.setSelectionModel(sm);
grid.setView(new GridView<Pet>());
GridRowEditing<Pet> gre = new GridRowEditing<>(grid);
Field<Boolean> checkBox = new CheckBox();
checkBox.setEnabled(false);
gre.addEditor(cc0, checkBox);
gre.addEditor(cc1, new TextField());
_listStore.add(new Pet(1, "Lassie"));
_listStore.add(new Pet(2, "Scooby"));
_listStore.add(new Pet(3, "Snoopy"));
ContentPanel contentPanel = new ContentPanel();
contentPanel.add(grid);
RootLayoutPanel.get().add(contentPanel);
}
//-------------------------------------------------------------------------
private static class Pet {
private int _petId;
private String _petName;
public static ValueProvider<Pet, String> getPetNameValueProvider() {
return new ValueProvider<Pet, String>() {
#Override
public String getPath() {
return "Pet.PetName";
}
#Override
public String getValue(Pet pet) {
return pet._petName;
}
#Override
public void setValue(Pet pet, final String value) {
pet._petName = value;
}
};
}
public Pet(int petId, final String petName) {
_petId = petId;
_petName = petName;
}
public int getPetId() {
return _petId;
}
}
//-------------------------------------------------------------------------
}

Java FX8 UI update lags

I have some signal processing data which gets fed at roughly at 50Hz. I need to update a rectangle's opacity based on the signal value in real time. I am trying to develop the UI in JavaFX 8.
For time being I am simulating the signal value using random number generator in JavaFX service in my code.
I am using Platform.runLater to update the UI, however this doesn't update values in real time, I read through similar problems encountered by others and the normal suggestion is that not to call Platform.runLater often but to batch the updates.
In my case if I batch my updates, the frequency at which the opacity changes will not be equal to the signal frequency.
Any thoughts on how to achieve this?
public class FlickerController
{
#FXML
private Rectangle leftBox;
#FXML
private Rectangle rightBox;
#FXML
private ColorPicker leftPrimary;
#FXML
private ColorPicker leftSecondary;
#FXML
private ColorPicker rightPrimary;
#FXML
private ColorPicker rightSecondary;
#FXML
private Slider leftFrequency;
#FXML
private Slider rightFrequency;
#FXML
private Button startButton;
#FXML
private Label leftfreqlabel;
#FXML
private Label rightfreqlabel;
#FXML
private Label rightBrightness;
#FXML
private Label leftBrightness;
private boolean running = false;
DoubleProperty leftopacity = new SimpleDoubleProperty(1);
DoubleProperty rightopacity = new SimpleDoubleProperty(1);
private FlickerThread ftLeft;
private FlickerThread ftRight;
public void initialize()
{
leftopacity.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue)
{
Platform.runLater(new Runnable()
{
#Override
public void run()
{
double brightness = leftopacity.doubleValue();
leftBrightness.setText(""+brightness);
leftBox.opacityProperty().set(brightness);
}
});
}
});
rightopacity.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue)
{
Platform.runLater(new Runnable()
{
#Override
public void run()
{
double brightness = rightopacity.doubleValue();
rightBrightness.setText(""+brightness);
rightBox.opacityProperty().set(brightness);
}
});
}
});
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event)
{
if(running)
{
synchronized(this)
{
running=false;
}
startButton.setText("Start");
}
else
{
running=true;
ftLeft = new FlickerThread((int)leftFrequency.getValue(),leftopacity);
ftRight = new FlickerThread((int)rightFrequency.getValue(), rightopacity);
try
{
ftLeft.start();
ftRight.start();
}
catch(Throwable t)
{
t.printStackTrace();
}
startButton.setText("Stop");
}
}
});
leftFrequency.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue)
{
leftfreqlabel.setText(newValue.intValue()+"");
}
});
rightFrequency.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue)
{
rightfreqlabel.setText(newValue.intValue()+"");
}
});
}
class FlickerThread extends Service<Void>
{
private long sleeptime;
DoubleProperty localval = new SimpleDoubleProperty(1) ;
public FlickerThread(int freq, DoubleProperty valtoBind)
{
this.sleeptime = (1/freq)*1000;
valtoBind.bind(localval);
}
#Override
protected Task <Void>createTask()
{
return new Task<Void>() {
#Override
protected Void call() throws Exception
{
while(running)
{
double val = Math.random();
System.out.println(val);
localval.setValue(val);
Thread.sleep(sleeptime);
}
return null;
}
};
}
}
}
class FlickerThread extends Thread
{
private long sleeptime;
final AtomicReference<Double> counter = new AtomicReference<>(new Double(-1.0));
private Label label;
private Rectangle myrect;
public FlickerThread(int freq, Label label,Rectangle rect)
{
this.sleeptime = (long) ((1.0/freq)*1000.0);
System.out.println("Sleep time is "+sleeptime);
this.label = label;
this.myrect = rect;
}
#Override
public void run() {
double count = 1.0 ;
while (running) {
count = Math.random();
if (counter.getAndSet(count) == -1) {
updateUI(counter, label,myrect);
try
{
Thread.sleep(sleeptime);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
private void updateUI(final AtomicReference<Double> counter,
final Label label, final Rectangle myrect) {
Platform.runLater(new Runnable() {
#Override
public void run() {
double val = counter.getAndSet(-1.0);
final String msg = String.format("Brt: %,f", val);
label.setText(msg);
myrect.opacityProperty().set(val);
}
});
}
You have a calculation error in your code.
Consider:
1/100*1000=0
But:
1.0/100*1000=10.0
i.e. you need to use floating point arithmetic, not integer arithmetic.
There are numerous other issues with your code as pointed out in my previous comment, so this answer is more of a code review and suggested approach than anything else.
You can batch updates to runLater as in James's answer to Throttling javafx gui updates. But for an update rate of 100 hertz max, it isn't going to make a lot of difference performance-wise as JavaFX generally operates on a 60 hertz pulse cycle, unless you really overload it (which you aren't really doing in your example). So the savings you get by throttling updates will be pretty minimal.
Here is a sample you can try out (it uses James's input throttling technique):
import javafx.application.*;
import javafx.beans.property.DoubleProperty;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
public class InputApp extends Application {
private final ToggleButton controlButton = new ToggleButton("Start");
private final Rectangle box = new Rectangle(100, 100, Color.BLUE);
private final Label brightness = new Label();
private final Label frequencyLabel = new Label();
private final Slider frequency = new Slider(1, 100, 10);
private InputTask task;
#Override
public void start(Stage stage) throws Exception {
// initialize bindings.
brightness.textProperty().bind(
box.opacityProperty().asString("%.2f")
);
frequencyLabel.textProperty().bind(
frequency.valueProperty().asString("%.0f")
);
frequency.valueChangingProperty().addListener((observable, oldValue, newValue) -> {
if (controlButton.isSelected()) {
controlButton.fire();
}
});
// start and stop the input task.
controlButton.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
if (isSelected) {
task = new InputTask(
(int) frequency.getValue(),
box.opacityProperty()
);
Thread inputThread = new Thread(task, "input-task");
inputThread.setDaemon(true);
inputThread.start();
controlButton.setText("Stop");
} else {
if (task != null) {
task.cancel();
}
controlButton.setText("Start");
}
});
// create the layout
VBox layout = new VBox(
10,
frequency,
new HBox(5, new Label("Frequency: " ), frequencyLabel, new Label("Hz"),
controlButton,
box,
new HBox(5, new Label("Brightness: " ), brightness)
);
layout.setPadding(new Insets(10));
// display the scene
stage.setScene(new Scene(layout));
stage.show();
}
// simulates accepting random input from an input feed at a given frequency.
class InputTask extends Task<Void> {
private final DoubleProperty changeableProperty;
private final long sleeptime;
final AtomicLong counter = new AtomicLong(-1);
final Random random = new Random(42);
public InputTask(int inputFrequency, DoubleProperty changeableProperty) {
this.changeableProperty = changeableProperty;
this.sleeptime = (long) ((1.0 / inputFrequency) * 1_000);
}
#Override
protected Void call() throws InterruptedException {
long count = 0 ;
while (!Thread.interrupted()) {
count++;
double newValue = random.nextDouble(); // input simulation
if (counter.getAndSet(count) == -1) {
Platform.runLater(() -> {
changeableProperty.setValue(newValue);
counter.getAndSet(-1);
});
}
Thread.sleep(sleeptime);
}
return null;
}
}
public static void main(String[] args) {
System.out.println(1.0/100*1000);
}
}

JavaFX 2 TableView how to update cell when object is changed

I'm creating a TableView to show information regarding a list of custom objects (EntityEvents).
The table view must have 2 columns.
First column to show the corresponding EntityEvent's name.
The second column would display a button. The button text deppends on a property of the EntityEvent. If the property is ZERO, it would be "Create", otherwise "Edit".
I managed to do it all just fine, except that I can't find a way to update the TableView line when the corresponding EntityEvent object is changed.
Very Important: I can't change the EntityEvent class to use JavaFX properties, since they are not under my control. This class uses PropertyChangeSupport to notify listeners when the monitored property is changed.
Note:
I realize that adding new elements to the List would PROBABLY cause the TableView to repaint itself, but that is not what I need. I say PROBABLY because I've read about some bugs that affect this behavior.
I tried using this approach to force the repaint, by I couldn't make it work.
Does anyone knows how to do it?
Thanks very much.
Here is a reduced code example that illustrates the scenario:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
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.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
//=============================================================================================
public class EntityEvent {
private String m_Name;
private PropertyChangeSupport m_NamePCS = new PropertyChangeSupport(this);
private int m_ActionCounter;
private PropertyChangeSupport m_ActionCounterPCS = new PropertyChangeSupport(this);
public EntityEvent(String name, int actionCounter) {
m_Name = name;
m_ActionCounter = actionCounter;
}
public String getName() {
return m_Name;
}
public void setName(String name) {
String lastName = m_Name;
m_Name = name;
System.out.println("Name changed: " + lastName + " -> " + m_Name);
m_NamePCS.firePropertyChange("Name", lastName, m_Name);
}
public void addNameChangeListener(PropertyChangeListener listener) {
m_NamePCS.addPropertyChangeListener(listener);
}
public int getActionCounter() {
return m_ActionCounter;
}
public void setActionCounter(int actionCounter) {
int lastActionCounter = m_ActionCounter;
m_ActionCounter = actionCounter;
System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
m_ActionCounterPCS.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
}
public void addActionCounterChangeListener(PropertyChangeListener listener) {
m_ActionCounterPCS.addPropertyChangeListener(listener);
}
}
//=============================================================================================
private class AddPersonCell extends TableCell<EntityEvent, String> {
Button m_Button = new Button("Undefined");
StackPane m_Padded = new StackPane();
AddPersonCell(final TableView<EntityEvent> table) {
m_Padded.setPadding(new Insets(3));
m_Padded.getChildren().add(m_Button);
m_Button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// Do something
}
});
}
#Override protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(m_Padded);
m_Button.setText(item);
}
}
}
//=============================================================================================
private ObservableList<EntityEvent> m_EventList;
//=============================================================================================
#SuppressWarnings("unchecked")
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Table View test.");
VBox container = new VBox();
m_EventList = FXCollections.observableArrayList(
new EntityEvent("Event 1", -1),
new EntityEvent("Event 2", 0),
new EntityEvent("Event 3", 1)
);
final TableView<EntityEvent> table = new TableView<EntityEvent>();
table.setItems(m_EventList);
TableColumn<EntityEvent, String> eventsColumn = new TableColumn<>("Events");
TableColumn<EntityEvent, String> actionCol = new TableColumn<>("Actions");
actionCol.setSortable(false);
eventsColumn.setCellValueFactory(new Callback<CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<EntityEvent, String> p) {
EntityEvent event = p.getValue();
event.addActionCounterChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent event) {
// TODO: I'd like to update the table cell information.
}
});
return new ReadOnlyStringWrapper(event.getName());
}
});
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<EntityEvent, String> ev) {
String text = "NONE";
if(ev.getValue() != null) {
text = (ev.getValue().getActionCounter() != 0) ? "Edit" : "Create";
}
return new ReadOnlyStringWrapper(text);
}
});
// create a cell value factory with an add button for each row in the table.
actionCol.setCellFactory(new Callback<TableColumn<EntityEvent, String>, TableCell<EntityEvent, String>>() {
#Override
public TableCell<EntityEvent, String> call(TableColumn<EntityEvent, String> personBooleanTableColumn) {
return new AddPersonCell(table);
}
});
table.getColumns().setAll(eventsColumn, actionCol);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
// Add Resources Button
Button btnInc = new Button("+");
btnInc.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ev) {
System.out.println("+ clicked.");
EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
if (entityEvent == null) {
System.out.println("No Event selected.");
return;
}
entityEvent.setActionCounter(entityEvent.getActionCounter() + 1);
// TODO: I expected the TableView to be updated since I modified the object.
}
});
// Add Resources Button
Button btnDec = new Button("-");
btnDec.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ev) {
System.out.println("- clicked.");
EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
if (entityEvent == null) {
System.out.println("No Event selected.");
return;
}
entityEvent.setActionCounter(entityEvent.getActionCounter() - 1);
// TODO: I expected the TableView to be updated since I modified the object.
}
});
container.getChildren().add(table);
container.getChildren().add(btnInc);
container.getChildren().add(btnDec);
Scene scene = new Scene(container, 300, 600, Color.WHITE);
primaryStage.setScene(scene);
primaryStage.show();
}
//=============================================================================================
public Main() {
}
//=============================================================================================
public static void main(String[] args) {
launch(Main.class, args);
}
}
Try the javafx.beans.property.adapter classes, particularly JavaBeanStringProperty and JavaBeanIntegerProperty. I haven't used these, but I think you can do something like
TableColumn<EntityEvent, Integer> actionCol = new TableColumn<>("Actions");
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, Integer> ev) {
return new JavaBeanIntegerPropertyBuilder().bean(ev.getValue()).name("actionCounter").build();
});
// ...
public class AddPersonCell extends TableCell<EntityEvent, Integer>() {
final Button button = new Button();
public AddPersonCell() {
setPadding(new Insets(3));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
button.setOnAction(...);
}
#Override
public void updateItem(Integer actionCounter, boolean empty) {
if (empty) {
setGraphic(null);
} else {
if (actionCounter.intValue()==0) {
button.setText("Create");
} else {
button.setText("Add");
}
setGraphic(button);
}
}
}
As I said, I haven't used the Java bean property adapter classes, but the idea is that they "translate" property change events to JavaFX change events. I just typed this in here without testing, but it should at least give you something to start with.
UPDATE: After a little experimenting, I don't think this approach will work if your EntityEvent is really set up the way you showed it in your code example. The standard Java beans bound properties pattern (which the JavaFX property adapters rely on) has a single property change listener and an addPropertyChangeListener(...) method. (The listeners can query the event to see which property changed.)
I think if you do
public class EntityEvent {
private String m_Name;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private int m_ActionCounter;
public EntityEvent(String name, int actionCounter) {
m_Name = name;
m_ActionCounter = actionCounter;
}
public String getName() {
return m_Name;
}
public void setName(String name) {
String lastName = m_Name;
m_Name = name;
System.out.println("Name changed: " + lastName + " -> " + m_Name);
pcs.firePropertyChange("name", lastName, m_Name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public int getActionCounter() {
return m_ActionCounter;
}
public void setActionCounter(int actionCounter) {
int lastActionCounter = m_ActionCounter;
m_ActionCounter = actionCounter;
System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
pcs.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
}
}
it will work with the adapter classes above. Obviously, if you have existing code calling the addActionChangeListener and addNameChangeListener methods you would want to keep those existing methods and the existing property change listeners, but I see no reason you can't have both.

LWUIT TextArea text formatting how to?

I work on a LWUIT project that aims to computerize an Arabic book . That means each page of the
mentioned book accessed by a specific button
returns
To do that I created a form , array of buttons, and a textarea.
The setText( ) method of textarea widget is used to involve each page of the book
How?
When a button pressed
the setText( ) changes it's string according to the content of the
required page
returns
At the end of the project a problem of formatting faces me .
The book page's contents (Strings ) are unformatted.
returns
to solve the problem I tried a LWUIT HtmlComponent instead of textArea in order to format using
html tags , but it takes much of memory
(at least it cost more than 700 kb for an application).
So I wouldn't be able include all the pages of the book by this way.
returns
This my first trial
import javax.microedition.midlet.*;
import com.sun.lwuit.events.*;
import javax.microedition.midlet.*;
import com.sun.lwuit.layouts.*;
import com.sun.lwuit.*;
public class Arabic_Lang extends MIDlet {
public void startApp()
{
com.sun.lwuit.Display.init(this);
final com.sun.lwuit.Form main_form = new com.sun.lwuit.Form();
final com.sun.lwuit.Form f = new com.sun.lwuit.Form();
final com.sun.lwuit.TextArea txt1 = new com.sun.lwuit.TextArea();
f.addComponent(txt1);
final com.sun.lwuit.Button l[]= new com.sun.lwuit.Button [3];
final com.sun.lwuit.Button inter = new com.sun.lwuit.Button("inter");
final com.sun.lwuit.Form jjj8 = new com.sun.lwuit.Form();
jjj8.setTitle( "اللغة العربية");
jjj8.getStyle().setBgColor(0x006699);
jjj8.setScrollableX(true);
int i;
for(i=0;i<3;i++)
{
l[i] =new com.sun.lwuit.Button();
l[i].getStyle().setBgColor(0xFFF66);
main_form.addComponent(l[i]);
main_form.setScrollable (true);
main_form.setScrollableX(false);
}
l[0].setText("");
l[0].getStyle().setBgColor(0xffff00);
l[0].setText("arabic");
l[1].setText("arabic");
l[0].addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae) {
txt1.setText(" \u0628 \u0639\u0644\u0649 \u0644\u063A\u062A");
}
});
l[1].addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae) {
txt1.setText(" \u0628 \u0639\u0644\u0649 \u0644\u063A\u062A");
f.show();
}
});
jjj8.addComponent(inter);
inter.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae) {
main_form.show();
}
}
);
jjj8.show();
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
returns
And this is my trial to use htmlComponent
returns
import com.sun.lwuit.layouts.*;
import javax.microedition.midlet.*;
public class HelloLWUITMidlet3 extends MIDlet
{
public void startApp()
{
com.sun.lwuit.Display.init(this);
final com.sun.lwuit.Form form = new com.sun.lwuit.Form("");
final com.sun.lwuit.html.HTMLComponent htmlC = new com.sun.lwuit.html.HTMLComponent( );
htmlC.setRTL(true);
htmlC.setBodyText("هذه لغة عربية","UTF-8" );
form.addComponent(htmlC);
BorderLayout bl = new BorderLayout();
form.setScrollable(true);
form.show( );
}
public void pauseApp()
{
}
public void destroyApp(boolean unconditional) {
}
}
Store the pages of the book as HTML files in your src dir (in the jar root) and load them directly into the HTMLComponent as is shown in the LWUITDemo.

Resources