Populating a JComboBox with an ArrayList from another class - java

I am trying some self teachings and am working on a small app that reads details from a text file and shows them in a JComboBox for selection. The plan is to be able to select an item from the combobox, push the button and have a pop-up message appear with text depending on the selection, but I digress.
Currently, when it compiles the combobox is showing what I think to be some kind of exception. It says [Ljava.lang.Object;#175078b.
What am I doing wrong here?
This is the class that has the ArrayList:
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Scanner;
public class Stuff {
private ArrayList<String> list;
private String name;
private ArrayList<String> getList() {
return list;
}
private void setList(ArrayList list) {
this.list = list;
}
public Stuff() {
readNames();
}
public void readNames() {
File file = new File("Names.txt");
try {
ArrayList<String> list = new ArrayList<>();
Scanner in = new Scanner(file);
while (in.hasNextLine()) {
list.add(in.nextLine());
}
Collections.sort(list);
// for (int i = 0; i < list.size();++i){
// System.out.println(list.get(i));
// }
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
This is the class with the GUI:
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.util.ArrayList;
import javax.swing.*;
public class GUI extends JFrame{
private JLabel label = new JLabel("Who is the most awesome?");
private JComboBox box = new JComboBox();
private JFrame frame = new JFrame();
private JTextArea text = new JTextArea();
private JButton button = new JButton("Press Me");
private JPanel panel = new JPanel();
private ArrayList<Stuff> list = new ArrayList<>();
private JFrame getFrame(){
return frame;
}
private ArrayList<Stuff> getList(){
return list;
}
private void setList (ArrayList<Stuff> list){
list = list;
}
public GUI(){
buildGUI();
}
private void buildGUI(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setLayout(new FlowLayout());
panel.add(label);
panel.add(box);
panel.add(button);
box.addItem(list.toArray());
frame.add(text);
frame.add(panel, BorderLayout.CENTER);
frame.setSize(400,100);
frame.setVisible(true);
}
}

Your problem is in the line: box.addItem(list.toArray());
The addItem() method for a JComboBox requires an Object as a parameter. Typically, a String object is used as the parameter. Instead, what you are doing is trying to add the actual list reference to the combo box when you really meant to add every item in the list.
Instead, you should add the individual items in the list as follows:
for(Stuff stuff : list) {
box.addItem(stuff.getName());
}
EDIT: Reading your original worded problem again, I think your second code snippet regarding the GUI should not be using another ArrayList of Stuff if you actually intended to use the ArrayList in one instance of a Stuff object. Therefore, you should first change:
private ArrayList<Stuff> list = new ArrayList<>();
to
private Stuff = new Stuff();
and change the for loop to iterate through stuff.getList() instead of list. To clarify, the for loop might look like:
for(String detail : stuff.getList()) {
box.addItem(detail); // add each extracted detail from the text file that was stored in the list of the stuff object
}

The main problem is you're adding a single array to the combo box...
box.addItem(list.toArray());
You either need to build a ComboBoxModel around the list the list of items...
DefaultComboBoxModel model = new DefaultComboBoxModel(list.toArray(new Stuff[list.size()]));
box.setModel(model);
Or add each item from the list to the combo box
for (Stuff stuff : list) {
box.addItem(stuff);
}
See How to use combo boxes for more details
The responsibility for rendering values in JComboBox comes down to the ListCellRenderer.
Take a look at Providing a Custom Renderer for more details.
This approach allows you to custom how the object is rendered without having to modify the underlying object itself. It also means that you maintain the object relationship. Rather then having to provide some kind of mapping between the view and the original list of objects, you can simply get a direct reference of the object from the combo box, because the ListCellRenderer is performing the transformation for you.
Now, having said that, this will prevent the user from been able to use combo boxes built in search feature.
To that end, check Combo Box With Custom Renderer for a possible work around. This is authored by our very own camickr

Related

how can I search an item by property in a JList

I have an item class and I store items in a JList, I would like to look for an item in a JList by the name of the item. I have a JButton called search that implements the next code, and nameToSearch variable gets the string written in a JTextfield,
ArrayList<Item> backUp = new ArrayList<>();
ArrayList<Item> itemsFound = new ArrayList<>();
for (int i = 0; i < listModel.getSize(); i++) {
backUp.add(listModel.getElementAt(i));
if (listModel.getElementAt(i).getName().compareToIgnoreCase(nameToSearch) >= 0) {
Item foundItem = listModel.getElementAt(i);
itemsFound.add(foundItem);
}
}
//clear the listModel to display the found items
listModel.removeAllElements();
//add the found items to the listModel to be displayed
for (Item s: itemsFound) {
listModel.addElement(s);}
I meant to get all the items in a backup arrayList to show all of the items later, the if statement checks is the item.getName() is is what the user is looking for and it adds that element to a itemfound ArrayList, it is supposed to have the item found in the arrayList, then i remove all the elements from the defaultListModel listmodel and then add the found items in the defaultListModel listModel so the found items are the only ones on the JList for that moment.
But this doesnt work,it doesnt do anything. any suggestion on how to do it is well appreciated
A very simple solution can be to:
Store all items into a collection of your preference, or array.
Use a DefaultListModel (which is programmatically dynamic) for the JList.
For each search operation clear the model and then add one after another the items which match the search criteria.
For example:
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
public class Main {
//Assuming the class Item is similar to the following implementation:
public static class Item {
private final String name;
public Item(final String name) {
this.name = Objects.requireNonNull(name);
}
public String getName() {
return name;
}
//Overriding toString method, to avoid implementing a custom ListCellRenderer...
#Override
public String toString() {
return getName();
}
}
public static void main(final String[] args) {
//The following list contains all the initial Items lets say:
final List<Item> allItems = Arrays.asList(new Item("abc"), new Item("ABC"),
new Item("def"), new Item("DEF"));
//Create a DefaultListModelof Items (which is programmatically dynamic):
final DefaultListModel<Item> listModel = new DefaultListModel<>();
//Supply the model to the list on creation time (or later with setModel):
final JList<Item> list = new JList<>(listModel);
//Initialize the listModel to initially contain all items:
allItems.forEach(item -> listModel.addElement(item));
final JTextField searchField = new JTextField(10);
final JButton searchButton = new JButton("Search");
searchButton.addActionListener(e -> {
final String searchText = searchField.getText();
listModel.clear();
if (searchText.isEmpty()) //If the search text field is empty, lets say we want to display all values:
allItems.forEach(item -> listModel.addElement(item));
else
allItems.forEach(item -> {
if (item.getName().equalsIgnoreCase(searchText))
listModel.addElement(item);
});
});
final JPanel pageStart = new JPanel(); //FlowLayout
pageStart.add(searchField);
pageStart.add(searchButton);
final JPanel all = new JPanel(new BorderLayout());
all.add(pageStart, BorderLayout.PAGE_START);
all.add(new JScrollPane(list), BorderLayout.CENTER);
final JFrame frame = new JFrame("Searchable JList");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(all);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

Adding New Item to JComboBox Does Not Display

I have a JComboBox that is editable. When the user enters a new item, I want that added to the list and display it as the selected item. I am able to add it to the list but I cannot seem to make it display as the selected item. By default I display an empty string ("") which is what the user would edit to add the new item.
public class EventComboBoxListener implements ActionListener {
private JComboBox<String> eventBox=null;
public EventComboBoxListener(JComboBox<String> event_) {
eventBox=event_;
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Selected: " + eventBox.getSelectedItem());
System.out.println(", Position: " + eventBox.getSelectedIndex());
if (eventBox.getSelectedIndex() < 0) {
eventBox.addItem(eventBox.getSelectedItem().toString());
eventBox.setSelectedItem(eventBox.getSelectedItem().toString());
}
}
}
It doesn't make sense to me that I have to use setSelectedItem with the getSelectedItem. That it does not work is no surprise but I don't know what else to do. The newly added item shows up in the list as it should but how do I make it the selected item in the display at the same time? I can select it after but that should not be necessary.
Added MVCE:
Main
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
public class Test {
public static void main(String[] args) {
String[] list= {"","A","B","C"};
TestTableModel model=new TestTableModel(null,new String[] {"col1","col2"});
JTable table=new JTable(model);
JDialog dialog=new JDialog();
JScrollPane scroller=new JScrollPane(table);
JComboBox<String> box=new JComboBox<String>(list);
box.setEditable(true);
box.setSelectedIndex(0);
box.addActionListener(new EventComboBoxListener(box));
JTextField field=new JTextField();
field.setPreferredSize(new Dimension(75,30));
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setLayout(new FlowLayout());
dialog.setSize(new Dimension(400,100));
dialog.add(scroller);
dialog.pack();
dialog.setVisible(true);
table.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(box));
table.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(field));
model.insertRow(0,new Object[] {"","placeholder"});
}
}
TestTableModel class
import javax.swing.table.DefaultTableModel;
public class TestTableModel extends DefaultTableModel {
/**
*
*/
private static final long serialVersionUID = 1L;
public TestTableModel(Object[][] data_,String[] columnNames_) {
super(data_,columnNames_);
}
}
First of all some comments about the MCVE (since you will be including one with every question in the future).
We expect the code so be in a single source file so we can easily copy/paste compile and test. We don't want 3 files lying around on our machine that we need clean up after testing.
Only relevant code directly related to the problem should be included. Why do you have the TestTableModel class. Are the "column names" relevant to the problem? The point is always test your MCVE using standard JDK classes when possible.
Regarding the EventComboListener class. Again, this can be added to the combo box by using and annoymouse inner class or a lambda. This keeps the code in a single class.
The newly added item shows up in the list as it should but how do I make it the selected item in the display at the same time?
I found that playing with your MCVE the ActionListener of the combo box is invoked at different times.
So my suggestion is to add the ActionListener to the editor of the combo box. Then we know for sure the ActionListener is only invoked when you press the Enter key. Once you press the Enter key the editor is stopped and the value is saved to the model.
So the logic would be something like:
//box.addActionListener(new EventComboBoxListener(box));
ComboBoxEditor editor = box.getEditor();
JTextField textField = (JTextField)editor.getEditorComponent();
textField.addActionListener( new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
String item = textField.getText();
DefaultComboBoxModel model = (DefaultComboBoxModel)box.getModel();
if (model.getIndexOf(item) == -1)
{
box.addItem(item);
box.setSelectedIndex( box.getItemCount() - 1 );
}
}
});
So the trick is to set the select index (not the selected item). But first the logic checks to make sure the item has not already been added to the combo box.

JComboBox ItemListener error

Looking at adding event listeners to JComboBoxes.
Ive done the usual window etc. Created a new JComboBox and then .addItem() islands into it.
I have then attempted to use .addItemListener(this) on my newly created combobox
But theres a problem, its mentioning abstract class which means i have not done something. Can anyone see where ive gone wrong?
Ive tried .addItemListener(this) on the individual entries and that did not work. Ive tried declaring the JComboBox inside and outside of the constructor.
It may be worth noting the method itemStateChange is from the book, I have had to build around that block.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class ComboBoxPractice extends JFrame implements ItemListener
{
//create islands
JLabel selection = new JLabel();
JComboBox islands = new JComboBox();
public ComboBoxPractice()
{
// set a window
super("action");
setSize(300,100);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
// set a container
Container content = getContentPane();
FlowLayout layout = new FlowLayout();
content.setLayout(layout);
//add item listener
islands.addItemListener(this);
// add items to list
islands.addItem("Corfu");
islands.addItem("Crete");
islands.addItem("Canada");
islands.addItem("Canary Islands");
//add island and label to container
content.add(islands);
content.add(selection);
}
public void itemStateChange(ItemEvent event)
{
String choice = event.getItem().toString();
selection.setText("chose" + choice);
}
}
#Override
public void itemStateChanged(ItemEvent event)
{
String choice = event.getItem().toString();
selection.setText("chose" + choice);
}
Try changing it to that. with the #Override on top. This then doesn't throw an error for me and works.

How to refresh JTable with always-changing tableModel?

2015.5.5 22:11 updated. I found that, when I create a sorter and call setRowSorter() method in the MyTable's construction, afterwards, it
will keep the original line number(in which the data still refresh
correctly but not easy to discover)even though the dataModel inside is
already changed many times which can be proven as
printf(getModel().getRowCount()). What's more,
MyTable.getAutoCreateRowSorter() always return true. I must explicitly
call setAutoCreateRowSorter(true) to fix the issue it if I called
setRowSorter(). I am happy but this is still wierd.
[2015.5.5 6:19 updated] I have found a way to access: make a "setRow" with the combination of insertRow() and removeRow() and everytime I
update, I setrRow all the rows in the table. It reflect immediately in
the UI. BUT there will be a series of error begin with "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Invalid index" and I guess it's about some kiind of swing thread problem because I can see "RepaintManager" or "paint" in the error. It occurs especially when I move the scrollbar when it's running.(but it still occur if I don't move it)
I have a JTable in a JScrollPane in a JFrame. I initial a MyTableModel with a data and use it to Create a JTable.Like this
MyTableModel extends DefaultTableModel{
Data data;
//object
MyTableModel (Data a){
data = a;
// do something to initial the table model,like build object[][] and add rows.
}
}
class main{
MyTableModel tm = new MyTableModel(data);
Jtable table = new JTable(tm);
JScrollpane jsp = new JScrollpane(table);
JFrame window = new JFrame();
window.getxxxpane().add(jsp);
}
So, as my data is always changing/updating and the changed row is plural and impossible to caculate.
while(true){
data.change();
refreshing the table to display the data immediately;
}
my idea is to simply build a new MyTableModel object and set it as the table's model like:
table.setModel(new MyTableModel(data)); //data had been changed.
which doesn't work.
and I tried this:
tm = MyTableModel(data);
tm.fireTableDataChanged();
which doesn't work either.
and the combination as well:
MyTableModol nm = new MyTableModel(data); //data had been changed
table.setModel(nm);
nm.fireTableDataChanged();
Could someone please give me some clue to change the TableModel object in an unchangable Jtable and update everytime.I dont want to change the tableModel Object because the calculation is huge, instead ,i Want to always create a new object with the construction method's parameter(changed data).
the most worst method is to remove the JScrollpane and rebuild one table/tablemodel/jscrollpane and re-add it, in which I have to call window.setVisible(true). window.repait() doesn't work,either,unless I move it.
I create a space-wasting but runnable program for demostration ,which most of them are nonsense.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Formatter;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
class TM extends DefaultTableModel {
int[] inside;
static String[] columnNames = { "Hellium", "Radon",
};
TM(int[] data) {
super(columnNames, 0);
this.inside = data;
update();
}
void update() {
Object[][] data = new Object[2][columnNames.length];
for (int i = 0; i < 2; ++i) {
data[i][0] = inside[0];
data[i][1] = inside[1];
// setValueAt(aValue, row, column);
addRow(data[i]);
}
fireTableDataChanged();
}
}
class idkName {
TM tm;
JButton jb, jb2;
int data[] = { 1, 2 };
int data2[] = { 9, 10 };
JTable table;
JScrollPane jsp;
JFrame twindow;
idkName() {
JFrame window = new JFrame();
window.setSize(400, 400);
window.setLayout(new BorderLayout());
jb = new JButton("show");
jb2 = new JButton("change model");
window.add(jb, BorderLayout.EAST);
window.add(jb2, BorderLayout.WEST);
twindow = new JFrame();
jb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
tm = new TM(data);
table = new JTable(tm);
jsp = new JScrollPane(table);
twindow.getContentPane().add(jsp);
twindow.setSize(500, 500);
twindow.setVisible(true);
}
});
window.setVisible(true);
jb2.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
// tm = new TM(data2);
tm = new TM(data2);
System.out.println(""+tm.getValueAt(0,0));
tm.fireTableDataChanged();
twindow.setVisible(true);
}
});
}
}
public class main2 {
TM tm;
public static void main(String[] args) {
idkName i = new idkName();
}
}
If you're going to extend DefaultTableModel, then when the data is changed, you need to update it in DefaultTableModel's internal representation using the setValueAt or setDataVector methods. If you have extensive changes (which it sounds like you do), use the setDataVector method to change all the cells and the table structure at once. Your example code looks like it's missing some updates because it's not pushing the new values in to the DefaultTableModel's data vectors.
But since you have a lot of updates, you're probably better off avoiding DefaultTableModel and just extending AbstractTableModel, using your own custom data storage, and calling the appropriate fireXxx() methods whenever your data changes. This will probably be more efficient in terms of both data conversion and number of events raised.
And then make sure all the event and value change work is done on the AWT event thread, using SwingWorkers or other threading support.

Java - updating textFields from JList

I am making an address book GUI with Java and I have a JList that displays all the names of the people in my ArrayList (this is populated by the updateinfo method mentioned below). I want it so when I click an item on the JList, the TextFields are then updated with that persons details. Before I have only used buttons and therefore actionListeners. I think I understand that a JList must use ListSelectionListener but I cannot seem to implement this. I have added a snippet of my code below. Can somebody please help?? For continuity with my actionlisteners I would like to have it as an inner class but this is not vital
JList jl;
DefaultListModel list;
list = new DefaultListModel();
this.jl = new JList(this.list);
//add ListSelectionListener????
updateList();
this.add(this.jl, layout);
You can add the listener and then just query the currently selected index.
I did a sample for you, I hope you find it useful.
This is the relevant section:
private JComponent list() {
final JList list = new JList( data);
list.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
int i = list.getSelectedIndex();
nameTextField.setText( i >= 0 ? data.get( i ) : "" );
}
});
return new JScrollPane( list );
}
Bear in mind that's not the only way to go, this is just an starting point for you.
Here's the complete working sample:
import java.util.Vector;
import java.util.Arrays;
import java.awt.BorderLayout;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JLabel;
import javax.swing.JComponent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.ListSelectionEvent;
public class JListSample {
private Vector<String> data = new Vector<String>(
Arrays.asList( new String [] {
"one", "two", "three"
})
);
private JTextField nameTextField;
public static void main( String [] args) {
JListSample s = new JListSample();
s.run();
}
public void run() {
JFrame frame = new JFrame("Selection test");
frame.add( list(), BorderLayout.WEST );
frame.add( editPanel() );
frame.pack();
frame.setVisible( true );
}
private JComponent list() {
final JList list = new JList( data);
list.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
int i = list.getSelectedIndex();
nameTextField.setText( i >= 0 ? data.get( i ) : "" );
}
});
return new JScrollPane( list );
}
private JComponent editPanel() {
JPanel panel = new JPanel();
panel.add( new JLabel("Name:") );
nameTextField = new JTextField(10);
panel.add( nameTextField );
return panel;
}
}
This is what is displayed:
sample http://img177.imageshack.us/img177/6294/capturadepantalla200911k.png
You just add the selection listener to the list, like that:
jl.addSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
// evaluate e if necessary and call a method
// in your class to write the text in the textfield
int selectedRow = e.getFirstIndex(); // more complicate for multiselects
updateTextFieldWithName(selectedRow); // to be implemented
}
});
Using an anonymous class like here is the quickest way. It's a bit hard to read but a typical pattern.
(just read you preferred an inner class, but I can't code that here on the fly with no IDE at hand...)
Yes you will want to use a ListSelectionListener for this, you will also probably want to set the list to single selection(ListSelectionModel.SINGLE_SELECTION). This will allow the user to only select one item in the list. You can then add you listSelectionListener, and in the valueChanged of the event do something like the following(not exact syntax).
valueChanged(ListSelectionEvent e){
int idx = e.getFirstIndex();
int idx2 = e.getLastIndex(); //idx and idx2 should be the same if you set SingleSel
if(idx==idx2){
//here you can get the person detail however you have them stored. You can get them from the model like so,
Object personObj = MYLIST.getModel().getElementAt(int index);
}
}
I think I understand that a JList must
use ListSelectionListener but I cannot
seem to implement this
Well, then start by reading the JList API. You will find a link to the Swing tutorial on "How to Use Lists", which contains a working example.
Also in the tutorial you will find a section on "How to Write a List Selection Listener" which contains a second example.
Start with the tutorial for your basic questions.

Categories

Resources