This is my first attempt at a decent GUI for a Java app and I needed to use JLists with custom ListModels in order to represent certain structures.
//The 2 below structures implement the ListModel interface, using an internal
//ArrayList, in order to be used as
//a model for 2 different JLists in my GUI.
private PropertyList propertiesList = new PropertyList();
private SelectedProperties selProperties = new SelectedProperties();
//and these are the two JLists they are the models for
private javax.swing.JList Properties_JList;
private javax.swing.JList SelectedProperties_JList;
Here I populate my first JList via a stream:
private void OpenFile_MenuItemActionPerformed(java.awt.event.ActionEvent evt) {
final JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(null);
int returnVal = fc.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
this.Properties_JList.setModel(propertiesList);
this.propertiesList.AddFromFile(file);
} else {
//...
}
}
which happens to be working perfectly fine. I import a few entries by reading the file and they are all displayed as expected in a .toString() representation.
The problem is the second JList:
private void AddToSelected_JButtonActionPerformed(java.awt.event.ActionEvent evt) {
Property p = (Property) this.Properties_JList.getSelectedValue();
this.SelectedProperties_JList.setModel(selProperties);
this.selProperties.InsertProperty(p);
this.SelectedProperties_JList.revalidate();
}
Which appears to be displaying only the very first item I attempt to add to it through the above button event, and I have no idea why. I considered moving both .setModel(...) calls right after the form's initComponents() call but if I do that none of the lists gets populated, at all.
Logging messages made it clear that the internal structures are getting populated, but even though they are both respective ListModels for my JLists, one of them isn't working as expected.
A sufficient portion of the code is generated by Netbeans and I have spent hours looking up the API but still have trouble finding out what I'm doing wrong. Any ideas?
Related
I'm building this form that I want to be able to change what fields are displayed based on what setting you select.
There's going to be 3 panels, first with text fields gathering information, second for notes, third for a checklist. The main frame will add those panels onto it and use one buttons to gather the information from each panel and compile it into a text area so you can just copy/paste it after it's been formatted.
So the problem I'm having is getting the information passed over to the main frame. I have created a formlistener to pass information from the form using an object FormEvent I have created which will carry over the necessary information. I have then created a method in the form to pull the relevant information depending on which form is being used at the time. My problem is getting that method to call properly. This is the ActionListener for the button:
confirm.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
form.getUbntInfo(); // this is where i have it set
form.setFormListener(new FormListener() {
// would like it to go here so that it creates the event
// object to pass before
// it passes the information, but it causes compile error here
public void formEventOccured(FormEvent e) {
String complaint = e.getComplaint();
String ping = e.getPing();
String ap = e.getAp();
signal0 = e.getSignal0();
signal1 = e.getSignal1();
chain0 = e.getChain0();
chain1 = e.getChain1();
sinr0 = e.getSinr0();
sinr1 = e.getSinr1();
sinr0 = e.getLan();
System.out.println("local signal: " + signal0);
}
});
Essentially the getUbntInfo(); method needs to have a formListener set so that it can pass create the object to pass along the information. As it stands I can press the button twice and it passes the information the second time but it will not do it the first time. What am I doing wrong?
A frequent problem I encounter when programming is how to handle an unknown number of objects. By handling I mean referencing them, manipulating them etc. As for me, this would be when developing smaller games and programs.
Currently I am working on a score-keeping program, which should display the names of the players, their score as well and various other features. Furthermore, there should be two buttons that allow for adding and removing players from the score table which is what I'll be focusing on here. It might look something like this:
//A very limited version of my program
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class Application extends JFrame implements ActionListener{
//Fields, variables and components
Container mainCont = getContentPane(); //Main container, the window itself
private JPanel buttonPanel;
private JPanel namePanel;
private JButton addPlayerButton;
private JButton removePlayerButton;
//...
//Many more components
public Application(){
//Basic window initiation
setTitle("Score Keeper");
this.setSize(650, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainCont.setBackground(Color.BLACK);
this.setContentPane(mainCont);
buttonPanel = new JPanel();
namePanel = new JPanel();
addPlayerButton = new JButton();
addPlayerButton.addActionListener(this);
buttonPanel.add(addPlayerButton);
removePlayerButton = new JButton();
removePlayerButton.addActionListener(this);
buttonPanel.add(removePlayerButton);
this.add(buttonPanel);
this.add(namePanel);
this.setVisible(true);
//Other code omitted for now
//Includes other graphic components, layout managers etc.
}
/*
* Action-Listener.
* Performs an event on an action.
*/
#Override
public void actionPerformed(ActionEvent event){
if(event.getSource() == addPlayerButton){
Application.Player newPlayer = this.new Player(); //Creates a new object, i.e. a new player
//See below for Player class
}
if(event.getSource() == removePlayerButton){
//******
// This is where the problem lies
}
}
//I use a nested class to create a new player
public class Player{
//Components etc.
private String name;
private JLabel nameLabel;
public Player(){
name = getName();
nameLabel = new JLabel(name);
namePanel.add(nameLabel);
}
public String getName(){
//This basically gets input from the user to assign a name to the new player
//Code omitted for now
}
}
}
So far all is good. This program basically only has two buttons, where the addPlayerButton adds a player object, which has a name that is displayed on the screen. Every time this button is pressed a new player is added to the screen. And this can be done an indefinite number of times.
The problem comes when we want to remove a player. How can we do that? We can't reference it by name, as all player objects are practically anonymous.
The alternative, of course, would be to pre-define a fixed amount of player objects:
class Application extends JFrame implements ActionListener{
//Fields, variables and components
Container mainCont = getContentPane(); //Main container, the window itself
private JPanel buttonPanel;
private JPanel namePanel;
private JButton addPlayerButton;
private JButton removePlayerButton;
private Player player1;
private Player player2;
private Player player3;
//...
//Etc.
Then we would be able to directly address each player object, but this is simply too impractical. We cannot add more players than the pre-defined amount, and if we want fewer players we have a bunch of player objects that are never used. Furthermore, we would have to hardcode every initiation of every player - every nameLabel would have to be manually added to the screen etc.
Please share your knowledge on how you handle this kind of problem, of how you deal with an unknown number of objects.
Thanks for taking the time and for the help!
P.S. I'm still pretty new to this forum. Please let me know if there is anything with this question I can change etc. I did my research and found no previous question that tackled this, but if there is one I missed feel free to let me know!
EDIT 1: Okay. There were a lot of great answers. I chose the one using hashmaps as the right solution, since I consider this the best solution for the premises I provided. The way I actually solved my problem is that I added, to the player object, a JButton that removes the player object it is stored in. I also scrapped the concept of using a nested class for the player and just implemented it in a separate class.
What I've learnt overall, though, is that when handling objects and you don't know the amount of objects it is generally best to store them in a collection of some sort. My preference is the Hashmap, as it provides an easy way of accessing the object based on one of its properties, like a String name or similar.
You can use a Map/Hashmap and each time you create a player, add them to the map.
You also have to change from directly drawing the player on the screen, probably to drawing all the players in the Map, that way when a player gets removed from the Map it would no longer be drawn.
You would do something like this:
Map<String, Player> map = new HashMap<>();
map.put(player.Name, player);
And then you would draw everything in that hashmap. To remove you just need to provide the name of the player to remove.
map.remove(player.Name);
And of course then you would change your code a bit to render everything inside the map, I believe you need a way to know which player is to be removed, you might want to add a text field to input the name of the player to be removed.
What you could do if you want to delete a Player based on it's name is the following:
// Create a list of players, which you can define globally
ArrayList<Player> players = new ArrayList<>();
// The name of the player to find
String name = "theNameOfThePlayerToFind";
// Loop through the players and remove the player with the given name
for (Player player : players) {
if (player.getName().equals(name)) {
players.remove(player);
}
}
You can also add new players to lists easily:
players.add(new Player());
I would #Katada Freije method of using a HashMap. Just to elaborate a little, you basically have a collection of Players with their names as a key. You then use the key to remove the Player.
But I might also avoid this as some scenarios have multiple Players with the same name. I'd go with a List<Player>. This way the Player will be defined by the index rather than the name. You'd then use the index to remove the player with some in inbuilt methods.
Let us assume that you are using a JList to display the current players. Java's Swing separates the model (where the objects that are displayed are actually stored) and the view and control (the JList that displays them). This design is called MVC, and is very common.
We have different ways of storing the actual Player objects. Which to choose depends on how you plan to manipulate your players. The most straightforward is to use an array, but that only works if you will never have too many players:
Player[] players = new Player[MAX_PLAYERS](); // define MAX_PLAYERS somewhere
int currentPlayers = 0; // always < MAX_PLAYERS
To expose this to the JList, you would use a custom adapter model as follows (in an inner class with access to the players array):
private final class PlayerListModel extends AbstractListModel<Player> {
#Override
Player getElementAt(int position) { return players[position]; }
#Override
int getSize() { return currentPlayers; }
}
You can then pass this to the JList at construction time:
private PlayerListModel playerListModel = new PlayerListModel();
private JList playersListView = new JList(playerListModel);
Now, to remove a player by name, you would first update the model, and then cause the view to be refreshed:
private void removePlayerByName(String name) {
int toRemove = -1;
for (int i=0; i<currentPlayers; i++) {
Player p = players[i];
if (p.getName().equals(name)) {
toRemove = i;
break;
}
}
if (toRemove != -1) {
// update model
currentPlayers --;
players[toRemove] = players[currentPlayers];
// update views
playerListModel.fireContentsChanged(this, toRemove, currentPlayers);
}
}
Instead of a players array, it would be much easier and safer to use an ArrayList<Player> players. However, if you are naming your variables player1, player2 and so on, I think that you should begin with arrays. If you want to have much faster lookup of players, then a TreeMap<String, Player> would keep them sorted by name and easy to find. You would, in both cases, have to update the model and the removePlayerByName function accordingly. For example, if using the TreeMap, it would be much shorter (and faster):
private void removePlayerByName(String name) {
if (players.containsKey(name)) {
players.remove(name);
playerListModel.fireContentsChanged(this, 0, currentPlayers);
}
}
On the other hand, it is far more common to find interfaces where you click on the player that you want to remove to select it, and then click the remove button to actually do so. You can know which player was selected with this code:
Player selected = playersListView.getSelectedValue();
If there is a selection (selected != null) you could either call removePlayerByName(selected.getName()), or even better, code a removePlayer(Player p) that did not rely on names, but on a (currently missing) implementation of Player.equals(Player another).
I want to create a DND action from a JList to the OS. My solution for now is to use a TransferHandler. In the method createTransferable I create the Transferable with the files I want to copy. But now there is my Problem: in some cases I have to download the files from a FTP-Server before I can copy the files. The very heavy download operation runs in a JavaSwingWorker (hidden behind the statement d.download(tmpDir);). Now the system trys to copy files which are not downloaded already.
Now i need a mechanism that allows me to create the transferable after I have downloaded the files. Is there a solution for my problem? Please help me!
Thanks!
Here is my method:
public Transferable createTransferable(JComponent c) {
JList list = (JList) c; // we know it's a JList
List<PictureDecorator> selectedPictures = getSelectedValues(list.getModel());
Vector cpFiles = new Vector();
List<Picture> donePictures = new ArrayList<Picture>();
List<Picture> notDonePictures = new ArrayList<Picture>();
String tmpDir = System.getProperty("java.io.tmpdir");
for(PictureDecorator pd : selectedPictures){
if(pd.getPic().getStatus() == PictureStatus.DONE)
donePictures.add(pd.getPic());
else
notDonePictures.add(pd.getPic());
}
Downloader d = new Downloader(parent, loginInformation, sced, donePictures, order);
d.download(tmpDir);
for(Picture p : donePictures){
cpFiles.add(new File(tmpDir + File.separator + p.getPicture().getName()));
}
for(Picture p : notDonePictures) {
cpFiles.add(p.getPicture());
}
TransferableFile tf = new TransferableFile(cpFiles);
return tf;
}
I need something that initiates the drag procedure then I get the path where the drag goes and then I can download the pictures and copy it to the destination path.
EDIT: Or another formulation: How I can find out the drop destination when I drop into the operating system?
To start the drag you need either a TransferHandler on the JList or alternatively a DragSource in combination with a DragGestureListener. Below you can see an example for doing that with a JTextField:
final JTextField textField = new JTextField(50);
DragGestureListener dragListener = new DragGestureListener() {
#Override
public void dragGestureRecognized(DragGestureEvent dge) {
// how the drag cursor should look like
Cursor cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
// the component being dragged
JTextField tf = (JTextField) dge.getComponent();
// Here a Transferable is created directly for a single file name
dge.startDrag(cursor, new TransferableFile(tf.getText()));
}
};
final DragSource ds = new DragSource();
ds.createDefaultDragGestureRecognizer(textField, DnDConstants.ACTION_COPY, dragListener);
You can put the above code inside your window creation procedure.
Your resulting transferable (TransferableFile in your case) should support the DataFlavor.javaFileListFlavor and you should return a List of Files from the getTransferData(DataFlavor flavor) method.
I believe this is also the method where the downloading should take place because that's the last point under your control before JVM-OS take over.
Now regarding the SwingWorker problem you can wait inside the method until the download completes. Perhaps modify your Downloader class to expose a boolean flag so you would be able to do something like while (!downloader.isDone()) { Thread.sleep(millisToSleep) };
[Edit: I must admit I don't like the idea of keeping the EventDispath thread busy but if this solves your current problem perhaps you can investigate later a more elegant solution]
A little warning: Since you don't have access to the drop location you cannot know how many times the getTransferData will be called. It is better to take this into account and create a simple cache (a Map sounds reasonable) with the temp files you have downloaded so far. In case you find the file in the cache you return its corresponding temp file directly and don't download it again.
Hope that helps
I have a JPanel on which I've dynamically added quite a few JButtons. All of this is working perfectly. Later on in my program execution, I need to refer back to these buttons and pull out the button text. I'm having trouble figuring out how to refer back to them.
When I created each button, I gave it a unique name. Let's say this is the code where I created the button:
public void createButton(Container parent, String btnName) {
JButton btn = new JButton("xyz");
btn.setName(btnName);
btn.addActionListner(new ActionListner() {
//code
}
parent.add(btn);
}
In another method, I'm trying to retrieve the label on the button since it may have changed at run time. Do I need to keep an array of these buttons as they are created? Or is there a way that I can refer back to them directly?
This is what I was working on, but it's stupid. Can anyone suggest a correct approach?
public String getBtnLabel(String btnName) {
JButton btn = (JButton) btnName;
return btn.getText();
}
If the answer is that I just need to create the array and then iterate over it, that's fine. Just looking for other options.
You need to use a Map<String, JButton> so when you create your dynamic buttons you give them some sort of unqiue name:
//somewhere at the top of your class
private final Map<String, JButton> myButtonMap = new HashMap<>();
public void createButton(Container parent, String btnName) {
JButton btn = new JButton("xyz");
btn.setName(btnName);
btn.addActionListner(new ActionListner() {
//code
}
parent.add(btn);
myButtonMap.put(btnName, btn);
}
And then simply get from the map
public String getBtnLabel(String btnName) {
return myButtonMap.get(btnName).getText();
}
This will obviously throw an NPE if the button isn't defined...
Also you will need to delete from your map when you're done with it otherwise you're asking for a memory leak...
I suggest you to use a Map< String, JButton >.
At creation time you put new button into it with buttons.put( name, btn )
In event handler you use JButton btn = buttons.get( name )
Yes you need to keep references to the buttons. An array would be an option, but since arrays are awkward to use, you should prefer a List.
If you have a a reference to the JPanel containing the buttons, you could get them from it. but that is likely to be rather bothersome.
I would recommend keeping a list of your buttons or a reference to them in a map, however you could do this:
for (Component i : parent.getComponents()) {
if (i.getName().equals(btnName)) {
JButton b = (JButton) i;
// do stuff..
}
}
Using the parent component and iterating over the added components.
I am trying to add file names to my JList but without success. Here is the piece of the code:
DefaultListModel model = new DefaultListModel();
listLayer.setModel(model);
model.addElement(file.getName());
listLayer is a JList into which I would like to add file name. For information, I am writing my GUI application in netBeans so I can not create a new JList object within this code as it was already created automatically when added JList to my layout. Therefore I can just access it through its methods.
Thanks a lot,
Michal.
-------------------------------------------------------------------------
Ok I will try to extend it more:
private void openActionPerformed(java.awt.event.ActionEvent evt) {
JFileChooser fileChooser = new JFileChooser("C:/");
FileFilter filter1 = new MyCustomFilter();
fileChooser.setFileFilter(filter1);
int returnVal = fileChooser.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
//String[] tokens = file.getName().split(".");
//String name = tokens[0];
DefaultListModel model = new DefaultListModel();
listLayer.setModel(model);
model.addElement(file.getName());
} else {
System.out.println("File access cancelled by user.");
}
}
and yes, my JList called listLayer is declared in non-modifiable section of the code like this:
private javax.swing.JList listLayer;
Thanks again for any help. Michal
Thanks for posting more code. Now quite possibly we can answer your question. A problem I see is that you're recreating a DefaultListModel each time the button is pressed and setting the JList with this new model effectively removing all data that was previously held by the list. A way to avoid doing this is to simply get the model that the JList already has, which should be a DefaultListModel, and add items to it. You will need to cast the object returned by getModel() since per the API, Java only knows this to be a ListModel object, and ListModel doesn't have the addElement(...) method that DefaultListModel does.
Something perhaps like so:
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
// note the cast since getModel() only returns a ListModel
DefaultListModel model = (DefaultListModel)listLayer.getModel(); // changed**
model.addElement(file.getName());
}
You recommend me to use not DefaultListModel, but ListModel?
I don't know enough about the rest of your program to say. If you have a single, relatively static JList, DefaultListModel may be perfect. If your program models a constantly changing selection of File instances, then you may want to implement ListModel or even a shared model, as shown here. The latter simply forwards some methods to the default implementation.