Using ListCellRenderers on selected entries - java

I'm using a ListCellRenderer to edit the appearance of entries in a JList. Once they are selected (by clicking them, this code is within a click event) I call the ListCellRenderer to change the color of the text. If they are selected again I want the text to return to the normal color. The problem I am having is that once I select a second entry the first entry goes back to its normal color. How can I keep selected entries the selected color until they are actually deselected? Here is the section where I actually call the function:
for(int i = 0; i < selectedEntries.size() - 1; i++){
System.out.println("Inside the for loop at entry:" + i);
if(selectedEntries.get(i).equals(selectedEntry)){
selectedEntries.remove(i);
removed = true;
renderer.getListCellRendererComponent(logEntries, value, index, false, false);
System.out.println("Duplicate Entry Removed From List");
}
}
if(!removed){
selectedEntries.add(selectedEntry);
renderer.getListCellRendererComponent(logEntries, value, index, true, false);
}
Just for ease of interpretation selectedEntries is an ArrayList containing each selected entry's text.

Once they are selected (by clicking them, this code is within a click event) I call the ListCellRenderer to change the color of the text
No, that's not how it should work, the ListCellRenderer will be called again (by the JList) and the isSelected parameter will be true, to which you would render the values differently
The ListCellRenderer is responsible for rendering the entire state, selected or not selected.
Have a look at Writing a Custom Cell Renderer for more details
For example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
DefaultListModel<String> model = new DefaultListModel<>();
model.addElement("Apples");
model.addElement("Bananas");
model.addElement("Peachs");
model.addElement("Pears");
JList<String> listOfStrings = new JList<>(model);
listOfStrings.setCellRenderer(new FancyPancyListCellRenderer());
add(new JScrollPane(listOfStrings));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class FancyPancyListCellRenderer extends DefaultListCellRenderer {
protected static final Font SELECTED_FONT = new Font("Comic Sans MS", Font.PLAIN, 12);
#Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (isSelected) {
setForeground(Color.YELLOW);
setFont(SELECTED_FONT);
} else {
setFont(UIManager.getFont("Label.font"));
}
return this;
}
}
}
Also, MouseListener really isn't a suitable means by which to detect changes in the selection, what happens if the user selects rows using the keyboard? You should be using a ListSelectionListener instead (but not to correct this issue).
Have a look at How to Write a List Selection Listener and How to Use Lists for more details

Related

Tooltip to show a keystroke in java

Pressing the Control key I want to show a Jtooltip of a Jbutton.
How can I show a tooltip to the desired keystrokes?
Not sure if I understand the question, but you can use Control+F1 to display the tooltip of the currently focused component.
There is a private method in the TooltipManager called show which takes a JComponent as a parameter, which is used to show the tooltip. This is actually used by the TooltipManager when CTRL+F1 is pressed...
So, my first recommendation would be, use CTRL+F1 because it's built in. My second recommendation is to use CTRL+F1 because people press CTRL for a lot of reasons (like copy/paste, menu short cuts, etc), which could be rather annoying if you keep popping up tool tips all the time. My third recommendation is to use CTRL+F1 because the show method is private
However, because I'm simply curious (and completely crazy), you "could" (but I wouldn't recommend it) use a dirty, dirty hack (I fell like Phillip Fry), which is just as likely to blow up in your face as solve the problem (but I was curious about how to bind an action to the CTRL key)
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JButton btn = new JButton("Banana");
btn.setToolTipText("Hello");
add(btn);
TooltipPopup.register(this);
}
}
public static class TooltipPopup {
public static void register(JComponent comp) {
new TooltipPopup(comp);
}
private JComponent parent;
private boolean showing = false;
private TooltipPopup(JComponent parent) {
this.parent = parent;
bindKeyStrokeTo(parent,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
"help.press",
KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, InputEvent.CTRL_DOWN_MASK),
new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
if (!showing) {
Component comp = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (comp != null && comp instanceof JComponent) {
JComponent focused = (JComponent) comp;
try {
Class clazz = ToolTipManager.sharedInstance().getClass();
Method method = clazz.getDeclaredMethod("show", JComponent.class);
method.setAccessible(true);
method.invoke(ToolTipManager.sharedInstance(), focused);
showing = true;
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
}
});
bindKeyStrokeTo(parent,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
"help.release",
KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0, true),
new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
showing = false;
}
});
}
public void bindKeyStrokeTo(JComponent parent, int condition, String name, KeyStroke keyStroke, Action action) {
InputMap im = parent.getInputMap(condition);
ActionMap am = parent.getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
}
}
All this does is binds a press and release Action to the CTRL for a given component (parent container), which will find the currently focused component and show it's tool tip.
It uses a reflection "tick" (hack) to call the private show method of the ToolTipManager.
You need the "press" and "release" actions, because otherwise you will get a repeated key event, which will make the tool tip "flash"
camickr solution is the better (and correct) choice, this was a simply "I wonder how" hack

Adding scrolling items dynamically to specific places on a JscrollPane

The short version of the question: I have a JScrollPane with a large number of items being scrolled. At certain times I'd like to add some items to the JScrollPane and have them appear NOT at the end of the list, but after (for example) the 17th item.
Normally if the code in question is within the JScrollPane derived class, I just say this.add(); But I believe this goes to the end of the list.
Rationales: Case 1: The underlying data I'm scrolling is an ordered list of items but additions are allowed at any point of the list. When I add things to the middle of the list, I want to avoid loading the whole list again into the JScrollPane.
Case 2: I'm simulating a scrolling list with expanding subcategories. So if the user picks an item that has expanding subcategories, my Action code recognizes this case, looks into program data to find the subcategories, and then adds them to the JScrollPane beneath the chosen category. Don't worry about it, but there will also be some visual indication that it is a subcategory, supplied by my action code.
I can code all of this. All I am hoping to learn is how to add something to a JScrollPane without it being added to the end of the list.
The answer depends on what this component is that you are adding your items to. If you are using a JList, you could use something like DefaultListModel#add(int, Object) to add an item to a specific location.
If you're using something based on a JComponent (like a JPanel), you can use JComponent#add(Component, int) to specify the location that the component should be added, this assumes that the location exists (ie you can't add a component to position 100 if the container only contains a single component).
The following example generates a random number, determines the location that the number should be inserted in order to maintain a ordered list and inserts a component at that point. It will also attempt to scroll the JScrollPane to make the item visible
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test10 {
public static void main(String[] args) {
new Test10();
}
public Test10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JPanel numbers;
private List<Integer> values;
public TestPane() {
values = new ArrayList<>(25);
setLayout(new BorderLayout());
numbers = new JPanel(new GridLayout(0, 1));
add(new JScrollPane(numbers));
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int next = (int) (Math.random() * Integer.MAX_VALUE);
int insertion = Collections.binarySearch(values, next);
if (insertion < 0) {
insertion = (Math.abs(insertion)) - 1;
}
values.add(insertion, next);
JLabel label = new JLabel(Integer.toString(next));
numbers.add(label, insertion);
numbers.revalidate();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Point p = label.getLocation();
JViewport vp = (JViewport) numbers.getParent();
JScrollPane sp = (JScrollPane) vp.getParent();
vp.setViewPosition(p);
}
});
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}

How to set the title of a JComboBox when nothing is selected?

I want to have a JCombobox in my Swing application, which shows the title when nothing is selected. Something like this:
COUNTRY ▼
Spain
Germany
Ireland
I want "COUNTRY" to show when the selected index is -1 and thus, the user wouldn't be able to select it. I tried to put it on the first slot and then overriding the ListCellRenderer so the first element appears greyed out, and handling the events so when trying to select the "title", it selects the first actual element, but I think this is a dirty approach.
Could you lend me a hand?
Overriding the ListCellRenderer is a good approach, but you tried something overly complicated. Just display a certain string if you are rendering the cell -1 and there is no selection (value is null). You are not limited to display elements on the list.
The following is an example program that demonstrates it:
Full Code:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
public class ComboBoxTitleTest
{
public static final void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
new ComboBoxTitleTest().createAndShowGUI();
}
});
}
public void createAndShowGUI()
{
JFrame frame = new JFrame();
JPanel mainPanel = new JPanel();
JPanel buttonsPanel = new JPanel();
frame.add(mainPanel);
frame.add(buttonsPanel, BorderLayout.SOUTH);
String[] options = { "Spain", "Germany", "Ireland", "The kingdom of far far away" };
final JComboBox comboBox = new JComboBox(options);
comboBox.setRenderer(new MyComboBoxRenderer("COUNTRY"));
comboBox.setSelectedIndex(-1); //By default it selects first item, we don't want any selection
mainPanel.add(comboBox);
JButton clearSelectionButton = new JButton("Clear selection");
clearSelectionButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
comboBox.setSelectedIndex(-1);
}
});
buttonsPanel.add(clearSelectionButton);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class MyComboBoxRenderer extends JLabel implements ListCellRenderer
{
private String _title;
public MyComboBoxRenderer(String title)
{
_title = title;
}
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean hasFocus)
{
if (index == -1 && value == null) setText(_title);
else setText(value.toString());
return this;
}
}
}
index == -1 in the renderer is the head component that, by default, displays the selected item and where we want to put our title when there's no selection.
The renderer knows that there's nothing selected because the value passed to it is null, which is usually the case. However if for some weird reasons you had selectable null values in your list, you can just let the renderer consult which is the explicit current selected index by passing it a reference to the comboBox, but that is totally unrealistic.

For one column of a JTable, how do I put a unique combo box editor in each row?

Here's the idea: Let's say I have a class extending TableModel, with something like a List<List<String>> collection field. On an event, I want to clear out a JTable and re-add rows where one specific column is a combo box; the items in combo box n are the items in the List<String> from collection.get(n) in my list. So I'm iterating over collection adding rows, and I'm iterating over each collection.get(n) adding combo box items.
There are no event listeners. When a separate button is clicked I can work out what needs to happen as long as I can getSelectedIndex on each combo box.
I have spent two and a half hours turning my GUI into code spaghetti where I've extended virtually everything and I have maximum coupling on practically everything. I think I might even have two disjoint collections of JComboBox instances. I say this to stress that it would do absolutely no good for me to post any code I currently have.
I have read the oracle tutorial on JTable, all of these that strangely don't have an explanation I can understand.
So basically I just need an idea of what I need, because I'm not getting anywhere with the resources I've found.
So, basically, you need a TableCelLEditor that is capable of seeding a JComboBox with the rows from the available list, for example...
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class TableCellEditor {
public static void main(String[] args) {
new TableCellEditor();
}
public TableCellEditor() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<List<String>> values = new ArrayList<>(25);
for (int row = 0; row < 10; row++) {
List<String> rowValues = new ArrayList<>(25);
for (int index = 0; index < 10; index++) {
rowValues.add("Value " + index + " for row " + row);
}
values.add(rowValues);
}
DefaultTableModel model = new DefaultTableModel(new Object[]{"Data"}, 10);
JTable table = new JTable(model);
table.setShowGrid(true);
table.setGridColor(Color.GRAY);
table.getColumnModel().getColumn(0).setCellEditor(new MyEditor(values));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MyEditor extends DefaultCellEditor {
private List<List<String>> rowValues;
public MyEditor(List<List<String>> rowValues) {
super(new JComboBox());
this.rowValues = rowValues;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JComboBox cb = (JComboBox) getComponent();
List<String> values = rowValues.get(row);
DefaultComboBoxModel model = new DefaultComboBoxModel(values.toArray(new String[values.size()]));
cb.setModel(model);
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
}
}

TableHeaderRenderer with Nimbus

I'm have a custom table header renderer that will have the standard label and a button inside a JComponent.
The issue I'm having is with the label returned by the default renderer. The call to the default renderer provides the standard label. If I return that as is, it looks as expected. If I try to modify the background or border nothing changes. Modifying the foreground does have the intended effect, however. I do not want to view the sort icons, so I'm attempting to construct a JLabel that looks the same, minus the icons. That is not working correctly either. My JLabel is opaque.
JLabel l = (JLabel)table.getTableHeader().getDefaultRenderer().getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
this.lbl.setBackground(l.getBackground());
return this.lbl;
I looked at the source for DefaultTableHeaderRenderer and I can't find anything special that the default class is doing.
I've also tried the following with no effect.
this.lbl.setOpaque(true);
this.lbl.setFont(UIManager.getFont("TableHeader.font"));
this.lbl.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
this.lbl.setBackground(UIManager.getColor("TableHeader.background"));
this.lbl.setForeground(UIManager.getColor("TableHeader.foreground"));
return this.lbl;
EDIT: Clarification. Both code snippets above are inside getTableCellRenderComponent() of my custom renderer. I've tried both ways and neither has worked.
Try using the UIManagers values directly.
TableHeader.background = DerivedColor(color=214,217,223 parent=control offsets=0.0,0.0,0.0,0 pColor=214,217,223
TableHeader.font = javax.swing.plaf.FontUIResource[family=SansSerif,name=sansserif,style=plain,size=12]
TableHeader.foreground = DerivedColor(color=0,0,0 parent=text offsets=0.0,0.0,0.0,0 pColor=0,0,0
TableHeader.opaque = true
Something like UIManager.getColor("TableHeader.background") for example
The border I think you'll find is actually painted by the UI delegate directly.
Updated with example
From the included image, it's obvious that using UIManager does provide some of the basic information need to get the values used by the header, but it does highlight that the renderer is doing some special painting to get the shading.
The second column is the default renderer and the third is cheeky. It is basically stealing the cell renderer directly from the table header...
package testcellrenderer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
public class TestCellRenderer {
public static void main(String[] args) {
new TestCellRenderer();
}
public TestCellRenderer() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
DefaultTableModel model = new DefaultTableModel(
new Object[][]{{"Testing", "Testing", "Testing"}},
new Object[]{"Test A", "Test B", "Test C"}
);
JTable table = new JTable(model);
table.getColumn("Test A").setCellRenderer(new TestTableCellRenderer());
table.getColumn("Test C").setCellRenderer(table.getTableHeader().getDefaultRenderer());
setLayout(new BorderLayout());
add(new JScrollPane(table));
}
}
protected class TestTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Color background = UIManager.getColor("TableHeader.background");
Color foreground = UIManager.getColor("TableHeader.foreground");
Font font = UIManager.getFont("TableHeader.font");
boolean opaque = UIManager.getBoolean("TableHeader.opaque");
setBackground(background);
setForeground(foreground);
setFont(font);
setOpaque(opaque);
return this;
}
}
}

Categories