I posted a question earlier today and was directed by MadProgrammer to use ListCellRenderer to achieve the desired results. I have it almost working, but it is showing the same entry twice in the combobox and I don't know why. Please help me solve this riddle. The code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class NotWorking extends JPanel {
private JPanel editPanel;
private JComboBox<String> editComboLevel;
private JComboBox editCombo;
private String[] levels = {"Level 1", "Level 2", "Level 3"};
private static ArrayList<NotWorking> course = new ArrayList<NotWorking>();
public static String courseNum, courseTitle, courseLevel;
public JPanel createContentPane (){
Integer[] intArray = new Integer[course.size()];
for (int i = 0; i < course.size(); i++) {
intArray[i] = new Integer(i);
}
editPanel = new JPanel(new GridLayout(4,2));
editPanel.setPreferredSize(new Dimension(100,75));
editPanel.add(editCombo = new JComboBox(intArray));
ComboBoxRenderer renderer= new ComboBoxRenderer();
editCombo.setRenderer(renderer);
return editPanel;
}
NotWorking() {}
NotWorking(String courseNum, String courseTitle, String courseLevel) {
this.courseNum = courseNum;
this.courseTitle = courseTitle;
this.courseLevel = courseLevel;
}
#Override
public String toString() {
String courseInfo = getCourseNum() + ", " + getCourseTitle() + ", " + getCourseLevel();
return courseInfo;
}
public String getCourseNum() {
return this.courseNum;
}
public String getCourseTitle() {
return this.courseTitle;
}
public String getCourseLevel() {
return this.courseLevel;
}
public void setCourseNum(String courseNum) {
this.courseNum = courseNum;
}
public void setCourseTitle(String courseTitle) {
this.courseTitle = courseTitle;
}
public void setCourseLevel(String courseLevel) {
this.courseLevel = courseLevel;
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Example of Code Snippet");
NotWorking myCourse = new NotWorking();
frame.setContentPane(myCourse.createContentPane());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
course.add(new NotWorking("Course1", "Course1 desc", "Level 1"));
course.add(new NotWorking("Course2", "Course2 desc", "Level 2"));
createAndShowGUI();
for(NotWorking item : course)
System.out.println(item);
}
});
}
class ComboBoxRenderer extends JLabel
implements ListCellRenderer {
public ComboBoxRenderer() {
setOpaque(true);
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
}
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
int selectedIndex = ((Integer)value).intValue();
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setText(getCourseNum());
return this;
}
}
}
As you can see there are 2 additions to the ArrayList. I am limiting the display in the combobox to only show the course number, but Course2 is showing up twice and when I print out the contents of the ArrayList I see all the details for Course2 shown twice and none for Course1. Any help would be greatly appreciated. Cheers
Using a custom renderer is only half of the solution. A custom renderer will break the default items selection by using the keyboard of a combo box. See Combo Box With Custom Renderer for more information and a more complete solution.
The main problem with your code is the NotWorking class. This class should NOT extend JPanel. It is just a class that is used to hold the 3 properties of the class. The course number, title and level should NOT be static variables. There should be no reference to Swing components.
Your design should be one class for the NotWorking class and another class to create the GUI.
Start with the section from the Swing tutorial on How to Use Combo Boxes for a better design. Then customize the ComboBoxDemo.java example code from the tutorial to add your NotWorking class to the combo box instead of adding String data.
Related
I have a JTabbedPane with two JPanels that need to stay in seperate classes. In PageOne, I want to be able to increment MyInteger by clicking the add button, and I then want to be able to print that integer in PageTwo by clicking the button there. It prints the correct value in PageOne, but prints 0 when I pass it to the PageTwo class and print it there.
How can I pass the value in such a way that it prints the correct value when clicking the button in both JPanels? I figure it has something to do with how I inherit from PageOne, but couldn't find a way of changing it on SO that solved my problem.
Main class:
import javax.swing.*;
public class MyJFrame {
PageOne pageOne;
PageTwo pageTwo;
public MyJFrame() {
JFrame f = new JFrame();
pageOne = new PageOne();
pageTwo = new PageTwo();
JTabbedPane jTabbedPane = new JTabbedPane();
jTabbedPane.addTab("Page One", pageOne);
jTabbedPane.addTab("Page Two", pageTwo);
f.add(jTabbedPane);
f.setSize(200,120);
f.setVisible(true);
}
public static void main(String[] args) throws InterruptedException {
new MyJFrame();
}
}
JPanel One:
import javax.swing.*;
public class PageOne extends JPanel {
public Integer myInteger = 0;
public JButton add;
public PageOne() {
add = new JButton();
add.setText("Increment number");
add(add);
add.addActionListener(actionEvent -> {
myInteger++;
printOne();
});
}
public void printOne() {
System.out.println("Page One:" + myInteger);
}
}
JPanel Two:
import javax.swing.*;
public class PageTwo extends JPanel {
PageOne pageOneRef = new PageOne();
public JButton button;
public PageTwo() {
JPanel panel = new JPanel();
button = new JButton("Click me");
panel.add(button);
add(panel);
button.addActionListener(e -> printTwo());
}
public void printTwo() {
System.out.println("Page Two:" + pageOneRef.myInteger);
}
}
The basic answer is, you need some kind of "container" which can be shared between the two components. This is commonly achieved through the use of a "model" of some kind.
See:
Model-View-Controller
Observer Pattern
Writing Event Listeners
for an overview of the concepts presented below
Runnable example
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
DefaultIntegerModel model = new DefaultIntegerModel();
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Page One", new PageOne(model));
tabbedPane.addTab("Page Two", new PageTwo(model));
frame.add(tabbedPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface IntegerModel {
public interface Observer {
public void valueDidChange(IntegerModel source, int value);
}
public int getValue();
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
}
public interface MutableIntegerModel extends IntegerModel {
public void setValue(int value);
}
public class DefaultIntegerModel implements MutableIntegerModel {
private int value;
private List<Observer> observers;
public DefaultIntegerModel() {
this(0);
}
public DefaultIntegerModel(int value) {
this.value = value;
observers = new ArrayList<Observer>(8);
}
#Override
public void setValue(int value) {
this.value = value;
fireValueDidChange(value);
}
#Override
public int getValue() {
return value;
}
#Override
public void addObserver(Observer observer) {
observers.add(observer);
}
#Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
protected void fireValueDidChange(int value) {
for (Observer observer : observers) {
observer.valueDidChange(this, value);
}
}
}
public class PageOne extends JPanel {
public JButton add;
private MutableIntegerModel model;
public PageOne(MutableIntegerModel model) {
this.model = model;
add = new JButton();
add.setText("Increment number");
add(add);
add.addActionListener(actionEvent -> {
model.setValue(model.getValue() + 1);
printOne();
});
}
public void printOne() {
System.out.println("Page One:" + model.getValue());
}
}
public class PageTwo extends JPanel {
private JButton button;
private JLabel label;
private IntegerModel model;
public PageTwo(IntegerModel model) {
this.model = model;
model.addObserver(new IntegerModel.Observer() {
#Override
public void valueDidChange(IntegerModel source, int value) {
System.out.println("Page two value did change to " + value);
label.setText(Integer.toString(model.getValue()));
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel(Integer.toString(model.getValue()));
add(label, gbc);
button = new JButton("Click me");
button.addActionListener(e -> printTwo());
add(button, gbc);
}
public void printTwo() {
System.out.println("Page Two:" + model.getValue());
}
}
}
But why are there two models
Stop for a second and think about the responsibilities of each component.
PageOne want's to update the model, in order to do so, it also needs to know the value of the model. The model makes no assumption about "how" the consumer of this model will do that (so I didn't provide a increment method), it just allows the consumer to set the value it wants
PageTwo just wants to display the value (and be notified when some change occurs), so it doesn't need a mutable version of the model.
This restricts what consumers maybe able to do to the model rather the exposing functionality to parties which don't need it (and might be tempted to abuse it)
This is a demonstration and your needs may differ, but I'm bit of a scrooge when I design these kinds of things, I need the consumers to prove to me that they need functionality, rather then "assuming" what functionality they "might" require 😉
This is a practice known is "information hiding", which is supported by Polymorphism in OO languages
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.
This is my first question on stackoverflow and I'm in need of some help.
As part of a grander java application, I would like to bring up a JDialog with a couple of JComboBoxes querying the user to select a printer to use and then select the association resolution at which to print.
However, if I select a printer and the resolution which I pick is shared amongst several printers, then when I pick a printer which contains the same resolution, the drop down menu which gets displayed for the resolution combo box is invisible. The size of the drop down menu is correct, it just doesn't get populated. Try my code out and you'll see what I mean.
For example, two of my printing options are Win32 Printer : Kyocera FS-1035MFP KX and Win32 Printer : Adobe PDF (print to pdf). They both share the resolution 300x300, so if I select this resolution for the Kyocera and then select the Adobe PDF printer, the drop down menu will be the correct size, but will be empty.
I'm not really sure what is going on. Hopefully someone can help me. Thank you for your time.
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Vector;
import javax.print.DocFlavor;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.PrinterResolution;
import javax.swing.*;
public final class ComboDemo extends JDialog {
private JComboBox selectPrinterBox;
private JLabel selectPrinterLabel;
private JComboBox selectResolutionBox;
private JLabel selectResolutionLabel;
private PrintService printService;
private Resolution resolution;
private DocFlavor flavor;
private PrintRequestAttributeSet aset;
private Vector<Resolution> resolutionVector;
private double xDPI = 300.0;
private double yDPI = 300.0;
public PrintService[] getPrintServices() {
this.flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
this.aset = new HashPrintRequestAttributeSet();
return PrintServiceLookup.lookupPrintServices(flavor, aset);
}
public Vector<Resolution> getVectorOfResolutions(PrintService service) {
PrinterResolution[] supportedResolutions =
(PrinterResolution[]) service.getSupportedAttributeValues(
javax.print.attribute.standard.PrinterResolution.class,
flavor, aset);
Vector<Resolution> resolutions = new Vector<Resolution>();
for (PrinterResolution supportedResolution : supportedResolutions) {
Resolution res = new Resolution();
res.setxDPI(supportedResolution.getResolution(PrinterResolution.DPI)[0]);
res.setyDPI(supportedResolution.getResolution(PrinterResolution.DPI)[1]);
resolutions.add(res);
}
return resolutions;
}
public ComboDemo() {
super();
initComponents();
setContent();
setItemListeners();
}
public void initComponents() {
this.selectPrinterLabel = new JLabel("Select Printer: ");
PrintService[] services = this.getPrintServices();
this.selectPrinterBox = new JComboBox(services);
this.printService = (PrintService) this.selectPrinterBox.getSelectedItem();
this.resolutionVector = this.getVectorOfResolutions(printService);
this.resolution = new Resolution();
this.selectResolutionLabel = new JLabel("Select Resolution: ");
this.selectResolutionBox = new JComboBox(this.resolutionVector);
}
public void setContent() {
JPanel selectPrinterPanel = new JPanel();
selectPrinterPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
selectPrinterPanel.add(selectPrinterLabel);
selectPrinterPanel.add(selectPrinterBox);
JPanel selectResolutionPanel = new JPanel();
selectResolutionPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
selectResolutionPanel.add(selectResolutionLabel);
selectResolutionPanel.add(selectResolutionBox);
JPanel mainPanel = new JPanel();
BoxLayout fP = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
mainPanel.setLayout(fP);
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
mainPanel.add(selectPrinterPanel);
mainPanel.add(selectResolutionPanel);
this.setContentPane(mainPanel);
this.setTitle("ComboDemo");
this.setLocation(85, 79);
this.pack();
}
public void setItemListeners() {
selectPrinterBox.addItemListener(
new ItemListener() {
#Override
public void itemStateChanged(ItemEvent evt) {
if (evt.getSource().equals(selectPrinterBox)) {
if (evt.getStateChange() == ItemEvent.SELECTED) {
System.out.println("in printerBox itemListener");
printService = (PrintService) selectPrinterBox.getSelectedItem();
resolution = (Resolution) selectResolutionBox.getSelectedItem();
System.out.println("resolution (PrinterBox) : " + resolution.toString());
resolutionVector.clear();
resolutionVector.addAll(getVectorOfResolutions(printService));
if (resolutionVector == null) {
System.out.println("resVec is null");
}
if (resolutionVector.contains(resolution)) {
selectResolutionBox.setSelectedIndex(resolutionVector.lastIndexOf(resolution));
} else {
selectResolutionBox.setSelectedIndex(0);
}
}
}
}
});
selectResolutionBox.addItemListener(
new ItemListener() {
#Override
public void itemStateChanged(ItemEvent evt) {
if (evt.getSource().equals(selectResolutionBox)) {
if (evt.getStateChange() == ItemEvent.SELECTED) {
System.out.println("in resolutionBox itemListener");
resolution = (Resolution) selectResolutionBox.getSelectedItem();
System.out.println("resolution (ResolutionBox) : " + resolution.toString());
xDPI = (double) resolution.getxDPI();
yDPI = (double) resolution.getyDPI();
}
}
}
});
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ComboDemo cd = new ComboDemo();
cd.setVisible(true);
}
});
}
}
class Resolution {
private int xDPI;
private int yDPI;
public int getxDPI() {
return xDPI;
}
public void setxDPI(int xDPI) {
this.xDPI = xDPI;
}
public int getyDPI() {
return yDPI;
}
public void setyDPI(int yDPI) {
this.yDPI = yDPI;
}
#Override
public String toString() {
return (this.getxDPI() + "x" + this.getyDPI());
}
#Override
public boolean equals(Object obj) {
if ( obj instanceof Resolution ) {
Resolution r = (Resolution) obj;
return (this.xDPI == r.xDPI) && (this.yDPI == r.yDPI);
}
return false;
}
#Override
public int hashCode() {
return (this.getxDPI()*1000)+ this.getyDPI();
}
}
The problem is that you are manipulating the Vector that backs up the implicit ComboBoxModel you are using, behind the back of that ComboBoxModel. If the resolution is not contained, you call setSelectedIndex(0) which eventually triggers a refresh of the items of the combo box (because of some twisted internals of JComboBox/DefaultComboBoxModel.
So:
Either use a ComboBoxModel and when you want to modify the content of the JComboBox, do it with the ComboBoxModel (take a look at the DefaultComboBoxModel)
Or use the JComboBox API (removeAllItems, addItem)
Use an ActionListener on the comboboxes. instead of ItemListener You will only be notified of "selection-change" events
When trying to save an arraylist of my class Click, I get this error: java.io.NotSerializableException:javax.swing.text.DefaultHighlighter$LayeredHighlightInfo
on this line of code: os.writeObject(saveList);. Even though I made my Click class implement serializable. Does anyone know the cause of this?
Here is my save Method:
public static void saveArray(ArrayList<Click> saveList) {
JFileChooser c = new JFileChooser();
c.showSaveDialog(new JFrame());
File f = c.getSelectedFile();
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(
f.getAbsolutePath()));
os.writeObject(saveList);
} catch (IOException e1) {
e1.printStackTrace();
}
}
And here is my Click class:
public static class Click implements Serializable {
JTextField xClickField;
JTextField yClickField;
JTextField clickIntervalField;
JTextField repeatTimesField;
boolean isLeft;
Integer clickX;
Integer clickY;
Integer clickInterval;
Integer clickTimes;
public Click(boolean left){
xClickField = new JTextField();
yClickField = new JTextField();
clickIntervalField = new JTextField();
repeatTimesField = new JTextField();
clickX = 0;
clickY = 0;
clickInterval = 0;
clickTimes = 0;
isLeft = left;
addToJPanel();
}
public void addToJPanel() {
xClickField.setText(clickX.toString());
yClickField.setText(clickY.toString());
clickIntervalField.setText(clickInterval.toString());
repeatTimesField.setText(clickTimes.toString());
panel.add(xClickField);
panel.add(yClickField);
panel.add(clickIntervalField);
panel.add(repeatTimesField);
frame.setVisible(false);
frame.setVisible(true);
}
public void removeFromJPanel() {
panel.remove(xClickField);
panel.remove(yClickField);
panel.remove(clickIntervalField);
panel.remove(repeatTimesField);
frame.setVisible(false);
frame.setVisible(true);
}
}
By the way I took out a chunk of code from the Click class. So if you think that the error could be in that portion of the code, I will gladly add it in.
Thanks in advance!
Implementing Serializable is not sufficient to make an object serializable. For example, a Socket is not serializable: it doesn't make sense to serialize a socket. So, if you have a Foo class that has a field of type Socket and that implements Serializable, how do you intend to serialize a Foo instance. It won't work. All the fields of a serializable object msut also be serializable, recursively.
And, as Hovercraft says in his comment, you should serialize data, not swing components.
You're serializing JTextFields and other Swing components which is a waste of time and resources and is completely unnecessary. You should be serializing the state of your GUI, the data held by the class's fields. If you understand MVC, you should be serializing the model, not the view. If you don't understand MVC, Google it or read this article and learn the key concepts as they are key to creating GUI programs in any language.
Also, for my money, I'd use JAXB or some other XML-based tool to serialize your data as it is saved in text format and thus understandable when read.
Example of separating GUI from model and using a property change listener to listen and respond to property changes:
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class SimpleClickEg {
private static void createAndShowGui() {
SimpleClickPanel clickPanel = new SimpleClickPanel();
JFrame frame = new JFrame("SimpleClickEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(clickPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class SimpleClickPanel extends JPanel {
private static final int PREF_WIDTH = 800;
private static final int PREF_HIEGHT = 600;
private JTextField clickCountField = new JTextField(5);
private JTextField clickXField = new JTextField(5);
private JTextField clickYField = new JTextField(5);
private SimpleClick click = new SimpleClick();
public SimpleClickPanel() {
add(new JLabel("Click X:"));
add(clickXField);
add(new JLabel("Click Y:"));
add(clickYField);
add(new JLabel("Click Count:"));
add(clickCountField);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
click.setClickPoint(e.getPoint());
}
});
click.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (SimpleClick.CLICK_COUNT.equals(evt.getPropertyName())) {
clickCountField.setText(String.valueOf(click.getClickCount()));
} else if (SimpleClick.CLICK_X.equals(evt.getPropertyName())) {
clickXField.setText(String.valueOf(click.getClickX()));
} else if (SimpleClick.CLICK_Y.equals(evt.getPropertyName())) {
clickYField.setText(String.valueOf(click.getClickY()));
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_WIDTH, PREF_HIEGHT);
}
public SimpleClick getClick() {
return click;
}
}
class SimpleClick implements Serializable {
private static final long serialVersionUID = 1L;
public static final String CLICK_COUNT = "click count";
public static final String CLICK_X = "click x";
public static final String CLICK_Y = "click y";
private int clickCount;
private int clickX;
private int clickY;
private transient SwingPropertyChangeSupport spcSupport = new SwingPropertyChangeSupport(
this);
public int getClickCount() {
return clickCount;
}
public void setClickCount(int clickCount) {
Integer oldValue = this.clickCount;
Integer newValue = clickCount;
this.clickCount = newValue;
spcSupport.firePropertyChange(CLICK_COUNT, oldValue, newValue);
}
public void incrementClickCount() {
setClickCount(getClickCount() + 1);
}
public void setClickPoint(Point p) {
setClickX(p.x);
setClickY(p.y);
incrementClickCount();
}
public int getClickX() {
return clickX;
}
public void setClickX(int clickX) {
Integer oldValue = this.clickX;
Integer newValue = clickX;
this.clickX = newValue;
spcSupport.firePropertyChange(CLICK_X, oldValue, newValue);
}
public int getClickY() {
return clickY;
}
public void setClickY(int clickY) {
Integer oldValue = this.clickY;
Integer newValue = clickY;
this.clickY = newValue;
spcSupport.firePropertyChange(CLICK_Y, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
spcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
spcSupport.removePropertyChangeListener(listener);
}
}
As you can see the error clearly states that javax.swing.text.DefaultHighlighter is not serializable.
Now this class is used by composition inside the JTextField, which is a GUI component and it is not meant to be serialized. From your code it seems that you don't need to serialize the fields themselves, so just mark them as transient and you are done.
As a side note: it is always good to split what is your data from what is your GUI so that you can easily serialize just data and foget about anything concerning the GUI. This helps in general, not just in serialization, to preserve encapsulation and use OOP as it is meant to be used.
The problem is that your Click class has references to JTextField instances, and these (presumably) have references to some Swing class called DefaultHighlighter.LayeredHighlightInfo ... and that is not serializable.
You probably need to declare the 4 JTextField variables as transient. As a general rule, Java GUI classes such as Swing components are not effectively serializable.
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.