java inter object communication - java

Still learning Java.
Again Swing has caused me to ask this but it is really a general OO question. If I have a master class (that contains main()) it creates a new object "A" that does something, the master class now has a reference to that object, how does object "B" get access to the attributes of that object?
The only way I can think of is for the master class to create a new object "B", passing object "A" as a parameter to the constructor, which I suppose is O.K. but doesn't this make event handling potentially difficult.
For example, and perhaps this is a poor design which is causing the problem. I have a master class with the programme logic, that creates a standard Swing frame, with a menu, the menu items having action listeners. But the actionlistener needs to interact with external objects.
So some code (ignoring the details) :
The main class, containing the programme logic and the save and load methods, etc :
public final class TheProgramme implements WindowListener }
private static final TheProgramme TP = new TheProgramme();
// Declare Class variables, instance variables etc.
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShewGUI();
}
});
}
private static void createAndShewGUI() {
TP.populateAndShew();
}
private void populateAndShew() {
final StandardFrame sF = new StandardFrame("TheProgramme");
theFrame = sF.getMainFrame();
theFrame.addWindowListener(this);
theFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
theFrame.pack(); theFrame.setVisible(true);
}
...
}
So we have created a standard frame object which has created a menu, empty panel and status bar, but with event listeners on the menu items :
public class StandardFrame {
// Declare instance variables that must be visible to the ActionListener inner class
public StandardFrame(String theTitle) {
mainFrame = new JFrame(theTitle);
mainFrame.setJMenuBar(createMenuBar()); // ... the menu bar and ...
mainFrame.setContentPane(createBlankPanel()); // ... a blank panel
java.net.URL imageURL = TheProgramme.class.getResource("images/icon.png");
if (imageURL != null) {
ImageIcon icon = new ImageIcon(imageURL);
mainFrame.setIconImage(icon.getImage());
}
}
public JMenuBar createMenuBar() {
ActionListener menuEvents = new MenuListener();
JMenuBar aMenuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F);
...
aMenuBar.add(fileMenu);
...
JMenuItem newItem = new JMenuItem("New", KeyEvent.VK_N); newItem.addActionListener(menuEvents);
newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
...
fileMenu.add(newItem);
...
return aMenuBar;
}
}
class MenuListener implements ActionListener {
public void actionPerformed(ActionEvent ae) {
String actionCommand = ae.getActionCommand();
switch (actionCommand) {
case "New":
// !!! here we need to call a method in an object to create a new document object !!!
break;
case "Reformat":
// !!! here we need to call a method in the object created above
}
}
}
The first problem is the actionlistener on the menu items calls a method to create an object but it is not a call to a static method.
The second problem is it needs to be be able to call a method in that new object later on as a result of another menu choice.

The classic way to do this in Model-View-Controller is to bind the objects together at runtime. The controller, your action listener, takes parameters to indicate which view and model it is to act on.
This use of parameters is also called "Dependency Injection," as Aqua mentions.
public static void main( String[] args )
{
Model model = new Model();
View view = new View();
ActionListener listener = new MyActionListener( model, view );
view.addActionListener( listener );
}
private static class MyActionListener implements ActionListener
{
private Model model;
private View view;
public MyActionListener( Model model, View view )
{
this.model = model;
this.view = view;
}
}
In Java you can cheat a little since the ActionEvent has a pointer to the source of the event (normally the view/JComponent that generated the event.
private static class MyActionListener implements ActionListener
{
private Model model;
public MyActionListener( Model model )
{
this.model = model;
}
#Override
public void actionPerformed( ActionEvent e )
{
JComponent source = (JComponent) e.getSource();
// source == "view"...
}
}
To set a new document, you can create a "document holder" class. The "new" menu item puts a new document in the holder class. All other menu items "get" the document from the holder class. This is a fairly strict OO paradigm which uses no static methods or fields, although it is a little tedious.
Set up:
public static void main( String[] args )
{
ModelDocumentHolder model = new ModelDocumentHolder();
View view = new View();
ActionListener listener = new NewDocument( model );
view.addActionListener( listener );
View view2 = new View();
view2.addActionListener( new RegularListener( model ) );
}
New document listener:
private static class NewDocument implements ActionListener
{
private ModelDocumentHolder model;
public NewDocument( ModelDocumentHolder model )
{
this.model = model;
}
#Override
public void actionPerformed( ActionEvent e )
{
model.setDoc( new Document() );
}
}
Most other menu items:
private static class RegularListener implements ActionListener
{
private ModelDocumentHolder model;
public RegularListener( ModelDocumentHolder model )
{
this.model = model;
}
#Override
public void actionPerformed( ActionEvent e )
{
JComponent source = (JComponent) e.getSource();
Document doc = model.getDoc();
// do stuff...
}
}
The holder class:
private static class ModelDocumentHolder
{
private Document doc;
public Document getDoc()
{
return doc;
}
public void setDoc( Document doc )
{
this.doc = doc;
}
}

Generally, (and it's really hard to know if this is an answer to your question, as it's quite vague) this is what setModel(...) and addListener(...) are for.
The "constructor coordinator" aka "master class", creates the models (the swing Model classes). It creates the views (JWidgets) and it sets the models of the views. In Swing it is easy to rely upon the default constructed model (populated with the JWidget default constructor), but maybe in your case, you should find the one widget causing problems and rewrite it make the model setting explicit.
If you have extended a Jwhatever, then keep in mind that setModel(...) typically does something like
if (this.model != null) {
this.model.removeListener(this);
}
// clear the cached last "view" of the model
clearCachedData(...);
if (model != null) {
this.model = model;
// restore the "view" of the new model.
grabCachedData(...);
this.model.addListener(this);
}

I hope my interpretation of the question is correct. You can inject/provide whatever object you need to the action implementation. Here is an example that uses an interface for better abstraction as a callback from actionPerformed. When action completes it call its callback to notify whoever is interested. In this case, the panel is notified and updates its text area with some text.
The interface:
public interface ActionCallback {
public void documentCreated(String name);
}
Here is the UI:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TestAction extends JPanel implements ActionCallback {
private JTextArea area;
public TestAction() {
setLayout(new BorderLayout());
area = new JTextArea();
add(new JScrollPane(area));
}
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void documentCreated(String name) {
area.append(String.format("Created %s\n", name));
}
public static class NewAction extends AbstractAction {
private ActionCallback callback;
private Component parent;
public NewAction(ActionCallback callback, Component parent){
super("New");
this.callback = callback;
this.parent = parent;
}
#Override
public void actionPerformed(ActionEvent e) {
String value = JOptionPane.showInputDialog(parent, "Name", "new name");
if (value != null){
callback.documentCreated(value);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestAction panel = new TestAction();
frame.add(panel);
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menuBar.add(menu);
JMenuItem item = new JMenuItem(new NewAction(panel, frame));
menu.add(item);
frame.setJMenuBar(menuBar);
frame.pack();
frame.setVisible(true);
}
});
}
}

Passing a reference of yourself (A) to an other object (B) tends to happen quite frequently in GUI code. You could use an context object, which you pass to all classes and which contains references to relevant references and might hold some global information. In simple cases, "main program class" is used as context and passed around.
Depending on what classes you use, this might also be useful: Component#getParent().

Related

Passing objects to a gui in java

I'm currently in a java class and I'm trying to work on build a my first interface. The one below is not the one I'm working on, but a test one so I can figure out how to do what I want to do. Here is the code, the question will follow:
package test;
import java.awt.*;
import java.awt.event.*;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.*;
/**
*
* #author StrifeX
*/
public class Test {
public static class buttonTest extends JFrame {
private int number;
private JButton push;
public ButtonTest(){
setLayout(new GridBagLayout ());
//Creates an instance of the layout
GridBagConstraints panel = new GridBagConstraints();
// Establishes the pixels suronding each object within the layout
panel.insets = new Insets (5, 5, 5, 5);
//create the withdraw button and its properties
push = new JButton("push");
panel.fill = GridBagConstraints.HORIZONTAL;
panel.gridx = 0;
panel.gridy = 0;
panel.gridwidth = 1;
add(push, panel);
MyEvent buttonClick = new MyEvent();
push.addActionListener(buttonClick);
}//end constructor
public class MyEvent implements ActionListener {
public void actionPerformed (ActionEvent buttonClick){
String operation = buttonClick.getActionCommand();
if(operation.equals("Withdraw")){
JOptionPane.showMessageDialog( null, "test" );
}
}
}
}//end button test
public static class TestObject{
int testField;
public void testObject(){
testField = 10;
}
public void setterMethod(int newInt){
this.testField = newInt;
}
public int getterMethod(){
return this.testField;
}
}
public static void main(String[] args)
{
TestObject obj1 = new TestObject();
buttonTest button = new buttonTest();
// Establish basic parameters for the GUI
button.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.setLocationRelativeTo(null);
button.setVisible(true);
button.setSize(450, 350);
button.setTitle("Change the Balance");
}
}
What I want to do is build an object, then use that objects methods with the GUI button. I can build the object, and pass it to the constructor that make the GUI (buttonTest), but the Event class cant see that object, and from the little I know, I can't pass it to the event class, but maybe the actionPerformed method like this:
public void actionPerformed (ActionEvent buttonClick, testObject thing)
However, when I tried to do that, I received and error that said
buttonTest.event is not abstract and does not override abstract method
actionPerformed(actionEvent) in ActionListener.
Any help would be greatly appreciated. Thank you.
The problem is wrongly defined the following:
public void actionPerformed (ActionEvent buttonClick, testObject thing)
The correct definition will be:
public void actionPerformed (ActionEvent buttonClick)
Getting passed all your previous issues and getting to the heart of the question
Passing objects to a gui in java
You basically need to pass the TestObject to all the classes which need access to it.
Start by modifying your MyEvent class to take a reference to TestObject
public class MyEvent implements ActionListener {
private TestObject obj;
public MyEvent(TestObject obj) {
this.obj = obj;
}
#Override
public void actionPerformed(ActionEvent buttonClick) {
String operation = buttonClick.getActionCommand();
// Now you have acces to obj
if (operation.equals("Withdraw")) {
JOptionPane.showMessageDialog(null, "test");
}
}
}
This will require you to modify your ButtonTest class to allow it to pass the TestObject through to the event handler...
public static class ButtonTest extends JFrame {
//...
public ButtonTest(TestObject obj) {
//...
MyEvent buttonClick = new MyEvent(obj);
//...
And finally, you create and pass the object to the instance of ButtonTest which needs it
public static void main(String[] args) {
TestObject obj1 = new TestObject();
ButtonTest button = new ButtonTest(obj1);
// Establish basic parameters for the GUI
button.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.setLocationRelativeTo(null);
button.setVisible(true);
button.setSize(450, 350);
button.setTitle("Change the Balance");
}

Implement 2 classes for ActionListener for buttons

I have 2 classes, the first class is where I am creating GUI and all of the components needed. Including the buttons. This is being done outside of the main method and in there own respective methods. I want to .addActionListener, but from another class outside of this one. I do not want to use inner classes.
Here is the classes containing Main and the Gui components and the button.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class PasswordGeneratorGui {
private JFrame interfaceFrame;
private JPanel interfacePanel;
private JMenuBar interfaceMenuBar;
private JMenu interfaceMenu;
private JMenuItem interfaceMenuItemFile;
private JButton interfaceButtonGenerate;
public static void main(String[] args) {
new PasswordGeneratorGui();
}
public PasswordGeneratorGui() {
createInterfacePanel();
createInterfaceFrame();
createInterfaceMenuBar();
createInterfaceMenu();
createInterfaceMenuItem();
createInterfaceButton();
PasswordGeneratorButtonHandler b = new PasswordGeneratorButtonHandler();
interfaceFrame.add(interfacePanel);
interfaceFrame.setVisible(true);
}
public void createInterfacePanel() {
interfacePanel = new JPanel();
interfacePanel.setLayout(null);
}
public void createInterfaceFrame() {
interfaceFrame = new JFrame();
interfaceFrame.setTitle("Password Generator");
interfaceFrame.setBounds(50, 50, 700, 400);
interfaceFrame.setResizable(false);
interfaceFrame.setJMenuBar(interfaceMenuBar);
}
public void createInterfaceMenuBar() {
interfaceMenuBar = new JMenuBar();
interfaceMenuBar.setBounds(0, 0, 700, 20);
interfaceMenuBar.setVisible(true);
interfacePanel.add(interfaceMenuBar);
}
public void createInterfaceMenu() {
interfaceMenu = new JMenu("File");
interfaceMenuBar.add(interfaceMenu);
}
public void createInterfaceMenuItem() {
interfaceMenuItemFile = new JMenuItem("Exit");
interfaceMenu.add(interfaceMenuItemFile);
}
**public void createInterfaceButton() {
interfaceButtonGenerate = new JButton("Generate");
interfaceButtonGenerate.setBounds(0, 358, 700, 20);
interfaceButtonGenerate.addActionListener();
interfacePanel.add(interfaceButtonGenerate);
}**
}
Here is the class for the ActionListener
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class PasswordGeneratorButtonHandler implements ActionListener {
PasswordGeneratorButtonHandler generate = new PasswordGeneratorButtonHandler();
public PasswordGeneratorButtonHandler() {
}
public void interfaceButtonGenerateHandler(ActionEvent event) {
System.exit(1);
}
#Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
}
}
I just want to be able to call the AcitonListener method from the second class. I have tried initiating a new instance of the class and calling it however I think I wasn't quite going in the correct direction.
I'm a little confused about what your asking. You said
I just want to be able to call the AcitonListener method from the second class
Taken literally this means that while you're inside of the PasswordGeneratorButtonHandler class, you want to call the actionPerformed() method. If so, just use this.actionPerformed(), where this is a special keyword in java, representing the current instance of your class.
If however you want to add your handler to the button you created in the first class, which seems like what you might want to do, then you just need to call the JButton#addActionListener() method.
public PasswordGeneratorGui() {
createInterfacePanel();
createInterfaceFrame();
createInterfaceMenuBar();
createInterfaceMenu();
createInterfaceMenuItem();
createInterfaceButton();
PasswordGeneratorButtonHandler b = new PasswordGeneratorButtonHandler();
interfaceButtonGenerate.addActionListener(b); // Add handler to button
interfaceFrame.add(interfacePanel);
interfaceFrame.setVisible(true);
}
Also, inside of the PasswordGeneratorButtonHandler class, you instantiate an instance of the class called generate. This is unnecessary.

How can I access an Array List's elements inside an ActionListener from another ActionListener?

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();
};
}

How do swing component's model can request redraw?

I have my custom component which holds it's data in my custom data class. My component extends JComponent while data is fully custom.
What is conventional pattern a data class can notify component it was changed? Should I just implement some event inside data class and let component subscribe to it when data is set? Or there is predefined pattern is Swing library?
I am looking, for example, at ListModel<E> interface and see that is just has addListDataListener(ListDataListener l) and removeListDataListener(ListDataListener l). Is this that mechanism model notifies List?
For the record, this is mostly my personal opinion (since this is more of an opinion question).
Typically with a Swing application it's best to separate your code out into Model/View/Control (MVC). This means that the actual Swing components are your Viow, your listeners are your Control, and your code that actually does stuff is the Model. In this case both your View and Model only know about the Control (and the Control knows about both the View and Model).
So if your Model updates - it notifies the Control, which updates the View. It's the same thing with the View (Listeners in the View execute, notifying the Control, which updates the Model).
The advantage of this is that it loosely couples the View and the Model (the view only cares about showing stuff to the user, and the model only cares about the data, and they don't care what each other are doing so long as they get the right information).
Here's an example (for simplicity they're all in one file, but usually you'd have the MVC each in their own file at the very least):
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
public class MVCSeparation {
// Model: (Number crunching math-y) or (data processing) stuff
public static class Model{
ArrayList<String> data = new ArrayList<String>();
public void addData(String value){
data.add(value);
}
public int getCount(){
return data.size();
}
public String randomValue(){
String result = "";
if(data.size() > 0){
int index = (int)(Math.random() * data.size());
System.out.println(index);
result = data.get(index);
}
System.out.println("Getting Value: " + result);
return result;
}
}
// View: Pretty graphics and visuals
public static class View extends Box{
JLabel text = new JLabel("Random Value:");
JTextField newItem = new JTextField(10);
JButton submit = new JButton("Submit");
public View(){
super(BoxLayout.Y_AXIS);
add(text);
add(newItem);
add(submit);
}
public void setSubmitAction(ActionListener submitAction){
submit.addActionListener(submitAction);
}
public void setDisplayText(String value){
text.setText("Random Value: " + value);
}
public String getText(){
String result = newItem.getText();
newItem.setText("");
return result;
}
public void startupApp(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(this);
frame.validate();
frame.pack();
frame.setVisible(true);
}
}
// Processing User Interactions and Data Updates (links two above together)
public static class Control{
Model m = new Model();
View v = new View();
public Control(){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
v.setSubmitAction(new SubmitText());
v.startupApp();
}});
//Randomly update label
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
v.setDisplayText(m.randomValue());
}});
}
}
// Listener to notify us of user interactions on the View
public class SubmitText implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
m.addData(v.getText());
}
}
}
public static void main(String[] args) {
new Control();
}
}
You can use Observable or PropertyChangeListener.

Communication between JOptionPane buttons and a custom panel

I have made a multiple input dialog by building a JPanel with the fields I want and adding it to a JOption pane
JMainPanel mainPanel = new JMainPanel(mensaje, parametros, mgr);
int i = JOptionPane.showOptionDialog(null, mainPanel, "Sirena",
JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
new String[] {"Aceptar", "Cancelar"}, "Aceptar");
However I'm having trouble with the buttons, because some of the fields are required. How can I make the "Ok" button to be enabled once every required field is up, or making the click on the button to make the validations and do not close the pane until every required field is filled?
From the Java API, I found this:
options - an array of objects indicating the possible choices the user
can make; if the objects are components, they are rendered properly;
non-String objects are rendered using their toString methods; if this
parameter is null, the options are determined by the Look and Feel
So, can't I pass custom buttons as parameter?
Looks like I will have to make my own JDialog? for which case, I don't know how to make it return an int just like JOptionPane does, any recommended tutorial?
In the example options is {"Aceptar", "Cancelar"} which are the displayed buttons,
PS. I have full controll over the fields I added to the JPanel.
This is a screenshot of the JOptionPane:
I don't think that you can de-activate a JOptionPane's selections buttons, but one way to still use the JOptionPane is to simply re-display it if the required fields have not been set. You could display an error message JOptionPane first describing the error, and then display a new JOptionPane that holds the same JPanel as its second parameter -- so that the data already entered has not been lost. Otherwise, you may want to create your own JDialog which by the way isn't that hard to do.
Edit
I'm wrong. You can enable and disable the dialog buttons if you use a little recursion.
For example:
import java.awt.Component;
import java.awt.Container;
import java.awt.event.*;
import java.util.HashSet;
import java.util.Set;
import javax.swing.*;
public class Foo extends JPanel {
private static final String[] DIALOG_BUTTON_TITLES = new String[] { "Aceptar", "Cancelar" };
private JCheckBox checkBox = new JCheckBox("Buttons Enabled", true);
private Set<AbstractButton> exemptButtons = new HashSet<AbstractButton>();
public Foo() {
JButton exemptBtn = new JButton("Exempt Button");
JButton nonExemptBtn = new JButton("Non-Exempt Button");
add(checkBox);
add(exemptBtn);
add(nonExemptBtn);
exemptButtons.add(checkBox);
exemptButtons.add(exemptBtn);
checkBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
allBtnsSetEnabled(checkBox.isSelected());
}
});
}
private void allBtnsSetEnabled(boolean enabled) {
JRootPane rootPane = SwingUtilities.getRootPane(checkBox);
if (rootPane != null) {
Container container = rootPane.getContentPane();
recursiveBtnEnable(enabled, container);
}
}
private void recursiveBtnEnable(boolean enabled, Container container) {
Component[] components = container.getComponents();
for (Component component : components) {
if (component instanceof AbstractButton && !exemptButtons.contains(component)) {
((AbstractButton) component).setEnabled(enabled);
} else if (component instanceof Container) {
recursiveBtnEnable(enabled, (Container) component);
}
}
}
public int showDialog() {
return JOptionPane.showOptionDialog(null, this, "Sirena",
JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
DIALOG_BUTTON_TITLES, "Aceptar");
}
private static void createAndShowGui() {
Foo foo = new Foo();
int result = foo.showDialog();
System.out.println(DIALOG_BUTTON_TITLES[result]);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
This code uses listeners to check the state of a JCheckBox, but you can have listeners (DocumentListeners) listening to text field documents if you desire to know if they have data or not. The code then gets the JRootPane that holds the JCheckBox, then the root pane's contentPane, and all components of the dialog are held by this. It then recurses through all the components held by the dialog. If a component is a Container, it recurses through that container. If the component is an AbstractButton (such any JButton or checkbox), it enables or disables -- except for buttons held in the exempt buttons set.
A better example with document listeners
import java.awt.*;
import java.awt.event.*;
import java.util.HashSet;
import java.util.Set;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class Foo2 extends JPanel {
private static final String[] DIALOG_BUTTON_TITLES = new String[] {
"Aceptar", "Cancelar" };
private static final int FIELD_COUNT = 10;
private Set<AbstractButton> exemptButtons = new HashSet<AbstractButton>();
private JTextField[] fields = new JTextField[FIELD_COUNT];
public Foo2() {
setLayout(new GridLayout(0, 5, 5, 5));
DocumentListener myDocListener = new MyDocumentListener();
for (int i = 0; i < fields.length; i++) {
fields[i] = new JTextField(10);
add(fields[i]);
fields[i].getDocument().addDocumentListener(myDocListener);
}
// cheating here
int timerDelay = 200;
Timer timer = new Timer(timerDelay , new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
checkDocsForText();
}
});
timer.setRepeats(false);
timer.setInitialDelay(timerDelay);
timer.start();
}
private void checkDocsForText() {
for (JTextField field : fields) {
if (field.getText().trim().isEmpty()) {
allBtnsSetEnabled(false);
return;
}
}
allBtnsSetEnabled(true);
}
private void allBtnsSetEnabled(boolean enabled) {
JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane != null) {
Container container = rootPane.getContentPane();
recursiveBtnEnable(enabled, container);
}
}
private void recursiveBtnEnable(boolean enabled, Container container) {
Component[] components = container.getComponents();
for (Component component : components) {
if (component instanceof AbstractButton && !exemptButtons.contains(component)) {
((AbstractButton) component).setEnabled(enabled);
} else if (component instanceof Container) {
recursiveBtnEnable(enabled, (Container) component);
}
}
}
public int showDialog() {
return JOptionPane.showOptionDialog(null, this, "Sirena",
JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
DIALOG_BUTTON_TITLES, "Aceptar");
}
private class MyDocumentListener implements DocumentListener {
public void removeUpdate(DocumentEvent arg0) {
checkDocsForText();
}
public void insertUpdate(DocumentEvent arg0) {
checkDocsForText();
}
public void changedUpdate(DocumentEvent arg0) {
checkDocsForText();
}
}
private static void createAndShowGui() {
Foo2 foo = new Foo2();
int result = foo.showDialog();
if (result >= 0) {
System.out.println(DIALOG_BUTTON_TITLES[result]);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I suggest you to define some properties into your JPanel extended class, and use PropertyChangeListener to listen the occured changes and enable/disable relative buttons.
Here's an article.
Another issue maybe finding the ok/cancel buttons in the hierarchy of components, since the JDialog is created through JOptionPane and you haven't a reference to the buttons. Here's a useful thread .
You can add a property to a JComponent using putClientProperty method.
When changes occurs to a given property a PropertyChanged event is raised.
So in your example you can define a boolean property indicating that required that are inserted into the JDialog. Then add a PropertyChangeListener that when is notified enable/disable the ok button.

Categories

Resources