My use case is that a List<String> is passed to a Jpanel and for each String in the List, the JPanel renders a UI component. This UI component consists of 3 buttons and my current code for my given use case is as follows. -- The code for the 'UI component' follows --
public class MacroEditorEntity implements ActionListener {
private String macro;
private JButton upButton;
private JButton downButton;
private JButton MacroDetailsButton;
public MacroEditorEntity(String macro) {
this.macro = macro;
upButton = new JButton("Up");
downButton = new JButton("Down");
MacroDetailsButton = new JButton(macro);
upButton.addActionListener(this);
downButton.addActionListener(this);
MacroDetailsButton.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent evt) {
if(evt.getSource().equals(MacroDetailsButton))
{
System.out.println(macro);
}
}
public JButton GetUpButton()
{
return upButton;
}
public JButton GetDownButton()
{
return downButton;
}
public JButton getMacroDetailsButton()
{
return MacroDetailsButton;
}
}
The code for my Panel is as follows --
public class MacroEditor extends JPanel implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
private List<String> stringlist;
public MacroEditor(List<String> list) {
this.stringlist = list;
setupComponents();
validate();
setVisible(true);
}
public void setupComponents()
{
Box allButtons = Box.createVerticalBox();
for(String string : stringlist)
{
MacroEditorEntity entry = new MacroEditorEntity(string);
Box entryBox = Box.createHorizontalBox();
entryBox.add(entry.GetUpButton());
entryBox.add(Box.createHorizontalStrut(15));
entryBox.add(entry.getMacroDetailsButton());
entryBox.add(Box.createHorizontalStrut(15));
entryBox.add(entry.GetDownButton());
allButtons.add(entryBox);
}
add(allButtons);
}
#Override
public void propertyChange(PropertyChangeEvent arg0) {
revalidate();
repaint();
}
}
The code works fine for all Strings in the passed List. I want my Panel to pick up any change that may happen to the List like additions or deletions and add/remove relevant corresponding UI components accordingly. I think this can be done by using PropertyChangeListener but have not been able to account for that in my code.
Any ideas or suggestions on how i can make my Panel render/rerender stuff as soon as there are changes to the List would be of help.
What you need here is an observable collection. This should do it: http://commons.apache.org/dormant/events/apidocs/org/apache/commons/events/observable/ObservableCollection.html
Edit:
Here's the code snippet you requested:
public class ObservableListExample implements StandardPostModificationListener,
StandardPreModificationListener {
public static void main(String[] args) {
new ObservableListExample();
}
public ObservableListExample() {
ObservableList list = ObservableList.decorate(new ArrayList<>(),
new StandardModificationHandler());
list.getHandler().addPostModificationListener(this);
list.getHandler().addPreModificationListener(this);
//....
}
#Override
public void modificationOccurring(StandardPreModificationEvent event) {
// before modification
Collection changeCollection = event.getChangeCollection();
if (event.isTypeAdd()) {
// changeCollection contains added elements
} else if (event.isTypeReduce()) {
// changeCollection contains removed elements
}
}
#Override
public void modificationOccurred(StandardPostModificationEvent event) {
// after modification
Collection changeCollection = event.getChangeCollection();
if (event.isTypeAdd()) {
// changeCollection contains added elements
} else if (event.isTypeReduce()) {
// changeCollection contains removed elements
}
}
}
By the way: Another concept that helps to bind buisness objects to your GUI and react to modifications (bidirectionally) is Data Binding. Have a look at this, a Data Binding Library commonly used with Swing.
Related
In my project, my problem is that the JLabel won't show the incremented value from the getter. It should be adding up everytime I choose the correct radiobutton.
This is the first JFrame
public class DifEasy extends javax.swing.JFrame {
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// jPanel1.setVisible(false);
if (q1a1.isSelected()){
ScoreStorage mehh = new ScoreStorage();
mehh.setRawscore(mehh.getRawscore()+1);
}
this.setVisible(false);
new DifEasy1().setVisible(true);
}
This is the 2nd JFrame
public class DifEasy1 extends javax.swing.JFrame {
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
if (q1a1.isSelected()){
ScoreStorage mehh = new ScoreStorage();
mehh.setRawscore(mehh.getRawscore()+1);
}
this.setVisible(false);
new DifEasy2().setVisible(true);
}
This is the 3rd JFrame
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
if (q1a1.isSelected()){
ScoreStorage mehh = new ScoreStorage();
mehh.setRawscore(mehh.getRawscore()+1);
jLabel1.setText(String.valueOf(mehh.getRawscore()));
}
}
btw I just put a JLabel there for testing. After clicking the JButton (Given that I choosed the q1a1 radiobutton), the JLabel should change into 3, but it shows up only 0.
Getters and Setters class
public class ScoreStorage {
private int Rawscore = 0;
public void setRawscore(int rawscore){
this.Rawscore = Rawscore;
}
public int getRawscore(){
return Rawscore;
}
public synchronized void increment(){
setRawscore(Rawscore);
}
public int reset(){
Rawscore = 0;
return Rawscore;
}
}
(Based on the comments from RubioRic and MadProgrammer)
The code has two problems:
the Setter in ScoreStorage doesn't work:
You've got a typo in ScoreStorage.setRawscore, you are assigning this.Rawscore = Raswcore instead of this.Rawscore = rawscore therefore the value of Rawscore is always 0.
(also note that ScoreStorage.increment() probably doesn't do what it should since it only reassign the value.)
You create multiply ScoreStorage objects.
Each time you select an option, you are creating a brand new instance of ScoreStorage, which is initialised to 0.
You can implement a method setScoreStorage or create a constructor that accepts that argument in your JFrames.
Here is a short example how to pass one ScoreStorage between the different JFrame with a constructor
public class DifEasy extends JFrame {
private ScoreStorage scoreStorage;
public DifEasy(ScoreStorage scoreStorage) {
this.scoreStorage = scoreStorage;
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
if (q1a1.isSelected()){
scoreStorage.setRawscore(scoreStorage.getRawscore()+1);
}
this.setVisible(false);
new DifEasy1(scoreStorage).setVisible(true);
}
I have three classes, Main, DrawingPanel, and ToolboxPanel. ToolboxPanel contains all my buttons, including an Undo button. DrawingPanel is where I draw objects. I want the undo button to become enabled when an object is drawn on the screen, and disabled when there are no more objects left on the screen. Main creates an instance of DrawingPanel and of ToolboxPanel. I can get my undo button to work correctly if I use static methods and call, say, Main.setUndoStatus(false); from drawingPanel. The setUndoStatus then calls a setter in toolboxPanel. However, I've been reading about the Observer pattern and listeners and think I'm probably not doing it in a best-practice way.
How should I go about this using the observer pattern and/or mouse listeners correctly? (Or any "better" way of doing it).
Here's some code somewhat similar to what I'm doing.
public class Main
{
DrawingPanel drawingPanel;
ToolboxPanel toolboxPanel;
public Main()
{
drawingPanel = new DrawingPanel();
toolboxPanel = new ToolboxPanel(drawingPanel);
}
}
//A static method here to setUndoStatus, but I feel like I shouldn't have it
public static void setUndoStatus(boolean b)
{
{
toolboxPanel.setUndoStatus(b);
}
}
public class ToolboxPanel
{
JButton undoButton;
public ToolboxPanel(DrawingPanel drawingPanel)
{
undoButton = new JButton("Undo");
undoButton.setEnabled(false);
undoButton.addActionListener
(
new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
drawingPanel.undo();
undoButton.setEnabled(drawingPanel.getUndoStatus());
}
}
);
}
public void setUndoStatus(boolean status)
{
undoButton.setEnabled(status);
}
}
public class DrawingPanel
{
public DrawingPanel()
{
addMouseListener(new MouseAdapter()
{
public void mouseReleased(MouseEvent e)
{
//Some code here that's unrelated
if(objectsExist == true) //If something gets drawn, whatever
{
Main.setUndoStatus(true); //Don't like this
}
}
});
}
}
I want to be able to have a canvas component that adds spots to places where the user double clicks, when a spot is added, and then I want to be able to notify the listbox placed in the main frame that a spot has been added.
My knowledge of event broadcasting is poor, is there a better way of doing this?
Main class: initialize drawing component:
will this be best for an unnamed inner class?
private JList lbx;
private DefaultListModel<String> lbxModel;
private DrawComp draw;
public static void main(String args[]) {
draw.addListener(new DrawCompListener() {
#Override
public void spotAdded(DrawComp source, Spot spot) {
lbxModel.addElement("spot+" added");
}
});
}
interface that will be passed through to the queue:
public interface DrawCompListener {
void spotAdded(DrawComp source, Spot spot);
Queue Class that will implement the interface: (should this be here?)
public class DrawCompListenerQueue implements DrawCompListener {
private ArrayList<DrawCompListener> listeners = new ArrayList<DrawCompListener>();
public void addListener(DrawCompListener listener) {
listeners.add(listener);
}
#Override
public void spotAdded(DrawComp source, Spot spot) {
for(DrawCompListener listener : listeners) {
listener.spotAdded(source, spot);
}
}
The actual drawing component to place the spots and pass through the info to the main class for the Jlist
public class DrawComp extends JPanel implements MouseListener {
private Vector<Spot> spots = new Vector<Spot>();
private DrawCompListenerQueue listenerQ = new DrawCompListenerQueue();
public void addListener(DrawCompListener listener) {
listenerQ.addListener(listener);
}
adding a spot:
public void mouseClicked(MouseEvent e) {
// left-click = add spot
if ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 2)) {
// add spot
Spot s = new Spot();
s.p = e.getPoint();
spots.add(s);
repaint();
// notify listeners
listenerQ.spotAdded(this, s);
Do not reinvent the wheel use the Glazed list lib.
I have two Action Listener inner-classes inside one main class. Each one corresponds to its own button. One of the Action Listeners is coded to generate an Array List. The other simply writes that Array List to a Text Field.
My question is how can I refer to/access that data from the other Action Listener? The code below compiles but when I check the contents of the Array List from the second Action Listener, it is empty ([]).
I'm guessing this has something to do with the Array List re-instantiating when the other Action Listener's actionPerformed method is called. How can I work around this? (The code here is just the 2 Action Listeners).
// Create a Button Listener Inner Class for Input Route Button.
class InputRouteButtonHandler implements ActionListener {
List<String> routeStopList = new ArrayList<String>();
public void actionPerformed(ActionEvent event) {
String city1 = (String) cityCombo1.getSelectedItem();
String city2 = (String) cityCombo2.getSelectedItem();
if (city1.equals(city2)) {
JOptionPane.showMessageDialog(null, "Invalid route chosen. Please choose two different cities.");
} else {
routeStopList.add(city1); //Add city1 to start of array.
int dialogResult;
do {
String routeStop = JOptionPane.showInputDialog("Enter a stop between the 2 cities:");
routeStopList.add(routeStop);
dialogResult = JOptionPane.showConfirmDialog(null, "Add another stop?");
} while (dialogResult.equals(JOptionPane.YES_OPTION));
routeStopList.add(city2); //Add city2 to end of array.
System.out.println(routeStopList); //Just checking ArrayList contents
}
}
}
// Create a Button Listener Inner Class for Route Button.
class RouteButtonHandler extends InputRouteButtonHandler implements ActionListener {
public void actionPerformed(ActionEvent event) {
String city1 = (String) cityCombo1.getSelectedItem();
String city2 = (String) cityCombo2.getSelectedItem();
System.out.println(routeStopList); //Just checking ArrayList contents
if (city1.equals(city2)) {
JOptionPane.showMessageDialog(null, "Invalid route chosen. Please choose two different cities.");
} else {
for (int i = 0; i < routeStopList.size(); i++) {
String addedRoute = routeStopList.get(i);
adminPanelTextArea.append(addedRoute + "\n");
}
}
}
}
You are right, your problem is due to your creating two ArrayLists, lists that have absolulely no relationship with each other, other than holding the same type of objects and having the same names. A solution is to create one Model class that is shared by both ActionListener classes, and in this model class, have your ArrayList. Then give your ArrayList classes a setModel(Model model) method or constructor, and pass in a reference to the single Model object into both ActionListeners.
One other consideration is to use a single Control class to handle your listener type code, and then have your Control class hold a Model field.
As an aside, this is dangerous code:
if (city1 == city2) {
Don't compare Strings using ==. Use the equals(...) or the equalsIgnoreCase(...) method instead. Understand that == checks if the two objects are the same which is not what you're interested in. The methods on the other hand check if the two Strings have the same characters in the same order, and that's what matters here.
For example, say you have two buttons that want to manipulate a JList, one wanting to add text, the other wanting to clear it, then you could pass the JList's model into both button handlers. An example program could look like:
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class ShareList extends JPanel {
private static final String PROTOTYPE_CELL_VALUE = "ABCDEFGHIJKLMNOP";
private static final int VISIBLE_ROW_COUNT = 10;
private JTextField textField = new JTextField(10);
private DefaultListModel<String> listModel = new DefaultListModel<>();
private JList<String> myList = new JList<>(listModel);
public ShareList() {
myList.setPrototypeCellValue(PROTOTYPE_CELL_VALUE);
myList.setVisibleRowCount(VISIBLE_ROW_COUNT);
myList.setFocusable(false);
JPanel buttonPanel = new JPanel();
AddHandler addHandler = new AddHandler(listModel, this);
textField.addActionListener(addHandler);
buttonPanel.add(new JButton(addHandler));
buttonPanel.add(new JButton(new ClearHandler(listModel)));
JPanel rightPanel = new JPanel(new BorderLayout());
rightPanel.add(textField, BorderLayout.NORTH);
rightPanel.add(buttonPanel, BorderLayout.CENTER);
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
add(new JScrollPane(myList));
add(rightPanel);
}
public String getText() {
textField.selectAll();
return textField.getText();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("ShareList");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ShareList());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class AddHandler extends AbstractAction {
private DefaultListModel<String> listModel;
private ShareList shareList;
public AddHandler(DefaultListModel<String> listModel, ShareList shareList) {
super("Add");
putValue(MNEMONIC_KEY, KeyEvent.VK_A);
this.listModel = listModel;
this.shareList = shareList;
}
public void actionPerformed(ActionEvent e) {
String text = shareList.getText();
listModel.addElement(text);
};
}
#SuppressWarnings("serial")
class ClearHandler extends AbstractAction {
private DefaultListModel<String> listModel;
public ClearHandler(DefaultListModel<String> listModel) {
super("Clear");
putValue(MNEMONIC_KEY, KeyEvent.VK_C);
this.listModel = listModel;
}
public void actionPerformed(ActionEvent e) {
listModel.clear();
};
}
I have 2 classes.
when I put bold 3 lines in the method addCourses() the dialog does not show combobox in the Panel
but when I remove from addCourses and put those bold lines in the constructor, JComboBox are shown in the Panel.
But data will not show because data items updates to ComboBox will happen after Constructor is created.
How can I solve this problem.
this.mainPanel.add(courseCombo, BorderLayout.NORTH);
this.mainPanel.add(sessionCombo, BorderLayout.CENTER);
this.mainPanel.add(courseButton, BorderLayout.SOUTH);
public class Updator {
CourseListFrame clf = new CourseListFrame();
for(...){
clf.addContentsToBox(displayName, className);
}
clf.addCourses();
}
and second class is
public class CourseListFrame extends JDialog implements ActionListener {
public JPanel mainPanel = new JPanel(new BorderLayout(2, 2));
public JButton courseButton = new JButton(("Submit"));
public JComboBox courseCombo;
public JComboBox sessionCombo;
public Multimap<String, String> map; // = HashMultimap.create();
public static CourseListFrame courseListDialog;
public CourseListFrame() {
super(this.getMainFrame());
this.getContentPane().add(mainPanel);
map = HashMultimap.create();
courseCombo = new JComboBox();
courseCombo.addItem("Select Courses");
courseCombo.addActionListener(this);
sessionCombo = new JComboBox();
}
public void addContentsToBox(String course, String session) {
map.put(course, session);
courseCombo.addItem(course);
}
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox) e.getSource();
String str = (String) cb.getSelectedItem();
setSessionCombo(str);
}
public void setSessionCombo(String course) {
if (map.containsKey(course)) {
sessionCombo.removeAllItems();
Iterator it = map.get(course).iterator();
while (it.hasNext()) {
sessionCombo.addItem(it.next());
}
}
}
public void addCourses() {
this.mainPanel.add(courseCombo, BorderLayout.NORTH);
this.mainPanel.add(sessionCombo, BorderLayout.CENTER);
this.mainPanel.add(courseButton, BorderLayout.SOUTH);
}
public static void showCourseListDialog() {
if (courseListDialog == null) {
courseListDialog = new CourseListFrame();
}
courseListDialog.pack();
courseListDialog.setVisible(true);
courseListDialog.setSize(260, 180);
}
}
The reason why they arent showing is because you are probably calling the static showCourseListDialog() to show your dialog. This method will test whether your static courseListDialog is null, and if so, create one and set that dialog visible, not the clf that you instantiated.
If in your showCourseListDialog() you call the addCourses() method after instantiating your 'singleton', you should be OK:
public static void showCourseListDialog() {
if (courseListDialog == null) {
courseListDialog = new CourseListFrame();
courseListDialog.addCourses();// <<---- this is key!
}
courseListDialog.pack();
courseListDialog.setVisible(true);
courseListDialog.setSize(260, 180);
}
That said, by having the static courseListDialog, it is apparent that you want that dialog to be a singleton. If that is the case, I would at least make your constructor private. You want to proactively avoid the situation that you are getting into where you can construct multiple instances of a singleton. You still would have a race condition to deal with in your showCourseListDialog, but as you will only be calling this method in the EDT, you should be safe.
Take a look at this and other topics on Singleton development in Java (and dont forget to read the con arguments where it is described as an anti-pattern)