I have many custom editors for a JTable and it's an understatement to say that the usability, particularly in regard to editing with the keyboard, is lacking.
The main reason for this is that my editors are always created with a similar (though often more complex) situation to this:
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(field, BorderLayout.CENTER);
field.setText((String) value);
container.add(new JButton("..."), BorderLayout.EAST);
return container;
}
I.E a panel with more than one component inside. The actual text editor is a descendant of the component being returned as the editor.
So, rendering issues aside, from what I can tell, the JTable is focusing the component that is returned by the getTableCellEditorComponent method so when you press a key with a cell highlighted it passes focus and the key press to the panel, thinking that's the editor.
Is there anyway I can inform JTable that the "real" editor is the JTextfield?
Adding a hacky requestFocusInWindow on the correct component is insufficient as the key press won't get passed on.
If I read your question correctly, you want the user to be able to type into a cell immediately, without activating the cell editor first, i.e., you want whatever keystroke activated the cell to be the first character entered into the text field.
My first attempt was to add a propertyChangeListener on the focusOwner property of the KeyboardFocusManager, only to notice that the focus never leaves the JTable. You probably ran into that as well. Time for plan B.
I got that "first keypress" thing to work by adding a KeyListener to the table that records the last KeyEvent for the keyPressed() method in an instance field. The getTableCellEditorComponent() method reads the character from there. I also needed that hacky requestFocusInWindow() call you mention if the user is to keep typing any characters after the first one.
For my sample app, I created a subclass of JTable that adds a KeyListener to itself. It's a much better idea to make your CellEditor instance implement KeyListener and add that to the regular JTable instead, but I'll leave that to you.
Here's your code snippet as I modified it:
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(field, BorderLayout.CENTER);
// Will want to add an instanceof check as well as a check on Character.isLetterOrDigit(char).
char keypressed = ((StickyKeypressTable)table).getLastKeyPressed();
field.setText(String.valueOf(keypressed));
container.add(new JButton("..."), BorderLayout.EAST);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// This needs to be in an invokeLater() to work properly
field.requestFocusInWindow();
}
});
return container;
}
As far as nastiness goes this sits somewhere up there with Vogon Poetry, but it should solve your immediate problem.
I fixed something similar in 2 steps
First override the editCellAt from your JTable and call requestFocus after preparing the editor:
public boolean editCellAt( int row, int column, EventObject e )
{
if ( cellEditor != null && !cellEditor.stopCellEditing() )
{
return false;
}
if ( row < 0 || row >= getRowCount() ||
column < 0 || column >= getColumnCount() )
{
return false;
}
if ( !isCellEditable(row, column) )
return false;
TableCellEditor editor=getCellEditor(row, column);
if ( editor != null && editor.isCellEditable(e) )
{
editorComp=prepareEditor(editor, row, column);
if ( editorComp == null )
{
removeEditor();
return false;
}
//aangepast
Rectangle rect=getCellRect(row, column, false);
if ( datamodel_.useAdaptedEditorRect() )
rect=datamodel_.changeRectangle(rect, editorComp);
editorComp.setBounds(rect);
add(editorComp);
editorComp.validate();
setCellEditor(editor);
setEditingRow(row);
setEditingColumn(column);
editor.addCellEditorListener(this);
//NEXT LINE ADDED
editorComp.requestFocus();
return true;
}
return false;
}
Then overload the requestFocus from your JPanel and make sure your textfield is put as editorComponent:
public class EditorPanel extends JPanel {
JComponent editorComponent;
public boolean isRequestFocusEnabled()
{
return true;
}
public void requestFocus()
{
editorComponent.requestFocus();
}
}
You can always grab the keyEvent and set it yourself:
AWTEvent event = EventQueue.getCurrentEvent();
if ( event instanceof KeyEvent )
{
char newSelection = ( (KeyEvent) event).getKeyChar();
int keyCode = ( (KeyEvent) event ).getKeyCode();
editorComponent.requestFocus();
if ( editorComponent instanceof JTextField )
{
if ( ( newSelection >= (char) FIRST_ALLOWED_CHAR ) && ( newSelection != (char) LAST_ALLOWED_CHAR ) ) //comes from DefaultKeyTypedAction
( (JTextField) editorComponent ).setText(Character.toString(newSelection));
if ( keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE )
( (JTextField) editorComponent ).setText("");
}
}
else
editorComponent.requestFocus();
I think that I solved it.
To tell you the truth, I don't know what solved the problem, since I'm using a custom editor, a custom renderer and stuff...
When a cell is highlighted and I press "abc", the 3 letters go on screen (cell, in this case).
grid.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent ke) {
int l = grid.getSelectedRow();
int c = grid.getSelectedColumn();
grid.editCellAt(l, c);
}
});
Well... I tried... =)
(I don't know if it's the same because my JTable uses JTextField and JComboBox as editors).
I had very similar problem. In my case I had complex TableCellEditor which consists of JSpinner and some other components. The problem was that when cell editor started I wanted to transfer focus to its internal component. I fixed this by calling panel.transferFocusDownCycle() but this in turn caused keyboard events to stop working - when my internal component had focus and I pressed key up, I was expecting that component will intercept this event and change its value. Instead table changed row focus to one above... I fixed this by adding KeyListener and dispatching all key events directly to the internal component.
This is wrapper class based on JPanel I wrote to make my life easier.
public class ContainerPanel extends JPanel implements KeyListener, FocusListener {
private JComponent component = null;
public ContainerPanel(JComponent component) {
this.component = component;
addKeyListener(this);
addFocusListener(this);
setFocusCycleRoot(true);
setFocusTraversalPolicy(new ContainerOrderFocusTraversalPolicy());
add(component);
}
#Override
public void keyTyped(KeyEvent e) {
component.dispatchEvent(e);
}
#Override
public void keyPressed(KeyEvent e) {
component.dispatchEvent(e);
}
#Override
public void keyReleased(KeyEvent e) {
component.dispatchEvent(e);
}
#Override
public void focusGained(FocusEvent e) {
component.transferFocusDownCycle();
}
#Override
public void focusLost(FocusEvent e) {
}
}
Related
How can I prevent strings in a JTable and allow and show only numbers?
like for example I press "a" on my keyboard I won't not even that "a" will be displayed in the JTable cell. literally nothing should happen unless a user types in a number. so how can I prevent even not showing "a" ?
I had a similar issue some time ago and solved by validating with an KeyListener. This is a dirty way of doing it, but it works. The only weakness is if you're trying to edit a lot of cells quickly if you're a fast writer. Anyhow, here's the code that worked for me. I've added some commentary, but in short; we're overriding the normal validation and check with a TextField KeyListener if the given key is the one we allow in the TextField. If we allow the key, we enable TextField editing, if not, we turn it off to prevent the character being printed in the TextField. I hope this helps you.
UPDATE 1:
adding a celleditor on the TestField to prevent premature data insertion.
public class TableValidation extends JFrame
{
public static void main(String args[])
{
TableValidation x = new TableValidation();
x.setVisible(true);
}
JPanel topPanel;
JTable table = new JTable();
JScrollPane scrollPane;
String[] columnNames;
String[][] dataValues;
public TableValidation()
{
this.setTitle("JTable Cell Validation");
this.setDefaultCloseOperation (EXIT_ON_CLOSE);
this.setSize(300,112);
// make our panel to tin the table to
topPanel = new JPanel();
topPanel.setLayout(new BorderLayout());
this.getContentPane().add(topPanel);
// set some initial data for the table
columnNames = new String[] {"Anything" ,"Numbers only"};
dataValues = new String[][] { {"h4x0r","1337"} };
table.setRowHeight(50);
table.setModel( new CustomTableModel(dataValues, columnNames) );
TableColumn tableColumn = table.getColumnModel().getColumn(1); // apply our validation to the 2nd column
JTextField textfield = new JTextField(); // the textbox to which we test our validation
// setup our validation system. were passing the textfield as out celleditor source
tableColumn.setCellEditor(new MyCellEditor(textfield));
table.setCellSelectionEnabled(true);
scrollPane = new JScrollPane(table);
topPanel.add(scrollPane,BorderLayout.CENTER);
textfield.addKeyListener(new KeyAdapter()
{
public void keyTyped(KeyEvent e)
{
// check what keys can pass our test
if (textfield.isFocusOwner())
if (e.getKeyChar() != KeyEvent.VK_BACK_SPACE) // we allow backspace, obviously
if (!Character.isDigit(e.getKeyChar())) // if key is not a digit.. cancel editing
{
// when it detects an invalid input, set editable to false. this prevents the input to register
textfield.setEditable(false);
textfield.setBackground(Color.WHITE);
return;
}
textfield.setEditable(true);
}
});
}
}
class MyCellEditor extends AbstractCellEditor implements TableCellEditor
{
private static final long serialVersionUID = 1L;
private JTextField textField;
public MyCellEditor(JTextField textField)
{
this.textField=textField;
}
#Override
public boolean isCellEditable(EventObject e)
{
if (super.isCellEditable(e)) {
if (e instanceof MouseEvent) {
MouseEvent me = (MouseEvent) e;
return me.getClickCount() >= 2;
}
if (e instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) e;
return ke.getKeyCode() == KeyEvent.VK_F2;
}
}
return false;
}
#Override
public Object getCellEditorValue()
{
return this.textField.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
this.textField.setFont(table.getFont());
this.textField.setText(value.toString());
return this.textField;
}
}
class CustomTableModel extends DefaultTableModel
{
CustomTableModel(String[][] data,String[] names)
{
super(data, names);
}
// we always pass true in our tablemodel so we can validate somewhere else
public boolean isCellEditable(int row,int cols)
{
return true;
}
}
I'm building a custom table cell editor so it adjusts row height during editing. I have this code, but instead of resizing the cell it seams to resize the whole panel, or the frame. When I try to enter a character in a cell the main frame width narrows down to a couple of pixels.
Can anyone see the problem?
class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {
MyTextpane component = new MyTextpane();
MyTable table;
private int row;
private int col;
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int rowIndex, int vColIndex) {
((MyTextpane) component).setText((String) value);
component.addKeyListener(new KeyListener1());
this.table =(MyTable) table;
this.row = rowIndex;
this.col = vColIndex;
return component;
}
public Object getCellEditorValue() {
return ((MyTextpane) component).getText();
}
public class KeyListener1 implements KeyListener {
#Override
public void keyTyped(KeyEvent ke) {
}
#Override
public void keyPressed(KeyEvent ke) {
}
#Override
public void keyReleased(KeyEvent ke) {
adjustRowHeight(table, row, col);
}
private java.util.List<java.util.List<Integer>> rowColHeight = new ArrayList<java.util.List<Integer>>();
private void adjustRowHeight(JTable table, int row, int column) {
//The trick to get this to work properly is to set the width of the column to the
//textarea. The reason for this is that getPreferredSize(), without a width tries
//to place all the text in one line. By setting the size with the with of the column,
//getPreferredSize() returnes the proper height which the row should have in
//order to make room for the text.
int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth();
setSize(new Dimension(cWidth, 1000));
int prefH = getPreferredSize().height;
while (rowColHeight.size() <= row) {
rowColHeight.add(new ArrayList<Integer>(column));
}
java.util.List<Integer> colHeights = rowColHeight.get(row);
while (colHeights.size() <= column) {
colHeights.add(0);
}
colHeights.set(column, prefH);
int maxH = prefH;
for (Integer colHeight : colHeights) {
if (colHeight > maxH) {
maxH = colHeight;
}
}
if (table.getRowHeight(row) != maxH) {
table.setRowHeight(row, maxH);
}
}
}
}
have look at
my answer about doLayout(could be fired from CellEditor)
or (more than confortable way to use TextUtils) comment by #kleopatra about getPreferredSize
this could (very) confusing the users,
because I miss JScrollPane, there have to override MaxSize, max size is height & weight for JScrollPane, otherwise part of CellEditor can going outside of screeens bounds .........,
don't do that, put there JScrollPane with JTextComponents, override PreferredSize for CellEditor,
everything are wrong, my view,
create applications modal popup window (based only on JDialog, becasue JWindow doesn't alloved input to the JTextComponent) with JTextComponent, implements there KeyBindings for ESC key, the same for lost Fucus for JDialog, then could be undecorated without any issue
put there Save JButton
output from Save Button reditect to the selected cell, you can't lost focus from application modal inside JTable
contents should be formatted, filtered, modified one JDialog for all cells from JTable
As an alternative to resizing the row while editing, consider TablePopupEditor, which uses JTextArea.
How can I instruct my Swing component to grab focus right now? requestFocus() doesn't seem to be dispatched instantly.
Ideally, I would like this (ran from EDT):
textInput.requestFocusInWindow();
System.out.println(textInput.hasFocus());
To print true.
Below is the SSCCE. Notes/requirements:
Table is navigated with keyboard. Column C2 has a compound editor.
When I type a letter in column C2, editing starts. Text component inside the compound editor gains focus. It needs to type the letter that started the editor. Implementation of this point is marked with comments saying "Trick".
The text field is a 3rd party editor that has a focus listener interfering with my code. Here it's simulated as selectAll().
Presently the order of dispatch is: Type letter into text component, then dispatch focus listeners. Then the next letters are dispatched correctly because it's the text field what has focus.
I need it to set focus on the text component, dispatch focus listeners, and then pass key event to it.
public class JTableIssue extends JFrame {
public JTableIssue() {
JTable table = new JTable(new Object[][] {
{ "Apple", "Orange", "Strawberry" },
{ "Pineapple", "Orange", "Zergz" } }, new Object[] { "C1",
"C2", "C3" });
table.getColumn("C2").setCellEditor(new MyEditor());
add(new JScrollPane(table));
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JTableIssue().setVisible(true);
}
}
class MyEditor extends AbstractCellEditor implements TableCellEditor {
MyTextField textField = new MyTextField();
JPanel panel;
MyEditor() {
panel = new JPanel(new BorderLayout()){
// Trick: Pass all key typed to text field
#Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
int condition, boolean pressed) {
if (ks.getKeyEventType() == KeyEvent.KEY_TYPED) {
textField.processKeyBinding(ks, e, condition, pressed);
}
return super.processKeyBinding(ks, e, condition, pressed);
}
};
textField.addFocusListener(new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
textField.selectAll();
}
});
panel.add(textField, BorderLayout.CENTER);
// Trick: Pass focus to text field when editor is added to table
panel.addAncestorListener(new AncestorListener() {
public void ancestorRemoved(AncestorEvent event) {
}
public void ancestorMoved(AncestorEvent event) {
}
public void ancestorAdded(AncestorEvent event) {
textField.requestFocus();
}
});
}
public Object getCellEditorValue() {
return textField.getText();
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
textField.setText(value.toString());
return panel;
}
}
class MyTextField extends JTextField {
// Trick: "public"
#Override
public boolean processKeyBinding(javax.swing.KeyStroke ks,
java.awt.event.KeyEvent e, int condition, boolean pressed) {
return super.processKeyBinding(ks, e, condition, pressed);
};
}
I figured it out.
Events in AncestorListener and processKeyBinding() are a part of handling the same event: "key typed".
Apparently the only way to grab focus is requestFocus(), which is added to event queue after the current stream of events triggered by "key typed". So grabbing focus and executing FocusListeners will always be executed later.
The solution is: In processKeyBinding(), don't pass the key to the inner component immediately. Enqueue it in event queue, so that it's performed after focus transfer and listeners. That is, wrap:
if (ks.getKeyEventType() == KeyEvent.KEY_TYPED) {
textField.processKeyBinding(ks, e, condition, pressed);
}
Into SwingUtilities.invokeLater().
I don't think that this is possible. UI actions are inherently asynchronous (although usually very fast), and there's no way to force them to behave synchronously. If you really need this, you can fire an event in the text input's focus handler, and wait on that event in the other thread.
I don't see how to control the order of events and in general that is not something you should be trying to do.
3.The text field is a 3rd party editor that has a focus listener interfering
with my code
Maybe you can remove the FocusListener from the editor. Maybe you can then invoke the listener directly by using
savedFocusListener.focusGained(FocusEvent);
in the AncestorListener code that set focus on the text field.
For my application, I want a Combo Box that displays its elements when dropped down as a Tree. Problem is, I'm not versed well enough in Swing to know how to go about doing this. At least without ending up writing a new widget from scratch, or something to that effect.
How would I do something like this without creating one from scratch?
I think I would implement this as a JTree component in a JViewPort, followed by an expansion button. When collapsed, it would look like a combo box. When you click the expansion button, the viewport would expand, allowing you to scroll and select a node in the JTree. When you selected the node, the view port would collapse back to only show the selected node and the expansion button.
Hey, guess what! This is your lucky day.
I've used this framework in the past. It is very complete. I didn't know they have this
already.
JIDE Soft
alt text http://img89.imageshack.us/img89/8324/combotreejj1.png
Is not too expensive, but it will take you some time to understand the API ( it is not that is complex, but they've created a LOT of new stuff )
Override the getListCellRendererComponent methode and create the components in level order.
For every tree level move the painted string 3 spaces to right.
Example:
1
. a
. b
2
. c
The original implementation you can look from
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
//Get the selected index. (The index param isn't
//always valid, so just use the value.)
int selectedIndex = ((Integer)value).intValue();
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
//Set the icon and text. If icon was null, say so.
ImageIcon icon = images[selectedIndex];
String pet = petStrings[selectedIndex];
setIcon(icon);
if (icon != null) {
setText(pet);
setFont(list.getFont());
} else {
setUhOhText(pet + " (no image available)",
list.getFont());
}
return this;
}
You can create a ComboBoxEditor whose component ( returned by getEditorComponent ) is a JTree
Although you may have already tried that.
I don't know how would it look like. Post an screenshot if you make it work. :)
EDIT
I give it a quick dirty try. Its awful, but is a start.
alt text http://img120.imageshack.us/img120/2563/yiakxk2.png
Here's the code, for what is worth. :(
Probably you should start thinking in alternatives. What about a fake Combo that is a JButton without border when pushed a hidden panel will appear with the tree displayed.
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class ComboTree {
public static void main( String [] args ) {
JComboBox c = new JComboBox( new String [] { "Hello", "there"});
c.setModel( new CustomComboModel() );
c.setEditor( new TreeComboEditor() );
c.setRenderer( new TreeComboEditor() );
JFrame frame = new JFrame();
frame.add( c , BorderLayout.NORTH ) ;
frame.pack();
frame.setVisible( true );
}
}
class CustomComboModel implements ComboBoxModel {
public Object getSelectedItem() { return ":P"; }
public void setSelectedItem(Object anItem) {}
public void addListDataListener(ListDataListener l) {}
public Object getElementAt(int index) { return "at " + index ; }
public int getSize() { return 2; }
public void removeListDataListener(ListDataListener l) {}
}
class TreeComboEditor implements ComboBoxEditor, ListCellRenderer {
// Editor interface
public void addActionListener(ActionListener l) {}
public Component getEditorComponent() {
return new JTree() ;
}
public Object getItem() { return "";}
public void removeActionListener(ActionListener l) {}
public void selectAll() {}
public void setItem(Object anObject) {}
// Render interface
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
return new JTree();
}
}
What would be the best way to have a list of items with a checkbox each in Java Swing?
I.e. a JList with items that have some text and a checkbox each?
A wonderful answer is this CheckBoxList. It implements Telcontar's answer (though 3 years before :)... I'm using it in Java 1.6 with no problems. I've also added an addCheckbox method like this (surely could be shorter, haven't used Java in a while):
public void addCheckbox(JCheckBox checkBox) {
ListModel currentList = this.getModel();
JCheckBox[] newList = new JCheckBox[currentList.getSize() + 1];
for (int i = 0; i < currentList.getSize(); i++) {
newList[i] = (JCheckBox) currentList.getElementAt(i);
}
newList[newList.length - 1] = checkBox;
setListData(newList);
}
I tried out the demo for the Jidesoft stuff, playing with the CheckBoxList I encountered some problems (behaviors that didn't work). I'll modify this answer if I find problems with the CheckBoxList I linked to.
Create a custom ListCellRenderer and asign it to the JList.
This custom ListCellRenderer must return a JCheckbox in the implementantion of getListCellRendererComponent(...) method.
But this JCheckbox will not be editable, is a simple paint in the screen is up to you to choose when this JCheckbox must be 'ticked' or not,
For example, show it ticked when the row is selected (parameter isSelected), but this way the check status will no be mantained if the selection changes. Its better to show it checked consulting the data below the ListModel, but then is up to you to implement the method who changes the check status of the data, and notify the change to the JList to be repainted.
I Will post sample code later if you need it
ListCellRenderer
Just implement a ListCellRenderer
public class CheckboxListCellRenderer extends JCheckBox implements ListCellRenderer {
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
setComponentOrientation(list.getComponentOrientation());
setFont(list.getFont());
setBackground(list.getBackground());
setForeground(list.getForeground());
setSelected(isSelected);
setEnabled(list.isEnabled());
setText(value == null ? "" : value.toString());
return this;
}
}
and set the renderer
JList list = new JList();
list.setCellRenderer(new CheckboxListCellRenderer());
this will result in
Details at Custom swing component renderers.
PS: If you want radio elements just replace extends JCheckbox with extends JRadioButton.
I'd probably be looking to use a JTable rather than a JList and since the default rendering of a checkbox is rather ugly, I'd probably be looking to drop in a custom TableModel, CellRenderer and CellEditor to represent a boolean value. Of course, I would imagine this has been done a bajillion times already. Sun has good examples.
Better solution for Java 7 and newer
I stumbled upon this question and realized that some of the answers are pretty old and outdated. Nowadays, JList is generic and thus there are better solutions.
My solution of the generic JCheckBoxList:
import java.awt.Component;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
#SuppressWarnings("serial")
public class JCheckBoxList extends JList<JCheckBox> {
protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
public JCheckBoxList() {
setCellRenderer(new CellRenderer());
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int index = locationToIndex(e.getPoint());
if (index != -1) {
JCheckBox checkbox = (JCheckBox) getModel().getElementAt(index);
checkbox.setSelected(!checkbox.isSelected());
repaint();
}
}
});
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
public JCheckBoxList(ListModel<JCheckBox> model){
this();
setModel(model);
}
protected class CellRenderer implements ListCellRenderer<JCheckBox> {
public Component getListCellRendererComponent(
JList<? extends JCheckBox> list, JCheckBox value, int index,
boolean isSelected, boolean cellHasFocus) {
JCheckBox checkbox = value;
//Drawing checkbox, change the appearance here
checkbox.setBackground(isSelected ? getSelectionBackground()
: getBackground());
checkbox.setForeground(isSelected ? getSelectionForeground()
: getForeground());
checkbox.setEnabled(isEnabled());
checkbox.setFont(getFont());
checkbox.setFocusPainted(false);
checkbox.setBorderPainted(true);
checkbox.setBorder(isSelected ? UIManager
.getBorder("List.focusCellHighlightBorder") : noFocusBorder);
return checkbox;
}
}
}
For dynamically adding JCheckBox lists you need to create your own ListModel or add the DefaultListModel.
DefaultListModel<JCheckBox> model = new DefaultListModel<JCheckBox>();
JCheckBoxList checkBoxList = new JCheckBoxList(model);
The DefaultListModel are generic and thus you can use methods specified by JAVA 7 API here like this:
model.addElement(new JCheckBox("Checkbox1"));
model.addElement(new JCheckBox("Checkbox2"));
model.addElement(new JCheckBox("Checkbox3"));
I recommend you use a JPanel with a GridLayout of 1 column. Add the checkBoxes to the JPanel, and set the JPanel as the data source of a JScrollPane. And to get the selected CheckBoxes, just call the getComponents() of the JPanel to get the CheckBoxes.
Odds are good w/ Java that someone has already implemented the widget or utility you need. Part of the benefits of a large OSS community. No need to reinvent the wheel unless you really want to do it yourself. In this case it would be a good learning exercise in CellRenderers and Editors.
My project has had great success with JIDE. The component you want, a Check Box List, is in the JIDE Common Layer (which is OSS and hosted on java.net). The commercial stuff is good too, but you don't need it.
http://www.jidesoft.com/products/oss.htm
https://jide-oss.dev.java.net/
I don't like the solutions that put a Checkbox into the model. The model should only contain data not display elements.
I found this http://www.java2s.com/Tutorials/Java/Swing_How_to/JList/Create_JList_of_CheckBox.htm
which I optimized a bit. The ACTIVE flag represents the Checkbox, the SELECTED flag shows what entry the cursor sits on.
my version requires a renderer
import java.awt.Component;
import javax.swing.JCheckBox;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
class CheckListRenderer extends JCheckBox implements ListCellRenderer<Entity> {
#Override
public Component getListCellRendererComponent(JList<? extends Entity> list,
Entity value, int index, boolean isSelected, boolean cellHasFocus) {
setEnabled(list.isEnabled());
setSelected(value.isActive()); // sets the checkbox
setFont(list.getFont());
if (isSelected) { // highlights the currently selected entry
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setText(value.toString()+" - A" + value.isActive()+" - F"+cellHasFocus+" - S"+isSelected );
return this;
}
}
and an entity that got the active field:
public class Entity {
private boolean active = true;
public boolean isActive() {
return active;
}
public void setActive(boolean isActive) {
this.active = isActive;
}
}
Now you only have to add this to your JList:
list = new JList<Entity>();
list.setModel(new DefaultListModel<Entity>());
list.setCellRenderer(new CheckListRenderer());
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent event) {
if (event.getX() < 20) {
// Quick and dirty: only change the tick if clicked into the leftmost pixels
#SuppressWarnings("unchecked")
JList<Entity> list = ((JList<Entity>) event.getSource());
int index = list.locationToIndex(event.getPoint());// Get index of item clicked
if (index >= 0) {
Entity item = (Entity) list.getModel().getElementAt(index);
item.setActive(!item.isActive()); // Toggle selected state
list.repaint(list.getCellBounds(index, index));// Repaint cell
}
}
}
});
All of the aggregate components in Swing--that is, components made up other components, such as JTable, JTree, or JComboBox--can be highly customized. For example, a JTable component normally displays a grid of JLabel components, but it can also display JButtons, JTextFields, or even other JTables. Getting these aggregate components to display non-default objects is the easy part, however. Making them respond properly to keyboard and mouse events is a much harder task, due to Swing's separation of components into "renderers" and "editors." This separation was (in my opinion) a poor design choice and only serves to complicate matters when trying to extend Swing components.
To see what I mean, try enhancing Swing's JList component so that it displays checkboxes instead of labels. According to Swing philosophy, this task requires implementing two interfaces: ListCellRenderer (for drawing the checkboxes) and CellEditor (for handling keyboard and mouse events on the checkboxes). Implementing the ListCellRenderer interface is easy enough, but the CellEditor interface can be rather clumsy and hard to understand. In this particular case, I would suggest forgetting CellEditor entirely and to handle input events directly, as shown in the following code.
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
public class CheckBoxList extends JList
{
protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
public CheckBoxList()
{
setCellRenderer(new CellRenderer());
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent e)
{
int index = locationToIndex(e.getPoint());
if (index != -1) {
JCheckBox checkbox = (JCheckBox)
getModel().getElementAt(index);
checkbox.setSelected(
!checkbox.isSelected());
repaint();
}
}
}
);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
protected class CellRenderer implements ListCellRenderer
{
public Component getListCellRendererComponent(
JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus)
{
JCheckBox checkbox = (JCheckBox) value;
checkbox.setBackground(isSelected ?
getSelectionBackground() : getBackground());
checkbox.setForeground(isSelected ?
getSelectionForeground() : getForeground());
checkbox.setEnabled(isEnabled());
checkbox.setFont(getFont());
checkbox.setFocusPainted(false);
checkbox.setBorderPainted(true);
checkbox.setBorder(isSelected ?
UIManager.getBorder(
"List.focusCellHighlightBorder") : noFocusBorder);
return checkbox;
}
}
}
Here, I intercept mouse clicks from the listbox and simulate a click on the appropriate checkbox. The result is a "CheckBoxList" component that is both simpler and smaller than an equivalent component using the CellEditor interface. To use the class, simply instantiate it, then pass it an array of JCheckBox objects (or subclasses of JCheckBox objects) by calling setListData. Note that the checkboxes in this component will not respond to keypresses (i.e. the spacebar), but you could always add your own key listener if needed.
Source: DevX.com
Here is just a little addition to the JCheckBoxList by Rawa. This will add the ability to select using space bar. If multiple items are selected, all will be set to inverted value of the first item.
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
int index = getSelectedIndex();
if (index != -1 && e.getKeyCode() == KeyEvent.VK_SPACE) {
boolean newVal = !((JCheckBox) (getModel()
.getElementAt(index))).isSelected();
for (int i : getSelectedIndices()) {
JCheckBox checkbox = (JCheckBox) getModel()
.getElementAt(i);
checkbox.setSelected(newVal);
repaint();
}
}
}
});
this is yet another example of making list with checkboxes
class JCheckList<T> extends JList<T> {
protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
public void setSelected(int index) {
if (index != -1) {
JCheckBox checkbox = (JCheckBox) getModel().getElementAt(index);
checkbox.setSelected(
!checkbox.isSelected());
repaint();
}
}
protected static class CellListener
extends DefaultListModel
implements ListDataListener {
ListModel ls;
public CellListener(ListModel ls) {
ls.addListDataListener(this);
int i = ls.getSize();
for (int v = 0; v < i; v++) {
var r = new JCheckBox();
r.setText(ls.getElementAt(v).toString());
this.addElement(r);
}
this.ls = ls;
}
#Override
public void intervalAdded(ListDataEvent e) {
int begin = e.getIndex0();
int end = e.getIndex1();
for (; begin <= end; begin++) {
var r = new JCheckBox();
r.setText(ls.getElementAt(begin).toString());
this.add(begin, r);
}
}
#Override
public void intervalRemoved(ListDataEvent e) {
int begin = e.getIndex0();
int end = e.getIndex1();
for (; begin <= end; end--) {
this.remove(begin);
}
}
#Override
public void contentsChanged(ListDataEvent e) {
}
}
public JCheckList() {
setCellRenderer(new CellRenderer());
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int index = locationToIndex(e.getPoint());
setSelected(index);
}
}
);
addKeyListener(new KeyListener(){
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE){
int index = JCheckList.this.getSelectedIndex();
setSelected(index);
}
}
#Override
public void keyReleased(KeyEvent e) {
}
});
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
#Override
public void setModel(ListModel<T> d) {
var r = new CellListener(d);
d.addListDataListener(r);
super.setModel(r);
}
protected class CellRenderer implements ListCellRenderer {
public Component getListCellRendererComponent(
JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
JCheckBox checkbox = (JCheckBox) value;
checkbox.setBackground(isSelected
? getSelectionBackground() : getBackground());
checkbox.setForeground(isSelected
? getSelectionForeground() : getForeground());
checkbox.setEnabled(isEnabled());
checkbox.setFont(getFont());
checkbox.setFocusPainted(false);
checkbox.setBorderPainted(true);
checkbox.setBorder(isSelected
? UIManager.getBorder(
"List.focusCellHighlightBorder") : noFocusBorder);
return checkbox;
}
}
}