JavaFX: Create/Parse Custom Combined Elements From FXML File and Skin Objects












0















While poking around and looking into how the "HTMLEditor" control object of JavaFX works I found out about how it is essentially the base for a more complex "Skin" that in itself is like a combination of more nodes. All the buttons, choice boxes and editor itself, etc, is combined in the larger Class "HTMLEditor". I have some working code that cuts down the effort by an estimated 80% but I also have a history of not knowing the normal way and creating far too complex workarounds. In short: I can do what I want right now, but I'm probably just making my work hard and buggy again.



That is a very helpful and nice thing to know. Is there a way I can create a custom class similar to this (where I ceate a custom class defining its role and functions with a custom skin defining its functionality) and then have the FXML API aknowledge and parse it automatically? I don't like having the FXML file and its contents but then having to manually add Nodes in the code. It seems confusing (at least to me).



For example, creating a combination of a TableView (with N columns each of a specific type) and have it automatically create TextFields, ChoiceBoxes, etc corresponding to each column and Buttons to Add, Copy, Modify and Remove elements to that TableView. I've had to do this multiple times and I'm currently in the process of creating utilities for it (which is how I found out about the skins of larger control objects).



The best I currently have is creating the TableView, its column objects, input controls and buttons in the FXML file and then having them linked by a method.



The best idea I'm thinking about at the moment is having a special Node like in the case above "EditableTable" and before FXML actually goes and tries to parse it (and fails because it does not know the Class) replace it with the extended, underlying controls. That would, however, still require a lot of work giving it custom IDs and in retrospect connecting all the FXML objects.



I'll paste the code as I currently have, but it's rather lengthy for what could probably be done much easier, but this is currently the best I got and it works for now. Advise is appreciated but not required, I'd just like to know if I'm programming complex workarounds because I don't know the easy way everybody uses once again.



package utils.fxmlUtils;

//T is a reference to the Class extending Bindeable. Required for functions. Probably a bit of a dirty trick but no idea how else
public abstract class Bindeable<T extends Bindeable<T>> implements Cloneable
{
//Creates the T. Required to call from Bindeable
public abstract T createFrom(String elements);

//Gets all the Column data as String
public abstract String getData();

//Sets all the Column data as String
public abstract void setData(String elements);

//Clones itself
public T clone()
{
return createFrom(getData());
}
}

package application;

import utils.fxmlUtils.Bindeable;

//Example for Bindeable. All this is is a large collection of Strings
public class Property extends Bindeable<Property>
{
private String tag;
private String propertyName;
private String propertyModifier;
private String modifierType;
private String numberType;

public Property(String tag, String propertyName, String propertyModifier, String modifierType, String numberType)
{
this.tag = tag;
this.propertyName = propertyName;
this.propertyModifier = propertyModifier;
this.modifierType = modifierType;
this.numberType = numberType;
}

//MUST HAVE a constructor using only String in order to allow the TableEditBinder to create the objects by invoking this constructor. Probably a wonky way to do it but no idea how else to do
public Property(String elements)
{
this(elements[0], elements[1], elements[2], elements[3], elements[4]);
}

public String getTag()
{
return tag;
}

public void setTag(String tags)
{
this.tag = tags;
}

public String getPropertyName()
{
return propertyName;
}

public void setPropertyName(String propertyName)
{
this.propertyName = propertyName;
}

public String getPropertyModifier()
{
return propertyModifier;
}

public void setPropertyModifier(String propertyModifier)
{
this.propertyModifier = propertyModifier;
}

public String getModifierType()
{
return modifierType;
}

public void setModifierType(String modifierType)
{
this.modifierType = modifierType;
}

public String getNumberType()
{
if(modifierType.equals("Add Base Value") || modifierType.equals("Multiply Value"))
{
return numberType;
} else
{
return "";
}
}

public void setNumberType(String numberType)
{
this.numberType = numberType;
}

public Property clone()
{
return new Property(tag, propertyName, propertyModifier, modifierType, numberType);
}

@Override
public Property createFrom(String elements)
{
return new Property(elements);
}

@Override
public String getData()
{
return new String {tag, propertyName, propertyModifier, modifierType, numberType};
}

@Override
public void setData(String elements)
{
tag = elements[0];
propertyName = elements[1];
propertyModifier = elements[2];
modifierType = elements[3];
numberType = elements[4];
}
}

package utils.fxmlUtils;

import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.paint.Color;

//"Library" of Lighting effects for nodes. Identifies them as (in)valid, potentially invalid and currently being tested, as well as neutral and inactive (off)
public interface LightingIdentifiers
{
public static Lighting correctOn = new Lighting();
public static Lighting incorrectOn = new Lighting();
public static Lighting inProgressOn = new Lighting();
public static Lighting warningOn = new Lighting();
public static Lighting correctOff = new Lighting();
public static Lighting incorrectOff = new Lighting();
public static Lighting inProgressOff = new Lighting();
public static Lighting warningOff = new Lighting();
public static Lighting off = new Lighting();
public static Lighting neutral = new Lighting();

//Initializes and creates all the various lightings. Must be executed before using them to work.
public static void initialize()
{
correctOn.setDiffuseConstant(2);
correctOn.setSpecularConstant(2);
correctOn.setSpecularExponent(40);
correctOn.setSurfaceScale(0);
Light l = new Light.Distant();
l.setColor(new Color(0.5, 1, 0.5, 1));
correctOn.setLight(l);
incorrectOn.setDiffuseConstant(2);
incorrectOn.setSpecularConstant(2);
incorrectOn.setSpecularExponent(40);
incorrectOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 0.5, 0.5, 1));
incorrectOn.setLight(l);
inProgressOn.setDiffuseConstant(2);
inProgressOn.setSpecularConstant(2);
inProgressOn.setSpecularExponent(40);
inProgressOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.5, 1, 1));
inProgressOn.setLight(l);
warningOn.setDiffuseConstant(2);
warningOn.setSpecularConstant(2);
warningOn.setSpecularExponent(40);
warningOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 1, 0.5, 1));
warningOn.setLight(l);
correctOff.setDiffuseConstant(2);
correctOff.setSpecularConstant(2);
correctOff.setSpecularExponent(40);
correctOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.5, 0.25, 1));
correctOff.setLight(l);
incorrectOff.setDiffuseConstant(2);
incorrectOff.setSpecularConstant(2);
incorrectOff.setSpecularExponent(40);
incorrectOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.25, 0.25, 1));
incorrectOff.setLight(l);
inProgressOff.setDiffuseConstant(2);
inProgressOff.setSpecularConstant(2);
inProgressOff.setSpecularExponent(40);
inProgressOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
inProgressOff.setLight(l);
warningOff.setDiffuseConstant(2);
warningOff.setSpecularConstant(2);
warningOff.setSpecularExponent(40);
warningOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.5, 1));
warningOff.setLight(l);
off.setDiffuseConstant(2);
off.setSpecularConstant(2);
off.setSpecularExponent(40);
off.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
off.setLight(l);
neutral.setLight(null);
}
}

package utils.fxmlUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Control;
import javafx.scene.control.PasswordField;
import javafx.scene.control.Spinner;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import utils.ArrayUtils;

public interface TableEditBinder
{
/*
* Initiates the process of binding a TableView and its Bindeable row objects to the create, copy, modify and delete Buttons as well as Controls representing the input fields.
* Edit fields must correspond to the various TableRows. Number of TableRows and Controls must be equal. Column N must respond to input field N (== they must be in the right order).
* Currently implemented: TextField
* TextArea
* PasswordField
* ChoiceBox<String>
* Spinner<Integer>
* Spinner<Double>
* Spinner<String>
* TODO: Implement more
*/
public static void bindTableView(TableView<? extends Bindeable<?>> table, Class<? extends Bindeable<?>> tableType, Button create, Button copy, Button modify, Button delete, Control... editFields)
{
//Create TableView ChangeListener
ChangeListener<Bindeable<?>> bl = new ChangeListener<Bindeable<?>>()
{
//Sub-Listeners for the various Controls
private ChangeListener<?> subListeners;

//changed method. If no sublisteners exist creates them. After that if sublisteners exist and a non-null new value exists fills the input Controls
@SuppressWarnings("unchecked")
@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
if (subListeners == null)
{
try
{
connect(newValue);
} catch (Exception e)
{
e.printStackTrace();
}
}
if (subListeners != null)
{
if (newValue == null)
{
newValue = table.getSelectionModel().getSelectedItem();
}
if (newValue != null)
{
for (int i = 0; i < subListeners.length; i++)
{
((ChangeListener<Bindeable<?>>) subListeners[i]).changed(observable, oldValue, newValue);
}
}
}
}

//Connection method. Requires a non-null object of the class. Only executed if the connector is non-null
@SuppressWarnings({ "unchecked", "rawtypes" })
public void connect(Bindeable<?> connector) throws Exception
{
//checks if all conditions work out. Throws an execption if TableView and edit fields are of different numbers
if (connector == null)
{
return;
}
String methods = new String[table.getColumns().size()];
for (int i = 0; i < methods.length; i++)
{
methods[i] = ((PropertyValueFactory) table.getColumns().get(i).getCellValueFactory()).getProperty();
}
if (methods.length != editFields.length || editFields.length != table.getColumns().size())
{
throw new Exception("Error: Method length != Editable length");
}
//creates the sub-listeners and binds them to the respective Controls
subListeners = new ChangeListener<?>[methods.length];
for (int i = 0; i < editFields.length; i++)
{
if (editFields[i].getClass().equals(TextField.class))
{
subListeners[i] = bind((TextField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(TextArea.class))
{
subListeners[i] = bind((TextArea) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(PasswordField.class))
{
subListeners[i] = bind((PasswordField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(ChoiceBox.class))
{
subListeners[i] = bind((ChoiceBox<String>) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(Spinner.class))
{
try
{
subListeners[i] = bindIntSpinner((Spinner<Integer>) editFields[i], connector, methods[i]);
} catch (Exception e)
{
try
{
subListeners[i] = bindDoubleSpinner((Spinner<Double>) editFields[i], connector, methods[i]);
} catch (Exception f)
{
subListeners[i] = bindStringSpinner((Spinner<String>) editFields[i], connector, methods[i]);
}
}
}
}
}
};
//Adding listeners to TableView and Buttons
table.getSelectionModel().selectedItemProperty().addListener(bl);
create.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
create((TableView<Bindeable<?>>) table, editFields, tableType);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
copy.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
copy((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
modify.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
modify((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table), editFields);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
delete.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
remove((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
}

//Copy functionality
public static void copy(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
Bindeable<?> next = null;
for (int i = 0; i < items.length; i++)
{
next = items[i].clone();
table.getItems().add(next);
table.getSelectionModel().select(next);
}
table.refresh();
}

//Create functionality
public static void create(TableView<Bindeable<?>> table, Control inputs, Class<? extends Bindeable<?>> tableObjects)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
{
String base = itemize(inputs);
if(base == null)
{
return;
}
Bindeable<?> next = null;
table.getSelectionModel().clearSelection();
next = tableObjects.getConstructor(String.class).newInstance(new Object {base});
table.getItems().add(next);
table.getSelectionModel().select(next);
table.refresh();
}

//Takes all the input data from the controls as String values and returns them
@SuppressWarnings("unchecked")
public static String itemize(Control inputs)
{
String base = new String[inputs.length];
for (int i = 0; i < inputs.length; i++)
{
if (inputs[i].getEffect().equals(LightingIdentifiers.incorrectOn))
{
return null;
}
if (inputs[i].getClass().equals(TextField.class))
{
base[i] = ((TextField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(TextArea.class))
{
base[i] = ((TextArea) inputs[i]).getText();
} else if (inputs[i].getClass().equals(PasswordField.class))
{
base[i] = ((PasswordField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(ChoiceBox.class))
{
base[i] = ((ChoiceBox<String>) inputs[i]).getSelectionModel().getSelectedItem();
} else if (inputs[i].getClass().equals(Spinner.class))
{
try
{
base[i] = ((Spinner<Integer>) inputs[i]).getValue() + "";
} catch (Exception e)
{
try
{
base[i] = ((Spinner<Double>) inputs[i]).getValue() + "";
} catch (Exception f)
{
base[i] = ((Spinner<String>) inputs[i]).getValue() + "";
}
}
}
}
return base;
}

//Modify functionality
public static void modify(TableView<Bindeable<?>> table, Bindeable<?> items, Control inputs)
{
String itemized = itemize(inputs);
if(itemized == null)
{
return;
}
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
items[i].setData(itemized);
table.getSelectionModel().select((Bindeable<?>) items[i]);
}
table.refresh();
}

//Remove functionality
public static void remove(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
table.getItems().remove(items[i]);
}
table.refresh();
}

//Returns all selected items in the TableView
public static Bindeable<?> getAllSelected(TableView<Bindeable<?>> table)
{
return table.getSelectionModel().getSelectedItems().toArray(new Bindeable<?>[table.getSelectionModel().getSelectedItems().size()]);
}

//Binds a TextField
public static ChangeListener<Bindeable<?>> bind(TextField textField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a TextArea
public static ChangeListener<Bindeable<?>> bind(TextArea textArea, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textArea.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a PasswordField
public static ChangeListener<Bindeable<?>> bind(PasswordField passwordField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
passwordField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a ChoiceBox<String>
public static ChangeListener<Bindeable<?>> bind(ChoiceBox<String> choiceBox, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
choiceBox.getSelectionModel().select((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Integer>
public static ChangeListener<Bindeable<?>> bindIntSpinner(Spinner<Integer> integerSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
integerSpinner.getValueFactory().setValue(Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Double>
public static ChangeListener<Bindeable<?>> bindDoubleSpinner(Spinner<Double> doubleSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
try
{
doubleSpinner.getValueFactory().setValue((double) Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
doubleSpinner.getValueFactory().setValue(Double.parseDouble((String) m.invoke(newValue)));
}
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<String>
public static ChangeListener<Bindeable<?>> bindStringSpinner(Spinner<String> stringSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
stringSpinner.getValueFactory().setValue((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}
}









share|improve this question























  • TL; DR; see How do I ask a good question?, in particular "Pretend you're talking to a busy colleague "

    – c0der
    Nov 24 '18 at 5:05


















0















While poking around and looking into how the "HTMLEditor" control object of JavaFX works I found out about how it is essentially the base for a more complex "Skin" that in itself is like a combination of more nodes. All the buttons, choice boxes and editor itself, etc, is combined in the larger Class "HTMLEditor". I have some working code that cuts down the effort by an estimated 80% but I also have a history of not knowing the normal way and creating far too complex workarounds. In short: I can do what I want right now, but I'm probably just making my work hard and buggy again.



That is a very helpful and nice thing to know. Is there a way I can create a custom class similar to this (where I ceate a custom class defining its role and functions with a custom skin defining its functionality) and then have the FXML API aknowledge and parse it automatically? I don't like having the FXML file and its contents but then having to manually add Nodes in the code. It seems confusing (at least to me).



For example, creating a combination of a TableView (with N columns each of a specific type) and have it automatically create TextFields, ChoiceBoxes, etc corresponding to each column and Buttons to Add, Copy, Modify and Remove elements to that TableView. I've had to do this multiple times and I'm currently in the process of creating utilities for it (which is how I found out about the skins of larger control objects).



The best I currently have is creating the TableView, its column objects, input controls and buttons in the FXML file and then having them linked by a method.



The best idea I'm thinking about at the moment is having a special Node like in the case above "EditableTable" and before FXML actually goes and tries to parse it (and fails because it does not know the Class) replace it with the extended, underlying controls. That would, however, still require a lot of work giving it custom IDs and in retrospect connecting all the FXML objects.



I'll paste the code as I currently have, but it's rather lengthy for what could probably be done much easier, but this is currently the best I got and it works for now. Advise is appreciated but not required, I'd just like to know if I'm programming complex workarounds because I don't know the easy way everybody uses once again.



package utils.fxmlUtils;

//T is a reference to the Class extending Bindeable. Required for functions. Probably a bit of a dirty trick but no idea how else
public abstract class Bindeable<T extends Bindeable<T>> implements Cloneable
{
//Creates the T. Required to call from Bindeable
public abstract T createFrom(String elements);

//Gets all the Column data as String
public abstract String getData();

//Sets all the Column data as String
public abstract void setData(String elements);

//Clones itself
public T clone()
{
return createFrom(getData());
}
}

package application;

import utils.fxmlUtils.Bindeable;

//Example for Bindeable. All this is is a large collection of Strings
public class Property extends Bindeable<Property>
{
private String tag;
private String propertyName;
private String propertyModifier;
private String modifierType;
private String numberType;

public Property(String tag, String propertyName, String propertyModifier, String modifierType, String numberType)
{
this.tag = tag;
this.propertyName = propertyName;
this.propertyModifier = propertyModifier;
this.modifierType = modifierType;
this.numberType = numberType;
}

//MUST HAVE a constructor using only String in order to allow the TableEditBinder to create the objects by invoking this constructor. Probably a wonky way to do it but no idea how else to do
public Property(String elements)
{
this(elements[0], elements[1], elements[2], elements[3], elements[4]);
}

public String getTag()
{
return tag;
}

public void setTag(String tags)
{
this.tag = tags;
}

public String getPropertyName()
{
return propertyName;
}

public void setPropertyName(String propertyName)
{
this.propertyName = propertyName;
}

public String getPropertyModifier()
{
return propertyModifier;
}

public void setPropertyModifier(String propertyModifier)
{
this.propertyModifier = propertyModifier;
}

public String getModifierType()
{
return modifierType;
}

public void setModifierType(String modifierType)
{
this.modifierType = modifierType;
}

public String getNumberType()
{
if(modifierType.equals("Add Base Value") || modifierType.equals("Multiply Value"))
{
return numberType;
} else
{
return "";
}
}

public void setNumberType(String numberType)
{
this.numberType = numberType;
}

public Property clone()
{
return new Property(tag, propertyName, propertyModifier, modifierType, numberType);
}

@Override
public Property createFrom(String elements)
{
return new Property(elements);
}

@Override
public String getData()
{
return new String {tag, propertyName, propertyModifier, modifierType, numberType};
}

@Override
public void setData(String elements)
{
tag = elements[0];
propertyName = elements[1];
propertyModifier = elements[2];
modifierType = elements[3];
numberType = elements[4];
}
}

package utils.fxmlUtils;

import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.paint.Color;

//"Library" of Lighting effects for nodes. Identifies them as (in)valid, potentially invalid and currently being tested, as well as neutral and inactive (off)
public interface LightingIdentifiers
{
public static Lighting correctOn = new Lighting();
public static Lighting incorrectOn = new Lighting();
public static Lighting inProgressOn = new Lighting();
public static Lighting warningOn = new Lighting();
public static Lighting correctOff = new Lighting();
public static Lighting incorrectOff = new Lighting();
public static Lighting inProgressOff = new Lighting();
public static Lighting warningOff = new Lighting();
public static Lighting off = new Lighting();
public static Lighting neutral = new Lighting();

//Initializes and creates all the various lightings. Must be executed before using them to work.
public static void initialize()
{
correctOn.setDiffuseConstant(2);
correctOn.setSpecularConstant(2);
correctOn.setSpecularExponent(40);
correctOn.setSurfaceScale(0);
Light l = new Light.Distant();
l.setColor(new Color(0.5, 1, 0.5, 1));
correctOn.setLight(l);
incorrectOn.setDiffuseConstant(2);
incorrectOn.setSpecularConstant(2);
incorrectOn.setSpecularExponent(40);
incorrectOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 0.5, 0.5, 1));
incorrectOn.setLight(l);
inProgressOn.setDiffuseConstant(2);
inProgressOn.setSpecularConstant(2);
inProgressOn.setSpecularExponent(40);
inProgressOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.5, 1, 1));
inProgressOn.setLight(l);
warningOn.setDiffuseConstant(2);
warningOn.setSpecularConstant(2);
warningOn.setSpecularExponent(40);
warningOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 1, 0.5, 1));
warningOn.setLight(l);
correctOff.setDiffuseConstant(2);
correctOff.setSpecularConstant(2);
correctOff.setSpecularExponent(40);
correctOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.5, 0.25, 1));
correctOff.setLight(l);
incorrectOff.setDiffuseConstant(2);
incorrectOff.setSpecularConstant(2);
incorrectOff.setSpecularExponent(40);
incorrectOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.25, 0.25, 1));
incorrectOff.setLight(l);
inProgressOff.setDiffuseConstant(2);
inProgressOff.setSpecularConstant(2);
inProgressOff.setSpecularExponent(40);
inProgressOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
inProgressOff.setLight(l);
warningOff.setDiffuseConstant(2);
warningOff.setSpecularConstant(2);
warningOff.setSpecularExponent(40);
warningOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.5, 1));
warningOff.setLight(l);
off.setDiffuseConstant(2);
off.setSpecularConstant(2);
off.setSpecularExponent(40);
off.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
off.setLight(l);
neutral.setLight(null);
}
}

package utils.fxmlUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Control;
import javafx.scene.control.PasswordField;
import javafx.scene.control.Spinner;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import utils.ArrayUtils;

public interface TableEditBinder
{
/*
* Initiates the process of binding a TableView and its Bindeable row objects to the create, copy, modify and delete Buttons as well as Controls representing the input fields.
* Edit fields must correspond to the various TableRows. Number of TableRows and Controls must be equal. Column N must respond to input field N (== they must be in the right order).
* Currently implemented: TextField
* TextArea
* PasswordField
* ChoiceBox<String>
* Spinner<Integer>
* Spinner<Double>
* Spinner<String>
* TODO: Implement more
*/
public static void bindTableView(TableView<? extends Bindeable<?>> table, Class<? extends Bindeable<?>> tableType, Button create, Button copy, Button modify, Button delete, Control... editFields)
{
//Create TableView ChangeListener
ChangeListener<Bindeable<?>> bl = new ChangeListener<Bindeable<?>>()
{
//Sub-Listeners for the various Controls
private ChangeListener<?> subListeners;

//changed method. If no sublisteners exist creates them. After that if sublisteners exist and a non-null new value exists fills the input Controls
@SuppressWarnings("unchecked")
@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
if (subListeners == null)
{
try
{
connect(newValue);
} catch (Exception e)
{
e.printStackTrace();
}
}
if (subListeners != null)
{
if (newValue == null)
{
newValue = table.getSelectionModel().getSelectedItem();
}
if (newValue != null)
{
for (int i = 0; i < subListeners.length; i++)
{
((ChangeListener<Bindeable<?>>) subListeners[i]).changed(observable, oldValue, newValue);
}
}
}
}

//Connection method. Requires a non-null object of the class. Only executed if the connector is non-null
@SuppressWarnings({ "unchecked", "rawtypes" })
public void connect(Bindeable<?> connector) throws Exception
{
//checks if all conditions work out. Throws an execption if TableView and edit fields are of different numbers
if (connector == null)
{
return;
}
String methods = new String[table.getColumns().size()];
for (int i = 0; i < methods.length; i++)
{
methods[i] = ((PropertyValueFactory) table.getColumns().get(i).getCellValueFactory()).getProperty();
}
if (methods.length != editFields.length || editFields.length != table.getColumns().size())
{
throw new Exception("Error: Method length != Editable length");
}
//creates the sub-listeners and binds them to the respective Controls
subListeners = new ChangeListener<?>[methods.length];
for (int i = 0; i < editFields.length; i++)
{
if (editFields[i].getClass().equals(TextField.class))
{
subListeners[i] = bind((TextField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(TextArea.class))
{
subListeners[i] = bind((TextArea) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(PasswordField.class))
{
subListeners[i] = bind((PasswordField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(ChoiceBox.class))
{
subListeners[i] = bind((ChoiceBox<String>) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(Spinner.class))
{
try
{
subListeners[i] = bindIntSpinner((Spinner<Integer>) editFields[i], connector, methods[i]);
} catch (Exception e)
{
try
{
subListeners[i] = bindDoubleSpinner((Spinner<Double>) editFields[i], connector, methods[i]);
} catch (Exception f)
{
subListeners[i] = bindStringSpinner((Spinner<String>) editFields[i], connector, methods[i]);
}
}
}
}
}
};
//Adding listeners to TableView and Buttons
table.getSelectionModel().selectedItemProperty().addListener(bl);
create.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
create((TableView<Bindeable<?>>) table, editFields, tableType);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
copy.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
copy((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
modify.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
modify((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table), editFields);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
delete.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
remove((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
}

//Copy functionality
public static void copy(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
Bindeable<?> next = null;
for (int i = 0; i < items.length; i++)
{
next = items[i].clone();
table.getItems().add(next);
table.getSelectionModel().select(next);
}
table.refresh();
}

//Create functionality
public static void create(TableView<Bindeable<?>> table, Control inputs, Class<? extends Bindeable<?>> tableObjects)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
{
String base = itemize(inputs);
if(base == null)
{
return;
}
Bindeable<?> next = null;
table.getSelectionModel().clearSelection();
next = tableObjects.getConstructor(String.class).newInstance(new Object {base});
table.getItems().add(next);
table.getSelectionModel().select(next);
table.refresh();
}

//Takes all the input data from the controls as String values and returns them
@SuppressWarnings("unchecked")
public static String itemize(Control inputs)
{
String base = new String[inputs.length];
for (int i = 0; i < inputs.length; i++)
{
if (inputs[i].getEffect().equals(LightingIdentifiers.incorrectOn))
{
return null;
}
if (inputs[i].getClass().equals(TextField.class))
{
base[i] = ((TextField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(TextArea.class))
{
base[i] = ((TextArea) inputs[i]).getText();
} else if (inputs[i].getClass().equals(PasswordField.class))
{
base[i] = ((PasswordField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(ChoiceBox.class))
{
base[i] = ((ChoiceBox<String>) inputs[i]).getSelectionModel().getSelectedItem();
} else if (inputs[i].getClass().equals(Spinner.class))
{
try
{
base[i] = ((Spinner<Integer>) inputs[i]).getValue() + "";
} catch (Exception e)
{
try
{
base[i] = ((Spinner<Double>) inputs[i]).getValue() + "";
} catch (Exception f)
{
base[i] = ((Spinner<String>) inputs[i]).getValue() + "";
}
}
}
}
return base;
}

//Modify functionality
public static void modify(TableView<Bindeable<?>> table, Bindeable<?> items, Control inputs)
{
String itemized = itemize(inputs);
if(itemized == null)
{
return;
}
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
items[i].setData(itemized);
table.getSelectionModel().select((Bindeable<?>) items[i]);
}
table.refresh();
}

//Remove functionality
public static void remove(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
table.getItems().remove(items[i]);
}
table.refresh();
}

//Returns all selected items in the TableView
public static Bindeable<?> getAllSelected(TableView<Bindeable<?>> table)
{
return table.getSelectionModel().getSelectedItems().toArray(new Bindeable<?>[table.getSelectionModel().getSelectedItems().size()]);
}

//Binds a TextField
public static ChangeListener<Bindeable<?>> bind(TextField textField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a TextArea
public static ChangeListener<Bindeable<?>> bind(TextArea textArea, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textArea.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a PasswordField
public static ChangeListener<Bindeable<?>> bind(PasswordField passwordField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
passwordField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a ChoiceBox<String>
public static ChangeListener<Bindeable<?>> bind(ChoiceBox<String> choiceBox, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
choiceBox.getSelectionModel().select((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Integer>
public static ChangeListener<Bindeable<?>> bindIntSpinner(Spinner<Integer> integerSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
integerSpinner.getValueFactory().setValue(Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Double>
public static ChangeListener<Bindeable<?>> bindDoubleSpinner(Spinner<Double> doubleSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
try
{
doubleSpinner.getValueFactory().setValue((double) Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
doubleSpinner.getValueFactory().setValue(Double.parseDouble((String) m.invoke(newValue)));
}
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<String>
public static ChangeListener<Bindeable<?>> bindStringSpinner(Spinner<String> stringSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
stringSpinner.getValueFactory().setValue((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}
}









share|improve this question























  • TL; DR; see How do I ask a good question?, in particular "Pretend you're talking to a busy colleague "

    – c0der
    Nov 24 '18 at 5:05
















0












0








0


0






While poking around and looking into how the "HTMLEditor" control object of JavaFX works I found out about how it is essentially the base for a more complex "Skin" that in itself is like a combination of more nodes. All the buttons, choice boxes and editor itself, etc, is combined in the larger Class "HTMLEditor". I have some working code that cuts down the effort by an estimated 80% but I also have a history of not knowing the normal way and creating far too complex workarounds. In short: I can do what I want right now, but I'm probably just making my work hard and buggy again.



That is a very helpful and nice thing to know. Is there a way I can create a custom class similar to this (where I ceate a custom class defining its role and functions with a custom skin defining its functionality) and then have the FXML API aknowledge and parse it automatically? I don't like having the FXML file and its contents but then having to manually add Nodes in the code. It seems confusing (at least to me).



For example, creating a combination of a TableView (with N columns each of a specific type) and have it automatically create TextFields, ChoiceBoxes, etc corresponding to each column and Buttons to Add, Copy, Modify and Remove elements to that TableView. I've had to do this multiple times and I'm currently in the process of creating utilities for it (which is how I found out about the skins of larger control objects).



The best I currently have is creating the TableView, its column objects, input controls and buttons in the FXML file and then having them linked by a method.



The best idea I'm thinking about at the moment is having a special Node like in the case above "EditableTable" and before FXML actually goes and tries to parse it (and fails because it does not know the Class) replace it with the extended, underlying controls. That would, however, still require a lot of work giving it custom IDs and in retrospect connecting all the FXML objects.



I'll paste the code as I currently have, but it's rather lengthy for what could probably be done much easier, but this is currently the best I got and it works for now. Advise is appreciated but not required, I'd just like to know if I'm programming complex workarounds because I don't know the easy way everybody uses once again.



package utils.fxmlUtils;

//T is a reference to the Class extending Bindeable. Required for functions. Probably a bit of a dirty trick but no idea how else
public abstract class Bindeable<T extends Bindeable<T>> implements Cloneable
{
//Creates the T. Required to call from Bindeable
public abstract T createFrom(String elements);

//Gets all the Column data as String
public abstract String getData();

//Sets all the Column data as String
public abstract void setData(String elements);

//Clones itself
public T clone()
{
return createFrom(getData());
}
}

package application;

import utils.fxmlUtils.Bindeable;

//Example for Bindeable. All this is is a large collection of Strings
public class Property extends Bindeable<Property>
{
private String tag;
private String propertyName;
private String propertyModifier;
private String modifierType;
private String numberType;

public Property(String tag, String propertyName, String propertyModifier, String modifierType, String numberType)
{
this.tag = tag;
this.propertyName = propertyName;
this.propertyModifier = propertyModifier;
this.modifierType = modifierType;
this.numberType = numberType;
}

//MUST HAVE a constructor using only String in order to allow the TableEditBinder to create the objects by invoking this constructor. Probably a wonky way to do it but no idea how else to do
public Property(String elements)
{
this(elements[0], elements[1], elements[2], elements[3], elements[4]);
}

public String getTag()
{
return tag;
}

public void setTag(String tags)
{
this.tag = tags;
}

public String getPropertyName()
{
return propertyName;
}

public void setPropertyName(String propertyName)
{
this.propertyName = propertyName;
}

public String getPropertyModifier()
{
return propertyModifier;
}

public void setPropertyModifier(String propertyModifier)
{
this.propertyModifier = propertyModifier;
}

public String getModifierType()
{
return modifierType;
}

public void setModifierType(String modifierType)
{
this.modifierType = modifierType;
}

public String getNumberType()
{
if(modifierType.equals("Add Base Value") || modifierType.equals("Multiply Value"))
{
return numberType;
} else
{
return "";
}
}

public void setNumberType(String numberType)
{
this.numberType = numberType;
}

public Property clone()
{
return new Property(tag, propertyName, propertyModifier, modifierType, numberType);
}

@Override
public Property createFrom(String elements)
{
return new Property(elements);
}

@Override
public String getData()
{
return new String {tag, propertyName, propertyModifier, modifierType, numberType};
}

@Override
public void setData(String elements)
{
tag = elements[0];
propertyName = elements[1];
propertyModifier = elements[2];
modifierType = elements[3];
numberType = elements[4];
}
}

package utils.fxmlUtils;

import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.paint.Color;

//"Library" of Lighting effects for nodes. Identifies them as (in)valid, potentially invalid and currently being tested, as well as neutral and inactive (off)
public interface LightingIdentifiers
{
public static Lighting correctOn = new Lighting();
public static Lighting incorrectOn = new Lighting();
public static Lighting inProgressOn = new Lighting();
public static Lighting warningOn = new Lighting();
public static Lighting correctOff = new Lighting();
public static Lighting incorrectOff = new Lighting();
public static Lighting inProgressOff = new Lighting();
public static Lighting warningOff = new Lighting();
public static Lighting off = new Lighting();
public static Lighting neutral = new Lighting();

//Initializes and creates all the various lightings. Must be executed before using them to work.
public static void initialize()
{
correctOn.setDiffuseConstant(2);
correctOn.setSpecularConstant(2);
correctOn.setSpecularExponent(40);
correctOn.setSurfaceScale(0);
Light l = new Light.Distant();
l.setColor(new Color(0.5, 1, 0.5, 1));
correctOn.setLight(l);
incorrectOn.setDiffuseConstant(2);
incorrectOn.setSpecularConstant(2);
incorrectOn.setSpecularExponent(40);
incorrectOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 0.5, 0.5, 1));
incorrectOn.setLight(l);
inProgressOn.setDiffuseConstant(2);
inProgressOn.setSpecularConstant(2);
inProgressOn.setSpecularExponent(40);
inProgressOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.5, 1, 1));
inProgressOn.setLight(l);
warningOn.setDiffuseConstant(2);
warningOn.setSpecularConstant(2);
warningOn.setSpecularExponent(40);
warningOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 1, 0.5, 1));
warningOn.setLight(l);
correctOff.setDiffuseConstant(2);
correctOff.setSpecularConstant(2);
correctOff.setSpecularExponent(40);
correctOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.5, 0.25, 1));
correctOff.setLight(l);
incorrectOff.setDiffuseConstant(2);
incorrectOff.setSpecularConstant(2);
incorrectOff.setSpecularExponent(40);
incorrectOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.25, 0.25, 1));
incorrectOff.setLight(l);
inProgressOff.setDiffuseConstant(2);
inProgressOff.setSpecularConstant(2);
inProgressOff.setSpecularExponent(40);
inProgressOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
inProgressOff.setLight(l);
warningOff.setDiffuseConstant(2);
warningOff.setSpecularConstant(2);
warningOff.setSpecularExponent(40);
warningOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.5, 1));
warningOff.setLight(l);
off.setDiffuseConstant(2);
off.setSpecularConstant(2);
off.setSpecularExponent(40);
off.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
off.setLight(l);
neutral.setLight(null);
}
}

package utils.fxmlUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Control;
import javafx.scene.control.PasswordField;
import javafx.scene.control.Spinner;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import utils.ArrayUtils;

public interface TableEditBinder
{
/*
* Initiates the process of binding a TableView and its Bindeable row objects to the create, copy, modify and delete Buttons as well as Controls representing the input fields.
* Edit fields must correspond to the various TableRows. Number of TableRows and Controls must be equal. Column N must respond to input field N (== they must be in the right order).
* Currently implemented: TextField
* TextArea
* PasswordField
* ChoiceBox<String>
* Spinner<Integer>
* Spinner<Double>
* Spinner<String>
* TODO: Implement more
*/
public static void bindTableView(TableView<? extends Bindeable<?>> table, Class<? extends Bindeable<?>> tableType, Button create, Button copy, Button modify, Button delete, Control... editFields)
{
//Create TableView ChangeListener
ChangeListener<Bindeable<?>> bl = new ChangeListener<Bindeable<?>>()
{
//Sub-Listeners for the various Controls
private ChangeListener<?> subListeners;

//changed method. If no sublisteners exist creates them. After that if sublisteners exist and a non-null new value exists fills the input Controls
@SuppressWarnings("unchecked")
@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
if (subListeners == null)
{
try
{
connect(newValue);
} catch (Exception e)
{
e.printStackTrace();
}
}
if (subListeners != null)
{
if (newValue == null)
{
newValue = table.getSelectionModel().getSelectedItem();
}
if (newValue != null)
{
for (int i = 0; i < subListeners.length; i++)
{
((ChangeListener<Bindeable<?>>) subListeners[i]).changed(observable, oldValue, newValue);
}
}
}
}

//Connection method. Requires a non-null object of the class. Only executed if the connector is non-null
@SuppressWarnings({ "unchecked", "rawtypes" })
public void connect(Bindeable<?> connector) throws Exception
{
//checks if all conditions work out. Throws an execption if TableView and edit fields are of different numbers
if (connector == null)
{
return;
}
String methods = new String[table.getColumns().size()];
for (int i = 0; i < methods.length; i++)
{
methods[i] = ((PropertyValueFactory) table.getColumns().get(i).getCellValueFactory()).getProperty();
}
if (methods.length != editFields.length || editFields.length != table.getColumns().size())
{
throw new Exception("Error: Method length != Editable length");
}
//creates the sub-listeners and binds them to the respective Controls
subListeners = new ChangeListener<?>[methods.length];
for (int i = 0; i < editFields.length; i++)
{
if (editFields[i].getClass().equals(TextField.class))
{
subListeners[i] = bind((TextField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(TextArea.class))
{
subListeners[i] = bind((TextArea) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(PasswordField.class))
{
subListeners[i] = bind((PasswordField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(ChoiceBox.class))
{
subListeners[i] = bind((ChoiceBox<String>) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(Spinner.class))
{
try
{
subListeners[i] = bindIntSpinner((Spinner<Integer>) editFields[i], connector, methods[i]);
} catch (Exception e)
{
try
{
subListeners[i] = bindDoubleSpinner((Spinner<Double>) editFields[i], connector, methods[i]);
} catch (Exception f)
{
subListeners[i] = bindStringSpinner((Spinner<String>) editFields[i], connector, methods[i]);
}
}
}
}
}
};
//Adding listeners to TableView and Buttons
table.getSelectionModel().selectedItemProperty().addListener(bl);
create.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
create((TableView<Bindeable<?>>) table, editFields, tableType);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
copy.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
copy((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
modify.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
modify((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table), editFields);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
delete.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
remove((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
}

//Copy functionality
public static void copy(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
Bindeable<?> next = null;
for (int i = 0; i < items.length; i++)
{
next = items[i].clone();
table.getItems().add(next);
table.getSelectionModel().select(next);
}
table.refresh();
}

//Create functionality
public static void create(TableView<Bindeable<?>> table, Control inputs, Class<? extends Bindeable<?>> tableObjects)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
{
String base = itemize(inputs);
if(base == null)
{
return;
}
Bindeable<?> next = null;
table.getSelectionModel().clearSelection();
next = tableObjects.getConstructor(String.class).newInstance(new Object {base});
table.getItems().add(next);
table.getSelectionModel().select(next);
table.refresh();
}

//Takes all the input data from the controls as String values and returns them
@SuppressWarnings("unchecked")
public static String itemize(Control inputs)
{
String base = new String[inputs.length];
for (int i = 0; i < inputs.length; i++)
{
if (inputs[i].getEffect().equals(LightingIdentifiers.incorrectOn))
{
return null;
}
if (inputs[i].getClass().equals(TextField.class))
{
base[i] = ((TextField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(TextArea.class))
{
base[i] = ((TextArea) inputs[i]).getText();
} else if (inputs[i].getClass().equals(PasswordField.class))
{
base[i] = ((PasswordField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(ChoiceBox.class))
{
base[i] = ((ChoiceBox<String>) inputs[i]).getSelectionModel().getSelectedItem();
} else if (inputs[i].getClass().equals(Spinner.class))
{
try
{
base[i] = ((Spinner<Integer>) inputs[i]).getValue() + "";
} catch (Exception e)
{
try
{
base[i] = ((Spinner<Double>) inputs[i]).getValue() + "";
} catch (Exception f)
{
base[i] = ((Spinner<String>) inputs[i]).getValue() + "";
}
}
}
}
return base;
}

//Modify functionality
public static void modify(TableView<Bindeable<?>> table, Bindeable<?> items, Control inputs)
{
String itemized = itemize(inputs);
if(itemized == null)
{
return;
}
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
items[i].setData(itemized);
table.getSelectionModel().select((Bindeable<?>) items[i]);
}
table.refresh();
}

//Remove functionality
public static void remove(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
table.getItems().remove(items[i]);
}
table.refresh();
}

//Returns all selected items in the TableView
public static Bindeable<?> getAllSelected(TableView<Bindeable<?>> table)
{
return table.getSelectionModel().getSelectedItems().toArray(new Bindeable<?>[table.getSelectionModel().getSelectedItems().size()]);
}

//Binds a TextField
public static ChangeListener<Bindeable<?>> bind(TextField textField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a TextArea
public static ChangeListener<Bindeable<?>> bind(TextArea textArea, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textArea.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a PasswordField
public static ChangeListener<Bindeable<?>> bind(PasswordField passwordField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
passwordField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a ChoiceBox<String>
public static ChangeListener<Bindeable<?>> bind(ChoiceBox<String> choiceBox, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
choiceBox.getSelectionModel().select((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Integer>
public static ChangeListener<Bindeable<?>> bindIntSpinner(Spinner<Integer> integerSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
integerSpinner.getValueFactory().setValue(Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Double>
public static ChangeListener<Bindeable<?>> bindDoubleSpinner(Spinner<Double> doubleSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
try
{
doubleSpinner.getValueFactory().setValue((double) Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
doubleSpinner.getValueFactory().setValue(Double.parseDouble((String) m.invoke(newValue)));
}
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<String>
public static ChangeListener<Bindeable<?>> bindStringSpinner(Spinner<String> stringSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
stringSpinner.getValueFactory().setValue((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}
}









share|improve this question














While poking around and looking into how the "HTMLEditor" control object of JavaFX works I found out about how it is essentially the base for a more complex "Skin" that in itself is like a combination of more nodes. All the buttons, choice boxes and editor itself, etc, is combined in the larger Class "HTMLEditor". I have some working code that cuts down the effort by an estimated 80% but I also have a history of not knowing the normal way and creating far too complex workarounds. In short: I can do what I want right now, but I'm probably just making my work hard and buggy again.



That is a very helpful and nice thing to know. Is there a way I can create a custom class similar to this (where I ceate a custom class defining its role and functions with a custom skin defining its functionality) and then have the FXML API aknowledge and parse it automatically? I don't like having the FXML file and its contents but then having to manually add Nodes in the code. It seems confusing (at least to me).



For example, creating a combination of a TableView (with N columns each of a specific type) and have it automatically create TextFields, ChoiceBoxes, etc corresponding to each column and Buttons to Add, Copy, Modify and Remove elements to that TableView. I've had to do this multiple times and I'm currently in the process of creating utilities for it (which is how I found out about the skins of larger control objects).



The best I currently have is creating the TableView, its column objects, input controls and buttons in the FXML file and then having them linked by a method.



The best idea I'm thinking about at the moment is having a special Node like in the case above "EditableTable" and before FXML actually goes and tries to parse it (and fails because it does not know the Class) replace it with the extended, underlying controls. That would, however, still require a lot of work giving it custom IDs and in retrospect connecting all the FXML objects.



I'll paste the code as I currently have, but it's rather lengthy for what could probably be done much easier, but this is currently the best I got and it works for now. Advise is appreciated but not required, I'd just like to know if I'm programming complex workarounds because I don't know the easy way everybody uses once again.



package utils.fxmlUtils;

//T is a reference to the Class extending Bindeable. Required for functions. Probably a bit of a dirty trick but no idea how else
public abstract class Bindeable<T extends Bindeable<T>> implements Cloneable
{
//Creates the T. Required to call from Bindeable
public abstract T createFrom(String elements);

//Gets all the Column data as String
public abstract String getData();

//Sets all the Column data as String
public abstract void setData(String elements);

//Clones itself
public T clone()
{
return createFrom(getData());
}
}

package application;

import utils.fxmlUtils.Bindeable;

//Example for Bindeable. All this is is a large collection of Strings
public class Property extends Bindeable<Property>
{
private String tag;
private String propertyName;
private String propertyModifier;
private String modifierType;
private String numberType;

public Property(String tag, String propertyName, String propertyModifier, String modifierType, String numberType)
{
this.tag = tag;
this.propertyName = propertyName;
this.propertyModifier = propertyModifier;
this.modifierType = modifierType;
this.numberType = numberType;
}

//MUST HAVE a constructor using only String in order to allow the TableEditBinder to create the objects by invoking this constructor. Probably a wonky way to do it but no idea how else to do
public Property(String elements)
{
this(elements[0], elements[1], elements[2], elements[3], elements[4]);
}

public String getTag()
{
return tag;
}

public void setTag(String tags)
{
this.tag = tags;
}

public String getPropertyName()
{
return propertyName;
}

public void setPropertyName(String propertyName)
{
this.propertyName = propertyName;
}

public String getPropertyModifier()
{
return propertyModifier;
}

public void setPropertyModifier(String propertyModifier)
{
this.propertyModifier = propertyModifier;
}

public String getModifierType()
{
return modifierType;
}

public void setModifierType(String modifierType)
{
this.modifierType = modifierType;
}

public String getNumberType()
{
if(modifierType.equals("Add Base Value") || modifierType.equals("Multiply Value"))
{
return numberType;
} else
{
return "";
}
}

public void setNumberType(String numberType)
{
this.numberType = numberType;
}

public Property clone()
{
return new Property(tag, propertyName, propertyModifier, modifierType, numberType);
}

@Override
public Property createFrom(String elements)
{
return new Property(elements);
}

@Override
public String getData()
{
return new String {tag, propertyName, propertyModifier, modifierType, numberType};
}

@Override
public void setData(String elements)
{
tag = elements[0];
propertyName = elements[1];
propertyModifier = elements[2];
modifierType = elements[3];
numberType = elements[4];
}
}

package utils.fxmlUtils;

import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.paint.Color;

//"Library" of Lighting effects for nodes. Identifies them as (in)valid, potentially invalid and currently being tested, as well as neutral and inactive (off)
public interface LightingIdentifiers
{
public static Lighting correctOn = new Lighting();
public static Lighting incorrectOn = new Lighting();
public static Lighting inProgressOn = new Lighting();
public static Lighting warningOn = new Lighting();
public static Lighting correctOff = new Lighting();
public static Lighting incorrectOff = new Lighting();
public static Lighting inProgressOff = new Lighting();
public static Lighting warningOff = new Lighting();
public static Lighting off = new Lighting();
public static Lighting neutral = new Lighting();

//Initializes and creates all the various lightings. Must be executed before using them to work.
public static void initialize()
{
correctOn.setDiffuseConstant(2);
correctOn.setSpecularConstant(2);
correctOn.setSpecularExponent(40);
correctOn.setSurfaceScale(0);
Light l = new Light.Distant();
l.setColor(new Color(0.5, 1, 0.5, 1));
correctOn.setLight(l);
incorrectOn.setDiffuseConstant(2);
incorrectOn.setSpecularConstant(2);
incorrectOn.setSpecularExponent(40);
incorrectOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 0.5, 0.5, 1));
incorrectOn.setLight(l);
inProgressOn.setDiffuseConstant(2);
inProgressOn.setSpecularConstant(2);
inProgressOn.setSpecularExponent(40);
inProgressOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.5, 1, 1));
inProgressOn.setLight(l);
warningOn.setDiffuseConstant(2);
warningOn.setSpecularConstant(2);
warningOn.setSpecularExponent(40);
warningOn.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(1, 1, 0.5, 1));
warningOn.setLight(l);
correctOff.setDiffuseConstant(2);
correctOff.setSpecularConstant(2);
correctOff.setSpecularExponent(40);
correctOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.5, 0.25, 1));
correctOff.setLight(l);
incorrectOff.setDiffuseConstant(2);
incorrectOff.setSpecularConstant(2);
incorrectOff.setSpecularExponent(40);
incorrectOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.5, 0.25, 0.25, 1));
incorrectOff.setLight(l);
inProgressOff.setDiffuseConstant(2);
inProgressOff.setSpecularConstant(2);
inProgressOff.setSpecularExponent(40);
inProgressOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
inProgressOff.setLight(l);
warningOff.setDiffuseConstant(2);
warningOff.setSpecularConstant(2);
warningOff.setSpecularExponent(40);
warningOff.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.5, 1));
warningOff.setLight(l);
off.setDiffuseConstant(2);
off.setSpecularConstant(2);
off.setSpecularExponent(40);
off.setSurfaceScale(0);
l = new Light.Distant();
l.setColor(new Color(0.25, 0.25, 0.25, 1));
off.setLight(l);
neutral.setLight(null);
}
}

package utils.fxmlUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Control;
import javafx.scene.control.PasswordField;
import javafx.scene.control.Spinner;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import utils.ArrayUtils;

public interface TableEditBinder
{
/*
* Initiates the process of binding a TableView and its Bindeable row objects to the create, copy, modify and delete Buttons as well as Controls representing the input fields.
* Edit fields must correspond to the various TableRows. Number of TableRows and Controls must be equal. Column N must respond to input field N (== they must be in the right order).
* Currently implemented: TextField
* TextArea
* PasswordField
* ChoiceBox<String>
* Spinner<Integer>
* Spinner<Double>
* Spinner<String>
* TODO: Implement more
*/
public static void bindTableView(TableView<? extends Bindeable<?>> table, Class<? extends Bindeable<?>> tableType, Button create, Button copy, Button modify, Button delete, Control... editFields)
{
//Create TableView ChangeListener
ChangeListener<Bindeable<?>> bl = new ChangeListener<Bindeable<?>>()
{
//Sub-Listeners for the various Controls
private ChangeListener<?> subListeners;

//changed method. If no sublisteners exist creates them. After that if sublisteners exist and a non-null new value exists fills the input Controls
@SuppressWarnings("unchecked")
@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
if (subListeners == null)
{
try
{
connect(newValue);
} catch (Exception e)
{
e.printStackTrace();
}
}
if (subListeners != null)
{
if (newValue == null)
{
newValue = table.getSelectionModel().getSelectedItem();
}
if (newValue != null)
{
for (int i = 0; i < subListeners.length; i++)
{
((ChangeListener<Bindeable<?>>) subListeners[i]).changed(observable, oldValue, newValue);
}
}
}
}

//Connection method. Requires a non-null object of the class. Only executed if the connector is non-null
@SuppressWarnings({ "unchecked", "rawtypes" })
public void connect(Bindeable<?> connector) throws Exception
{
//checks if all conditions work out. Throws an execption if TableView and edit fields are of different numbers
if (connector == null)
{
return;
}
String methods = new String[table.getColumns().size()];
for (int i = 0; i < methods.length; i++)
{
methods[i] = ((PropertyValueFactory) table.getColumns().get(i).getCellValueFactory()).getProperty();
}
if (methods.length != editFields.length || editFields.length != table.getColumns().size())
{
throw new Exception("Error: Method length != Editable length");
}
//creates the sub-listeners and binds them to the respective Controls
subListeners = new ChangeListener<?>[methods.length];
for (int i = 0; i < editFields.length; i++)
{
if (editFields[i].getClass().equals(TextField.class))
{
subListeners[i] = bind((TextField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(TextArea.class))
{
subListeners[i] = bind((TextArea) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(PasswordField.class))
{
subListeners[i] = bind((PasswordField) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(ChoiceBox.class))
{
subListeners[i] = bind((ChoiceBox<String>) editFields[i], connector, methods[i]);
} else if (editFields[i].getClass().equals(Spinner.class))
{
try
{
subListeners[i] = bindIntSpinner((Spinner<Integer>) editFields[i], connector, methods[i]);
} catch (Exception e)
{
try
{
subListeners[i] = bindDoubleSpinner((Spinner<Double>) editFields[i], connector, methods[i]);
} catch (Exception f)
{
subListeners[i] = bindStringSpinner((Spinner<String>) editFields[i], connector, methods[i]);
}
}
}
}
}
};
//Adding listeners to TableView and Buttons
table.getSelectionModel().selectedItemProperty().addListener(bl);
create.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
create((TableView<Bindeable<?>>) table, editFields, tableType);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
copy.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
copy((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
modify.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
modify((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table), editFields);
} catch (Exception e)
{
e.printStackTrace();
}
}
});
delete.setOnAction(new EventHandler<ActionEvent>()
{
@SuppressWarnings("unchecked")
@Override
public void handle(ActionEvent event)
{
try
{
remove((TableView<Bindeable<?>>) table, getAllSelected((TableView<Bindeable<?>>) table));
} catch (Exception e)
{
e.printStackTrace();
}
}
});
}

//Copy functionality
public static void copy(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
Bindeable<?> next = null;
for (int i = 0; i < items.length; i++)
{
next = items[i].clone();
table.getItems().add(next);
table.getSelectionModel().select(next);
}
table.refresh();
}

//Create functionality
public static void create(TableView<Bindeable<?>> table, Control inputs, Class<? extends Bindeable<?>> tableObjects)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
{
String base = itemize(inputs);
if(base == null)
{
return;
}
Bindeable<?> next = null;
table.getSelectionModel().clearSelection();
next = tableObjects.getConstructor(String.class).newInstance(new Object {base});
table.getItems().add(next);
table.getSelectionModel().select(next);
table.refresh();
}

//Takes all the input data from the controls as String values and returns them
@SuppressWarnings("unchecked")
public static String itemize(Control inputs)
{
String base = new String[inputs.length];
for (int i = 0; i < inputs.length; i++)
{
if (inputs[i].getEffect().equals(LightingIdentifiers.incorrectOn))
{
return null;
}
if (inputs[i].getClass().equals(TextField.class))
{
base[i] = ((TextField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(TextArea.class))
{
base[i] = ((TextArea) inputs[i]).getText();
} else if (inputs[i].getClass().equals(PasswordField.class))
{
base[i] = ((PasswordField) inputs[i]).getText();
} else if (inputs[i].getClass().equals(ChoiceBox.class))
{
base[i] = ((ChoiceBox<String>) inputs[i]).getSelectionModel().getSelectedItem();
} else if (inputs[i].getClass().equals(Spinner.class))
{
try
{
base[i] = ((Spinner<Integer>) inputs[i]).getValue() + "";
} catch (Exception e)
{
try
{
base[i] = ((Spinner<Double>) inputs[i]).getValue() + "";
} catch (Exception f)
{
base[i] = ((Spinner<String>) inputs[i]).getValue() + "";
}
}
}
}
return base;
}

//Modify functionality
public static void modify(TableView<Bindeable<?>> table, Bindeable<?> items, Control inputs)
{
String itemized = itemize(inputs);
if(itemized == null)
{
return;
}
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
items[i].setData(itemized);
table.getSelectionModel().select((Bindeable<?>) items[i]);
}
table.refresh();
}

//Remove functionality
public static void remove(TableView<Bindeable<?>> table, Bindeable<?> items)
{
table.getSelectionModel().clearSelection();
for (int i = 0; i < items.length; i++)
{
table.getItems().remove(items[i]);
}
table.refresh();
}

//Returns all selected items in the TableView
public static Bindeable<?> getAllSelected(TableView<Bindeable<?>> table)
{
return table.getSelectionModel().getSelectedItems().toArray(new Bindeable<?>[table.getSelectionModel().getSelectedItems().size()]);
}

//Binds a TextField
public static ChangeListener<Bindeable<?>> bind(TextField textField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a TextArea
public static ChangeListener<Bindeable<?>> bind(TextArea textArea, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
textArea.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a PasswordField
public static ChangeListener<Bindeable<?>> bind(PasswordField passwordField, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
passwordField.setText((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a ChoiceBox<String>
public static ChangeListener<Bindeable<?>> bind(ChoiceBox<String> choiceBox, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
choiceBox.getSelectionModel().select((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Integer>
public static ChangeListener<Bindeable<?>> bindIntSpinner(Spinner<Integer> integerSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
integerSpinner.getValueFactory().setValue(Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<Double>
public static ChangeListener<Bindeable<?>> bindDoubleSpinner(Spinner<Double> doubleSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
try
{
doubleSpinner.getValueFactory().setValue((double) Integer.parseInt((String) m.invoke(newValue)));
} catch (Exception e)
{
doubleSpinner.getValueFactory().setValue(Double.parseDouble((String) m.invoke(newValue)));
}
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}

//Binds a Spinner<String>
public static ChangeListener<Bindeable<?>> bindStringSpinner(Spinner<String> stringSpinner, Bindeable<?> connector, String propertyName)
{
return new ChangeListener<Bindeable<?>>()
{
Method m = null;

@Override
public void changed(ObservableValue<? extends Bindeable<?>> observable, Bindeable<?> oldValue, Bindeable<?> newValue)
{
try
{
if (m == null)
{
m = connector.getClass().getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1, propertyName.length()));
}
stringSpinner.getValueFactory().setValue((String) m.invoke(newValue));
} catch (Exception e)
{
e.printStackTrace();
System.err.println();
System.err.println(ArrayUtils.printObject(connector.getClass().getMethods()));
}
}
};
}
}






java javafx tableview bind fxml






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 23 '18 at 22:07









PCK4DPCK4D

11




11













  • TL; DR; see How do I ask a good question?, in particular "Pretend you're talking to a busy colleague "

    – c0der
    Nov 24 '18 at 5:05





















  • TL; DR; see How do I ask a good question?, in particular "Pretend you're talking to a busy colleague "

    – c0der
    Nov 24 '18 at 5:05



















TL; DR; see How do I ask a good question?, in particular "Pretend you're talking to a busy colleague "

– c0der
Nov 24 '18 at 5:05







TL; DR; see How do I ask a good question?, in particular "Pretend you're talking to a busy colleague "

– c0der
Nov 24 '18 at 5:05














0






active

oldest

votes











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53453392%2fjavafx-create-parse-custom-combined-elements-from-fxml-file-and-skin-objects%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























0






active

oldest

votes








0






active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53453392%2fjavafx-create-parse-custom-combined-elements-from-fxml-file-and-skin-objects%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

404 Error Contact Form 7 ajax form submitting

How to know if a Active Directory user can login interactively

TypeError: fit_transform() missing 1 required positional argument: 'X'