Java Swing存在的问题

huangapple go评论75阅读模式
英文:

Troubles with Java Swing

问题

我有以下代码

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Main {
    public static void main(String[] args) {
        Window window = new Window("这是一个标题", 450, 350);
        JButton buttonExit = new Button("退出", 75, 25);
        window.addElement(buttonExit);
        window.build();
    }
}

class Window {
    public JFrame frame;
    public JPanel panel;
    public String title;

    public Window(String title, int width, int height) {
        this.frame = new JFrame(title);
        this.frame.setPreferredSize(new Dimension(width, height));
        this.frame.setLocationRelativeTo(null);
        this.panel = new JPanel();
        this.panel.setPreferredSize(new Dimension(width, height));
        this.frame.add(panel);
    }

    public void build() {
        this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.frame.pack();
        this.frame.setVisible(true);
        this.frame.setSize(frame.getPreferredSize());
    }

    public void addElement(JButton element) {
        this.panel.add(element);
    }
}

class Button extends JButton {
    public Button(String text, int width, int height) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setText(text);
        button.setVisible(true);
        new ButtonHandler(button);
    }
}

class ButtonHandler implements ActionListener {
    public ButtonHandler(JButton button) {
        button.addActionListener(this);
    }

    public void actionPerformed(ActionEvent actionEvent) {
        System.exit(0);
    }
}

我对此有两个问题
1. 按钮被压缩无法显示文本
2. 事件处理程序无法工作我似乎不明白为什么

另外我知道我没有在这里指定布局管理器但我之前实现过这个而且没有解决我的问题我尝试过 FlowLayoutManager 和 GridBagLayout [这是我期望的因为它很灵活])。

有人可以告诉我我在这里做错了什么吗我之前只用过 C#、WPF 和 WinForms
英文:

I have the following code:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main
{
public static void main(String[] args)
{
Window window = new Window("This is a title", 450, 350);
JButton buttonExit = new Button("Exit", 75, 25);
window.addElement(buttonExit);
window.build();
}
}
class Window // extend the current class
{
public Window window;
public JFrame frame;
public JPanel panel;
public String title;
// instantiate object with the constructor
public Window(String title, int width, int height)
{
this.frame = new JFrame(title);
this.frame.setPreferredSize(new Dimension(width, height));
this.frame.setLocationRelativeTo(null); // centers the main window relative to the center of the screen dimension
this.panel = new JPanel();
this.panel.setPreferredSize(new Dimension(width, height));
//this.panel.setLayout(new FlowLayout());
this.frame.add(panel);
}
public void build()
{
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.pack(); // removes all unnecessary pixel space from the form
this.frame.setVisible(true);
this.frame.setSize(frame.getPreferredSize());
}
public void addElement(JButton element)
{
this.panel.add(element);
}
}
class Button extends JButton // extend the current class
{
public Button(String text, int width, int height)
{
JButton button = new JButton();
button.setPreferredSize(new Dimension(width, height));
button.setText(text);
button.setVisible(true);
new ButtonHandler(button);
}
}
class ButtonHandler implements ActionListener
{
public ButtonHandler(JButton button)
{
button.addActionListener(this);
}
public void actionPerformed(ActionEvent actionEvent) {
System.exit(0);
}
}

I have two problems with this:

  1. The button is compressed and won't show its text
  2. I cannot get the event handler to work and don't appear to get why

As a side note, I know that I don't specify a LayoutManager here, but I had this implemented before and it didn't solve my issue (I tried the FlowLayoutManager and the GridBagLayout [this would be my desired one, due to its flexibility]).

Can someone tell me, what I am doing wrong here? I've only worked with C# and WPF/WinForms before...

答案1

得分: 1

问题1:

你的自定义 Button 类是 JButton 的子类,但构造函数中也有一个 JButton(名为 button)。
问题在于你将 ButtonHandler 类安装到构造函数中的 button 上,而不是自定义的 Button 本身(在构造函数内部用 this 表示)。

问题2:

当你设置自定义类 Window 中命名为 frameJFrame 属性的 [preferred] 大小时,你实际上设置的是整个 JFrame 的大小,而不是框架内容的 [preferred] 大小,这包括位于框架顶部的栏(显示框架标题的位置)。
这导致框架内容的空间比 preferred 大小小,因为 preferred 大小是设置给整个框架的。
我知道你也设置了命名为 panelJPanel 的 preferred 大小,它被添加到框架中,但是当你调用 pack 方法对框架进行布局时,框架的 preferred 大小优先于框架内容的 preferred 大小,所以这可能是你看到按钮被压缩的原因。

让我通过一些代码演示我的意思:

import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestFramePrefSz {
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(() -> {
            final JFrame frame = new JFrame("Testing JFrame preferred size");
            final JPanel contents = new JPanel();
            contents.setPreferredSize(new Dimension(200, 200));
            frame.setPreferredSize(new Dimension(200, 200));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(contents);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
            System.out.println(contents.getSize());
        });
    }
}

正如你所看到的,打印的维度对象(实际上是面板的实际大小)约为 184x161,而不是请求的 200x200,因为框架的 preferred 大小也设置为了 200x200(其中包括框架的标题等)。

解决方案是只设置内容的 preferred 大小,而不是框架的 preferred 大小(至少在这种特定情况下是如此)。

因此,你应该:

  1. build 方法内部删除 this.frame.setSize(frame.getPreferredSize()); 这行代码。
  2. 在名为 Window 的自定义类的构造函数内部删除 this.frame.setPreferredSize(new Dimension(width, height)); 这行代码。

问题3:

在名为 Window 的自定义类的构造函数内部,this.frame.setLocationRelativeTo(null); 这行代码是无效的。
想象一下,当你调用这个方法时,它必须确定要设置的框架的位置。
所以它首先需要知道屏幕的大小,然后是框架本身的大小。
但是在你调用这个方法的时候,框架的大小是多少?它大约是 0x0。不是你可能期望的 preferred 大小。
这使得框架位置的计算并不会使框架在屏幕中居中。
那是因为 preferred 大小是框架的一个属性,而不是大小的属性。
因此,你要么在调用这个方法之前先调用 setSize,要么最好是设置框架内容(即 this.panel)的 preferred 大小,然后在框架上调用 pack,最后调用 this.frame.setLocationRelativeTo(null) 方法。
然后,你可以设置框架为可见,以查看它在屏幕上的位置(应该是居中的)。

因此,解决方案是遵循以下模式:

  1. 创建框架,将框架内容添加到其中,并设置内容的 preferred 大小。
  2. 在框架上调用 pack(记住,这会根据框架内容的 preferred 大小或框架自身的 preferred 大小来更改框架的大小)。
  3. 在框架上调用 setLocationRelativeTo(null)
  4. 在框架上调用 setVisible(true)

如果你看一下你的代码,实际上是在做以下操作:

  1. 创建框架。
  2. 设置框架的 preferred 大小。
  3. 在框架上调用 setLocationRelativeTo(null)(但此时框架的大小尚未设置)。
  4. 将框架内容添加到其中(即 panel)。
  5. 调用 addElement,向 panel 添加更多内容。
  6. 在框架上调用 pack(记住,框架的 preferred 大小在此时已设置,因此它将覆盖任何其他 preferred 大小,比如框架内容的 preferred 大小)。
  7. 在框架上调用 setVisible(true)
  8. 在框架上调用 setSize,使用框架的 preferred 大小。因此,这会覆盖步骤 6 中的框架大小。
英文:

Issue 1:

Your custom Button class is-a JButton but also has-a JButton (named button) in the constructor.
The problem here is you install the ButtonHandler class to the button of the constructor, not the custom Button itself (which is referred to as this inside the constructor).

Issue 2:

When you set the [preferred] size of the JFrame property named frame (in the custom class named Window), you are not setting the frame's contents' [preferred] size, but the size of the whole JFrame, which includes the bar located at the top of the frame (which has the title of the frame).
That lets the contents of the frame to have a space less than the preferred size, because the preferred size is set to the whole frame.
I know, you are also setting the preferred size of the JPanel named panel, which is added to the frame, but when you pack the frame, then the preferred size of the frame is prioritized rather than the preferred size of the contents of the frame, so that's probably why you are seeing the button compressed.

Let me demonstrate what I mean, with a bit of code:

import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestFramePrefSz {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final JFrame frame = new JFrame("Testing JFrame preferred size");
final JPanel contents = new JPanel();
contents.setPreferredSize(new Dimension(200, 200));
frame.setPreferredSize(new Dimension(200, 200));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
System.out.println(contents.getSize());
});
}
}

As you can see, the dimension object printed (which is the actual size of the panel) is about 184x161 rather than 200x200 requested, because the preferred size of the frame is also set to 200x200 (which includes the title of the frame etc...).

The solution, is to only set the preferred size of the contents, not the frame (in this particular scenario at least).

So you should:

  1. Remove the line this.frame.setSize(frame.getPreferredSize()); inside the build method.
  2. Remove the line this.frame.setPreferredSize(new Dimension(width, height)); inside the constructor of the custom class named Window.

Issue 3:

The line this.frame.setLocationRelativeTo(null); inside the constructor of the custom class named Window, is not effective in that place.
Imagine that, when you call this method, it has to determine the location of the frame to set it.
So it needs to know first of the size of the screen and then the size of the frame itself.
But what is the size of the frame at the point where you call this method? It is about 0x0. Not the preferred size as you might expect.
That makes the calculation of the frame's location to be such that the frame will not be centered at the screen.
That's because the preferred size is a property of the frame, which is a different property than the size.
So you either have to setSize prior making the call, or better to set the preferred size of the contents of the frame (ie this.panel), then call pack on the frame and finally call the method this.frame.setLocationRelativeTo(null).
Then you are free to set the frame to visible to see where it is located in the screen (ie should be centered).

So the solution is to follow a pattern like the following:

  1. Create the frame, add the contents of the frame to it and set the contents' preferred size.
  2. Call pack on the frame (remember this call will change the size of the frame, according to the preferred sizes of the contents of the frame or the frame's itself).
  3. Call setLocationRelativeTo(null) on the frame.
  4. Call setVisible(true) on the frame.

If you take a look at your code, you are instead doing:

  1. Create the frame.
  2. Set the preferred size of the frame.
  3. Call setLocationRelativeTo(null) on the frame (but the size of the frame is not set yet).
  4. Add the contents of the frame to it (ie the panel).
  5. Call addElement which adds more content to the panel.
  6. Call pack on the frame (remember the preferred size of the frame is set up to this point, so it will override any other preferred sizes, such as the contents' preferred size).
  7. Call setVisible(true) on the frame.
  8. Call setSize on the frame, with the preferred size of it. So you are overwriting the size the frame has had from step 6.

答案2

得分: 0

我不知道你正在使用什么教程。我推荐使用Oracle的教程,使用JFC/Swing创建GUI。你可以跳过Netbeans部分,但我建议浏览其余部分。

我创建了以下GUI。

Java Swing存在的问题

退出按钮可用,关闭GUI。右上角的X按钮也可关闭GUI。

这是可运行的示例代码。代码后面是解释。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class JButtonExample implements Runnable{

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JButtonExample());
    }

    private JFrame frame;

    @Override
    public void run() {
        frame = new JFrame("这是标题");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                exitProcedure();
            }
        });

        frame.add(createMainPanel(), BorderLayout.CENTER);

        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JPanel createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setPreferredSize(new Dimension(300, 200));
        panel.setBorder(BorderFactory.createEmptyBorder(
                75, 100, 75, 100));

        JButton button = new JButton("退出");
        button.addActionListener(new ExitListener(this));
        panel.add(button, BorderLayout.CENTER);

        return panel;
    }

    public void exitProcedure() {
        frame.setVisible(false);
        frame.dispose();
        System.exit(0);
    }

    public class ExitListener implements ActionListener {

        private JButtonExample example;

        public ExitListener(JButtonExample example) {
            this.example = example;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            example.exitProcedure();
        }

    }

}
  1. 我从main方法中调用了SwingUtilitiesinvokeLater方法。这个方法确保Swing组件在事件分发线程上创建和执行。

  2. 我将JFrame的代码与JPanel的代码分开。这样我可以一次专注于GUI的一部分。

  3. JFrame方法必须以特定顺序调用。这是我在大多数Swing应用程序中使用的顺序。

  4. WindowListenerWindowAdapter)使我的代码能够控制JFrame的关闭。这将允许退出按钮的actionListener关闭JFrame。WindowListener不是一个简单的概念。

  5. JFramedefaultCloseOperation通常设置为EXIT_ON_CLOSE。为了使WindowListener工作,我必须将defaultCloseOperation设置为DO_NOTHING_ON_CLOSE

  6. 我使用pack方法让JFrame确定自己的大小。

  7. 我设置了JPanel的首选大小。

  8. 我为JPanel创建了一个空边框,以便JButton会扩展以填充JPanel的其余部分。这是放置在BorderLayout中心的组件的行为。

  9. 我创建了一个ExitListener类。因为它是内部类,所以我不必创建构造函数或传递JButtonExample实例。我创建了一个构造函数,以便你可以看到如何做以及actionListener方法如何执行JButtonExample类的exitProcedure方法。

希望这个JButton示例对你有帮助。WindowListener对于简单的示例来说有点高级,但你可以看到它是如何实现的。

英文:

I don't know what you're using as a tutorial. I recommend the Oracle tutorial, Creating a GUI With JFC/Swing. You can skip the Netbeans section, but I recommend going through the rest of the sections.

I created the following GUI.

Java Swing存在的问题

The Exit button works, disposing of the GUI. The X in the upper right also disposes of the GUI.

Here's the runnable example code. The explanation follows the code.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class JButtonExample implements Runnable{
public static void main(String[] args) {
SwingUtilities.invokeLater(new JButtonExample());
}
private JFrame frame;
@Override
public void run() {
frame = new JFrame("This is a title");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
exitProcedure();
}
});
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(300, 200));
panel.setBorder(BorderFactory.createEmptyBorder(
75, 100, 75, 100));
JButton button = new JButton("Exit");
button.addActionListener(new ExitListener(this));
panel.add(button, BorderLayout.CENTER);
return panel;
}
public void exitProcedure() {
frame.setVisible(false);
frame.dispose();
System.exit(0);
}
public class ExitListener implements ActionListener {
private JButtonExample example;
public ExitListener(JButtonExample example) {
this.example = example;
}
@Override
public void actionPerformed(ActionEvent event) {
example.exitProcedure();
}
}
}
  1. I make a call to the SwingUtilities invokeLater method from the main method. This method makes sure that the Swing components are created and executed on the Event Dispatch Thread.

  2. I separate the JFrame code from the JPanel code. This is so I can focus on one part of the GUI at a time.

  3. The JFrame methods have to be called in a specific order. This is the order that I use for most of my Swing applications.

  4. The WindowListener (WindowAdapter) gives my code control over the closing of the JFrame. This will allow the Exit button actionListener to close the JFrame. A WindowListener is not a simple concept.

  5. The JFrame defaultCloseOperation is usually set to EXIT_ON_CLOSE. In order for the WindowListener to work, I had to set the defaultCloseOperation to DO_NOTHING_ON_CLOSE.

  6. I let the JFrame determine its own size by using the pack method.

  7. I set the preferred size of the JPanel.

  8. I created an empty border for the JPanel, so the JButton would expand to fill the rest of the JPanel. That's what happens to the component placed in the center of a BorderLayout.

  9. I created an ExitListener class. Because it's an inner class, I didn't have to create a constructor or pass the JButtonExample instance. I created a constructor so you can see how it's done, and how the actionListener method can execute the exitProcedure method of the JButtonExample class.

I hope this JButton example is helpful. The WindowListener is a bit advanced for a simple example, but you can see how it's done.

huangapple
  • 本文由 发表于 2020年7月24日 13:23:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/63067329.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定