Compound JTree Node allowing events to pass through to objects underneath - java

I'm trying to create a JTree in which some nodes are compound objects containing a JLabel and a JButton. The Node is representing a server and port shown by the JLabel, the JButton will use the Desktop API to open the default browser and go to the URL.
I have read the following already and have followed them as closely as I can. The Node is displayed how I want it (mostly - I can deal with making it nicer later) but when I try to click on the button the JTree is responding to the events, not the button.
java swing: add custom graphical button to JTree item
http://www.java2s.com/Code/Java/Swing-JFC/TreeCellRenderer.htm
https://stackoverflow.com/a/3769158/1344282
I need to know how to allow the events to pass through the JTree so that they are handled by the object(s) underneath - the JButton or JLabel.
Here is my TreeCellEditor:
public class UrlValidationCellEditor extends DefaultTreeCellEditor
{
public UrlValidationCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
{
super(tree, renderer);
}
#Override
public Component getTreeCellEditorComponent(JTree tree, Object value,
boolean isSelected, boolean expanded, boolean leaf, int row)
{
return renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);
}
#Override
public boolean isCellEditable(EventObject anEvent)
{
return true; // Or make this conditional depending on the node
}
}
Here is the TreeCellRenderer:
public class UrlValidationRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer
{
JLabel titleLabel;
UrlGoButton goButton;
JPanel renderer;
DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
public UrlValidationRenderer()
{
renderer = new JPanel(new GridLayout(1, 2));
titleLabel = new JLabel(" ");
titleLabel.setForeground(Color.blue);
renderer.add(titleLabel);
goButton = new UrlGoButton();
renderer.add(goButton);
renderer.setBorder(BorderFactory.createLineBorder(Color.black));
backgroundSelectionColor = defaultRenderer
.getBackgroundSelectionColor();
backgroundNonSelectionColor = defaultRenderer
.getBackgroundNonSelectionColor();
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
Component returnValue = null;
if ((value != null) && (value instanceof DefaultMutableTreeNode))
{
Object userObject = ((DefaultMutableTreeNode) value)
.getUserObject();
if (userObject instanceof UrlValidation)
{
UrlValidation validationResult = (UrlValidation) userObject;
titleLabel.setText(validationResult.getServer()+":"+validationResult.getPort());
goButton.setUrl(validationResult.getUrl());
if (selected) {
renderer.setBackground(backgroundSelectionColor);
} else {
renderer.setBackground(backgroundNonSelectionColor);
}
renderer.setEnabled(tree.isEnabled());
returnValue = renderer;
}
}
if (returnValue == null)
{
returnValue = defaultRenderer.getTreeCellRendererComponent(tree,
value, selected, expanded, leaf, row, hasFocus);
}
return returnValue;
}
}
I would appreciate any insight or suggestions. Thanks!

The renderers do not work this way. They are used as rubber stamps, which means that there is really only one instance of renderer that is painted all over the place as the JList is painted. So it cannot handle mouse inputs, as the objects are not really there - they are just painted.
In order to pass mouse events to objects underneath, you need to implement a cell editor. Sometimes, the editor looks different than the renderer (String renderers are labels, editors are textfields, for example). Following this logic, the editor must be implemented using another instance of a component.
Now you are going to render buttons and use them for manipulating (ie. editing). The editor then must be another instance of JButton, distinctive from the renderer. Implementing button as renderer is easy, but the tricky part is to implement is as an editor. You need to extend AbstractCellEditor and implement TreeCellEditor and ActionListener. The button is then a field of the editor class. In the constructor of the editor class, you initialize the button and add this as a new action listener for the button. In the getTreeCellEditorComponent method, you just return the button. In the actionPerformed, you call whatever code you need to do on button press and then call stopCellEditing().
This way it works for me.
I made a SSCCE that demonstrates the usage on a String Tree
public class Start
{
public static class ButtonCellEditor extends AbstractCellEditor implements TreeCellEditor, ActionListener, MouseListener
{
private JButton button;
private JLabel label;
private JPanel panel;
private Object value;
public ButtonCellEditor(){
panel = new JPanel(new BorderLayout());
button = new JButton("Press me!");
button.addActionListener(this);
label = new JLabel();
label.addMouseListener(this);
panel.add(button, BorderLayout.EAST);
panel.add(label);
}
#Override public Object getCellEditorValue(){
return value.toString();
}
#Override public void actionPerformed(ActionEvent e){
String val = value.toString();
System.out.println("Pressed: " + val);
stopCellEditing();
}
#Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row){
this.value = value;
label.setText(value.toString());
return panel;
}
#Override public void mouseClicked(MouseEvent e){
}
#Override public void mousePressed(MouseEvent e){
String val = value.toString();
System.out.println("Clicked: " + val);
stopCellEditing();
}
#Override public void mouseReleased(MouseEvent e){
}
#Override public void mouseEntered(MouseEvent e){
}
#Override public void mouseExited(MouseEvent e){
}
}
public static class ButtonCellRenderer extends JPanel implements TreeCellRenderer
{
JButton button;
JLabel label;
ButtonCellRenderer(){
super(new BorderLayout());
button = new JButton("Press me!");
label = new JLabel();
add(button, BorderLayout.EAST);
add(label);
}
#Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){
label.setText(value.toString());
return this;
}
}
public static void main(String[] args){
JTree tree = new JTree();
tree.setEditable(true);
tree.setCellRenderer(new ButtonCellRenderer());
tree.setCellEditor(new ButtonCellEditor());
JFrame test = new JFrame();
test.add(new JScrollPane(tree));
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setSize(500, 500);
test.setLocationRelativeTo(null);
test.setVisible(true);
}
}

the node should have 2 parts a label and a button. When the user clicks the label then some detailed information about the node should appear in a different part of the GUI. When the user clicks the button it should result in a browser window opening. ..
Don't do it that way. Instead, have just the label in the tree. Add the button to the same GUI that displays the 'detailed information about the node'.

Related

Created Jpanel with mutiple checkboxes and rendering to JTable cell causing issue

I created custom cell renderer which takes value parameter from method getTableCellRendererComponent and creates check boxes based on list of values which is List of Integer.
Issue is renderer is called multiple times when window is dragged or resized and panel is adding multiple times inside the cell of JTable, resulting in duplicate JCheckBoxes.
Renderer :
public class CheckBoxCellRenderer extends JPanel implements TableCellRenderer {
private static final long serialVersionUID = -6142180432465856711L;
JPanel panel = new JPanel(new FlowLayout());
public CheckBoxCellRenderer() {
setOpaque(true);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(value!=null){
Set<Integer> intValues = (Set<Integer>) value;
for(Integer intvalue : intValues){
panel.add(new JCheckBox(intvalue.toString()));
}
}
// panel.add(this);
// table.setRowHeight(row, this.getPreferredSize().height);
panel.revalidate();
panel.repaint();
return panel;
}
}
cell editor:
class CheckBoxCellEditor extends AbstractCellEditor implements TableCellEditor{
private static final long serialVersionUID = 7910769369831526953L;
JPanel panel = new JPanel(new FlowLayout());
#Override
public Object getCellEditorValue() {
// TODO Auto-generated method stub
return null;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
//table.setRowHeight(25);
//table.setCellSelectionEnabled(true);
if(value!=null){
Set<Integer> intValues = (Set<Integer>) value;
for(Integer intvalue : intValues){
panel.add(new JCheckBox(intvalue.toString()));
}
}
//table.setRowHeight(row, panel.getPreferredSize().height);
return panel;
}
}
Main panel :
(infoTable is ModelTable)
infoTable.getColumnModel().getColumn(2).setCellRenderer(new CheckBoxCellRenderer());
infoTable.getColumnModel().getColumn(2).setCellEditor(new CheckBoxCellEditor());
infoTable.addNewRows(modelList);
You need to be aware that usually only one instance of TableCellRenderer is created and it is used to render every cell in the JTable.
Every time the JTable needs to be redrawn, method getTableCellRendererComponent will be called. Obviously when you drag or resize the JTable, it needs to be redrawn.
A simple solution would be to first removeAll the JCheckBoxes from the JPanel before adding the new ones.
Note that the default layout manager for class JPanel is FlowLayout so no need to explicitly set it.
public class CheckBoxCellRenderer implements TableCellRenderer {
private static final long serialVersionUID = -6142180432465856711L;
JPanel panel = new JPanel();
#Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
if(value!=null){
panel.removeAll();
Set<Integer> intValues = (Set<Integer>) value;
for(Integer intvalue : intValues){
panel.add(new JCheckBox(intvalue.toString()));
}
}
return panel;
}
}

JTree node dosen't update after using a custom DefaultTreeCellRenderer

I have a JTree and a JtextField, So I have added listeners to The textfield so that if I Press Enter, the node is updated with the name entered in the field.
This works fine, the problem comes when I use a custom DefaultTreeCellRenderer to render disabled Nodes.After this I have to click manually to the node to see the change appear.
I did try reload(),nodeChanged() but no change.
The JTree panel:
public class RuleTree extends JPanel {
private static JTree autoscoreRuleTree;
private static DefaultTreeModel treeModel;
renameRule(){
/*
Code given below
*/
}
newRule(){
/*
Creates a new rule
*/
}
copyRule(){
/*
Copies a rule
*/
}
disableRule(){
/*
disables a rule codee given below
*/
}
The text field class:
public class RuleNamePanel extends JPanel {
private static JLabel ruleName;
private static JTextField ruleNameTextField;
/* To rename on pressing enter*/
ruleNameTextField.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
RuleTree.renameRule(ruleNameTextField.getText());
}
}
});
The render class: (Rule is a custom class)
public class AutoScoreRuleTreeRenderer extends DefaultTreeCellRenderer {
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean exp, boolean leaf,
int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);
Rule rule = (Rule) ((DefaultMutableTreeNode) value).getUserObject();
this.setEnabled(rule.isActivated());
return this;
}
}
And this is called when I press enter on the text field
public static void renameRule(String name) {
TreePath nodeToRename = autoscoreRuleTree.getSelectionPath();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodeToRename.getLastPathComponent();
Rule rule = (Rule) node.getUserObject();
rule.setName(name);
treeModel.nodeChanged(node);
}
The buttons panel
public class RuleModifyButtonPanel extends JPanel {
private JButton newRuleButton;
private JButton newChildButton;
private JButton duplicateRuleButton;
private JButton cutRuleButton;
private JButton pasteRuleButton;
private JButton disableRuleButton;
/*
Each button calls a method when clicked
*/
//When disable button is pressed this will internally call enableNode method
disableRuleButton.addActionListener(e -> RuleTree.disableRule());
}
Disable Node Method
private static void enableNodes(DefaultMutableTreeNode node, boolean enable) {
autoscoreRuleTree.setCellRenderer(new AutoScoreRuleTreeRenderer());
enablePanels(enable);
Rule rule = (Rule) node.getUserObject();
}
Expected to change on enter but changes when I manually click on the tree.
Before using render:
Enter the name > Pressed Enter
After Using render:
Enter the name after render > Press Enter > Click manually

JTable prevent Strings

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;
}
}

Placing JToggleButton with JPanel within into a JTable cell

I need to have a JToggleButton (that has custom background) that contains a JPanel with several JLabels within itself. That part works.
This button is placed afterwards in a JTable cell and is meant to be pressed by users. The problem is that i can only press the button on the second click. Apperenty on the first click the focus first jumps to the panel with JLabels and only afterwards to the actual button.
I tried several things to try solving this issue, but the same issue persists.
A) placing the JPanel with labels directly onto the JToggleButton#add().
B) using JLayeredPane to place Button and JPanel onto different Layers where JToggleButton takes constraint Integer(-) so that the JPanel with JLabels stays visible on top
Do you have any tips? Thanks
Below is a sample code that illustrates the problem. Clicking on the button only works second time.
public class ClickableCustomButtonInTable extends JToggleButton {
public ClickableCustomButtonInTable() {
Dimension d = new Dimension(100, 100);
JLabel lFirst = new JLabel("1st label");
lFirst.setPreferredSize(d);
JLabel lSecond = new JLabel("2nd label");
lSecond.setPreferredSize(d);
JPanel panel = new JPanel();
panel.setOpaque(true);
panel.setLayout(new BorderLayout());
panel.add(lFirst, BorderLayout.NORTH);
panel.add(lSecond, BorderLayout.SOUTH);
add(panel);
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
}
private static class CustomButtonRenderer implements TableCellRenderer {
private final ClickableCustomButtonInTable button = new ClickableCustomButtonInTable();
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
return button;
}
}
private static class CustomButtonEditor extends AbstractCellEditor
implements TableCellEditor {
private final ClickableCustomButtonInTable button = new ClickableCustomButtonInTable();
#Override
public Object getCellEditorValue() {
return button.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
return button;
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(new Dimension(200, 200));
Container content = frame.getContentPane();
TableModel model = new AbstractTableModel() {
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return null;
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return ClickableCustomButtonInTable.class;
}
};
JTable table = new JTable(model);
// table.setBounds(new Rectangle(0, 0, content.getWidth(), content
// .getHeight()));
table.setRowHeight(frame.getHeight());
table.setDefaultRenderer(ClickableCustomButtonInTable.class,
new CustomButtonRenderer());
table.setDefaultEditor(ClickableCustomButtonInTable.class,
new CustomButtonEditor());
content.add(table);
content.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
When the table captures a mouse event to select a cell it passes the mouse event on to the deepest component regardless of whether that component can handle mouse events. In your example the first click ends up on one of the JLabels, bypassing the JToggleButton completely. Once the JToggleButton has become the active cell editor, mouse clicks work upon it normally. If it was to lose the focus, it would once again require two-clicks to activate.
You can also see this if you notice in your demo you click on the button border, not on the contained panel, the button works as desired.
One way to work around this is to ensure that any mouse event that is targeted at any component within the JToggleButton. You can do this using this static method:
static void addEventBubble(final Container target, Container container) {
for(Component comp:container.getComponents()) {
if (comp instanceof Container) {
addEventBubble(target, (Container) comp);
}
comp.addMouseListener(new MouseAdapter() {
private MouseEvent retarget(MouseEvent e) {
return new MouseEvent(target, e.getID(), e.getWhen(),
e.getModifiers(), e.getX(), e.getY(),
e.getClickCount(), e.isPopupTrigger(),
e.getButton());
}
public void mousePressed(MouseEvent e) {
MouseEvent r = retarget(e);
for(MouseListener listen:target.getMouseListeners()) {
listen.mousePressed(r);
}
}
public void mouseReleased(MouseEvent e) {
MouseEvent r = retarget(e);
for(MouseListener listen:target.getMouseListeners()) {
listen.mouseReleased(r);
}
}
public void mouseClicked(MouseEvent e) {
MouseEvent r = retarget(e);
for(MouseListener listen:target.getMouseListeners()) {
listen.mouseClicked(r);
}
}
});
}
}
and then at the end of your constructor invoke:
addEventBubble(this,this);
After this any mouse event upon any component within the button will also reach the button and hence change its state. After doing this, I found the button reacted to every click as desired.
http://www.coderanch.com/t/570021/GUI/java/click-event-custom-JToggleButton-JTable

How do I make a list with checkboxes in Java Swing?

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;
}
}
}

Categories

Resources