i just started studying gui in java. i am now able to create windows with specific sizes while extending JFrame. however, i came to read posts from here that it is better not to extend JFrame. then i tried to create a window by setting the size in the JPanel instead, but the setSize doesn't seem to work (my code must lack something)
here's my code for my frame
import javax.swing.JFrame;
public class MyFrame{
private JFrame mainFrame;
private MyPanel mainPanel;
public MyFrame(){
mainFrame = new JFrame();
mainPanel = new MyPanel(50, 50);
mainFrame.add(mainPanel);
mainFrame.setVisible(true);
}
}
and here's my code for my panel
import javax.swing.JPanel;
public class MyPanel extends JPanel{
public MyPanel(int i, int j){
setSize(i, j);
}
}
i tried adding frame.pack() in my Frame class, because i thought the frame, not having it's size set, is too small for the panel to be seen -i was wrong
what's lacking in my code?
what's lacking in my code?
A preferred size for the custom component (Panel) for starters. #Override getPreferredSize() to return a logical value.
Then pack() the frame to ensure it is the smallest size needed to display the panel and any other components.
So, something like this:
import javax.swing.*;
public class Application {
private JFrame frame;
private CustomPanel panel;
public Application() {
frame = new JFrame();
// next 2 lines, just a good idea
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// at 50x50, the title bar on Windows is wider!
panel = new CustomPanel(200, 200);
frame.add(panel);
// make the frame smallest it can be and still show components
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new Application();
}
};
SwingUtilities.invokeLater(r);
}
}
class CustomPanel extends JPanel {
public CustomPanel(int w, int h) {
setPreferredSize(new java.awt.Dimension(w, h));
}
}
Related
Beginner programming student here. Trying to simply create JFrame object that allows me to click on the boundaries of the window and display the coordinates where I click. However, whenever I click a new location, the previous coordinates need to disappear. I have been told that one way to do this is by adding a JPanel to the center of the JFrame object. However when I do so I am getting an error that says that I am adding a window to a container. I may be extending something incorrectly from what I have read but I can't seem to figure it out.
public class Proj07 {
public static void main(String[] args){
new Proj07Runner();
}
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class Proj07Runner{
GUI gui = new GUI();
}
class MyFrame extends JFrame{
int XCoor;
int YCoor;
public void paint(Graphics g){
g.drawString("x = " + XCoor + ", y = " + YCoor, XCoor, YCoor);
}
}
class GUI{
public GUI(){
MyFrame displayWindow = new MyFrame();
displayWindow.setSize(300,100);
displayWindow.setTitle("Insert name here");
displayWindow.addWindowListener(new WProc1());
JPanel myPanel = new JPanel();
displayWindow.getContentPane().add(myPanel, "Center");
displayWindow.setVisible(true);
}
}
class MouseProc extends MouseAdapter{
public void mousePressed(MouseEvent e){
((MyFrame)e.getComponent()).XCoor = e.getX();
((MyFrame)e.getComponent()).YCoor = e.getY();
e.getComponent().repaint();
}
}
class WProc1 extends WindowAdapter{
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
Thank you guys for your help!
I am not 100% sure but I think the problem is where you put the listener:
You need to do everything on the JPanel, not the frame that is the container, so you should replace the declares with:
MyFrame displayWindow = new MyFrame();
displayWindow.setSize(300,100);
displayWindow.setTitle("Insert name here");
displayWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel myPanel = new JPanel();
myPanel.setSize(300,100);
myPanel.addMouseListener(new myMouse)
displayWindow.getContentPane().add(myPanel, "Center");
displayWindow.setVisible(true);
And then create a mouseListener instead of a mouseAdapter to add in the JPanel.
Also remove the last WindowsAdapter thing, just use exitonclose.
Edit:
When it comes down to what the frame should do it should only contain the panel, so you also need to move everything you were doing on the frame to the panel (such as the public void paint() and stuff).
I would go as far as saying that if you want to add multiple panels on a frame, you should consider putting all panels inside another panel (as a container) and then putting that panel inside the frame.
As far as I know, the setDivider() method of JSplitPane can take an int which is an absolute position from the left. For example, someSplitPane.setDivider(120); will set the divider 120px from the left. Is there any way I can do the same thing, but set the divider an absolute position from the right?
Simple implementation:
public class Window extends JFrame {
JSplitPane splitpane;
public Window() {
initComponents();
}
private void initComponents() {
setTitle("Debugging");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setMinimumSize(new Dimension(640, 480));
splitpane = new JSplitPane();
System.out.println(splitpane.getSize().width); // prints 0
splitpane.setDividerLocation(splitpane.getSize().width - 120);
getContentPane().add(splitpane);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Window().setVisible(true);
});
}
}
EDIT: I have written this code isolated from any existing code, and I reproduce the error. What looks to be happening is the JFrame is instantiating and appears on the desktop, and then about a second later, a 0 (from the commented print statement) is output to the console.
Assuming you create your GUI correctly by using the EDT you can add the following in the constructor of your class where you create the splitPane:
SwingUtilities.invokeLater(() ->
{
Dimension d = splitPane.getSize();
splitPane.setDividerLocation(d.width - 120);
});
This will add code to the EDT. So after the frame is visible and the split pane has been given a size, the divider location will be reset.
I made a few changes to your code and created this GUI.
I used a JFrame instead of extending a JFrame.
I set the preferred size of the JSplitPane, rather than the JFrame. Generally, you care more about the size of the JSplitPane than the JFrame. Usually, the JSplitPane will have two JPanels with Swing components, so you don't have to specify a preferred size at that point.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
public class JSplitPaneRight {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new JSplitPaneRight();
});
}
public JSplitPaneRight() {
initComponents();
}
public void initComponents() {
JFrame frame = new JFrame("JSplitPane Right");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createSplitPane(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JSplitPane createSplitPane( ) {
JSplitPane splitpane = new JSplitPane();
splitpane.setPreferredSize(new Dimension(640, 480));
int width = splitpane.getPreferredSize().width;
System.out.println(width); // prints 0
splitpane.setDividerLocation(width - 120);
return splitpane;
}
}
I've built a simple gui that adds panels based on user input. My initial problem was that when the panel was added the frame did not resize. Because it was a jpanel object that handled the user input adding a new panel to itself and therefore could not 'see' the jframe (at least I couldn't find how it could) I couldn't work out how to call repaint() or revalidate() on the parent frame from within that object. However,through trial and error I did find that this worked
JFrame jFrame = new JFrame(title){
#Override
public void invalidate(
super.invalidate();
this.pack();
}
};
But because I don't really know what goes on behind the scenes with invalidate I want to know whether this is a good idea or not (It seems kinda sketchy). Any advice would be great, thanks.
EDIT
Hope this makes the problem a bit clearer
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class TestFrame {
JFrame jframe;
NewPanel jpanel;
public TestFrame(){
jframe = new JFrame(); // without above addition frame won't resize
jpanel = new NewPanel();
jframe.add(jPanel);
jframe.pack();
jframe.setVisible(true);
}
public static void main(String [] args){
TestFrame testframe= new TestFrame();
}
}
class NewPanel extends JPanel implements ActionListener{
public NewPanel(){
JTextField textField = new JTextField (10);
textField.addActionListener(this);
this.add(textField);
}
// Adds a label when action is performed on textfield
#Overide
public void actionPerformed(ActionEvent ae){
JPanel extraPanel = new JPanel();
extraPanel.add(new JLabel("hi"));
this.add(extraPanel);
this.revalidate(); this.repaint();
}
}
Your issue is not that revalidate() or repaint() aren't working.
The issue here is that your JFrame has been pack()ed already and thus it has a preferred size set. If you want to change its size you need to call pack() on it again. Not necessarily to call it on invalidate().
I made some changes to your code to compile (typos) and I came with this:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestFrame {
JFrame jframe;
NewPanel jpanel;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TestFrame());
}
public TestFrame(){
jframe = new JFrame(); // without above addition frame won't resize
jpanel = new NewPanel();
jframe.add(jpanel);
jframe.pack();
jframe.setVisible(true);
}
class NewPanel extends JPanel implements ActionListener{
public NewPanel(){
JTextField textField = new JTextField (10);
textField.addActionListener(this);
this.add(textField);
}
// Adds a label when action is performed on textfield
#Override
public void actionPerformed(ActionEvent ae){
System.out.println("WOLOLO");
JPanel extraPanel = new JPanel();
extraPanel.add(new JLabel("hi"));
this.add(extraPanel);
this.revalidate();
this.repaint();
jframe.pack();
}
}
}
Another way to solve this is to override getPreferredSize method from the JPanel:
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
And you can delete jframe.pack() in the previous code.
// This works!
JPanel background = new JPanel();
background.setBackground(Color.BLACK);
background.setBounds(0,0,this.getSize().width,this.getSize().height);
add(background);`
// Ihis method doesn't work! Why ? And the classic method setBackground(Color.BLACK); has the same problem
JPanel background = new JPanel()
{
#Override
public void setBackground(Color bg){
super.setBackground(Color.BLACK);
}
#Override
public void setBounds(int a, int b, int c, int d){
super.setBounds(0,0,this.getSize().width,this.getSize().height);
}
};
add(background);
Definite issue you are going to encounter will come from calling setBounds method.
Call setBackground for your panel and just add it to JFrame by calling add method. JPanel will be added on center of JFrame by default since default layout for JFrame is a BorderLayout and it will perfectly fit without of calling setBounds. There is absolutely no need to complicate things by overriding any method.
This:
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestPanel {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JPanel panel = new JPanel();
panel.setBackground(Color.BLACK);
JFrame frame = new JFrame();
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//frame.pack();
frame.setSize(400, 300);
frame.setVisible(true);
});
}
}
...will work perfectly.
Though you have ovverriden methods, you haven't called them.
I've got one class called WindowTemplate that is the base for other (more complex) windows. It is an abstract class and then I'm trying to use the "extend" trick to add more stuff to the new window, keeping the original "skeleton". That is my problem though, because if I run WindowTemplate.createWindow(); or a_Welcome.createWindow(); (they should be point to the same thing), I get my "base" window. But when I run a_Welcome window = new a_Welcome(); (what should be the base + new stuff) I get only the extra bits that I added without the original features. Here is my code:
package windows;
import java.awt.*;
import javax.swing.*;
public abstract class WindowTemplate extends JFrame {
/**
* Create the GUI and show it. For thread safety, this method should be
* invoked from the event-dispatching thread.
*/
public static void createWindow() {
JFrame myFrame = new JFrame("My first window");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
myFrame.setSize(550, 450);
myFrame.setLocationRelativeTo(null);
// JLabel emptyLabel = new JLabel("");
// emptyLabel.setPreferredSize(new Dimension(550, 450));
// myFrame.getContentPane().setLayout(new CardLayout());
// myFrame.getContentPane().add(emptyLabel, BorderLayout.CENTER);
// myFrame.pack();
}
}
the class with new window and some extra stuff (ignore a_):
package windows;
import java.awt.*;
import javax.swing.*;
public class a_Welcome extends WindowTemplate {
public a_Welcome() {
JPanel area = new JPanel();
JLabel text = new JLabel("One line another line and another line"); // , JLabel.CENTER);
// text.setBounds(80, 400, 400, 50);
add(area);
// area.setLayout(null);
area.add(text, new CardLayout());
// area.add(text); // , BorderLayout.CENTER);
Font font = new Font("SansSerif", Font.BOLD, 30);
text.setFont(font);
text.setForeground(Color.green);
area.setBackground(Color.darkGray);
area.setSize(550, 450);
}
}
// timer-after 5 seconds-go to the next window (countdown in the bottom right corner)
and the main:
package windows;
public class Launcher {
public static void main(String[] args) {
// Schedule a job for the event-dispatching thread:
// creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
// WindowTemplate.createWindow();
// a_Welcome.createWindow();
a_Welcome window = new a_Welcome();
window.setVisible(true);
}
});
}
}
Thanks for your help!
Static method createWindow() always creates a new JFrame which is not a superclass of the WindowTemplate. Constructor of the a_Window is adding components to the WindowTemplate which hasn't been initialized since the static createWindow() creates an independent frame.
I would suggest you to change the static createWindow() into WindowTemplate constructor and try running main once again.
package windows;
import java.awt.*;
import javax.swing.*;
public abstract class WindowTemplate extends JFrame {
/**
* Create the GUI and show it. For thread safety, this method should be
* invoked from the event-dispatching thread.
*/
public WindowTemplate () {
JFrame myFrame = new JFrame("My first window");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
myFrame.setSize(550, 450);
myFrame.setLocationRelativeTo(null);
// JLabel emptyLabel = new JLabel("");
// emptyLabel.setPreferredSize(new Dimension(550, 450));
// myFrame.getContentPane().setLayout(new CardLayout());
// myFrame.getContentPane().add(emptyLabel, BorderLayout.CENTER);
// myFrame.pack();
}
}
You have another JFrame defined in the static createWindow() method. This means that you are adding the components to this frame that is scoped to the createWindow() method only and in the constructor you are adding to the a_Welcome instance.
You should do something like this
public class BaseWindow() {
//Constructor
public BaseWindow() {
init();
}
public void init() {
//add basic components
}
}
public class SubClassWindow() {
public SubClassWindow() {
super();
}
#Override
public void init() {
super.init(); //important so you get the base stuff
//add other components
}
}
Code not tested.
Another approach you might consider would be to have a JFrame that is just a wrapper and compose the window by adding a panel. Let's say you want a toolbar at the top of every window you're creating. Each window would have different buttons on the toolbar and a different set of components at the bottom. This way you are doing composition instead of inheritance, because inheritance can get ugly later on. (For discussions on that point, see this, this, and this for starters)
That would look something like:
public interface AppPanel {
List<JButton> getToolbarButtons();
boolean okToClose();
JPanel getGui();
}
public MyPanel extends JPanel implements AppPanel {
//standard swing components stuff set up here
public List<JButton> getToolbarButtons() {
//set up buttons and their actions
return buttonList;
}
public boolean okToClose() {
//ask user if they want to save, etc.
return true;
}
public JPanel getGui() {
return this;
}
}
public AppFrame extends JFrame {
private AppPanel panel;
public static AppFrame createFrame(AppPanel panel) {
AppFrame frame = new AppFrame(panel);
return frame;
}
public AppFrame(AppPanel panel) {
super();
this.panel = panel;
add(panel.getGui(), someLayoutConstraints);
panel.getToolbarButtons(); //do stuff with the buttons
//...
this.addWindowListener(new WindowAdapter() {
public void WindowClosing(WindowEvent e) {
if (panel.isOkToClose()) {
setVisible(false);
}
}
});
}
}