I have a JComboBox contain 9 image items. How can I arrange them to 3*3?
I want my items arrange like this
I've tried google it for several days, but I don't know what is the keyword for this question. Any advice is appreciated. Thanks for reading my question.
Edit:
Thanks for everyone's feedback.
I am making a map editor where I can put map element together.
The editor.
It is more intuitive to arrange the stone road 3*3. The user can easily know which elements match each other.
I am not necessarily using combo box. I've also consider using buttons, but I think that buttons will waste a lat of space. Because I will have more map elements in the future.
The popup for the combo box uses a JList component to display the items.
You can access and change the orientation of the JList to wrap items:
Object child = comboBox.getAccessibleContext().getAccessibleChild(0);
BasicComboPopup popup = (BasicComboPopup)child;
JList list = popup.getList();
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(3);
This will allow you to navigate through the items using the up/down keys.
To support navigation using the left/right keys you need to add additional Key Bindings to the combo box:
InputMap im = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(KeyStroke.getKeyStroke("LEFT"), "selectPrevious");
im.put(KeyStroke.getKeyStroke("RIGHT"), "selectNext");
However, the popup is based on the width of the largest item added to the combo box. This will be a problem as you won't see all the items. The items will scroll as you use the keys to navigate, but you won't see all 9 items at one time.
To solve this problem you check out Combo Box Popup. It has features that allow you to control the size/behaviour of the popup. You would use:
BoundsPopupMenuListener listener = new BoundsPopupMenuListener(true, false);
comboBox.addPopupMenuListener( listener );
It is possible to do this by giving the JComboBox a custom UI. However that UI is, at least in my case, a mess, so only use it as a workaround and clearly mark it as such with inline documentation.
The following code is made for the default Swing Look and Feel (Metal). If you want to use another L&F you need to exchange the class the custom UI is extending and maybe do some other adjustements. This will get tricky for distribution specific L&F (may force you to use reflection and delegation).
(If you also want to center your images, see this answer.)
Code:
(Sorry for the wierd comment wrapping, don't know whats wrong with my eclipse.)
To set the UI:
comboBox.setUI(new WrapComboBoxUI(3)); // 3 columns
Source code of WrapComboBoxUI:
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;
public class WrapComboBoxUI extends MetalComboBoxUI {
private int columnCount;
public WrapComboBoxUI() {
this(0);
}
/**
* #param columnCount
* the amount of items to render on one row. <br/>
* A value of 0 or less will cause the UI to fit as many items
* into one row as possible.
*/
public WrapComboBoxUI(int columnCount) {
this.columnCount = columnCount;
}
public static ComponentUI createUI(JComponent c) {
return new WrapComboBoxUI();
}
#Override
protected ComboPopup createPopup() {
ComboPopup created = super.createPopup();
try {
if (created instanceof JPopupMenu) {
JPopupMenu popup = (JPopupMenu) created;
JScrollPane scroll = (JScrollPane) popup.getComponent(0);
JList<?> elementList = (JList<?>) scroll.getViewport().getView();
elementList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
elementList.setVisibleRowCount(-1);
new Thread() {
#Override
public void run() {
while (true) {
try {
int fixedWidth = -1;
if (columnCount > 0) {
int width = elementList.getWidth() - elementList.getInsets().left - elementList.getInsets().right;
fixedWidth = width / columnCount;
}
boolean changed = false;
if (fixedWidth < 0 && elementList.getFixedCellWidth() >= 0 || fixedWidth >= 0 && elementList.getFixedCellWidth() < 0) {
// if changed from not fixed to fixed or
// other way around
changed = true;
} else if (fixedWidth > 0 && fixedWidth - elementList.getFixedCellWidth() > 1) {
// if width itself changed, ignoring slight
// changes
changed = true;
}
final int width = fixedWidth;
// no need to loop again before this is done, so
// we wait
if (changed)
SwingUtilities.invokeAndWait(() -> elementList.setFixedCellWidth(width));
sleep(100);
} catch (Throwable e) {
// ignored
}
}
};
}.start();
}
} catch (Throwable e) {
System.err.println("Failed to customize ComboBoxUI:");
e.printStackTrace();
}
return created;
}
}
Related
I am trying to build a GUI application that will let the user to choose product by clicking the button. I hold products in an ArrayList and then use this ArrayList and for loop to create proper number of JButtons. When user clicks the button price of that product should appear in the TextField.
My problem is: how to find out which button was clicked? If I was using Array of Buttons (JButton button[] = new JButton[3]) I would find it in the loop:
if (target.equals(button[i]))...
But I can't figure out how to find it when I use ArrayList of products to create buttons. Any help would be well appreciated. Here's my code (I tried many approaches so I only post the one I started with - it finds only the last item in the ArrayList).
public void addStuff() {
stuffList.add(new Stuff("Lemon Haze", 15.00));
stuffList.add(new Stuff("OG Kush", 16.00));
stuffList.add(new Stuff("Strawberry Cough", 18.00));
for (int i = 0; i < stuffList.size(); i++) {
stuffButton = new JButton();
stuffPanel.add(stuffButton);
stuffButton.setText(stuffList.get(i).getName());
stuffButton.addActionListener(this);
}
}
public void actionPerformed(ActionEvent e) {
Object target = e.getSource();
for (int i = 0; i < stuffList.size(); i++) {
if (target == stuffButton) {
subtotalTextF.setText(stuffList.get(i).getPrice() + "");
}
}
}
Create a specific class for your ActionListener, and give it a reference to your Stuff - this way you can create a specific instance for each button that automatically links back to the correct instance of Stuff, without trying to search on the fly:
stuffButton.addActionListener(new StuffListener(stuffList.get(i));
...
private class StuffListener implements ActionListener {
private final Stuff myStuff;
public StuffListener(Stuff stuff) {
this.myStuff = stuff;
}
public void actionPerformed(ActionEvent e) {
subtotalTextF.setText(String.valueOf(myStuff.getPrice()));
}
}
Note that you can accomplish this with a bit less code using lambdas, but figured this is the clearest way to explain the logic, which is the same either way.
On a side note, based on the code you've posted, the reason it's only getting the last button is because you're comparing to stuffButton, which is not changed from the last instance after your initialization loop is done.
I have a JComboBox with a custom model which extends DefaultComboBoxModel.
When I want to add an item to my combo box I add it to the model and repaint the JComboBox. However, this is leaving the internal field:
selectedItemReminder
unchanged. What should I be doing instead.
I'm not sure I understand what it is you are trying to achieve, but I might be temptered to modify the method to read more like...
private void setChildren(Collection<BoundedArea> children) {
int oldSize = getSize();
// Notify the listeners that the all the values have begin removed
fireIntervalRemoved(this, 0, oldSize - 1);
this.children.clear();
for (BoundedArea boundedArea : children) {
if (boundedArea.getBoundedAreaType() == childType) {
this.children.add(boundedArea);
}
}
int size = getSize();
// Notify the listeners that a bunch of new values have begin added...
fireIntervalAdded(this, 0, size - 1);
setSelectedItem(null);
}
The other issue I can see is you seem to be thinking that the list is 1 based, it's not, it's 0 based, that is, the first element is 0
Updated based on changes to the question
From what I can understand, intervalAdded and contentsChanged of the JComboBox check to see if the selected value in the combo box model has changed, if it has, it calls selectedItemChanged which fires appropriate events to signal the change of the selected item...
I would, when you change the model, set the currently selected item value to something like null BEFORE you fire any event notifications...
So, using the previous example, I would do something more like...
private void setChildren(Collection<BoundedArea> children) {
setSelectedItem(null);
int oldSize = getSize();
// Notify the listeners that the all the values have begin removed
fireIntervalRemoved(this, 0, oldSize - 1);
this.children.clear();
for (BoundedArea boundedArea : children) {
if (boundedArea.getBoundedAreaType() == childType) {
this.children.add(boundedArea);
}
}
int size = getSize();
// Notify the listeners that a bunch of new values have begin added...
fireIntervalAdded(this, 0, size - 1);
}
I am not sure how to ask this. The program I am working on is done, but it seems like it has excessive code. Here is part of the code:
chkDef1 = new JCheckBox
if (chkDef1.isSelected()) {
actual = chkDef1.getText();
}
else if (chkDef2.isSelected()) {
actual = chkDef2.getText();
}
else if (chkDef3.isSelected()) {
actual = chkDef3.getText();
}
else {
actual = chkDef4.getText();
}
There are other areas where there is a lot of duplicate code with the chkDef1 - 4 checkboxes. What I would like to do is use a loop in the areas where the code is duplicated and then just use 1 assignment statement.
I've tried :
if(('chkDef' + counter).isSelected())
I've also tried assigning "'chkDef' + counter" to a String variable and then adding isSelected. Unfortunately I keep getting error messages.
I am a novice programmer so I do not know if what I want to do is possible or what it is called. If it is possible an explanation of how would be appreciated.
Simply create a list of checkboxes and iterate through it.
ArrayList<JCheckBox> checkboxes = new ArrayList<JCheckBox>();
//Init your checkboxes array.
for(JCheckbox chkbox :checkboxes)
{
if(chkbox.isSelected())
{
actual = chkbox.getText() ; break;
}
}
Although, there could be a JCheckbox group that does what you want.
Looks like you can use ButtonGroup and get the elements to iterate through it.
You could create an array that contains all the check boxes and then loop through the array...
JCheckBox[] boxes = new JCheckBox[] {chkDef1,chkDef2,chkDef3,chkDef4}
for (JCheckBox box : boxes) {
if (box.isSelected()) {
actual = box.getText();
break; // We don't want to loop unnecessarily
}
}
Equally, you could create a simple method that takes a variable number of arguments...
public String getCheckedItem(JCheckBox... boxes) {
String actual = null;
for (JCheckBox box : boxes) {
if (box.isSelected()) {
actual = box.getText();
break; // We don't want to loop unnecessarily
}
}
return actual;
}
And call it like...
String actual = getCheckItem(chkDef1, chkDef2, chkDef3, chkDef4);
Personally, I'd return the check box, but that's up to you
If you're only interested in maintaining a single selected check box (ie not allowing multiple check boxes to be selected) then you should seriously consider using JRadioButtons and ButtonGroup instead.
Otherwise you could collect ALL the selected checked boxes...
public JCheckBox[] getCheckedItem(JCheckBox... boxes) {
List<JCheckBox> selected = new ArrayList<JCheckBox>(boxes.length);
for (JCheckBox box : boxes) {
if (box.isSelected()) {
selected.add(box);
}
}
return selected.toArray(new JCheckBox[selected.size]);
}
I have a JTable and a button next to it that calls deleteSelectedRows(), which does exactly what it sounds like:
public void deleteSelectedRows() {
int[] selected = jTable.getSelectedRows();
for(int i = selected.length - 1; i >= 0; i--) {
model.removeRow(selected[i]);
}
if(model.getRowCount() < 1) {
addEmptyRow();
}
}
But if a cell was in the act of being edited when it (and/or cells above it) were deleted, the edited cell stayed while the rest left, like this:
And then trying to exit out of the editing threw an ArrayIndexOutOfBoundsException since row 5 was trying to be accessed and there was only one row left in the table.
I then tried all sorts of fun and games with jTable.getEditingRow(). At first, adding an if(selected[i] != editing) before the removal seemed to work, but then removing rows above the edited cell caused problems.
Then I tried this:
public void deleteSelectedRows() {
int[] selected = jTable.getSelectedRows();
int editing = jTable.getEditingRow();
for(int s : selected) { //PS: Is there a better way of doing a linear search?
if(s == editing) {
return;
}
}
for(int i = selected.length - 1; i >= 0; i--) {
model.removeRow(selected[i]);
}
if(model.getRowCount() < 1) {
addEmptyRow();
}
}
But that doesn't delete anything, ever. Judging from printlns I sprinkled around, the last cell to be highlighted (that has the special border seen here on spam) is considered part of the editing row, and thus triggers my early return.
So I don't really care whether the solution involves fixing the original problem--that of the wacky results when a cell being edited is deleted--or this new problem--that of getEditingRow() not behaving as I expected, it's just that I need at least one of those to happen. That said, I would be interested to hear both solutions just out of academic curiosity. Thanks in advance.
Try to include the following lines before removing any rows from your model:
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
As Howard stated, it is necessary to stop the cell editing before modifying the model. But it is also necessary to check if the cell is actually being modified to avoid null pointer exceptions.
This is because the getCellEditor() method will return null if the table isn't being edited at the moment:
if (myTable.isEditing()) // Only if it's is being edited
myTable.getCellEditor().stopCellEditing();
...
there are cases where the cell editor may refuse to stop editing,
that can happen i.e. if you are using some complex editor that is waiting for user input on a dialog. In that case you should add an extra check:
if (myTable.isEditing())
if (!myTable.getCellEditor().stopCellEditing()) {
// If your update is user-generated:
JOptionPane.showMessageDialog(null, "Please complete cell edition first.");
// Either way return without doing the update.
return;
}
In your code, you are trying to delete only the rows that are not being edited, but that would also throw an ArrayOutOfBounds Exception when the cell editor stops editing. The best is to stop it before the refresh.
Finally, there seems to be also a property you can set in your table:
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
as explained here.
Whilst stopping any and all cells from editing before applying any changes works, it's a bit like using a sledge hammer to crack a nut. What happens, for example, if the cell that is editing is not the one being deleted? This is the next problem you'll encounter. For that reason and others there is a better way.
Firstly, use the framework to do the heavy lifting for you. Attach a TableModelListener to your table model table.getModel().addTableModelListener()... then in your listeners implementation catch the delete event and process as follows:
/**
* Implements {#link TableModelListener}. This fine grain notification tells listeners
* the exact range of cells, rows, or columns that changed.
*
* #param e the event, containing the location of the changed model.
*/
#Override
public void tableChanged(TableModelEvent e) {
if (TableModelEvent.DELETE == e.getType()) {
// If the cell or cells beng edited are within the range of the cells that have
// been been changed, as declared in the table event, then editing must either
// be cancelled or stopped.
if (table.isEditing()) {
TableCellEditor editor = table.getDefaultEditor(ViewHolder.class);
if (editor != null) {
// the coordinate of the cell being edited.
int editingColumn = table.getEditingColumn();
int editingRow = table.getEditingRow();
// the inclusive coordinates of the cells that have changed.
int changedColumn = e.getColumn();
int firstRowChanged = e.getFirstRow();
int lastRowChanged = e.getLastRow();
// true, if the cell being edited is in the range of cells changed
boolean editingCellInRangeOfChangedCells =
(TableModelEvent.ALL_COLUMNS == changedColumn ||
changedColumn == editingColumn) &&
editingRow >= firstRowChanged &&
editingRow <= lastRowChanged;
if (editingCellInRangeOfChangedCells) {
editor.cancelCellEditing();
}
}
}
}
}
In the example above I've assigned my own editor as the default editor for the table table.setDefaultRenderer(ViewHolder.class, new Renderer()); table.setDefaultEditor(ViewHolder.class, new Editor());.
Additionally instead of using a specific view I use a ViewHolder. The reason for this is to make the table generic in terms of the views it displays. Here is the generic ViewHolder.class:
/**
* Holds the view in a table cell. It is used by both the {#link Renderer}
* and {#link Editor} as a generic wrapper for the view.
*/
public static abstract class ViewHolder {
private static final String TAG = "ViewHolder" + ": ";
// the position (index) of the model data in the model list
protected final int position;
// the model
protected Object model;
// the view to be rendered
protected final Component view;
// the views controller
protected final Object controller;
/**
* #param view the view to be rendered
* #param position the position (index) of the data
*/
public ViewHolder(int position,
Object model,
Component view,
Object controller) {
this.position = position;
if (view == null) {
throw new IllegalArgumentException("item view may not be null");
}
if (model == null) {
throw new IllegalArgumentException("model may not be null");
}
this.controller = controller;
this.model = model;
this.view = view;
}
Now, each time your renderer or editor is called, construct a ViewHolder class and pass in your view / controller / position etc, and you're done.
The important thing to note here is that you do not have to catch the delete or change event before it happens. You should, in fact, catch it after the model changes. Why? Well after a change you know what has changed, because the TableModelListener tells you, helping you determine as to what to do next.
1) In the following method (actionListener) a user select a grade (e.g. A-F) from a JComboBox.
2) There are multiple JComboBoxes, and each selection made gets stored into a single String[] array.
PROBLEM:
Here is the dilemma, if a user goes back and changes a selection made from a random JComboBox the previous grade selection does not get replaced in the array, however the new selection made gets stored at the next array index.
How can I make the program replace the previous grade selection and not just add the new selection?
relevant variables:
int counter;
private JComboBox[] gradeField;
//grade.userGrades[] is array of grades taken from selected combo boxes
Action Listener anonymous class:
gradeField[counter].addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e) {
Object holder = e.getSource();
JComboBox tempGradeBox = (JComboBox)holder;
String theGrade = (String)tempGradeBox.getSelectedItem();
grade.userGrades[grade.getNext()] = theGrade;
grade.updateNext();
}
});
Thanks in advance for any help.
I save the grade in an array and increment the index,
Well you should not be incrementing the index. This assumes that the user selects the grades from the combo box in a sequential order. As you have discovered users can often work randomly.
Instead you need to know which combo box has been changed and then update the appropriate entry in your array.
Or a different solution might be to update your array at the end. So maybe you have a "Process Results" button. Then you can sequentually loop through all the combo boxes to get the selected value.
Update the user grade being at the same index as the combo box:
final int index = counter;
gradeField[counter].addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e) {
Object holder = e.getSource();
JComboBox tempGradeBox = (JComboBox)holder;
String theGrade = (String)tempGradeBox.getSelectedItem();
grade.userGrades[index] = theGrade;
}
});
Here's another variation of JB Nizet's answer:
class OuterClass
{
...
gradeField[counter].addActionListener( new GradeSettingActionListener( counter ) );
...
class GradeSettingActionListener implements ActionListener
{
// -- Doesn't have to be final here (it does in JB's answer), but I like to be restrictive.
private final int index;
public GradeSettingActionListener( int index )
{
this.index = index;
}
#Override
public void actionPerformed( ActionEvent e )
{
Object holder = e.getSource();
JComboBox tempGradeBox = (JComboBox) holder;
String theGrade = (String) tempGradeBox.getSelectedItem();
grade.userGrades[index] = theGrade;
}
}
}
This approach removes the anonymous class by adding an inner class. The inner class will still have access to grade. You don't gain much here unless there's a chance you'll be splitting out the inner class later.
Of course, camickr's suggestion to process all the grades at once may also be valid, depending on other requirements (i.e., whether additional processing is done after the grades are stored in the array, which seems likely).