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";
}
}
Related
I'm writing a GUI app in Java using the MVP design pattern. JButton objects belong in the View class and the ActionListener objects belong in the Presenter. I'm looking for a concise way to allow the Presenter to add ActionListeners to the View's JButtons without either (1) making the buttons public and (2) without having to add a bunch of methods to the view that look like
private JButton foo;
private JButton bar;
public void addActionListenerToButtonFoo(ActionListener l) {
foo.addActionListener(l);
}
public void addActionListenerToButtonBar(ActionListener l) {
bar.addActionListener(l);
}
// (imagine typing 10 more of these trivial functions and having
// them clutter up your code)
I found one technique that works reasonably well:
public class View {
class WrappedJButton {
private JButton b;
public WrappedJButton(String name){
this.b = new JButton(name);
}
public void addActionListener(ActionListener l) {
b.addActionListener(l);
}
}
public final WrappedJButton next = new WrappedJButton("Next");
public final WrappedJButton prev = new WrappedJButton("Previous");
public void setup() {
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
buttons.add(previous.b);
buttons.add(next.b);
}
} // end view
class Presenter {
public Presenter() {
View view = new View();
view.next.addActionListener(event -> {
// Respond to button push
});
}
} // end Presenter
This wrapper works well. Making the wrapped buttons public allows the Presenter to reference them by name (which allows my IDE to use code completion); but, because they are WrappedJButton objects, the only thing the Presenter can do is add an ActionListener. The View can get "full" access to the objects by grabbing the "real" button through the private b field.
Questions:
Is there an even better/cleaner solution? Perhaps something that
would eliminate the need to access the b field in the View?
Is there a way to generalize this solution so I don't have to
cut-and-paste WrappedJButton into every View class I write? I
tried moving WrappedJButton into an interface (which View
implements); but, when I do that, View no longer has access to the
private b field.
I think it would be ok to avoid copy-pasting the WrapperJButton class by exposing the wrapped buttons on the package level (assuming the the Presenter resides in a different package):
public class WrappedJButton {
final JButton b;
WrappedJButton(String name){
this.b = new JButton(name);
}
public void addActionListener(ActionListener l) {
b.addActionListener(l);
}
}
A different approach could be to store the buttons in a map:
class ButtonMap<E extends Enum<E>> {
private final EnumMap<E, JButton> map;
ButtonMap(Class<E> buttonEnum){
map = new EnumMap<>(buttonEnum);
for(E e : buttonEnum.getEnumConstants()){
map.put(e, new JButton(e.toString()));
}
}
JButton get(E e){
return map.get(e);
}
}
a view using this map could look like this:
public class View {
private final ButtonMap<ViewButton> buttonMap = new ButtonMap<>(ViewButton.class);
public enum ViewButton{
NEXT("Next"),
PREV("Prev");
private final String name;
private ViewButton(String name){
this.name = name;
}
#Override
public String toString(){
return name;
}
}
public void setup() {
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
buttons.add(buttonMap.get(ViewButton.PREV));
buttons.add(buttonMap.get(ViewButton.NEXT));
}
public void addActionListener(ViewButton button, ActionListener l){
buttonMap.get(button).addActionListener(l);
}
} // end view
The button map is hidden with a private field. Only the addActionListener method of the buttons is exposed.
I have two classes, and from the first jFrame1 (CotizacionGUI) I instantiate and make visible the other one (jFrame2), and I want to pass the instance of this jFrame1 (CotizacionGUI), to the other, in the constructor, to dispose it in an action triggered by the button at any moment...
public class CotizacionGUI extends javax.swing.JFrame{
public CotizacionGUI() {
initComponents();
}
private void buttonCallFrame2ActionPerformed(java.awt.event.ActionEvent evt) {
BuscarCotizacionGUI bC = new BuscarCotizacionGUI(thisjFrameinstance);
bC.setVisible();
}
}
And this is the Frame2 (BuscarCotizacionGUI), here is where I want to dispose the previous jFrame, triggered by the action performed event:
public class BuscarCotizacionGUI extends javax.swing.JFrame {
public BuscarCotizacionGUI(final JFrame otherFrame) {
initComponents();
this.setLocationRelativeTo(null);
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
otherFrame.dispose();
}
});
}
}
Can you help me guys please, I don't want to do it using other class, i want to pass the reference in the jFrame1, Thanks!
The instance of first JFrame is always available to you in the same class as this
public class CotizacionGUI extends javax.swing.JFrame{
public CotizacionGUI() {
initComponents();
}
private void buttonCallFrame2ActionPerformed(java.awt.event.ActionEvent evt) {
BuscarCotizacionGUI bC = new BuscarCotizacionGUI(this);
bC.setVisible();
}
}
Hope this is what you are looking for.
Good luck.
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;
}
}
}
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)