Bidirecional binding in Javafx StringProperty is not working in inverse direction - binding

I have a classical JavaBean which will be bond with a JavaFX TextField.
public class Cell {
public static final String CELL_VALUE = "Cell.Value";
private Optional<Integer> value;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public Optional<Integer> getValue() {
return value;
}
public void setValue(Optional<Integer> value) {
Optional<Integer> old = this.value;
this.value = value;
this.pcs.firePropertyChange(CELL_VALUE, old, value);
}
/**
* The values must be from 1 to 9. 0 or null will be converted to Option.none.
*/
public void setValue(int value) {
this.setValue(Optional.of(value));
}
}
Also I created a StringConverter subclass:
import java.util.Optional;
import javafx.util.StringConverter;
public class CellValueStringConverter extends StringConverter<Optional<Integer>> {
#Override
public String toString(Optional<Integer> value) {
System.out.printf("toString() : %s%n", value);
return value.isPresent()? String.valueOf(value.get()): "";
}
#Override
public Optional<Integer> fromString(String string) {
System.out.printf("fromString() : %s%n", string);
if(string.matches("^[1-9]$")) {
return Optional.of(Integer.valueOf(string));
}
if(string.isEmpty() || string.matches("^( |0)$")) {
return Optional.empty();
}
throw new IllegalArgumentException("Illegal value for a Cell: " + string);
}
}
In the controller class, before the main stage become visible, I made the binding between Cell value and TextField:
ObjectProperty<Optional<Integer>> valueProperty = JavaBeanObjectPropertyBuilder.create().bean(cell)
.name("value").build();
final StringProperty textProperty = textField.textProperty();
// Binding ...
textProperty.bindBidirectional(valueProperty, new CellValueStringConverter());
textField.addEventFilter(MouseEvent.MOUSE_CLICKED, me -> {
if (me.getClickCount() == 2) {
cell.setValue(random.nextInt(8) + 1);
}
});
textProperty.addListener(
(ov, oldValue, newValue) -> System.out.printf("textProperty : %s -> %s%n", oldValue, newValue));
valueProperty.addListener(
(ov, oldValue, newValue) -> System.out.printf("valueProperty: %s -> %s%n", oldValue, newValue));
cell.addPropertyChangeListener(
evt -> System.out.printf("cell : %s -> %s%n", evt.getOldValue(), evt.getNewValue()));
When I start the application, I get the message toString() : Optional.empty. When I type a value (Let's say "4") in an empty TextField, these messages are printed:
fromString() : 4
cell : Optional.empty -> Optional[4]
valueProperty: Optional.empty -> Optional[4]
textProperty : -> 4
If I type "8" in this TextField I got this:
fromString() :
cell : Optional[4] -> Optional.empty
valueProperty: Optional[4] -> Optional.empty
textProperty : 4 ->
fromString() : 8
cell : Optional.empty -> Optional[8]
valueProperty: Optional.empty -> Optional[8]
textProperty : -> 8
Finally, if I type "0", the Cell Will become empty:
fromString() :
cell : Optional[8] -> Optional.empty
valueProperty: Optional[8] -> Optional.empty
textProperty : 8 ->
So far, so good. But I if double click over the TextField, instead replace the text, nothing happens. Suppose that the cell value (and the texfField) is 4. When I double click, I get this message:
cell : Optional[4] -> Optional[8]
However the textField continues showing "4". The message in CellValueStringConverter.toString() is not showed.
Supposedly, when I wrapped the cell value in a ObjectProperty (JavaBeanObjectPropertyBuilder.create().bean(cell).name("value").build()), it should observe all changes in the value property. But is is not occuring. What is missing here?
Thanks,
Rafael Afonso

I think that I found a answer or at least a workaround. I had to add a new PropertyChangeEvent to my Cell object to that when the Cell.value attibute is changed, the valueProperty is directly setted:
cell.addPropertyChangeListener(evt -> {
#SuppressWarnings("unchecked")
final Optional<Integer> newValue = (Optional<Integer>) evt.getNewValue();
valueProperty.set(newValue);
});
At least, the things started to work as expected. When I double ciclk over an empty TextField, I get these messages:
cell : Optional.empty -> Optional[2]
toString() : Optional[2]
textProperty : -> 2
valueProperty: Optional.empty -> Optional[2]
And the textField is filled as expected. However, I thought strange that valueProperty message is the last to be printed althought his setter had been the first thing to be called.
If someone have a better idea, it will be welcome.

Related

How to sort a CharmListView with a specific order?

Is it possible to sort a CharmListView in a specific order ?
The list contains the names of the BLE bluetooth devices in range. How to do a sort in the following way ? :
If the device contains a character string :1- then this device belongs to Category XYZ;
if the character string contains :2- then this device belongs to Category XXX. etc ...
When displaying the CharmListView we should see firstly Category XYZ , then Category ABC
In CharmListView example The sorting is done with this code snippet :
if(ascending){
charmListView.setHeaderComparator((d1,d2) -> d1.compareTo(d2));
charmListView.setComparator((s1,s2) -> Double.compare(s1.getDensity(), s2.getDensity()));
ascending = false;
} else {
charmListView.setHeaderComparator((d1,d2) -> d2.compareTo(d1));
charmListView.setComparator((s1,s2) -> Double.compare(s2.getDensity(), s1.getDensity()));
ascending = true;
}
I do not understand how charmListView.setHeaderComparator((d1,d2) -> d2.compareTo(d1)); and
CharmListView.setComparator((s1,s2) -> Double.compare(s2.getDensity(), s1.getDensity())); are working.
By default, the headers are sorted in natural order (alphabetically). How to not sort alphabetically but just with a specific order that I provide ?
Category XYZ should be on top
This is the source code of the class where the CharmListView is created.
package com.hacare.views;
import com.gluonhq.attach.ble.BleDevice;
import com.gluonhq.attach.ble.BleService;
import com.gluonhq.charm.glisten.application.MobileApplication;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.control.CharmListCell;
import com.gluonhq.charm.glisten.control.CharmListView;
import com.gluonhq.charm.glisten.control.ListTile;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import com.hacare.objects.MyBleDevice;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class PrimaryPresenter {
/**
* List of ble devices nearby
*/
ObservableList<BleDevice> ble_device_list = FXCollections.observableArrayList();
#FXML
private View primary;
#FXML
private CharmListView<BleDevice, String> ble_device_charmlistview;
/**
* Controller init function
*/
public void initialize() {
primary.showingProperty().addListener((obs, oldValue, newValue) -> {
if (newValue) {
AppBar appBar = MobileApplication.getInstance().getAppBar();
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
MobileApplication.getInstance().getDrawer().open()));
appBar.setTitleText("Primary");
appBar.getActionItems().add(MaterialDesignIcon.BLUETOOTH.button(e ->
buttonClick()));
}
});
ble_device_charmlistview.setHeadersFunction(MyBleDevice::getO_device_type);
ble_device_charmlistview.setComparator((o1, o2) -> {
if(o2.getName().contains(":1-")){
//test statement
return 1;
}
//test statement
return 0;
});
ble_device_charmlistview.setCellFactory(bleDeviceStringCharmListView -> new CharmListCell<BleDevice>(){
#Override
public void updateItem(BleDevice item, boolean empty) {
super.updateItem(item, empty);
if(item!=null && !empty){
ListTile tile = new ListTile();
tile.textProperty().add(item.getName());
Image image = new Image("/icon.png");
if(item.getName().contains("HAcare_Y4")){
image = new Image("imgs/y-type-y4_64x64.jpg");
}
else{
image = new Image("/icon.png");
}
tile.setPrimaryGraphic(new ImageView(image));
setText(null);
setGraphic(tile);
}
}
});
ble_device_charmlistview.selectedItemProperty().addListener((observableValue, bleDevice, t1) ->
{
System.out.println(ble_device_charmlistview.getSelectedItem().getAddress());
}
);
}
#FXML
void buttonClick() {
ble_device_list.clear();
//dummy devices
BleDevice device1 = new BleDevice();
device1.setAddress("AB:CD:EF:GH:IJ:KL");
device1.setName("HAcare_Y4:1-CW71");
BleDevice device2 = new BleDevice();
device2.setAddress("MN:CD:EZ:AH:FJ:CL");
device2.setName("dummy device");
BleDevice device3 = new BleDevice();
device3.setAddress("AK:CD:ED:FE:AJ:KL");
device3.setName("HAcare_Y4:1-AZ5K");
ble_device_list.add(device1);
ble_device_list.add(device2);
ble_device_list.add(device3);
ble_device_charmlistview.setItems(ble_device_list);
BleService.create().ifPresent(ble ->
ble.startScanningDevices().addListener((ListChangeListener.Change<? extends BleDevice> c) -> {
while (c.next()) {
if (c.wasAdded()) {
for (BleDevice device : c.getAddedSubList()) {
System.out.println("Device found: " + device.getName());
ble_device_list.add(device);
ble_device_charmlistview.setItems(ble_device_list);
}
}
}
}));
}
}

'is' statement with a variable type

I want to check, if my variable k has a type calles T.
My approach was
int k=1;
Type T=int;
if(k is T) print('same type');
But it is not working. It works, if I write
if(k is int)
but I want to change the type in a variable.
Thank you for an answer
You could store the type in a string, and then use runtimeType and toString() to compare the variable's type with the type stored in the string:
int k = 1;
String type = "int";
if (k.runtimeType.toString() == type){
print("k is an integer");
}
You can't do type checks using Type objects in Dart.
A Type object is not the type, it's just a token representing the type which can be used with the dart:mirrorsreflection library. It cannot, really, be used for anything else.
If you need to do type checking, you need to store the type as a type variable, which means you need something generic, or store it in plain code as a closure.
The closure approach is simpler, but less readable:
int k = 1;
var typeChecker = (o) => o is int;
if (typeChecker(o)) print("k has the right type");
Using a generic helper class is more general:
class Typer<T> {
bool isType(Object o) => o is T;
bool operator >=(Typer other) => other is Typer<T>;
bool operator <=(Typer other) => other >= this;
}
...
var k = 1;
var type = Typer<int>();
if (type.isType(k)) print("k is integer");
In short, don't use Type for anything except dart:mirrors because it isn't really useful for anything else.
Some Type in the Dart returns a different kind of Type when using .runtimeType.
For example:
void main() async {
List value = [];
print(value.runtimeType); // print -> JSArray<dynamic>
}
I am using:
void main() async {
List value = [];
print(isSameType(target: value, reference: <Object>[])); // print -> false
value = [Object()];
print(isSameType(target: value, reference: <Object>[])); // print -> false
value = <Object>[];
print(isSameType(target: value, reference: <Object>[])); // print -> true
}
bool isSameType({required target, required reference}) =>
target.runtimeType == reference.runtimeType;
class Object {}
But I saw many comments saying the .runtimeType is for debugging and some comments said it will be not available in the future. So I am using this instead of the code above:
void main() async {
var value;
value = [];
print(value.runtimeType); // print -> JSArray<dynamic>
print(isSameType<List>(value)); // print -> true
value = [Test];
print(value.runtimeType); // print -> JSArray<Type>
print(isSameType<List<Test>>(value)); // print -> false
print(isSameType<List>(value)); // print -> true
value = [Test()];
print(value.runtimeType); // print -> JSArray<Test>
print(isSameType<List<Test>>(value)); // print -> true
print(isSameType<List>(value)); // print -> true
value = <Test>[];
print(value.runtimeType); // print -> JSArray<Test>
print(isSameType<List<Test>>(value)); // print -> true
print(isSameType<List>(value)); // print -> true
}
bool isSameType<type>(target) => target is type;
class Test {}
Basic example for using:
void main() async {
MyObject phoneNumber = MyObject<int>();
phoneNumber = await getDataFromUser();
if (phoneNumber.isSameType()) await uploadData(phoneNumber);
}
class MyObject<type> {
MyObject({this.data});
dynamic data;
bool isSameType() => data is type;
}
Future<dynamic> getDataFromUser() async {
return null;
}
Future<bool> uploadData(data) async {
return false;
}

How to convert a GLib.Value of type GStrv (string[]) to a GLib.Variant

In the following example one class property is of type Gstrv.
With ObjectClass.list_properties() one can query the Paramspec of all properties, and with get_property() all properties can be requested as GLib.Value. How would I access the Value of type GStrv and convert it to a GLib.Variant?
My GLib version is slightly outdated, so I do not have the GLib.Value.to_variant() function available yet :( .
public class Foo: GLib.Object {
public GLib.HashTable<string, int32> bar;
public Foo() {
bar = new GLib.HashTable<string, int32>(str_hash, str_equal);
}
public string[] bar_keys { owned get { return bar.get_keys_as_array(); } }
}
int main() {
var foo = new Foo();
Type type = foo.get_type();
ObjectClass ocl = (ObjectClass) type.class_ref ();
foreach (ParamSpec spec in ocl.list_properties ()) {
print ("%s\n", spec.get_name ());
Value property_value = Value(spec.value_type);
print ("%s\n", property_value.type_name ());
foo.get_property(spec.name, ref property_value);
// next: convert GLib.Value -> GLib.Variant :(
}
foo.bar.set("baz", 42);
return 0;
}
Output:
bar-keys
GStrv
Using GLib.Value.get_boxed() seems to be working.
Example:
// compile simply with: valac valacode.vala
public class Foo: GLib.Object {
public GLib.HashTable<string, int32> bar;
public Foo() {
bar = new GLib.HashTable<string, int32>(str_hash, str_equal);
}
public string[] bar_keys { owned get { return bar.get_keys_as_array(); } }
}
public Variant first_gstrv_property_as_variant(Object obj)
{
Type class_type = obj.get_type();
ObjectClass ocl = (ObjectClass) class_type.class_ref ();
foreach (ParamSpec spec in ocl.list_properties ()) {
print ("%s\n", spec.get_name ());
Value property_value = Value(spec.value_type);
print ("%s\n", property_value.type_name ());
obj.get_property(spec.name, ref property_value);
// next: convert GLib.Value -> GLib.Variant
if(property_value.type_name () == "GStrv") {
return new GLib.Variant.strv((string[])property_value.get_boxed());
}
}
return new GLib.Variant("s", "No property of type GStrv found");
}
int main() {
var foo = new Foo();
print("%s\n", first_gstrv_property_as_variant(foo).print(true));
foo.bar.set("baz", 42);
print("%s\n", first_gstrv_property_as_variant(foo).print(true));
foo.bar.set("zot", 3);
print("%s\n", first_gstrv_property_as_variant(foo).print(true));
return 0;
}
Output:
bar-keys
GStrv
#as []
bar-keys
GStrv
['baz']
bar-keys
GStrv
['baz', 'zot']
In the generated c-code this looks as follows:
_tmp18_ = g_value_get_boxed (&property_value);
_tmp19_ = g_variant_new_strv ((gchar**) _tmp18_, -1);
Passing -1 as length to g_variant_new_strv() means the string array is considered as null terminated. Inside g_variant_new_strv() the g_strv_length() function is used to determine the length.
Hopefully it will be useful to someone else someday. :-)

How do I bind Character to a TextField?

I've found an example of how to bind Integer to a TextField:
Binder<Person> b = new Binder<>();
b.forField(ageField)
.withNullRepresentation("")
.withConverter(new StringToIntegerConverter("Must be valid integer !"))
.withValidator(integer -> integer > 0, "Age must be positive")
.bind(p -> p.getAge(), (p, i) -> p.setAge(i));
The problem is - there is no StringToCharacterConverter and if have an error if I bind fields as is. The error is:
Property type 'java.lang.Character' doesn't match the field type 'java.lang.String'. Binding should be configured manually using converter.
You need to implement custom converter, here is very simplified version of what could be StringToCharacterConverter for getting the pattern what the they look like:
public class StringToCharacterConverter implements Converter<String,Character> {
#Override
public Result<Character> convertToModel(String value, ValueContext context) {
if (value == null) {
return Result.ok(null);
}
value = value.trim();
if (value.isEmpty()) {
return Result.ok(null);
} else if (value.length() == 1) {
Character character = value.charAt(0);
return Result.ok(character);
} else {
return Result.error("Error message here");
}
}
#Override
public String convertToPresentation(Character value, ValueContext context) {
String string = value.toString();
return string;
}
}

tableView Filter , no input in the table

The goal is to filter the tableView .So, when I input something to filter it works just fine , but then when I hit backspace or remove the inputs on the textField Area to go back,the tableView shows no content in the table ,Then I have to restart the program to reload and show the data.The data is saved and loaded from an xml file.
Also, I put an ObservaleList of contacts on the Data Class to load and store the contactList ,But on the Controller I have a similar list for the filter then the controller extends the data class to getContacts from there and add the filtredList to it. I am pretty much sure that the problem comes from that
public Data() {
contacts = FXCollections.observableArrayList();
}
public ObservableList<Contact> getContacts() {
return contacts;
}
This is below the filter handle
public void filterContactList(String oldValue, String newValue) {
ObservableList<Contact> filteredList = FXCollections.observableArrayList();
if (filterInput == null || newValue.length() < oldValue.length() || newValue == null){
contactsTable.setItems(getContacts());
}else {
newValue = newValue.toUpperCase();
for (Contact contact: contactsTable.getItems()){
String filterFirstName = contact.getFirstName();
String filterLastName = contact.getFirstName();
if (filterFirstName.toUpperCase().contains(newValue) || filterLastName.toUpperCase().contains(newValue)){
filteredList.add(contact);
}
}
contactsTable.setItems(filteredList);
}
}
and this is the init listener
filterInput.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
filterContactList((String) oldValue, (String) newValue);
}
});
Do yourself a favor an use FilteredList for this:
private FilteredList<Contact> filteredContacts = new FilteredList<>(getContacts());
...
contactsTable.setItems(filteredList);
...
public void filterContactList(String oldValue, String newValue) {
if (newValue == null) {
filteredContacts.setPredicate(null);
} else {
final String lower = newValue.toLowerCase();
filteredContacts.setPredicte(contact -> contact.getFirstName().toLowerCase().contains(lower) || contact.getLastName().toLowerCase().contains(lower));
}
}
BTW: since this is called from a listener to filterInput's textProperty, I removed the check for null.

Resources