I need an icon for my MenuItem's.
This is like a "worker class" to get the ImageView of the icon :
public class IconFactory {
private static ImageView HLP_BOOK_JFX;
public enum ICONS {
BASCET_REMOVE, BASCET_PUT, SAVE, OPEN, ARROW_RIGHT, ARROW_LEFT, ARROW_UP, ARROW_DOWN, CLOCK, ANALOG_SIGNAL, DIGITAL_SIGNAL, REFRESH, GREEN_PLUS, NETWORK, OK, CANCEL, RIGHT_NAV2, LEFT_NAV2, PLAY, PAUSE, LIST_ADD, PAGE_FIND, SET_PARAM, DOWNLOAD, UPLOAD, LOG_FILE, WARNING, INFO, LOG_DIAG, DATA_TRANS, TREE, FILTER, SEARCH, PARAM, ERASE, RESETDEF, RESETDEF2, DEBUG_BUG, INTERNATIONAL, CLOSE, HLP_BOOK
}
public static ImageView getImage(ICONS en) {
switch (en) {
case HLP_BOOK:
if (HLP_BOOK_JFX == null)
HLP_BOOK_JFX = new ImageView(new Image(IconFactory.class.getResourceAsStream("help_book.png")));
return HLP_BOOK_JFX;
}
return null;
}
When I use myMenuItem.setGraphic(IconFactory.getImage(ICONS.HLP_BOOK)) for a single menu item it works perfectly.
But then, when I want to generate two menus in a loop and set the same graphic, one MenuItem has no icon displayed. (the first one in loop in the code below).
My code:
while (keys.hasMoreElements()) {
// that will do 2 loops, do not care about how
MenuItem subMenuHelp = new MenuItem("MenuItem");
subMenuHelp.setGraphic(IconFactory.getImage(ICONS.HLP_BOOK));
subMenuHelp.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// do not care
openHelpFile(link);
}
});
System.out.println(((ImageView) subMenuHelp.getGraphic()).toString());
myMenu.getItems().add(subMenuHelp);
}
As you can see, I added a System.out.println to see if a graphic was set for the current item.
Result in console : both lines (MenuItem) with the same ImageView:
ImageView#79814766[styleClass=image-view]
ImageView#79814766[styleClass=image-view]
I did exactly the same in Swing (but with Icons and .setIcons() function) and it worked very well. I've also looked for a "repaint" function to force displaying but no way.
Hope you can help me!
This is because the same Node cannot be attached to the scene-graph multiple times and - as you even state - you are adding the same ImageView object.
From the documentation of Node:
If a program adds a child node to a Parent (including Group, Region,
etc) and that node is already a child of a different Parent or the
root of a Scene, the node is automatically (and silently) removed from
its former parent.
The solution is to modify getImage method of IconFactory to return a new ImageView instance on each call or to return Image instances rather than ImageView instances (the second one fits better to the name "IconFactory" I think).
You could store the Image instance instead of storing the ImageView to avoid re-loading the Image itself. You could check this question as reference: Reusing same ImageView multiple times in the same scene on JavaFX
A possible update on IconFactory:
public class IconFactory {
private static HashMap<ICON, Image> images = new HashMap<ICON, Image>();
public enum ICON {
BASCET_REMOVE, BASCET_PUT, SAVE, OPEN, ARROW_RIGHT, ARROW_LEFT, ARROW_UP, ARROW_DOWN, CLOCK, ANALOG_SIGNAL, DIGITAL_SIGNAL, REFRESH, GREEN_PLUS, NETWORK, OK, CANCEL, RIGHT_NAV2, LEFT_NAV2, PLAY, PAUSE, LIST_ADD, PAGE_FIND, SET_PARAM, DOWNLOAD, UPLOAD, LOG_FILE, WARNING, INFO, LOG_DIAG, DATA_TRANS, TREE, FILTER, SEARCH, PARAM, ERASE, RESETDEF, RESETDEF2, DEBUG_BUG, INTERNATIONAL, CLOSE, HLP_BOOK
}
public static Image getImage(ICON en) {
if (!images.containsKey(en)) {
switch (en) {
case HLP_BOOK:
images.put(en, new Image(IconFactory.class.getResourceAsStream("help_book.png"))); break;
default:
return null;
}
}
return images.get(en);
}
}
Usage after the update:
subMenuHelp.setGraphic(new ImageView(IconFactory.getImage(ICONS.HLP_BOOK)));
Related
I'm creating a media player in JavaFX. In one of my methods, I've created a way to search for metadata in a Media-file and then display it in ImageView. Works fine first time, but as soon as I want to call it again using another Media object, the image doesn't show up. I'm a bit confused and inexperienced, but I think that perhaps I need to reset/stop the listener before going to next object in line?
So my question is! How do you remove the listener when "image" has been found, what do you type to make it happen?
If you think that there's another reason why my image wont display the second time, please let me know as well.
Thanks on purpose.
private void displayAlbumCover (){
// Will start to show a blank CD
File file = new File("src/sample/images/blank_cd.jpeg");
Image image = new Image(file.toURI().toString());
albumCoverView.setImage(image);
// However if an album cover is found in the meta-data it will be displayed
ObservableMap<String,Object> meta_data=me.getMetadata();
meta_data.addListener((MapChangeListener<String, Object>) ch -> {
if(ch.wasAdded()){
String key=ch.getKey();
Object value=ch.getValueAdded();
switch(key){
case "image":
albumCoverView.setImage((Image)value);
break;
}
}
});
}
ObservableMap has removeListner method. You can keep the listener instance to variable and then remove it later.
private MapChangeListener<String, Object> listener;
private void displayAlbumCover (){
// ...
this.listener = //...
meta_data.addListener(listener);
}
private void removeListener() {
me.getMetadata().removeListener(this.listener);
}
https://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableMap.html#removeListener-javafx.collections.MapChangeListener-
I dont think my problem is that hard to solve but I have been searching for a while and cant figure it out.
I have two scene2d SelectBox widgets one above the other, in a table, on a stage. Let's call them A and B. Whatever is selected in A determines which list is shown in B. I implement this using a ChangeListener on A and all works fine (this isn't the problem).
However, my list A was getting extremely long (500+ items) so I wanted to add a TextField above it which would search and match the strings, replacing the old list of A with a shorter one, making it much easier to find what you are looking for. This works fine, I use a ChangeListener on the textfield to get the string, compare it to a main list of strings using a for loop and use aList.setItems(); to add the adjusted string to the SelectBox. The list displays (without a click, so I use aList.showList(); in the ChangeListener of the TextField) and I think this is where the problem occurs - instead of a click, showList() is called from elsewhere. Lets say I change my mind and want to select a different item from A, it will no longer drop down the menu on click. Yet if I change the text which is in the search bar, it displays the list. When the list is displayed, I can click an item and it hides as normal.
This might seems a bit confusing, so here is the code (edited for clarity, so if something is missing let me know)
SelectBox aSelect, bSelect;
TextField searchBar;
Stage stage;
Table table;
Skin skin;
ArrayList<String> completeAList;
ArrayList<String> abrevAList;
public chooseItemScreen()
{
stage = new Stage(new ScreenViewport());
skin = new Skin(Gdx.files.internal("uiskin.json"));
table = new Table();
table.setFillparent(true);
completeAList = new ArrayList<String>;
abrevAList = new ArrayList<String>;
aSelect = new SelectBox(skin);
//ItemList is a class with the list of strings as a static method
completeAList = ItemList.getAList();
aSelect.setItems(completeAList.toArray());
//bSelect omitted as is same as A
//aSelect changeListener also omitted as it is working fine
searchBar = new TextField("", skin);
searchBar.setMessageText("SEARCH LIST");
searchPokemon.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
updateASelect();
}
});
table.add(searchBar);
table.row();
table.add(aList);
stage.addActor(table);
Gdx.input.setInputProcessor(stage);
}
private void updateAList()
{
abrevAList.clear();
aSelect.clearItems();
aSelect.hideList()
for (String string: completeAList)
{
if (string.toLowerCase().startsWith(searchBar.getText().toLowerCase()))
{
abrevAList.add(string);
}
}
if (abrevAList.isEmpty())
{
abrevAList.add("NOT FOUND");
}
aSelect.setItems(abrevAList.toArray());
//It's at this point where I am no longer to click on aSelect
//I can still select an item from the drop down list, closing the list
//it's just I can't show list by clicking on the widget after that
aSelect.showList();
}
#Override
public void render(float delta) {
Gdx.gl20.glClearColor(0,0,0,0);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
stage.draw();
}
I added the following listener to tell if the selectBox was being clicked (which it was). I gave all actors names
stage.getRoot().addCaptureListener(new InputListener() {
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
System.out.println(event.getTarget().getName());
return false;
}
});
The click is recognised, just the list doesn't show. In my opinion, it is a problem with calling showList() and changing the list at the same time.
Any help is appreciated, and if you need more code or any other information, let me know.
Thanks
Set a fixed size to the selectbox when adding it to the table, something like
table.add(selectBox).width(someValue);
or
table.add(selectBox).growX();
Also, after reviewing your code, I suggest you to remove
aSelect.clearItems();
aSelect.hideList();
And make ArrayList be just libgdx Array< String>, it will make things easier, wont cause allocation when iterating with ':' and you wont need .toArray() when setting the items of your selectboxes. You also can set SelectBox type with SelectBox< String>, and, you can add a row in the same line with table.add(something).row().
After changing the size of the selectbox cell your code worked just fine in my side.
I am currently developing a javafx desktop application. It contains two observableList<Item>s and two HashMap<String,Double>s. I am currently develop the menuBar , which contains these menuItem s, i.e. Open, New, Save and Save As.
Consider the case where I have started the desktop application and added a few Item to the observableList<Item>. Then all of a sudden, I want to hit any one of the menuItems listed above. First thing I want to check in my program is whether the current workflow needs to be saved before proceeding to start a new workflow (New menuitem).
I have the following method in place at the moment but I think it looks very clumsy and inefficient.
The method I developed is to set a variable private static final boolean isSaved = false;
And then within the two observableLists, I added a Listener to them:
obslist1.addListener(new ListChangeListener<Item>(){
#Override
public void onChanged(ListChangeListener.Change change) {
isSaved = false;
}
});
The code for obslist2 is identical. And the isSaved variable is set to true only if the user actually presses the Save or Save As menuItem.
I find my method very clumsy and inefficient. Is there a better way to do this?
You can do something like
BooleanProperty saved = new SimpleBooleanProperty();
InvalidationListener savedListener = obs -> saved.set(false);
and then
private void save() {
// save the data...
// mark data as saved:
saved.set(true);
}
with
obslist1.addListener(savedListener);
obslist2.addListener(savedListener);
anythingElseThatMightChangeAndIsImportant.addListener(savedListener);
Your save button and menu item, etc can do
saveButton.disableProperty().bind(saved);
I have been trying to add a remove button so that it removes a selected row in my tableview. My problem is slightly different from those i have found elsewhere. My problem lies behind the fact that in my application i have used 1 FXML file as the basis of several different interfaces. When i initialize 1 of these and use the functionality of the remove button it removes the tableView rows fine and how it is supposed to. But when i initialize a second interface (still using the same FXML and henceforth same variable names) it only lets me delete items in the 2nd tableView and not in the first. I have a good idea as to why this is, but i do not know how to fix it.
Here are a few methods i have tried:
public void removeProject(ActionEvent event){
int index = projectTable.getSelectionModel().getSelectedIndex();
if(index >=0){
projectTable.getItems().remove(index);
}else
//Show warning
}
}
Another different approach:
public void removeProject(ActionEvent event){
ObservableList<Project> currentlySelected, allProjects;
currentlySelected = projectTable.getSelectionModel().getSelectedIndex();
allProjects = projectTable.getItems();
currentlySelected.forEach(allProjects::remove);
}
Also please keep in mind that both of these methods work fine until i initialize a second tableView. After this point the value i get from both my ObservableList<Project> currentlySelected and my int Indexare -1 when i am trying to select a row in a table which isn't the most recent initialization of the interface. Sorry if it sounds a bit confusing but it is a bit confusing, if i can clear anything up ill add an edit later
Cheers
Edit 1:
Here is an example where i am trying to remove from the table based on which interface it is in currently:
ObservableList<Project> itemsSelected;
switch(counter){
case 1:
itemsSelected = projectTable.getSelectionModel().getSelectedItems();
itemsSelected.forEach(projTableStorage.getProj1()::remove);
break;
case 2:
itemsSelected = projectTable.getSelectionModel().getSelectedItems();
itemsSelected.forEach(projectTableStorage.getProj2()::remove);
break;
A few things to note:
The projectTableStorage.getProj() is used to store all of the data in each table, the returned value is an ObservableList and i use this to set the items of the table whenever that interface is loaded so the data is not lost when swapping between interfaces, perhaps there is more efficient ways to go about it, this is just how i did it
There are 7 of these interfaces, i am just testing with 2 to make testing shorter and simpler for now at least
Edit 2:
Loading FXML files:
public AnchorPane initLayouts(FXMLLoader loader, AnchorPane projectLayout, MainLayoutController mainLay) throws IOException{
loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/control/view/ProjectLayout.fxml"));
loader.setController(mainLay);
projectLayout = (AnchorPane) loader.load();
return projectLayout;
}
in MainLayoutController:
public AnchorPane loadLayout(AnchorPane projectLayout, Project project, FXMLLoader loader)throws IOException{
projectLayout = project.initLayouts(loader, projectLayout, this);
return projectLayout;
}
load layout is called whenever a button is pressed
Edit 3:
Here is the 'removeProjec' code again
public void removeProject(ActionEvent event){
ObservableList<Project> itemsSelected, currentProject;
itemsSelected = getProjectTable().getSelectionModel().getSelectedItems();
itemsSelected.forEach(getProjectTable().getItems()::remove);
System.out.println("value of itemsSelected is : " + itemsSelected);
}
and my project table storage:
ObservableList<Project> project1= FXCollections.observableArrayList();
ObservableList<Project> project2 = FXCollections.observableArrayList();
public void setProject1(Project project){
project1.add(project);
}
public void setProject2(Project project){
project2.add(project);
}
public ObservableList<Project> getProject1(){
return project1;
}
public ObservableList<Project> getProject2(){
return project2;
}
And also just in case the getProjectTable method(Tried with and without annotation):
#FXML
public TableView<Project> getProjectTable(){
return projectTable;
}
Edit 4:
public void createNewProjectLayout(ActionEvent event) throws IOException{
if(event.getTarget() == newProjectLayoutButton1){
projectLayout1 = loadOrReloadProjectLayout(newProjectLayoutButton1, project1, projectLayout1, 1);
setTable(Counter);
}else if(event.getTarget() == newProjectLayoutButton2){
projectLayout2 = loadOrReloadProjectLayout(newProjectLayoutButton2, project2, projectLayout2, 2);
setTable(Counter);
}
A few things to note:
The loadOrReload is simply to load the file the first time it is clicked using the loadLayout method previously mentioned, and then reload the result of loadLayout for the next time it is pressed
The setTable is used to set any data stored previously in the table to be put in the table again using the observable lists from the ProjectTableStorage class
I have a JFileChooser that lets users choose an image for themselves. I want to limit the images they can choose to ones with square dimensions, for example -
width and height both 50
width and height both 75, etc...
So when they select an image with the JFileChooser and click 'Open' I need to validate the image size and if it doesn't have square dimensions I need to present the user with a dialog informing them "The image must have the same width and height".
I'm just learning swing so I don't know how to do this. Any ideas on how to do this? Is there a way of hooking the "Open" button's event handler?
You can hide all images that do not confirm to the rules with an implementation of a FileFilter:
JFileChooser fileChooser = new JFileChooser(new File(filename));
fileChooser.addChoosableFileFilter(new MyFilter());
// Open file dialog.
fileChooser.showOpenDialog(frame);
openFile(fileChooser.getSelectedFile());
class MyFilter extends javax.swing.filechooser.FileFilter {
public boolean accept(File file) {
// load the image
// check if it satisfies the criteria
// return boolean result
}
}
I tried overwriting
public void approveSelection ()
by deriving a own class from JFileChooser, and at first glance, it seemed to work.
The method is called, I can make a test on the selected file, and, if it fails, recall showOpenDialog (ref);.
But ...
It works fine, when I call a legitimate file, and it opens a new dialog, if not, but after that, the dialog won't close again normally, and if forced by the X of the window, I get a StackTrace printed. So I guess the state of the dialog is the critical thing here - it doesn't work if 'showOpenDialog' is called recursively.
Here is one of the variants I tested:
class ProportionalImageChooser extends JFileChooser
{
private Component ref;
public ProportionalImageChooser (File f)
{
super (f);
}
public int showOpenDialog (Component parent)
{
ref = parent;
return super.showOpenDialog (parent);
}
public void approveSelection () {
System.out.println ("approving selection!");
String fname = getSelectedFile ().getName ();
if (fname.matches (".*e.*")) {
cancelSelection ();
System.out.println ("Dialog: size doesn't match");
showOpenDialog (ref);
}
else super.approveSelection ();
}
}
To keep the test simple, I only tested the filename to include an 'e' or not.
So I suggest, use Boris' approach, and test your file after finishing the dialog. If it fails, immediately reopen a new one.