I have this java swing program, and im trying to figure out how can i create a button that upon clicking it will clear the text areas & change the icon of the person to put their hand down.
The buttons are dynamically generated using a for loop
And this
// To create buttons
for(int i=0 ; i < list.length; i++){
Participant pa = list[i];
JButton b = new JButton(pa.getNameButton(),participant);
b.addActionListener(e ->
{
String s = pa.toString() + questionPane.getText();
final ImageIcon raise = resizeIcon(new ImageIcon("src/raise.png"),30,30);
b.setIcon(raise);
JOptionPane.showMessageDialog(null,s,"Welcome to Chat Room",JOptionPane.INFORMATION_MESSAGE,pa.getImage());
});
p.add(b);
}
// Clear button logic
clearButton.addActionListener(e ->{
questionPane.setText("");
hostPane.setText("");
});
Okay, this is going to be a bit of fun.
The following example decouples much of the concept and makes use of a basic "observer pattern" to notify interested parties that the state has changed (ie, the chat's been cleared).
This is a basic concept where by you decouple the "what" from the "how", ie, "what" it is you want done (update the model) from the "how" it gets done (ie, button push). This makes it easier to adapt to more complex systems.
The example contains a ChatService, which has a single listener, which, for this example, simple tells interested parties that the chat has been cleared.
A more complex solution might have the ChatService generating events for when a user "raises" their hand, which allows the interested parties to deal with it in what ever way is relevant to them.
The example makes use of the Action API to decouple the work performed by each action from the UI itself. This helps create a single unit of work which is easier to deal with when you have a dynamic data set.
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
ChatService chatService = new ChatService();
JPanel panel = new JPanel();
String[] names = new String[] {"Bryan", "Alan", "George", "Henry"};
List<PeopleAction> actions = new ArrayList<>(names.length);
for (String name : names) {
PeopleAction action = new PeopleAction(chatService, name, false);
actions.add(action);
}
Random rnd = new Random();
actions.get(rnd.nextInt(names.length)).setRaised(true);
for (Action action : actions) {
JButton btn = new JButton(action);
panel.add(btn);
}
setLayout(new GridLayout(2, 1));
add(panel);
JPanel hostPane = new JPanel();
JButton clearButton = new JButton(new ClearAction(chatService));
hostPane.add(clearButton);
add(hostPane);
}
}
public class ChatService {
private List<ChatListener> listeners = new ArrayList<>(25);
public void addChatListeners(ChatListener listener) {
listeners.add(listener);
}
public void removeChatListener(ChatListener listener) {
listeners.remove(listener);
}
protected void fireChatCleared() {
if (listeners.isEmpty()) {
return;
}
for (ChatListener listener : listeners) {
listener.chatCleared();
}
}
public void clear() {
// Do what's required
fireChatCleared();
}
}
public interface ChatListener {
public void chatCleared();
}
public class PeopleAction extends AbstractAction implements ChatListener {
private String name;
private boolean raised;
public PeopleAction(ChatService chatService, String name, boolean raised) {
// You can use either LARGE_ICON_KEY or SMALL_ICON to set the icon
this.name = name;
if (raised) {
putValue(NAME, "* " + name);
} else {
putValue(NAME, name);
}
chatService.addChatListeners(this);
}
public void setRaised(boolean raised) {
if (raised) {
putValue(NAME, "* " + name);
} else {
putValue(NAME, name);
}
}
public boolean isRaised() {
return raised;
}
#Override
public void actionPerformed(ActionEvent evt) {
// Do what ever needs to be done
setRaised(!isRaised());
}
#Override
public void chatCleared() {
setRaised(false);
}
}
public class ClearAction extends AbstractAction {
private ChatService chatService;
public ClearAction(ChatService chatService) {
this.chatService = chatService;
putValue(NAME, "Clear");
}
#Override
public void actionPerformed(ActionEvent evt) {
chatService.clear();
}
}
}
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 want to get rid of the repeating code from an application. I tried to do it in several ways, but when I did it, the application did not work as I expected. Only putting the same code in a function had an effect.
In a nutshell, in the application I can delete records in 2 ways, either by pressing a button or by pointing to a record and deleting it with the right mouse button. Button deletion works, but I do not know how to make the mouse deletion have the same effect.
Button that deletes a record in a table.
deleteButton.addActionListener(event -> {
String name;
name = Name.getText();
try {
removeSelectedRow(table1);
pst = con.prepareStatement("delete from recipe where recipe_name = ?");
pst.setString(1, name);
pst.executeUpdate();
JOptionPane.showMessageDialog(null, "Record deleted");
Name.setText("");
Time.setText("");
Difficulty.setSelectedItem("");
Name.requestFocus();
} catch (SQLException e) {
e.printStackTrace();
}
});
here is a function that is supposed to delete with the right mouse button, as you can see it works but the code is almost identical to the previous example.
public void setDeleteButton(ActionEvent event) {
JMenuItem menu = (JMenuItem) event.getSource();
if (menu == menuItemRemove) {
removeSelectedRow(table1);
}
String name;
name = Name.getText();
try {
removeSelectedRow(table1);
pst = con.prepareStatement("delete from recipe where recipe_name = ?");
pst.setString(1, name);
pst.executeUpdate();
JOptionPane.showMessageDialog(null, "Record deleted");
Name.setText("");
Time.setText("");
Difficulty.setSelectedItem("");
Name.requestFocus();
} catch (SQLException e) {
e.printStackTrace();
}
}
function that points to a specific record
public void removeSelectedRow(JTable table) {
DefaultTableModel model = (DefaultTableModel) table1.getModel();
if (table.getSelectedRow() != -1) {
model.removeRow(table.getSelectedRow());
}
}
Okay, so this is going to require a slight shift in mindset. To make this truely flexible, you're going to need to support concepts such as "dependency inject" and "delegation".
The reason for this is, you "operation" needs a lot of information, but, we should be driving towards having a low level of cohesion or coupling between our classes. Your "operation", for example, shouldn't care "how" the row is deleted, only that when requested, it should be done.
So, lets start with some basic delegation...
public interface MutableTableSupportable {
public void addListSelectionListener(ListSelectionListener listener);
public void removeListSelectionListener(ListSelectionListener listener);
}
public interface TableRowDeletable extends MutableTableSupportable {
public int getSelectedRowCount();
public void removeSelectedRow();
}
Now, obviously, I'm overly simplifying this for more my needs, but here I've provided a "basic" level delegate and more focused delegate. Why? Because what happens if you want to provide a "insert" action? Why should it have "delete" functionality? Instead, we deliberately isolate the functionality we want to expose.
Next, we need to design our action...
public class DeleteRowAction extends AbstractAction {
private TableRowDeletable delgate;
public DeleteRowAction(TableRowDeletable delgate) {
putValue(SHORT_DESCRIPTION, "Delete the currently selected row");
putValue(MNEMONIC_KEY, KeyEvent.VK_D);
putValue(NAME, "Delete Row");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK));
this.delgate = delgate;
delgate.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectionDidChange();
}
});
selectionDidChange();
}
protected void selectionDidChange() {
setEnabled(delgate.getSelectedRowCount() > 0);
}
#Override
public void actionPerformed(ActionEvent e) {
delgate.removeSelectedRow();
}
}
Okay, nothing really special, which is kind of the point. It monitors the selection state so we can enable/disable the action and when triggered, we call our delegate to do the actual work. This decouples the action from the implementation, as the action doesn't need to know what type of TableModel is in use or what type of data source it might be using, it just wants to tell the delegate that it should carry out some kind of operation.
Also note, we've set up a keyboard short cut, which can be used by the JMenuItem and mnemonic support (hold down the Alt or Option key)
Okay, but that's really doing a lot for us, or is it...
Let's have a look at what a delete action might look like...
DeleteRowAction deleteRowAction = new DeleteRowAction(new TableRowDeletable() {
#Override
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
#Override
public void removeSelectedRow() {
DefaultTableModel model = (DefaultTableModel) table.getModel();
int visibleRowIndex = table.getSelectedRow();
if (visibleRowIndex == -1) {
return;
}
int modelIndex = table.convertRowIndexToModel(visibleRowIndex);
// I'm guessing here, but if you're deleting a row, you should
// use the row data
String recordId = (String) model.getValueAt(modelIndex, 0);
try (PreparedStatement pst = getConnection().prepareStatement("delete from recipe where recipe_name = ?")) {
pst.setString(1, recordId);
// You could check the number of rows effected by this change
pst.executeUpdate();
JOptionPane.showMessageDialog(TestPane.this, "Record deleted", "Success", JOptionPane.INFORMATION_MESSAGE);
model.removeRow(modelIndex);
} catch (SQLException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(TestPane.this, "Failed to delete row from database", "Error", JOptionPane.ERROR_MESSAGE);
}
}
#Override
public void addListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().addListSelectionListener(listener);
}
#Override
public void removeListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().removeListSelectionListener(listener);
}
});
Now, this is just an example, but the basic idea is, we've provide implementation for both the MutableTableSupportable and TableRowDeletable interfaces (but the DeleteRowAction doesn't care about the "how") and we've implemented the removeSelectedRow functionality to delete the row from the TableModel and database.
Again, DeleteRowAction doesn't care how this is implemented, it's just delegating that responsibility, so you could have multiple DeleteRowActions which work with different TableModels and data sources all at the same time π±
Delegation πͺ
Okay, but how would all that work together? Well, actually, really easily in fact
Runnable example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import java.sql.*;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JMenuBar menuBar = new JMenuBar();
JFrame frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.add(new TestPane(menuBar));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTable table;
public TestPane(JMenuBar menuBar) {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(new Object[][]{new Object[]{"Test"}}, new Object[]{"Test"});
table = new JTable(model);
add(new JScrollPane(table));
DeleteRowAction deleteRowAction = new DeleteRowAction(new TableRowDeletable() {
#Override
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
#Override
public void removeSelectedRow() {
JOptionPane.showMessageDialog(TestPane.this, "Delete the row please", "Debug", JOptionPane.INFORMATION_MESSAGE);
}
#Override
public void addListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().addListSelectionListener(listener);
}
#Override
public void removeListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().removeListSelectionListener(listener);
}
});
JMenu actionsMenu = new JMenu("Actions");
actionsMenu.add(deleteRowAction);
menuBar.add(actionsMenu);
JButton deleteButton = new JButton(deleteRowAction);
add(deleteButton, BorderLayout.SOUTH);
}
}
public interface MutableTableSupportable {
public void addListSelectionListener(ListSelectionListener listener);
public void removeListSelectionListener(ListSelectionListener listener);
}
public interface TableRowDeletable extends MutableTableSupportable {
public int getSelectedRowCount();
public void removeSelectedRow();
}
public class DeleteRowAction extends AbstractAction {
private TableRowDeletable delgate;
public DeleteRowAction(TableRowDeletable delgate) {
putValue(SHORT_DESCRIPTION, "Delete the currently selected row");
putValue(MNEMONIC_KEY, KeyEvent.VK_D);
putValue(NAME, "Delete Row");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK));
this.delgate = delgate;
delgate.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectionDidChange();
}
});
selectionDidChange();
}
protected void selectionDidChange() {
setEnabled(delgate.getSelectedRowCount() > 0);
}
#Override
public void actionPerformed(ActionEvent e) {
delgate.removeSelectedRow();
}
}
}
nb: This example removes the database support, as I don't have one and instead displays a message
Okay, lets take a quick look at some of the interesting things here...
Firstly...
JMenuBar menuBar = new JMenuBar();
JFrame frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.add(new TestPane(menuBar));
We inject the menu bar into the panel. This is done so that the panel can configure the menu bar as it needs. We could use a type of factory or another delegate here, but I'll leave that up to you to figure out.
Next...
JMenu actionsMenu = new JMenu("Actions");
actionsMenu.add(deleteRowAction);
menuBar.add(actionsMenu);
JButton deleteButton = new JButton(deleteRowAction);
add(deleteButton, BorderLayout.SOUTH);
We build the JMenu and add our delete row action and create a JButton, using the same Action ... for five lines of code, we've actually done a lot. We've been able to set up the text displayed by each component, the tooltip text, the accelerator key and mnemonic ... try doing that manually, and then need to change something down the track π (want to support localisation - need to make changes in one location)
But wait, we can do more!! π±
If we add...
InputMap inputMap = table.getInputMap(WHEN_FOCUSED);
ActionMap actionMap = table.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "deleteRow");
actionMap.put("deleteRow", deleteRowAction);
to the end of the constructor, we can can provide a key binding to the user, so that when the JTable has keyboard focus and the hit they Delete/Backspace key, it will trigger the action as well!!!
Now we have four ways to trigger the action:
Press the button
Open and trigger the menu items
Use the menus keyboard "accelerator" key binding
Hit the Delete key
Actions πͺ
Key bindings runnable example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JMenuBar menuBar = new JMenuBar();
JFrame frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.add(new TestPane(menuBar));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTable table;
public TestPane(JMenuBar menuBar) {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(new Object[][]{new Object[]{"Test"}}, new Object[]{"Test"});
table = new JTable(model);
add(new JScrollPane(table));
DeleteRowAction deleteRowAction = new DeleteRowAction(new TableRowDeletable() {
#Override
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
#Override
public void removeSelectedRow() {
JOptionPane.showMessageDialog(TestPane.this, "Delete the row please", "Debug", JOptionPane.INFORMATION_MESSAGE);
}
#Override
public void addListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().addListSelectionListener(listener);
}
#Override
public void removeListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().removeListSelectionListener(listener);
}
});
JMenu actionsMenu = new JMenu("Actions");
actionsMenu.add(deleteRowAction);
menuBar.add(actionsMenu);
JButton deleteButton = new JButton(deleteRowAction);
add(deleteButton, BorderLayout.SOUTH);
InputMap inputMap = table.getInputMap(WHEN_FOCUSED);
ActionMap actionMap = table.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "deleteRow");
actionMap.put("deleteRow", deleteRowAction);
}
}
public interface MutableTableSupportable {
public void addListSelectionListener(ListSelectionListener listener);
public void removeListSelectionListener(ListSelectionListener listener);
}
public interface TableRowDeletable extends MutableTableSupportable {
public int getSelectedRowCount();
public void removeSelectedRow();
}
public class DeleteRowAction extends AbstractAction {
private TableRowDeletable delgate;
public DeleteRowAction(TableRowDeletable delgate) {
putValue(SHORT_DESCRIPTION, "Delete the currently selected row");
putValue(MNEMONIC_KEY, KeyEvent.VK_D);
putValue(NAME, "Delete Row");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK));
this.delgate = delgate;
delgate.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectionDidChange();
}
});
selectionDidChange();
}
protected void selectionDidChange() {
setEnabled(delgate.getSelectedRowCount() > 0);
}
#Override
public void actionPerformed(ActionEvent e) {
delgate.removeSelectedRow();
}
}
}
But that's not all! We could also add a button to a JToolBar, because, why not!?
I've tried to apply the Observable/Observer pattern but there is something wrong with my code when I try to change a the textfield of a JTextPane.
I've got 3 classes, Play, Controller and SecondWindow here are a sample of their code.
public class Play() {
Controller c = new Controller();
SecondWindow sw = new SecondWindow();
c.addObserver(sw)
c.setText("blabla");
}
My class Controller:
public class Controller extends Observable(){
private String text ="";
private static Controller getInstance() {
if (instance == null) {
instance = new Controller();
}
return instance;
}
public void setText(String s) {
text = s;
setChanged();
notifyObservers();
}
}
and SecondWindow:
public class SecondWindow extends JFrame implements Observer{
private JPanel contentPane;
private Controller c;
private JTextPane txt = new JTextPane();
public SecondWindow () {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SecondWindow frame = new SecondWindow();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public SecondWindow() {
initComponents();
createEvents();
c = Controller.getInstance();
}
public void initComponents() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(1000, 0, 300,500);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
txt.setBounds(0, 0, 280, 460);
txt.enable(false);
contentPane.add(txt);
}
public void update(Observable arg0 , Object arg1){
// Things to change here
}
I can't manage to put the variable c in the textField (like a txt.setText(c.getText) instruction). I'm sure that it reads the method update, but I don't know how to make sure it works.
Hint: Per the Observerable API the notifyObservers method has an overload that accepts any object as a parameter:
public void notifyObservers(Object arg)
This can even be a String. And as per the Observer API, this object is then passed into the update method in the observer, and you can use it there.
void update(Observable o,
Object arg)
arg - an argument passed to the notifyObservers method.
Separate side issue here:
contentPane.setLayout(null);
For most Swing aficionados, seeing this is like hearing nails on a chalkboard -- it's painful. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one. Instead you will want to study and learn the layout managers and then nest JPanels, each using its own layout manager to create pleasing and complex GUI's that look good on all OS's.
Side issue number two: your code is not Swing thread safe, since the Swing GUI could very well be notified by the observable off of the Swing event dispatch thread or EDT. While it is not likely to cause frequent or serious problems with this simple program, in general it would be better to use a SwingPropertyChangeSupport and PropertyChangeListeners rather than Observer / Observable if you can.
Next Side Issue
This:
public class Controller extends Observable(){
isn't compilable / kosher Java. Same for the duplicate parameter-less constructors for the SecondWindow class. Yes, we know what you're trying to do, but it's hard enough trying to understand someone else's code, you really don't want to make it harder by posting kind-of sort-of uncompilable code, trust me.
For example, something simple could be implemented in Swing using PropertyChangeListeners, like so:
import java.util.concurrent.TimeUnit;
public class Play2 {
public static void main(String[] args) {
Model2 model2 = new Model2();
View2 view2 = new View2();
new Controller2(model2, view2);
view2.show();
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// one of the few times it's OK to ignore an exception
}
String text = String.format("Counter Value: %d", i);
model2.setText(text);
}
}
}
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Model2 {
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public static final String TEXT = "text"; // name of our "bound" property
private String text = "";
public String getText() {
return text;
}
public void setText(String text) {
String oldValue = this.text;
String newValue = text;
this.text = text;
pcSupport.firePropertyChange(TEXT, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
import javax.swing.*;
public class View2 {
private JPanel mainPanel = new JPanel();
private JTextField textField = new JTextField(10);
public View2() {
textField.setFocusable(false);
mainPanel.add(new JLabel("Text:"));
mainPanel.add(textField);
}
public JPanel getMainPanel() {
return mainPanel;
}
public void setText(String text) {
textField.setText(text);
}
public void show() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("View");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getMainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class Controller2 {
private Model2 model2;
private View2 view2;
public Controller2(Model2 model2, View2 view2) {
this.model2 = model2;
this.view2 = view2;
model2.addPropertyChangeListener(Model2.TEXT, new ModelListener());
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
view2.setText((String) pcEvt.getNewValue());
}
}
}
how do you trap the event before the new tab is switched to?
In every Tab I have JTable and i do something with it's data(delete, add , update). I would like to do data validation(save or cancel changes) before switching to the new tab. I use Java 1.5.
class ViewPanel extends JPanel
{
private void Components() {
setPreferredSize(new Dimension(700, 400));
tabbedPane.addTab("DC", ANSFER.getIcon(),new DcTabPanel(this), "DC");
tabbedPane.addTab("PC", THUMB4.getIcon(),new PcTabPanel(this), "PC");
tabbedPane.addChangeListener(this);
add(tabbedPane);
}
public void stateChanged(ChangeEvent e) {
}
}
JTabbedPane is backed by a SingleSelectionModel. If you extend DefaultSingleSelectionModel, you can override the setSelectedIndex method and implement your logic.
// in new selection model:
public void setSelectedIndex(int index) {
// do pre-switch things here
super.setSelectedIndex(index);
}
// in ViewPanel, on tabbedPane create:
tabbedPane.setModel(newSelectionModel);
The reason you can't simply use a ChangeListener is because that fires on change. By extending the selection model, you fire before the tab change.
You can prevent tab switching by extending JTabbedPane and override setSelectedIndex(int). Here is a small example illustrating that. It simply prevents from switching between non-contiguous tabs:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
public class Test2 {
private static class BlockingTabbedPane extends JTabbedPane {
public static interface TabSwitchAllower {
public boolean allowTabSwitch(int from, int to);
}
private TabSwitchAllower allower;
public BlockingTabbedPane(TabSwitchAllower allower) {
super();
this.allower = allower;
}
#Override
public void setSelectedIndex(int index) {
if (allower == null || allower.allowTabSwitch(getSelectedIndex(), index)) {
super.setSelectedIndex(index);
}
}
}
protected static void initUI() {
final JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BlockingTabbedPane.TabSwitchAllower allower = new BlockingTabbedPane.TabSwitchAllower() {
#Override
public boolean allowTabSwitch(int from, int to) {
if (Math.abs(from - to) == 1) {
return true;
} else {
JOptionPane.showMessageDialog(frame, "You can only switch between contiguous tabs");
}
return false;
}
};
JTabbedPane tabbedPane = new BlockingTabbedPane(allower);
for (int i = 0; i < 10; i++) {
tabbedPane.addTab("Tab-" + i, new JLabel("Hello tab " + i));
}
frame.add(tabbedPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initUI();
}
});
}
}
java actionlistener on a tab
How to Write a Change Listener (Oracle Docs)
JTabbedPane API (Oracle Docs)
Those two links should help you out. I haven't really worked with tabbedPanes, but I am assuming that the getSelectedComponent() will return the current selected tab. So you can have a handle to the currentTab which will be set during instantiation. Then you can have something like this.
class TabListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
// Replace JSlider with whatever your tab's data type is
JSlider source = (JSlider)e.getSource();
// Use the 'currentTab' handle to do what you want.
currentTab = getSelectedComponent();
// I'm assuming that the 'selected component' by the time this stuff
// runs is going to be the new selected tab.
}
}
I am not too confident about my answer, but I certainly hope that this will point you towards the right direction! Please say if you need any clarification or anything! If I happen to discover anything that I think might be useful, I'll be certain to edit my answer!
Been sitting here for hours now trying to figure this out, so a bit sympathy for this large question. :)
The Goal: I simply want to divide my done code into MVC (Model View Controller) parts. I have the game logics done and text based - the code works fine.
The Problem: Well, I want to implement this code into MVC, but where do explain for the MODEL that it should use text-based? Because the VIEW is only for the layout (graphically) correct? I am having a REALLY hard time figuring out where to begin at all. Any pointers would be so nice!
Here is my game logics code:
import mind.*;
import javax.swing.*;
import java.util.*;
import java.lang.*;
import java.awt.*;
public class Drive {
String[] mellan;
boolean gameEnd, checkempty, checkempty2, enemy, enemy2;
String gr,rd,tom;
int digits;
public Drive() {
// Gamepieces in textform
gr="G"; rd="R"; tom=" ";
mellan = new String[7];
String[] begin = {gr,gr,gr,tom,rd,rd,rd};
String[] end = {rd,rd,rd,tom,gr,gr,gr};
//input
Scanner in = new Scanner(System.in);
mellan=begin;
gameEnd=false;
while (gameEnd == false) {
for(int i=0; i<mellan.length; i++) {
System.out.print(mellan[i]);
}
System.out.print(" Choose 0-6: ");
digits = in.nextInt();
move();
checkWin();
}
}
void move() {
//BOOLEAN for gameruls!!!
checkempty = digits<6 && mellan[digits+1]==tom;
checkempty2 = digits>0 && mellan[digits-1]==tom;
enemy = (mellan[digits]==gr && mellan[digits+1]==rd && mellan[digits+2]==tom);
enemy2 = (mellan[digits]==rd && mellan[digits-1]==gr && mellan[digits-2]==tom);
if(checkempty) {
mellan[digits+1]=mellan[digits];
mellan[digits]=tom;
} else if (checkempty2) {
mellan[digits-1]=mellan[digits];
mellan[digits]=tom;
} else if (enemy) {
mellan[digits+2]=mellan[digits];
mellan[digits]=tom;
} else if (enemy2) {
mellan[digits-2]=mellan[digits];
mellan[digits]=tom;
}
}
void checkWin() {
String[] end = {rd,rd,rd,tom,gr,gr,gr};
for (int i=0; i<mellan.length; i++){
}
if (Arrays.equals(mellan,end)) {
for (int j=0; j<mellan.length; j++) {
System.out.print(mellan[j]);
}
displayWin();
}
}
void displayWin() {
gameEnd = true;
System.out.println("\nNicely Done!");
return;
}
// KΓΆr Drive!
public static void main(String args[]) {
new Drive();
}
}
Here is how I defined my DriveView thus far: (just trying to make one button to work)
import mind.*;
import javax.swing.*;
import java.util.*;
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class DriveView extends JFrame {
JButton ruta1 = new JButton("Green");
JButton ruta2 = new JButton("Green");
JButton rutatom = new JButton("");
JButton ruta6 = new JButton("Red");
private DriveModel m_model;
public DriveView(DriveModel model) {
m_model = model;
//Layout for View
JPanel myPanel = new JPanel();
myPanel.setLayout(new FlowLayout());
myPanel.add(ruta1);
myPanel.add(ruta2);
myPanel.add(rutatom);
myPanel.add(ruta6);
this.setContentPane(myPanel);
this.pack();
this.setTitle("Drive");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void addMouseListener(ActionListener mol) {
ruta2.addActionListener(mol);
}
}
And DriveController which gives me error at compile
import mind.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.lang.*;
public class DriveController {
private DriveModel m_model;
private DriveView m_view;
public DriveController(DriveModel model, DriveView view) {
m_model = model;
m_view = view;
view.addMouseListener(new MouseListener());
}
class MouseListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String mening;
mening = e.getActionCommand();
if (mening.equals("Green")) {
setForeground(Color.red);
}
}
}
}
Your game model can have more than one view: a GUI view, a console view, a status view, etc. Typically each view arranges to listen for changes in the model, and it then queries the model for the information it needs to render it's particular view. This simple game was designed specifically to illustrate the concepts. The section named "Design" elaborates in more detail.
Addendum: This outline corresponds roughly to this architecture, symbolized below.
public class MVCOutline {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
//#Override
public void run() {
new MVCOutline().create();
}
});
}
private void create() {
JFrame f = new JFrame("MVC Outline");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new MainPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class MainPanel extends JPanel {
public MainPanel() {
super(new BorderLayout());
Model model = new Model();
View view = new View(model);
Control control = new Control(model, view);
this.add(view, BorderLayout.CENTER);
this.add(control, BorderLayout.WEST);
}
}
class Control extends JPanel implements ... {
private Model model;
private View view;
public Control(Model model, View view) {
this.model = model;
this.view = view;
}
}
class View extends JPanel implements Observer {
private Model model;
public View(Model model) {
this.model = model;
model.addObserver(this);
}
public void update(Observable o, Object arg) {
// update GUI based on model
}
}
class Model extends Observable {
public void next() {
this.notifyObservers(...);
}
}
To take a stab (and this is kind of overkill), I would make a game state bean that would represent the state that the game is currently in; that would be a "model object". Looking at your code, it would probably contain String [] mellan. Then I would have a data access object that contains a reference to the game state bean and it would have methods for updating the game state.
The game logic for different actions would be in a service object that has a reference to the data access object and the controller would have a reference to the service object. It would call the different action methods depending on what interaction was received from the interface, the view.
Like I said, this is kind of overkill.