I'm trying to create a custom focus traversal policy for my swing app by passing a list of components (I do this as the traversal required is not the same as the Containers component order). Like this example:
public class ComponentTraversal extends FocusTraversalPolicy
{
private final java.util.List<Component> componentList;
public ComponentTraversal(java.util.List<Component> aList)
{
componentList = aList;
}
#Override
public Component getComponentAfter(Container aContainer, Component aComponent)
{
int index = componentList.indexOf(aComponent);
if (index == (componentList.size() - 1))
index = -1;
return componentList.get(index + 1);
}
#Override
public Component getComponentBefore(Container aContainer, Component aComponent)
{
int index = componentList.indexOf(aComponent);
if (index == 0)
index = componentList.size();
return componentList.get(index - 1);
}
#Override
public Component getFirstComponent(Container aContainer)
{
return componentList.get(0);
}
#Override
public Component getLastComponent(Container aContainer)
{
return componentList.get(componentList.size() - 1);
}
#Override
public Component getDefaultComponent(Container aContainer)
{
return componentList.get(0);
}
}
And run with this test class:
public class C extends JFrame
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(
new Runnable()
{
#Override
public void run()
{
new C();
}
}
);
}
public C()
{
super("A Test");
JPanel jp = new JPanel(new GridLayout(2,1));
JTextField jtf1 = new JTextField();
JTextField jtf2 = new JTextField();
jp.add(jtf1);
jp.add(jtf2);
java.util.List<Component> comps = new ArrayList<Component>();
comps.add(jtf1);
comps.add(jtf2); //This line
this.add(jp);
this.setFocusTraversalPolicy(new ComponentTraversal(comps));
this.setSize(200,100);
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
this.setVisible(true);
}
}
In most cases this works fine, as posted will work in both directions. If I comment out the line with the comment This line (i.e. adding only one component to the traversal list) it will work in the forwards direction (Tab)... retaining focus on itself.
But if I do it in reverse direction (Shift+Tab), the component loses focus and no exceptions are reported.
This isn't really a problem in terms of my application, as there will always be more than one component, but I tried this out and it bugs me and I was wondering if anyone had encountered this before, or knows what I'm doing wrong.
Related
I've used java Spinner in Java Swing, but it had up and down arrows, is there a way to set it's orientation so that the arrows are left and right ?
Thanks to Jean-François Savard's answer, I'm one step closer, but still not quite right, I have the following lines :
public void installUI(JComponent c)
{
super.installUI(c);
c.removeAll();
FlowLayout FL=new FlowLayout();
FL.setHgap(0);
c.setLayout(FL);
JComponent editor=createEditor();
editor.setPreferredSize(new Dimension(30,16));
c.add(editor);
c.add(createPreviousButton());
c.add(createNextButton());
}
The spacing is not correct, how to fix it ? I hard coded in the above lines, how to automatically provide proper space for the text ?
A short search on google lead me to a custom implementation of JSpinner to do this.
public class LeftRightSpinnerDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new LeftRightSpinnerDemo().makeUI();
}
});
}
public void makeUI() {
JSpinner spinner = new JSpinner();
spinner.setUI(new LeftRightSpinnerUI());
JFrame frame = new JFrame();
frame.add(spinner);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class LeftRightSpinnerUI extends BasicSpinnerUI {
public static ComponentUI createUI(JComponent c) {
return new LeftRightSpinnerUI();
}
#Override
protected Component createNextButton() {
Component c = createArrowButton(SwingConstants.EAST);
c.setName("Spinner.nextButton");
installNextButtonListeners(c);
return c;
}
#Override
protected Component createPreviousButton() {
Component c = createArrowButton(SwingConstants.WEST);
c.setName("Spinner.previousButton");
installPreviousButtonListeners(c);
return c;
}
// copied from BasicSpinnerUI
private Component createArrowButton(int direction) {
JButton b = new BasicArrowButton(direction);
Border buttonBorder = UIManager.getBorder("Spinner.arrowButtonBorder");
if (buttonBorder instanceof UIResource) {
b.setBorder(new CompoundBorder(buttonBorder, null));
} else {
b.setBorder(buttonBorder);
}
b.setInheritsPopupMenu(true);
return b;
}
#Override
public void installUI(JComponent c) {
super.installUI(c);
c.removeAll();
c.setLayout(new BorderLayout());
c.add(createNextButton(), BorderLayout.EAST);
c.add(createPreviousButton(), BorderLayout.WEST);
c.add(createEditor(), BorderLayout.CENTER);
}
}
Make sure to add the correct imports as I removed them to lighten the code.
Refer to this for the original post.
Below is the answer specific to my original question, but if you're trying to achieve a similar structure in general, it'd probably be more practical follow camickr's method below. To do it the way I originally wanted, you have to override JPanel and implement Scrollable for the outer container.
(credit to user Kylar - JTextArea on JPanel inside JScrollPane does not resize properly)
import javax.swing.*;
import java.awt.*;
import javax.swing.text.*;
public class MyEditor extends JTextPane {
JFrame window;
JScrollPane editScroll;
NumberLines numPane;
HoldMe please;
public static void main(String args[]) {
new MyEditor();
}
MyEditor() {
setOpaque(false);
setEditorKit(new TextWrapKit());
numPane = new NumberLines();
numPane.setPreferredSize(new Dimension(50,100));
please = new HoldMe();
please.setLayout(new BorderLayout());
please.add(this,BorderLayout.CENTER);
please.add(numPane,BorderLayout.WEST);
editScroll = new JScrollPane(please);
editScroll.setPreferredSize(new Dimension(500,500));
editScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
editScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().add(editScroll);
window.pack();
window.setVisible(true);
}
private class HoldMe extends JPanel implements Scrollable{
public Dimension getPreferredScrollableViewportSize() {
return super.getPreferredSize(); //tell the JScrollPane that we want to be our 'preferredSize' - but later, we'll say that vertically, it should scroll.
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 16;//set to 16 because that's what you had in your code.
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 16;//set to 16 because that's what you had set in your code.
}
public boolean getScrollableTracksViewportWidth() {
return true;//track the width, and re-size as needed.
}
public boolean getScrollableTracksViewportHeight() {
return false; //we don't want to track the height, because we want to scroll vertically.
}
}
private class NumberLines extends JPanel {
NumberLines() {
setBackground(new Color(120,120,120));
setOpaque(false);
repaint();
}
#Override
protected void paintComponent(Graphics g) {
g.fillRect(0,0,this.getWidth(),this.getHeight());
}
}
private class TextWrapKit extends StyledEditorKit {
ViewFactory defaultFactory=new TextWrapFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}
}
private class TextWrapFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new TextWrapView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
private class TextWrapView extends LabelView {
public TextWrapView(Element elem) {
super(elem);
}
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
}
}
EDIT: code replaced with SCCE, sorry for the trouble
Okay, so it's structured like this...
• Top class - extends JTextPane
• Place this JTextPane into a JPanel set to BorderLayout CENTER
• Place another JPanel into the same JPanel set to WEST
• Place the JPanel holding the two into a JScrollPane
• Ready for deployment (nope)
Basically, I can't figure out a way to do this without losing out on something critical. I can get them all together, but I'll lose scrolling. Or I'll get scrolling back, but then lose text wrapping. One time I got wrapping, and technically it scrolled but not as it should have, in otherwords, the scrollpane didn't detect the size of the text. I need the nested JPanel (1st one) to scroll with the JTextPane
The problem area is in the constructor below, sorry it's such a mess but I'm losing it over here trying different things... Probably isn't helping. I'd be so grateful if somebody could help me figure this out.
Thank you for reading.
import javax.swing.*;
import java.awt.*;
import javax.swing.text.*;
import java.awt.BorderLayout;
public class MyEditor extends JTextPane {
JFrame window;
JScrollPane editScroll;
public static void main(String args[]) {
new MyEditor();
}
MyEditor() {
setOpaque(false);
setEditorKit(new TextWrapKit());
JPanel numPane = new JPanel();
JPanel packEdit = new JPanel();
packEdit.setLayout(new BorderLayout());
packEdit.add(this,BorderLayout.CENTER);
packEdit.add(numPane,BorderLayout.WEST);
editScroll = new JScrollPane(packEdit);
editScroll.setPreferredSize(new Dimension(500,500));
editScroll.setViewportView(packEdit);
editScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
editScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(editScroll);
window.pack();
window.setVisible(true);
}
private class TextWrapKit extends StyledEditorKit {
ViewFactory defaultFactory=new TextWrapFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}
}
private class TextWrapFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new TextWrapView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
private class TextWrapView extends LabelView {
public TextWrapView(Element elem) {
super(elem);
}
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
}
}
extends JTextPane
Why?
setEditorKit(new TextWrapKit());
Why?
Get the scroll pane working with the default components. Then if for some reason you still need to customize you make the changes one at a time to make sure it still works. If it doesn't work any more then you know what the problem is.
packEdit.add(numPane,BorderLayout.WEST);
Looks to me like you are trying to add line number. The standard way to do this is to add your component to the row header of the scrollpane, instead of making it part of the panel added to the viewport. See Text Component Line Number for an example of this approach.
In the future a SSCCE should be posted with every question. You get one free (attempted) answer without a SSCCE.
I created a list basically copying the 'ListDemo.java' from the oracle site. I included a picture of what I have.
public static class BackpackList extends JPanel implements ListSelectionListener {
private JList list;
private DefaultListModel listModel;
private static final String useString = "Use";
private JButton useButton;
public BackpackList() {
super(new BorderLayout());
listModel = new DefaultListModel();
listModel.addElement("Flashlight");
listModel.addElement("Health potion");
listModel.addElement("Snacks");
//Create the list and put it in a scroll pane.
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.addListSelectionListener(this);
list.setVisibleRowCount(10);
JScrollPane listScrollPane = new JScrollPane(list);
useButton = new JButton(useString);
useButton.setActionCommand(useString);
useButton.addActionListener(new UseListener());
//Create a panel that uses BoxLayout.
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane,
BoxLayout.LINE_AXIS));
buttonPane.add(useButton);
buttonPane.add(Box.createHorizontalStrut(5));
buttonPane.add(new JSeparator(SwingConstants.VERTICAL));
buttonPane.add(Box.createHorizontalStrut(5));
buttonPane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
add(listScrollPane, BorderLayout.CENTER);
add(buttonPane, BorderLayout.PAGE_END);
}
class UseListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//This method can be called only if
//there's a valid selection
//so go ahead and remove whatever's selected.
int index = list.getSelectedIndex();
listModel.remove(index);
int size = listModel.getSize();
if (size == 0) { //Nobody's left, disable firing.
useButton.setEnabled(false);
}
else { //Select an index.
if (index == listModel.getSize()) {
//removed item in last position
index--;
}
list.setSelectedIndex(index);
list.ensureIndexIsVisible(index);
}
}
}
#Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false) {
if (list.getSelectedIndex() == -1) {
//No selection, disable fire button.
useButton.setEnabled(false);
}
else {
//Selection, enable the fire button.
useButton.setEnabled(true);
}
}
}
}
Question 1: I am setting up a backpack for a basic text based game. I want to set up specific actions depending on what item you have selected in the list. What would be the code to make it so the health potion would do something different than the snacks?
Question 2: How could I make it so it would say something along the lines of "x2 Snacks" if you have 2 snacks or "x3 Snacks" if you have 3 snacks, etc.
The backpack needs to keep track of items that are defined elsewhere. JList can hold a list of any kind of object so what you need to do is create objects for the inventory items. Below shows an example using an enum:
public class InventoryManager {
public enum InventoryItem {
LIGHT("Flashlight") {
boolean isOn;
#Override public void doAction() {
isOn = !isOn;
}
#Override public String toString() {
return name;
}
},
POTION("Health Potions") {
#Override public void doAction() {
Game.getPlayer().setHealth(Game.getPlayer().getHealth() + 25);
remove(1);
}
},
SNACK("Snacks") {
#Override public void doAction() {
Game.getPlayer().setEnergy(Game.getPlayer().getEnergy() + 10);
remove(1);
}
};
private final String name;
private int quantity = 0;
private InventoryItem(String n) {
name = n;
}
public abstract void doAction();
public void add(int q) {
if ((quantity += q) < 0) quantity = 0;
}
public void remove(int q) {
add(-q);
}
#Override public String toString() {
return name + " x" + quantity;
}
}
public static InventoryItem[] getHeldItems() {
EnumSet<InventoryItem> items = EnumSet.allOf(InventoryItem.class);
Iterator<InventoryItem> it = items.iterator();
while (it.hasNext()) {
if (it.next().quantity < 1) {
it.remove();
}
}
return items.toArray(new InventoryItem[items.size()]);
}
}
The enum example is entirely static so there are some problems with actually doing it this way (I chose it primarily because it's the shortest code). But ultimately what you'll have is a superclass Item with abstract methods that the subclasses implement differently. Then you will populate the JList with the items held. When the user selects an item from the list, list.getSelectedValue() returns an Item object you can use in the game.
// or Item can be an interface classes implement
public abstract class Item {
public void doAction() {
Game.updateState();
}
}
public class Light extends InventoryItem {
boolean lighted;
#Override public void doAction() {
lighted = !lighted;
super.doAction();
}
}
public class Potion extends InventoryItem {
#Override public void doAction() {
player.hp++;
super.doAction();
}
}
public class Snack extends InventoryItem {
#Override public void doAction() {
player.energy++;
super.doAction();
}
}
The other way to do this is to use straight program logic, for example:
switch (list.getSelectedItem()) {
case "Flashlight": {
toggleFlashlight();
break;
}
case "Health Potion": {
usePotion();
break;
}
case "Snack": {
useSnack();
break;
}
}
But I have a feeling trying to do it all with logic like that will ultimately turn out to be more complicated.
I have a simple Swing application with a JEditorPane wrapped in a JScrollPane.
Unfortunately screen reader software like JAWS or NVDA does not behave correctly.
When focus enters the JEditorPane it only reads the accessible name followed by "text" and then stops, when the expected behavior is to continue reading the contents of the JEditorPane.
If I do not wrap the JEditorPane in the JScrollPane it works as expected.
I have tried inspecting the accessible tree using Monkey, but I cannot see any relevant difference between a JEditorPane wrapped in a JScrollPane and one that is not wrapped.
Any ideas?
Here is a brief sample that demonstrates the problem. If focus enters the first JEditorPane, JAWS reads "first editorpane - edit". If focus enters the second JEditorPane, JAWS reads "second editorpane - edit - bar".
public final class SmallExample {
public static void main(String... aArgs){
JFrame frame = new JFrame("Test Frame");
JPanel panel = new JPanel();
JEditorPane editorPane1 = new JEditorPane();
editorPane1.setText("Foo");
editorPane1.getAccessibleContext().setAccessibleName("first editorpane");
editorPane1.getAccessibleContext().setAccessibleDescription("");
JScrollPane scrollPane = new JScrollPane( editorPane1, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
panel.add(scrollPane);
JEditorPane editorPane2 = new JEditorPane();
panel.add(editorPane2);
editorPane2.setText("Bar");
editorPane2.getAccessibleContext().setAccessibleName("second editorpane");
editorPane2.getAccessibleContext().setAccessibleDescription("");
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
I found a workaround myself:
If I
modify the accessible tree to skip the JScrollPane and JViewPort
avoid sending accessible property-changed events from the JEditorPane
then it works.
I would still very much appreciate any insight into why this works (I found the workaround by replacing the AccessibleContext for editorPane2 with the one for editorPane1 and gradually switching the methods back until I found the ones that I needed to override).
Here is a working example (it is not so brief anymore):
public final class Example {
public static void main(String... aArgs){
JFrame frame = new JFrame("Test Frame");
final JPanel panel = new JPanel(){
public AccessibleContext getAccessibleContext() {
if(accessibleContext==null){
accessibleContext = new AccessibleContextWrapper(super.getAccessibleContext()){
public Accessible getAccessibleChild(int i) {
Accessible accessibleChild = super.getAccessibleChild(i);
while(accessibleChild!=null && (accessibleChild instanceof JScrollPane || accessibleChild instanceof JViewport)){
accessibleChild = accessibleChild.getAccessibleContext().getAccessibleChild(0);
}
return accessibleChild;
}
};
}
return accessibleContext;
}
};
final JEditorPane editorPane = new JEditorPane(){
public AccessibleContext getSuperAccessibleContext() {
return super.getAccessibleContext();
}
#Override
public AccessibleContext getAccessibleContext() {
return new AccessibleContextWrapper(super.getAccessibleContext()){
public Accessible getAccessibleParent() {
Accessible parent = super.getAccessibleParent();
while(parent!=null && (parent instanceof JScrollPane || parent instanceof JViewport)){
parent = parent.getAccessibleContext().getAccessibleParent();
}
return parent;
}
public int getAccessibleIndexInParent() {
int res = super.getAccessibleIndexInParent();
Accessible parent = super.getAccessibleParent();
while(parent!=null && (parent instanceof JScrollPane || parent instanceof JViewport)){
res = parent.getAccessibleContext().getAccessibleIndexInParent();
parent = parent.getAccessibleContext().getAccessibleParent();
}
return res;
}
public void addPropertyChangeListener(
PropertyChangeListener listener) {
}
public void removePropertyChangeListener(
PropertyChangeListener listener) {
}
};
}
};
editorPane.setText("Foo");
editorPane.getAccessibleContext().setAccessibleName("first editorpane");
editorPane.getAccessibleContext().setAccessibleDescription("");
editorPane.getAccessibleContext();
JScrollPane scrollPane = new JScrollPane( editorPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
panel.add(scrollPane);
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static class AccessibleContextWrapper extends AccessibleContext {
private final AccessibleContext inner;
public AccessibleContextWrapper(AccessibleContext inner) {
this.inner = inner;
}
public String getAccessibleName() {
return inner.getAccessibleName();
}
public void setAccessibleName(String s) {
inner.setAccessibleName(s);
}
public String getAccessibleDescription() {
return inner.getAccessibleDescription();
}
public void setAccessibleDescription(String s) {
inner.setAccessibleDescription(s);
}
public AccessibleRole getAccessibleRole() {
return inner.getAccessibleRole();
}
public AccessibleStateSet getAccessibleStateSet() {
return inner.getAccessibleStateSet();
}
public Accessible getAccessibleParent() {
return inner.getAccessibleParent();
}
public void setAccessibleParent(Accessible a) {
inner.setAccessibleParent(a);
}
public int getAccessibleIndexInParent() {
return inner.getAccessibleIndexInParent();
}
public int getAccessibleChildrenCount() {
return inner.getAccessibleChildrenCount();
}
public Accessible getAccessibleChild(int i) {
return inner.getAccessibleChild(i);
}
public Locale getLocale() throws IllegalComponentStateException {
return inner.getLocale();
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
inner.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
inner.removePropertyChangeListener(listener);
}
public AccessibleAction getAccessibleAction() {
return inner.getAccessibleAction();
}
public AccessibleComponent getAccessibleComponent() {
return inner.getAccessibleComponent();
}
public AccessibleSelection getAccessibleSelection() {
return inner.getAccessibleSelection();
}
public AccessibleText getAccessibleText() {
return inner.getAccessibleText();
}
public AccessibleEditableText getAccessibleEditableText() {
return inner.getAccessibleEditableText();
}
public AccessibleValue getAccessibleValue() {
return inner.getAccessibleValue();
}
public AccessibleIcon[] getAccessibleIcon() {
return inner.getAccessibleIcon();
}
public AccessibleRelationSet getAccessibleRelationSet() {
return inner.getAccessibleRelationSet();
}
public AccessibleTable getAccessibleTable() {
return inner.getAccessibleTable();
}
public void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
inner.firePropertyChange(propertyName, oldValue, newValue);
}
}
}
I am trying to change JList rows dynamically. I need change nth row colour, highlight it(n is unknown during compilation). I saw a lot of examples with custom ListCellRenderer, but all were "static".
In other words I have JList with x rows. During runtime my "business logic" detects nth row is important. So I want make its background green, wait one second, and then make it white again. One more thing, don't wan change row selection.
What is the best way to do so?
Simple, set a custom ListCellRenderer to your JList using:
list.setCellRenderer(myListCellrenderer);
Now inside the overridden method getListCellRendererComponent() do something like this:
public Component getListCellRendererComponent(.....) {
Component c = super.getListCellRendererComponent();
c.setBackGround(Color.blue)
return c;
}
The above example assumed that your custom renderer overrid DefaultListCellRenderer
Based on ListDemo sample from SUN.
If you enter some text in the textfield which isn't in the list and you hit highlight it gets added.
If the text is in the list and you hit highlight the entry in the list gets temporarily highlighted blue.
Note the solution here with the match field is just for demo. For more correct implementation consider the other ideas proposed and consider using javax.swing.Timer
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ListDemo extends JPanel {
private JList list;
private DefaultListModel listModel;
public String match = null;
private static final String hireString = "Highlight";
private JTextField employeeName;
public ListDemo() {
super(new BorderLayout());
listModel = new DefaultListModel();
listModel.addElement("Test1");
listModel.addElement("Test2");
listModel.addElement("Test3");
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.setVisibleRowCount(5);
list.setCellRenderer(new MyListCellRenderer());
JScrollPane listScrollPane = new JScrollPane(list);
JButton hireButton = new JButton(hireString);
HireListener hireListener = new HireListener(hireButton);
hireButton.setActionCommand(hireString);
hireButton.addActionListener(hireListener);
hireButton.setEnabled(false);
employeeName = new JTextField(10);
employeeName.addActionListener(hireListener);
employeeName.getDocument().addDocumentListener(hireListener);
listModel.getElementAt(list.getSelectedIndex()).toString();
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane,
BoxLayout.LINE_AXIS));
buttonPane.add(Box.createHorizontalStrut(5));
buttonPane.add(employeeName);
buttonPane.add(hireButton);
buttonPane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
add(listScrollPane, BorderLayout.CENTER);
add(buttonPane, BorderLayout.PAGE_END);
}
class MyListCellRenderer extends JLabel implements ListCellRenderer {
public MyListCellRenderer() {
setOpaque(true);
}
public Component getListCellRendererComponent(JList paramlist, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setText(value.toString());
if (value.toString().equals(match)) {
setBackground(Color.BLUE);
SwingWorker worker = new SwingWorker() {
#Override
public Object doInBackground() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) { /*Who cares*/ }
return null;
}
#Override
public void done() {
match = null;
list.repaint();
}
};
worker.execute();
} else
setBackground(Color.RED);
return this;
}
}
class HireListener implements ActionListener, DocumentListener {
private boolean alreadyEnabled = false;
private JButton button;
public HireListener(JButton button) {
this.button = button;
}
public void actionPerformed(ActionEvent e) {
String name = employeeName.getText();
if (listModel.contains(name)) {
match = name;
list.repaint();
employeeName.requestFocusInWindow();
employeeName.selectAll();
return;
}
if (name.equals("")) {
Toolkit.getDefaultToolkit().beep();
employeeName.requestFocusInWindow();
employeeName.selectAll();
return;
}
int index = list.getSelectedIndex();
if (index == -1)
index = 0;
else
index++;
listModel.insertElementAt(employeeName.getText(), index);
employeeName.requestFocusInWindow();
employeeName.setText("");
list.setSelectedIndex(index);
list.ensureIndexIsVisible(index);
}
public void insertUpdate(DocumentEvent e) {
enableButton();
}
public void removeUpdate(DocumentEvent e) {
handleEmptyTextField(e);
}
public void changedUpdate(DocumentEvent e) {
if (!handleEmptyTextField(e))
enableButton();
}
private void enableButton() {
if (!alreadyEnabled)
button.setEnabled(true);
}
private boolean handleEmptyTextField(DocumentEvent e) {
if (e.getDocument().getLength() <= 0) {
button.setEnabled(false);
alreadyEnabled = false;
return true;
}
return false;
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("ListDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new ListDemo();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() { createAndShowGUI(); }
});
}
}
Your custom ListCellRenderer, which implements the method getListCellRendererComponent, will have access to both the JList and the value that it is redering. This gives you a couple options for how to determine when to paint the nth row green:
You could subclass JList and have the renderer ask it which color to use for the bg. The JList subclass could trigger a repaint when the business logic determines that it is time for the nth row to be green, and then start an Swing Timer to trigger a repaint returning the bg back to normal
When the business logic determines when you should show the row as green, you also have the option of setting state on the backing object of the row, and test it for that state within getListCellRendererComponent, setting the bg green if the state is correct. Again, you have the option of setting an Swing Timer to revert the state on the backing object.