How to build JavaFX styleable objects properly, that can be GC - javafx

I decided to refactor my application because of hugh memory leaks in the old version. For visualization objects, I decide to not more using fxml, but Styleable interface.
So I created a class Sim Photo like this :
public class SimPhoto extends Control {
private static final String DEFAULT_CLASS_NAME = "sim-photo";
private static final Double DEFAULT_STROKE_WIDTH = 0.0;
#Getter
#Setter
private static String DEFAULT_PHOTO = "";
private StyleableStringProperty imgPath;
private StyleableIntegerProperty arcHeight;
private StyleableIntegerProperty arcWidth;
private DoubleProperty strokeWidth;
private ObjectProperty<Paint> stroke;
private ObjectProperty<Paint> fill;
public SimPhoto() {
initialize();
}
public SimPhoto(#NamedArg("imgPath") String imgPath) {
this();
this.imgPathProperty().set(imgPath);
}
//Example of init properties
public final StyleableIntegerProperty arcHeightProperty() {
if (arcHeight == null) {
arcHeight = new SimpleStyleableIntegerProperty(
StyleableProperties.ARC_WIDTH,
SimPhoto.this,
"arcWidth",
0
);
}
return arcHeight;
}
public final StringProperty imgPathProperty() {
if(imgPath == null) {
imgPath = new SimpleStyleableStringProperty(
StyleableProperties.IMG_PATH,
SimPhoto.this,
"imgPath",
"");
}
return imgPath;
}
(...)
}
In my skin class, I use binding of properties from Control Class
public class SimPhotoSkin extends SkinBase<SimPhoto> {
#Getter
private Rectangle photoFond = new Rectangle();
private Rectangle photoView = new Rectangle();
private boolean invalidate = false;
private InvalidationListener invalidListener = this::invalidated;
private ChangeListener<String> pathListener = this::pathChanged;
public SimPhotoSkin(SimPhoto control) {
super(control);
initVisualization();
initListeners();
}
private void initVisualization() {
getChildren().addAll(photoFond, photoView);
if (getSkinnable().imgPathProperty() != null) {
setNewFond(getSkinnable().getImgPath());
}
}
private void initListeners() {
photoFond.widthProperty().bind(getSkinnable().widthProperty().subtract(5));
photoFond.heightProperty().bind(getSkinnable().heightProperty().subtract(5));
photoView.widthProperty().bind(photoFond.widthProperty().subtract(photoFond.strokeWidthProperty()));
photoView.heightProperty().bind(photoFond.heightProperty().subtract(photoFond.strokeWidthProperty()));
photoView.arcWidthProperty().bind(getSkinnable().arcWidthProperty());
photoView.arcHeightProperty().bind(getSkinnable().arcHeightProperty());
photoFond.arcWidthProperty().bind(getSkinnable().arcWidthProperty());
photoFond.arcHeightProperty().bind(getSkinnable().arcHeightProperty());
photoFond.fillProperty().bind(getSkinnable().fillProperty());
photoFond.strokeProperty().bind(getSkinnable().strokeProperty());
photoFond.strokeWidthProperty().bind(getSkinnable().strokeWidthProperty());
getSkinnable().imgPathProperty().addListener(pathListener);
}
private void pathChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {
(...)
}
private void setNewFond(String path) {
(...)
}
private void invalidated(Observable observable) {
invalidate = true;
}
}
I know that object cannot been GC while exist a reference to it. So I have a big problem, because event these objects are no more used, thay cannot be GC , and in my application ,when I need creating more than 300 objects at time is a big problem.
I tried to create method clean(), that will be unbind all bidnings and listeners, but it's not realy helpful. Problem still persist.
I'm thinking about any workaround like a Manager, that will store all objects in queue and while calling will return one objects disponibles or create new one.
But this is the last possibility, if I dont find any solution for my problem, and I would like avoid this.

Related

Unable to refresh/update browsefragment using ArrayObjectAdaptor's clear() and addAll()

I have a browsefragment thats displaying rows from a JSON file generated by PHP when a PHP page is called. I am trying to get the browsefragment to refresh the rows as the JSON data will be changing regularly.
Following the instructions on https://hackernoon.com/how-to-refresh-the-android-tv-browsefragment-6e4d2d3c6690, I added clear() and addAll() methods found in the ArrayOjectAdapter.
The main portion of my browseFragment is:
public class MainFragment extends BrowseSupportFragment
implements LoaderManager.LoaderCallbacks<LinkedHashMap<String, List<Video>>>{
private static final int BACKGROUND_UPDATE_DELAY = 300;
private static final String TAG = MainFragment.class.getSimpleName();
static final int GRID_ITEM_WIDTH = 300;
private static final int GRID_ITEM_HEIGHT = 200;
private final Handler mHandler = new Handler();
private ArrayObjectAdapter mRowsAdapter;
private Drawable mDefaultBackground;
private DisplayMetrics mMetrics;
private Runnable mBackgroundTask;
private Uri mBackgroundURI;
private BackgroundManager mBackgroundManager;
private CustomListRow mGridItemListRow;
private LoaderManager mLoaderManager;
private static final int CATEGORY_LOADER = 123;
ArrayList<Video> mItems = null;
private ArrayList<CustomListRow> mVideoListRowArray;
private static final int VIDEO_ITEM_LOADER_ID = 1;
private static PicassoBackgroundManager picassoBackgroundManager = null;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
prepareBackgroundManager();
setupUIElements();
loadRows();
setRows();
mLoaderManager = LoaderManager.getInstance(this);
mLoaderManager.initLoader(VIDEO_ITEM_LOADER_ID, null, this);
setupEventListeners();
prepareEntranceTransition();
picassoBackgroundManager = new PicassoBackgroundManager(getActivity());
picassoBackgroundManager.updateBackgroundWithDelay("http://twende.phidampalmgardens.com/images/bkground/background1.jpg");
updateRecommendations();
if(mVideoListRowArray != null) {
new Handler().postDelayed(new Runnable() {
public void run() {
//mLoaderManager.restartLoader(CATEGORY_LOADER, null, MainFragment.this);
mRowsAdapter.clear();
mRowsAdapter.addAll(0, mVideoListRowArray);
}
}, 2000);
}
}
Unfortunately, I still cannot get the rows to refresh/update without having to close and reopen the app
Try adding the mRowsAdapter.notifyDataSetChanged(); statement:
mRowsAdapter.clear();
mRowsAdapter.addAll(0, mVideoListRowArray);
mRowsAdapter.notifyDataSetChanged();

How do I handle media metadata in separate class?

Im making a mediaplayer using JavaFX Media classes. I made a SongModel class, that incapsulates all metadata from a file and creates Media and MediaPlayer instances.
It looks something like this:
private final StringProperty album =
new SimpleStringProperty(this, "album");
public String getAlbum(){ return album.get(); }
public void setAlbum(String value){ album.set(value); }
public StringProperty albumProperty() { return album; }
There are also artist, year, title, and albumCover fields that look just like that. Also, MediaPlayer property is exposed as a read-only:
public MediaPlayer getMediaPlayer(){ return mediaPlayer.get(); }
public ReadOnlyObjectProperty<MediaPlayer> mediaPlayerProperty(){
return mediaPlayer.getReadOnlyProperty();
}
I use a MapChangelistener to check if the field is available and then pass it to the handleMetadata method:
private void initializeMedia(String url){
try {
final Media media = new Media(url);
media.getMetadata().addListener(new MapChangeListener<String, Object>(){
#Override
public void onChanged(MapChangeListener.Change<? extends String, ? extends Object> ch) {
if(ch.wasAdded()){
handleMetadata(ch.getKey(), ch.getValueAdded());
}
}
});
mediaPlayer.setValue(new MediaPlayer(media));
mediaPlayer.get().setOnError(new Runnable() {
#Override
public void run() {
String errorMessage = mediaPlayer.get().getError().getMessage();
System.out.println("MediaPlayer error: "+errorMessage);
}
});
}catch(RuntimeException e){
System.out.println("Construction error: "+e);
}
}
private void handleMetadata(String key, Object value){
if(key.equals("album")){
setAlbum(value.toString());
} else if (key.equals("artist")){
setArtist(value.toString());
} if (key.equals("title")){
setTitle(value.toString());
} if (key.equals("year")){
setYear(value.toString());
} if (key.equals("image")){
setAlbumCover((Image)value);
}
}
Then I made an AbstractView class that provides access to SongModel:
public abstract class AbstractView {
protected final SongModel songModel;
protected final Node viewNode;
public AbstractView(SongModel songModel){
this.songModel = songModel;
this.viewNode = initView();
}
public Node getViewNode() {
return viewNode;
}
protected abstract Node initView();
}
But when I try to make a MetadataView class, I run into some problems.
Heres how it looks:
public class MetadataView extends AbstractView{
public Label artist;
public Label album;
public Label title;
public Label year;
public ImageView albumCover;
public MetadataView(SongModel songModel) {
super(songModel);
}
#Override
protected Node initView() {
artist = new Label();
artist.setId("artist");
album = new Label();
album.setId("album");
title = new Label();
title.setId("title");
year = new Label();
year.setId("year");
final Reflection reflection = new Reflection();
reflection.setFraction(0.2);
final URL url = getClass().getResource("resources/defaultAlbum.png");
Image image = new Image(url.toString());
albumCover = new ImageView(image);
albumCover.setFitWidth(240);
albumCover.setPreserveRatio(true);
albumCover.setSmooth(true);
albumCover.setEffect(reflection);
final GridPane gp = new GridPane();
gp.setPadding(new Insets(10));
gp.setHgap(20);
gp.add(albumCover, 0,0,1, GridPane.REMAINING);
gp.add(title, 1,0);
gp.add(artist, 1,1);
gp.add(album, 1,2);
gp.add(year, 1,3);
final ColumnConstraints c0 = new ColumnConstraints();
final ColumnConstraints c1 = new ColumnConstraints();
c1.setHgrow(Priority.ALWAYS);
gp.getColumnConstraints().addAll(c0,c1);
final RowConstraints r0 = new RowConstraints();
r0.setValignment(VPos.TOP);
gp.getRowConstraints().addAll(r0,r0,r0,r0);
return gp;
}
}
And heres how I call it in the start method:
metaDataView = new MetadataView(songModel);
The problem is that it displays only default metadata without taking it from the songmodel class. I tried running metadata view code together with data handling in one class and everything worked, but when i try to put them in separate classes - it doesnt. Music runs just fine, its just the data thats not displaying. Could anybody tell me what am I missing? How do i make it display metadata from a SongModel class? Ive spent a lot of time on that and dont want it to go to waste.
After a day of searching I have found an answer: binds. All I had to do was to bind label property of SongModel class to label property of MetadataView class:
title.textProperty().bind(songModel.titleProperty());
artist.textProperty().bind(songModel.artistProperty());
album.textProperty().bind(songModel.albumProperty());
year.textProperty().bind(songModel.yearProperty());
albumCover.imageProperty().bind(songModel.albumCoverProperty());

JavaFX Binding entire model to form

I spent full day to make my model observable by this JavaFx databinding.
At this point I already see, that if I change one property like this, it works like charm
selectedTestcase.setFolder("....");
but how I can observe the following and refresh my form:
if (maybeCase.isPresent()) {
selectedTestcase = maybeCase.get();
}
So I change the complete model. How I can make this?
Model:
#XmlRootElement(name = "Testcase")
#XmlAccessorType(XmlAccessType.PROPERTY)
public class Testcase {
private StringProperty Guid;
#XmlElement(name="GUID")
public String getGuid() {
return guidProperty().get();
}
public StringProperty guidProperty() {
if (Guid == null)
Guid = new SimpleStringProperty(this, "Guid");
return Guid;
}
public void setGuid(String guid) {
this.guidProperty().set(guid);
}
private StringProperty caseName;
#XmlElement(name="CaseName")
public String getCaseName() {
return caseNameProperty().get();
}
public StringProperty caseNameProperty() {
if (caseName == null)
caseName = new SimpleStringProperty();
return caseName;
}
public void setCaseName(String caseName) {
this.caseNameProperty().set(caseName);
}
ViewModel:
public Testcase selectedTestcase = new Testcase();
public void setSelectedTestcase(String folder, String filename) {
Optional<Testcase> maybeCase = this.AvailableTestCases.stream()
.filter((t -> t.TestcaseEqualsFolderAndName(folder, filename))).findFirst();
if (maybeCase.isPresent()) {
selectedTestcase = maybeCase.get();
}
}
Thanks in advance :)
Thanks to James_D and the Easybind Library:
MonadicObservableValue
That seems to be the solution:
private ObjectProperty<Testcase> testcaseObjectProperty = new SimpleObjectProperty<>(new Testcase());
public MonadicObservableValue<Testcase> selectedTestcase = EasyBind.monadic(testcaseObjectProperty);
public void setSelectedTestcase(String folder, String filename) {
Optional<Testcase> maybeCase = this.AvailableTestCases.stream()
.filter((t -> t.TestcaseEqualsFolderAndName(folder, filename))).findFirst();
if (maybeCase.isPresent()) {
testcaseObjectProperty.setValue(maybeCase.get());
}
}
and then the final binding:
txtCaseName.textProperty().bind(viewModel.selectedTestcase.flatMap(Testcase::caseNameProperty).orElse(""));
James; you are able to explain me this "orElse"?

JavaFX - Incompatible parameter type with using TreeView.EditEvent in lambda

In a JavaFX TreeView I'm using 'custom' classes which extend TreeItem. This makes me able to edit the items in the TreeView (I can double click them and edit the contents when running the application) but I can't seem to be able to set the .setOnEditCommit() method properly. I was hoping it'd work similar as the function in a tableview but I didn't have any luck yet.
This is my code in my controller in which I try to set the setOnEditCommit() method. In my TreeView called 'trvDivisies' I display football team divisions / competitions and one level lower I display all the teams that are in a certain division.
private void setUpTreeView() {
trvDivisies.setEditable(true);
trvDivisies.setShowRoot(false);
TreeItem<String> root = new TreeItem<>();
for (Divisie d : divisies) {
TreeItem<String> divisieTreeItem = d;
divisieTreeItem.valueProperty().set(d.getNaam());
for (VoetbalTeam vt : d.getVoetbalTeams()) {
TreeItem<String> voetbalTeamTreeItem = vt;
voetbalTeamTreeItem.valueProperty().setValue(vt.getTeamNaam());
divisieTreeItem.getChildren().add(voetbalTeamTreeItem);
}
root.getChildren().add(divisieTreeItem);
}
trvDivisies.setRoot(root);
trvDivisies.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
System.out.println(newValue);
}
});
trvDivisies.setCellFactory(TextFieldTreeCell.forTreeView());
// I get an error at the following line when compiling
trvDivisies.setOnEditCommit((TreeView.EditEvent p) -> {
TreeItem<String> selectedItem = p.getTreeItem();
if (selectedItem instanceof Divisie) {
updateDivisie((Divisie)selectedItem);
} else if (selectedItem instanceof VoetbalTeam) {
updateTeam((VoetbalTeam)selectedItem);
}
});
}
This is what my 'custom' classes look like.
public class Divisie extends TreeItem<String> {
private static int idCount = 0;
private int id;
private String naam;
private List<VoetbalTeam> voetbalTeams;
public int getId() {
return id;
}
public String getNaam() {
return naam;
}
public List<VoetbalTeam> getVoetbalTeams() {
return voetbalTeams;
}
public Divisie(int id, String naam) {
super(naam);
this.id = id;
this.naam = naam;
}
public Divisie(String naam) {
this.id = ++idCount;
this.naam = naam;
}
public void addTeam(VoetbalTeam toBeAdded) {
if (voetbalTeams == null) {
voetbalTeams = new LinkedList<>();
}
voetbalTeams.add(toBeAdded);
}
#Override
public String toString() {
return this.naam;
}
}
Second 'lower level' class
public class VoetbalTeam extends TreeItem<String> {
private static int idCount = 0;
private int id;
private String teamNaam;
private List<Speler> spelers;
public int getId() {
return id;
}
public String getTeamNaam() {
return teamNaam;
}
public List<Speler> getSpelers() {
return this.spelers;
}
public VoetbalTeam(int id, String teamNaam) {
super(teamNaam);
this.id = id;
this.teamNaam = teamNaam;
}
public VoetbalTeam(String teamNaam) {
super(teamNaam);
this.id = ++idCount;
this.teamNaam = teamNaam;
}
public void addSpeler(Speler nieuweSpeler) {
if (spelers == null) {
spelers = new LinkedList<>();
}
this.spelers.add(nieuweSpeler);
}
#Override
public String toString() {
return this.teamNaam;
}
}
When trying to run the application WITH the .setOnEditCommit() method I get an error saying:
Error:(97, 37) java: incompatible types: incompatible parameter types in lambda expression
I was hoping you guys can tell me what I need to change my TreeView.EditEvent lambda to or help me find an easier solution.
For a TreeView<T>, the signature of setOnEditCommit is
void setOnEditCommit(EventHandler<TreeView.EditEvent<T>> value)
Since you have (apparently) a TreeView<String>, you need
trvDivisies.setOnEditCommit((TreeView.EditEvent<String> p) -> {
// ...
});
Or, of course, you can just let the compiler do the work for you:
trvDivisies.setOnEditCommit(p -> {
// ...
});

How to access objects from event listener in javafx

I have an object Contract and it contains Summary and Observable List of another object ContractDetails inside it.
Now, I am using ContractDetails to populate in tableview from Contract object.
I have a save button, which on clicking needs to save Contract along with ContractDetails. I am able to access ContractDetails since they are in tableview.
How do I access Contract properties in eventlistener of save button.
The related code is given below
public class Contract {
private String tradeDate;
private String contractNote;
.....
.....
private String brokerId;
private ObservableList<ContractDetails> contractdetails = FXCollections.observableArrayList();
public Contract() {
}
public Contract(String tradeDate, String contractNote, ....., String brokerId,ObservableList<ContractDetails> contractdetails) {
this.tradeDate = tradeDate;
this.contractNote = contractNote;
....
....
this.contractdetails=contractdetails;
}
public String getTradeDate() {
return tradeDate;
}
public void setTradeDate(String tradeDate) {
this.tradeDate = tradeDate;
}
public String getContractNote() {
return contractNote;
}
public void setContractNote(String contractNote) {
this.contractNote = contractNote;
}
....
....
public ObservableList<ContractDetails> getContractdetails() {
return contractdetails;
}
public void setContractdetails(ObservableList<ContractDetails> contractdetails) {
this.contractdetails = contractdetails;
}
}
public class ContractDetails {
private String orderNo;
private String contractType;
private String symbol;
private String buysell;
private Integer quantity;
private Double buysellprice;
private Double netcontractValue;
public ContractDetails() {
}
public ContractDetails(String orderNo, String contractType, String symbol, String buysell, Integer quantity, Double buysellprice, Double netcontractValue) {
this.orderNo = orderNo;
this.symbol = symbol;
this.buysell = buysell;
this.quantity = quantity;
this.buysellprice = buysellprice;
this.netcontractValue = netcontractValue;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
....
....
public Double getNetcontractValue() {
return netcontractValue;
}
public void setNetcontractValue(Double netcontractValue) {
this.netcontractValue = netcontractValue;
}
}
In the controller
==================
public class ContractViewController implements Initializable {
#FXML
private TableView<ContractDetails> tblcontractfx;
#FXML
private TableColumn<ContractDetails, String> contractTypefx;
#FXML
private TableColumn<ContractDetails, String> symbolfx;
....
....
#FXML
private Button savefx;
#FXML
private TextField txtclientcodefx;
#FXML
private TextField txttradedtfx;
private void fetchContracts(TableView tableView, Contract contract)
{ txttradedtfx.setText(contract.getTradeDate());
txtclientcodefx.setText(contract.getClientCode());
symbolfx.setCellValueFactory(new PropertyValueFactory<ContractDetails, String>("symbol"));
contractTypefx.setCellValueFactory(new PropertyValueFactory<ContractDetails, String>("contractType"));
tableView.setItems((ObservableList) contract.getContractdetails());
#FXML
private void saveClicked(ActionEvent event) { DBConnection DBcon = new DBConnection();
//Now I am getting the contract details from tableview tblcontractfx
ObservableList<ContractDetails> contractdetails = tblcontractfx.getItems();
//How do I get the summary values from contract. I am able to get those which are in text fields like txttradedtfx and txtclientcodefx.However contractNote which I am not using, I still need to retrieve it to populate into database.
String clientCode=txtclientcodefx.getText();
Thanks
Just store the contract in a local variable.
Contract contract;
private void fetchContracts(TableView tableView, Contract contract)
{
this.contract = contract;
...
}
private void saveClicked(ActionEvent event) {
// here you have full access to the contract variable
String contractNote = contract.getContractNote();
}
As an alternative, if you insist on combining it all in a single table, you could put the Contract into the table via setUserData and retrieve it via getUserData.
By the way, I still don't get your code. Why is there a tableView parameter when you have full access to TableView<ContractDetails> tblcontractfx

Resources