I have a Main class that stores a TabbedComponent(extending JTabbedPane) as a variable. Another class (ToolbarComponent(extending JMenuBar) is also stored as a variable within my main class.
Upon a user event on the Toolbar, it calls the parent class (main), to get the TabbedComponent object and call a method to create a new tab. Which all works fine.
My issue is that when I attempt to click on a ta with my mouse, nothing changes. I'm pretty sure that I don't need a listener on MouseAdapter for something that simple, but will add it if I need it I guess.
Below is are stripped down versions of classes relevant to this issue
public class ExampleClass extends JFrame {
private TabbedBrowserPaneComponent cTabbedBrowserPane;
public ExampleClass() {
super("");
// Set up Components
this.cTabbedBrowserPane = new TabbedBrowserPaneComponent(this);
// Set up behaviour
setSize(500, 300);
setVisible(true);
}
/**
* #return the cTabbedBrowserPane
*/
public TabbedBrowserPaneComponent getTabbedBrowserPane() {
return cTabbedBrowserPane;
}
/**
* #param cTabbedBrowserPane the cTabbedBrowserPane to set
*/
public void setTabbedBrowserPane(TabbedBrowserPaneComponent cTabbedBrowserPane) {
this.cTabbedBrowserPane = cTabbedBrowserPane;
}
}
public class TabbedBrowserPaneComponent extends JTabbedPane {
// Parent class of the component
private JFrame parent = null;
public TabbedBrowserPaneComponent(JFrame parent) {
super();
setParent(parent);
// Add an initial pane
createNewTab();
parent.getContentPane().add(this);
}
public void createNewTab() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(), BorderLayout.CENTER);
this.addTab("Tab " + this.getTabCount(), panel);
}
/**
* #return the parent
*/
public JFrame getParent() {
return parent;
}
/**
* #param parent the parent to set
*/
public void setParent(JFrame parent) {
this.parent = parent;
}
}
To create a new tab, ToolBarComponent's listener calls like this
public class CreateNewTabAction extends AbstractAction {
// Parent
private JMenu parent;
public CreateNewTabAction(JMenu parent) {
super();
this.setParent(parent);
// Values for the tab
putValue(Action.NAME, "New Tab");
}
#Override
public void actionPerformed(ActionEvent e) {
ExampleClass.class.cast((parent.getParent().getParent())).getTabbedBrowserPane().createNewTab();
}
/**
* #return the parent
*/
public JMenu getParent() {
return parent;
}
/**
* #param parent the parent to set
*/
public void setParent(JMenu parent) {
this.parent = parent;
}
}
It this something really simply that I am missing?
Your code demonstrates a significant lack of design, sorry (I'm not trying to be mean, but I've spent the better part of 3 years undoing this kind of behavior so it gives me a nasty twitch).
Your problem is you are overriding getParent, which is method of Component used to determine where the component is actually added to. This is causing issues for the internal workings of the system.
There is no need to supply the parent frame to the tab component. If you REALLY need to get access back to the parent frame for some reason, consider using SwingUtilities.getWindowAncestor. If you are just planing to supply functionality for the tab in the frame, create a interface that can establish the contract between the tab and the controller/engine.
Don't get me started on the Action ...
An Example
I'm not sure what is you're actually trying to achieve, but there is absolutely no need to pass around a reference to the browser tab or the main frame. The elements of your program your passing them to just don't need to know that much information about their parents in order to achieve there work, also, you are significantly limiting the flexibility and re-usability of your components.
Below, I use a simple interface\controller that provides a contract between the various views and controls within the example. None of the various elements really need to know much more...
public class ExampleClass {
public static void main(String[] args) {
new ExampleClass();
}
public ExampleClass() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
BrowserPane browserPane = new BrowserPane();
CreateNewTabAction createNewTabAction = new CreateNewTabAction(browserPane);
JMenu mnu = new JMenu("Stuff");
mnu.add(createNewTabAction);
JMenuBar mb = new JMenuBar();
mb.add(mnu);
JToolBar tb = new JToolBar();
tb.add(createNewTabAction);
JFrame frame = new JFrame();
frame.setJMenuBar(mb);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(browserPane);
frame.add(tb, BorderLayout.NORTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface TabController {
public void createNewTab();
}
public class BrowserPane extends JPanel implements TabController {
private TabbedBrowserPaneComponent cTabbedBrowserPane;
public BrowserPane() {
setLayout(new BorderLayout());
// Set up Components
this.cTabbedBrowserPane = new TabbedBrowserPaneComponent();
add(cTabbedBrowserPane);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
public void createNewTab() {
cTabbedBrowserPane.createNewTab();
}
}
public class TabbedBrowserPaneComponent extends JTabbedPane {
public TabbedBrowserPaneComponent() {
super();
createNewTab();
}
public void createNewTab() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(), BorderLayout.CENTER);
this.addTab("Tab " + this.getTabCount(), panel);
}
}
public class CreateNewTabAction extends AbstractAction {
private TabController controller;
public CreateNewTabAction(TabController controller) {
super();
this.controller = controller;
putValue(Action.NAME, "New Tab");
}
#Override
public void actionPerformed(ActionEvent e) {
getController().createNewTab();
}
/**
* #return the parent
*/
public TabController getController() {
return controller;
}
}
}
Related
I'm creating a Java application using Swing and the MVC design pattern.
The application is designed as follows:
There is a class QuizPanel that extends JPanel. This panel is the container that holds the main 'Screens' of my application.
Each 'screen' of my application is a separate class that extends JPanel. These JPanel's are added/removed from the QuizPanel as required.
The top level QuizPanel implements my interface Switchable. The Switchable interface consists of a single SwitchView(ViewState state) method. I pass this to each 'screen' instantiated in the QuizPanel top level panel, so they are able to call SwitchView when a button is pressed.
On the login screen, the user enters a pin and student ID, if these match a token in the database, I need to pass the Token object to another screen of my application (a question screen that I haven't implemented yet) or have it available somehow. The token is retrieved from an embedded Derby database.
The only way I can think of doing this is creating a utility class with static Token variable that can be accessed by the other classes (this seems like a nasty way to do it). Am I having trouble with this because the design of my application is flawed? Is there any technique I can use to pass the Token across the different screens of my application?
Main
public static void main(String[] args) {
QuizPanel quizPanel = new QuizPanel();
JFrame frame = new JFrame("Quiz");
frame.setPreferredSize(new Dimension(400, 400));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(quizPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
QuizPanel Class
public class QuizPanel extends JPanel implements Switchable{
private MainMenuPane mainMenuPane;
private RegisterPane registerPane;
private LoginPane loginPane;
public QuizPanel() {
setLayout(new BorderLayout());
registerPane = new RegisterPane();
RegisterController registerController = new RegisterController(registerPane, this);
mainMenuPane = new MainMenuPane();
MainMenuController mainMenuController = new MainMenuController(mainMenuPane, this);
loginPane = new LoginPane();
LoginController loginController = new LoginController(loginPane, this);
switchView(ViewState.MAINMENU_STATE);
}
#Override
public void switchView(ViewState state) {
System.out.println("Changing state: " + state);
switch (state) {
case REGISTER_STATE:
removeAll();
setLayout(new BorderLayout());
add(registerPane, BorderLayout.CENTER);
repaint();
revalidate();
break;
case MAINMENU_STATE:
removeAll();
setLayout(new BorderLayout());
add(mainMenuPane, BorderLayout.CENTER);
repaint();
revalidate();
break;
case LOGIN_STATE:
removeAll();
setLayout(new BorderLayout());
add(loginPane, BorderLayout.CENTER);
repaint();
revalidate();
break;
default:
System.out.println("UNREGISTERED STATE!");
break;
}
}
}
Login Controller
public class LoginController implements ILoginController, ILoginViewObserver {
private ILoginView view;
private LoginModel loginModel;
private Switchable parentView;
public LoginController(ILoginView view, Switchable parentView) {
this.view = view;
this.loginModel = new LoginModel();
this.parentView = parentView;
view.addLoginViewObserver(this);
}
#Override
public ILoginView getLoginView() {
return view;
}
#Override
public void submitButtonWasPressed(Token token) {
Token verifiedToken = loginModel.verifyToken(token);
if (verifiedToken != null) {
System.out.println("Token (" + token.token + ") successfully verified");
// How can I pass the token to the new JPanel the parent view will be displaying?
} else {
System.out.println("Token is invalid");
}
}
#Override
public void cancelButtonWasPressed() {
parentView.switchView(ViewState.MAINMENU_STATE);
}
}
LoginModel Class
public class LoginModel {
private List<Token> tokens;
public LoginModel() {
TokenDao tokenAccessObject = new TokenAccessObject();
tokens = tokenAccessObject.getAllTokens();
}
public Token verifyToken(Token providedToken) {
for (Token token : tokens) {
if (token.studentID == providedToken.studentID){
if (token.token.compareTo(providedToken.token) == 0) {
return token;
}
}
}
return null;
}
}
I think that in this case you can use Singleton pattern. This pattern should be used as rarely as possible, but in your case (common information which must be accessed from different classes of application) you can use it (IMHO).
But in your case you can also use one Swing feature.
Any Swing window has a root pane. And each JComponent that is laid
out in the window has access to this pane.
JComponent has also possibility to store some user data in a map,
called "client properties". Because JRootPane extends JComponent
you can store/retrieve your token is this map.
Here is a simple code:
public class TokenUtils {
private static final String TOKEN_PROPERTY = "token";
public static Token findToken(JComponent component) {
JRootPane root = component.getRootPane();
if (root != null) {
return Token.class.cast(root.getClientProperty(TOKEN_PROPERTY));
}
return null;
}
public static void putToken(JComponent component, Token token) {
JRootPane root = component.getRootPane();
if (root != null) {
root.putClientProperty(TOKEN_PROPERTY, token);
}
}
}
Important: if you use more than one window, you must put the token into the each of them.
A methode to pass the value of the token to the parent JPanel is to add a methode in your interface like setToken(int token) and a global variable in your Quiz panel
QuizPanel:
private int token;
#Override
public void setToken(int token){
this.token = token;
}
Swiched Interface:
public void setToken(int token);
Login:
parentView.setToken(token);
Than you say parentView.setToken(token) in your LoginController. Now the token variable in the QuizPanel will be set.
You could save the token to a file then on the jpanel read that file to get the token
I know that calling overridable methods from constructors is a bad idea. But I also see that it's being done everywhere with Swing, where code like add(new JLabel("Something")); occurs in constructors all the time.
Take NetBeans IDE, for example. It is very picky about overridable calls in constructors. And yet, when it generates Swing code, it puts all those add() method calls into an initializeComponents() method... which is then called from the constructor! A nice way to hide a problem and disable the warning (NetBeans doesn't have a “a private method that calls overridable methods is called from a constructor” warning). But not really a way to solve the problem.
What's going on here? I've been doing it for ages, but always had an uneasy feeling about this. Is there a better way of initializing Swing containers, except for making an additional init() method (and not forgetting to call it every time, which is kind of boring)?
Example
Here is an extremely contrived example of how things can go wrong:
public class MyBasePanel extends JPanel {
public MyBasePanel() {
initializeComponents();
}
private void initializeComponents() {
// layout setup omitted
// overridable call
add(new JLabel("My label"), BorderLayout.CENTER);
}
}
public class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
#Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label); // NPE here
}
}
}
To avoid wiring Swing components together in the constructor, you could simply give the responsibility of the wiring to another object. For instance, you could give wiring duties to a Factory:
public class MyPanelFactory {
public MyBasePanel myBasePanel() {
MyBasePanel myBasePanel = new MyBasePanel();
initMyBasePanel(myBasePanel);
return myBasePanel;
}
public MyDerivedPanel myDerivedPanel() {
MyDerivedPanel myDerivedPanel = new MyDerivedPanel();
initMyBasePanel(myDerivedPanel);
return myDerivedPanel;
}
private void initMyBasePanel(MyBasePanel myBasePanel) {
myBasePanel.add(new JLabel("My label"), BorderLayout.CENTER);
}
}
Or you could go all out and instantiate all your Swing components with a dependency injection container and have the container trigger the wiring. Here's an example with Dagger:
#Module
public class MyPanelModule {
static class MyBasePanel extends JPanel {
private final JLabel myLabel;
MyBasePanel(JLabel myLabel) {
this.myLabel = myLabel;
}
void initComponents() {
this.add(myLabel, BorderLayout.CENTER);
}
}
static class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
MyDerivedPanel(JLabel myLabel) {
super(myLabel);
}
#Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label);
}
}
}
#Provides MyBasePanel myBasePanel(#Named("myLabel") JLabel myLabel) {
MyBasePanel myBasePanel = new MyBasePanel(myLabel);
myBasePanel.initComponents();
return myBasePanel;
}
#Provides MyDerivedPanel myDerivedPanel(#Named("myLabel") JLabel myLabel) {
MyDerivedPanel myDerivedPanel = new MyDerivedPanel(myLabel);
myDerivedPanel.initComponents();
return myDerivedPanel;
}
#Provides #Named("myLabel") JLabel myLabel() {
return new JLabel("My label");
}
}
One of OOP principles is: Prefer composition over inheritance. When I create a Swing GUI I never extend Swing components except I create a new general purpose Swing component (like a JTreeTable, JGraph, JCalendar etc.).
So my code looks like:
public class MyPanel {
private JPanel mainPanel;
public MyPanel() {
init();
}
private void init() {
mainPanel = new JPanel();
}
public Component getComponent() {
return mainPanel;
}
}
public class MyComposedPanel {
private JPanel mainPanel;
public MyComposedPanel() {
init();
}
private void init() {
mainPanel = new JPanel();
mainPanel.add(new MyPanel().getComponent());
}
public Component getComponent() {
return mainPanel;
}
}
This way has one disadvantage: there is no GUI builder which supports it ;)
Coming back after a while and reading the accepted answer, I realized that there is an even simpler way of solving this issue. If the responsibility of calling overridable methods can be moved off to another class, it can also be moved off to a static method, using the factory method pattern:
class MyBasePanel extends JPanel {
public static MyBasePanel create() {
MyBasePanel panel = new MyBasePanel();
panel.initializeComponents();
return panel;
}
protected MyBasePanel() {
}
protected void initializeComponents() {
// layout setup omitted
// overridable call
add(new JLabel("My label"), BorderLayout.CENTER);
}
}
class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
public static MyDerivedPanel create() {
MyDerivedPanel panel = new MyDerivedPanel();
panel.initializeComponents();
return panel;
}
protected MyDerivedPanel() {
}
#Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label); // no more NPE here
}
}
}
Of course, one still has to remember to call initializeComponents when subclassing, but at least not every time an instance is created! Properly documented, this approach can be both simple and reliable.
Netbeans is generating the function private.
private initializeComponents() {...}
Thus the method is not overridable. Only protected and public methods are overridable.
An extra function keeps your code much cleaner for the Netbeans expample.
But in general you can savely use private methods to initialize classes.
Moreover if you have multiple constructors it's practical to use one extra method for initialization.
class Foo {
int x,y;
String bar;
public Foo(x) {
this.x = x;
init();
}
public Foo(y) {
this.y = y;
init();
}
private void init() {
// .. something complicated or much to do
bar = "bla";
}
}
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().
I have made an applet name ParentApplet.java whose task is to create a child frame
Child frame coding is defined in ChildFrame.java
ParentApplet.java
public class ParentApplet extends Applet {
ChildFrame frame;
private static int time = 0;
#Override
public void start() {
frame.setVisible(true);
}
#Override
public void stop() {
frame.setVisible(false);
}
#Override
public void init() {
frame = new ChildFrame("Child");
this.setSize(400, 400);
}
#Override
public void paint(Graphics g) {
g.drawString("Child's Info : " + (++time), 50, 100);
g.drawString(frame.getMessage(), 400, 100);
System.out.println(frame.getMessage().isEmpty() ? "Empty" : frame.getMessage());
}
}
ChildFrame.java
public class ChildFrame extends Frame {
private String mess = "";
public ChildFrame(String title) {
super(title);
addMouseListener(new MyMouseAdapter(this));
addWindowListener(new MyWindowAdapter(this));
setSize(300, 500);
}
public String getMessage() {
return mess;
}
public void setMessage(String mess) {
this.mess = mess;
(new ParentApplet()).repaint();
System.out.println("Click");
}
}
MyMouseAdapter.java
public class MyMouseAdapter extends MouseAdapter {
ChildFrame frame;
public MyMouseAdapter(ChildFrame frame) {
this.frame = frame;
}
#Override
public void mouseClicked(MouseEvent e) {
frame.setMessage("Mouse Cliked in Child");
}
}
MyWindowAdapter.java
public class MyWindowAdapter extends WindowAdapter {
ChildFrame frame;
public MyWindowAdapter(ChildFrame frame) {
this.frame = frame;
}
#Override
public void windowClosing(WindowEvent we) {
frame.setVisible(false);
}
}
Now i am unable to reach the paint method again even after calling the repaint method from the ChildFrame class. Please suggest me whether i have done something wrong or some thing i need to understand.
Thanks in advance
Gagandeep Singh
The answer to your question is basically "you don't do that".
The Applet's paint() method is responsible for painting the contents of the actual applet component-- i.e. the visible component that appears in the web page. Your ChildFrame should then have a separate paint() method to paint itself (or in fact, would usually have a Canvas added to it, and that Canvas in turn has its own paint() method).
(Remember that in Java a "Frame" is effectively a "window"-- i.e. a standalone window that opens separately to the web page.)
You can call repaint() on whatever component from wherever you like. This will eventually lead to that component's paint() method being called. In your particular example, you shouldn't call "new ParentApplet()" -- you don't want to call repaint() on some randomly created new applet, but rather on the single already existing one. So change this by passing a reference to your applet into the constructor of ChildFrame which ChildFrame can then hold as an instance variable and re-use when needed:
public class ChildFrame extends Frame {
private String mess = "";
private final ParentApplet parentApplet;
public ChildFrame(ParentApplet applet, String title) {
super(title);
this.parentApplet = applet;
addMouseListener(new MyMouseAdapter(this));
addWindowListener(new MyWindowAdapter(this));
setSize(300, 500);
}
...
public void setMessage(String mess) {
this.mess = mess;
parentApplet.repaint();
}
}
I must admit that so far, it's not immediately obvious why you would have a setMessage() on a separate frame whose purpose is to set the message displayed in the applet. Why not put the setMessage() method on the applet in that case? But maybe you have another reason for doing it your way that isn't apparent so far.
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)