基础控件

这篇笔记我们讨论一些Swing中最基础的控件用法,它们涉及用户交互和基础的内容展示,这些都是构成GUI程序的基本元素。

JPanel容器

如果你对网页中使用的HTML语言比较熟悉,那么你就能够轻松理解JPanel有什么用。JPanel是一个“容器”,用于组织和管理控件的布局,这和HTML中的<div>类似,HTML中我们可以通过嵌套<div>标签并对其设置适当的布局CSS属性,最终一系列的标签和组件整合形成完整的Web页面,Swing也是类似的,我们的整个GUI界面其实也是由大量JPanel嵌套配合布局管理器实现的,这最终会形成一个树形结构,我们通常称之为GUI界面的“控件树”,树中的根节点通常是JFrame,叶子节点是各种具体功能的控件如按钮等,而那些中间节点其实就是JPanel

下面是一个简单的例子,它是一个“计算器”,其中JPanel(1)是计算器主要操作区域的容器,JPanel(2)中包含了计算器中的各种按钮,JPanel(3)是计算器底部的状态栏。

JFrame
  |_ JPanel(1)
    |_ JTextField
    |_ JPanel(2)
      |_ JButton
      |_ JButton
      |_ ...
  |_ JPanel(3)
    |_ JLabel

对应的布局代码如下。

package com.gacfox.demo;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class MainFrame extends JFrame {
    public MainFrame() {
        setLayout(new BorderLayout());
        JPanel pMain = new JPanel(new BorderLayout());
        JTextField tfCalcDisplay = new JTextField();
        pMain.add(tfCalcDisplay, BorderLayout.NORTH);
        JPanel pButtons = new JPanel(new GridLayout(4, 3));
        JButton btn1 = new JButton("1");
        JButton btn2 = new JButton("2");
        JButton btn3 = new JButton("3");
        JButton btn4 = new JButton("4");
        JButton btn5 = new JButton("5");
        JButton btn6 = new JButton("6");
        JButton btn7 = new JButton("7");
        JButton btn8 = new JButton("8");
        JButton btn9 = new JButton("9");
        JButton btnPlus = new JButton("+");
        JButton btn0 = new JButton("0");
        JButton btnEqual = new JButton("=");
        pButtons.add(btn1);
        pButtons.add(btn2);
        pButtons.add(btn3);
        pButtons.add(btn4);
        pButtons.add(btn5);
        pButtons.add(btn6);
        pButtons.add(btn7);
        pButtons.add(btn8);
        pButtons.add(btn9);
        pButtons.add(btnPlus);
        pButtons.add(btn0);
        pButtons.add(btnEqual);
        pMain.add(pButtons, BorderLayout.CENTER);
        JPanel pStatus = new JPanel(new FlowLayout(FlowLayout.LEFT));
        JLabel lStatus = new JLabel("就绪");
        pStatus.add(lStatus);
        add(pMain, BorderLayout.CENTER);
        add(pStatus, BorderLayout.SOUTH);
        pack();
        setLocationByPlatform(true);
        setTitle("Calculator");
    }
}

代码虽然长,但实际上其布局结构是简单且容易理解的,它和前面我们规划的页面布局结构“控件树”完全一致。我们创建的JPanel都被设置了布局管理器,以便合适的在其内部放置控件,此外我们也可以为JPanel设置背景色、边框等属性,这里就不逐一演示了,具体参考JDK的标准文档即可。注意代码的最后我们调用了JFramepack()方法,它用于自动根据内部控件的情况计算窗体大小,如果我们手动设置了窗体大小,则不必调用该方法。上面程序运行后,显示效果如下。

不过实际开发中,我们的界面可能远比上面复杂,在MainFrame类中初始化整个控件树通常都是不可能的,此时我们可以像继承JFrame一样继承JPanel,并一层层的组合整个GUI界面。

注:上面代码中,对于GUI控件对象变量的命名方式其实类似匈牙利命名法,它使用控件的类型缩写加变量名命名,例如用于展示计算内容的JTextField被命名为tfCalcDisplay(由于Swing中的控件类都以J开头因此省略了这个字母),这种命名法惯用于GUI编程,这可能继承自Win32API的风格,总而言之这和标准的驼峰命名法有些区别,它在许多负责图形界面开发的软件工程师中流行,但你不必一定遵循这个规则。

JLabel显示内容

Swing中,JLabel是个强大且常用的组件,它能够显示纯文本、富文本和图像。

显示纯文本

下面例子代码中,我们使用JLabel显示纯文本,它将Hello, Swing!打印在JLabel控件的中间。

package com.gacfox.demo;

import javax.swing.JFrame;
import javax.swing.JLabel;

public class MainFrame extends JFrame {
    public MainFrame() {
        JLabel lMain = new JLabel("Hello, Swing!", JLabel.CENTER);
        add(lMain);

        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

JLabel.CENTER指定了内容显示的对齐方式,默认的水平靠左的。

显示HTML富文本

JLabel支持HTML4.01富文本,但其支持相对有限,仅限于几个主要的HTML标签,JLabel不支持CSS和过于复杂的HTML结构。下面代码中,我们让Swing!字符串变为蓝色且加粗。

package com.gacfox.demo;

import javax.swing.JFrame;
import javax.swing.JLabel;

public class MainFrame extends JFrame {
    public MainFrame() {
        JLabel lMain = new JLabel("<html>Hello, <font size='5' color='blue'>Swing!</font></html>", JLabel.CENTER);
        add(lMain);

        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

注意,如果需要使用JLabel展示富文本,<html></html>是必要的。

显示图像

JLabel支持ImageIcon类型作为参数并显示其中的图片,它支持主流图片格式以及GIF动画(但无法控制播放帧率等)。下面例子使用JLabel显示了一个PNG图像。

package com.gacfox.demo;

import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class MainFrame extends JFrame {
    public MainFrame() {
        URL iconUrl = getClass().getResource("/icon.png");
        ImageIcon icon = new ImageIcon(iconUrl);
        JLabel lMain = new JLabel(icon, JLabel.CENTER);
        add(lMain);

        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

虽然JLabel能够方便的显示图像,但注意如果我们要实现类似“Windows图片查看器”的程序时不建议用JLabel,因为它缺乏一些必要的灵活性,这类程序建议使用Java2D API来绘制图像,使用Java2D我们能更轻松的实现图片的旋转、缩放甚至涉及图像处理算法的滤镜等操作。

JButton按钮

创建按钮并设置大小

JButton用于创建按钮,我们可以创建文本按钮、图片按钮和文本图片结合的按钮,下面例子代码我们分别创建了这3种按钮。

package com.gacfox.demo;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainFrame extends JFrame {
    public MainFrame() {
        URL iconUrl = getClass().getResource("/icon_sm.png");
        ImageIcon icon = new ImageIcon(iconUrl);

        JPanel pMain = new JPanel(new FlowLayout());
        JButton btnText = new JButton("Click Me!");
        btnText.setPreferredSize(new Dimension(85, 28));
        JButton btnIcon = new JButton(icon);
        btnIcon.setPreferredSize(new Dimension(85, 28));
        JButton btnTextAndIcon = new JButton("Click Me!", icon);
        btnTextAndIcon.setPreferredSize(new Dimension(125, 28));
        pMain.add(btnText);
        pMain.add(btnIcon);
        pMain.add(btnTextAndIcon);

        add(pMain);
        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

代码中,3个按钮被放置在一个JPanel容器中,为了保证按钮的整齐,我们这里还手动设置了按钮的大小。

设置按钮状态

我们有时需要将按钮禁用,启用和禁用状态可以调用setEnabled()方法实现,下面是一个例子。

package com.gacfox.demo;

import java.awt.FlowLayout;

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

public class MainFrame extends JFrame {
    public MainFrame() {
        JPanel pMain = new JPanel(new FlowLayout());
        JButton btnText = new JButton("Click Me!");
        btnText.setEnabled(false);
        pMain.add(btnText);

        add(pMain);
        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

被禁用的按钮会变成灰色且不可点击。

处理按钮点击事件

我们可以调用按钮的addActionListener()方法为其绑定点击事件的回调函数,下面是一个例子。

package com.gacfox.demo;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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

public class MainFrame extends JFrame {
    public MainFrame() {
        JPanel pMain = new JPanel(new FlowLayout());
        JButton btnText = new JButton("Click Me!");
        btnText.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        pMain.add(btnText);

        add(pMain);
        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

代码中,点击按钮后会退出程序。有关事件机制将在后续章节详细介绍。

菜单

窗体菜单栏

Swing中窗体菜单栏由JMenuBarJMenuJMenuItem这3个类封装,下面例子为JFrame设置菜单栏。

package com.gacfox.demo;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

public class MainFrame extends JFrame {
    public MainFrame() {
        JMenuBar mbMain = new JMenuBar();
        JMenu mFile = new JMenu("文件");
        JMenuItem miOpen = new JMenuItem("打开");
        mFile.add(miOpen);
        JMenuItem miSave = new JMenuItem("保存");
        mFile.add(miSave);
        mFile.addSeparator();
        JMenuItem miExit = new JMenuItem("退出");
        mFile.add(miExit);
        mbMain.add(mFile);
        setJMenuBar(mbMain);

        setSize(300, 200);
        setTitle("Hello, Swing!");
    }
}

代码中,我们设置了窗体菜单文件,它包含3个子菜单,分别是打开保存退出,在退出菜单的上方我们还加入了一个分隔线。

上下文菜单

上下文菜单俗称“右键菜单”,Swing需要使用JPopupMenu实现。

package com.gacfox.demo;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

public class MainFrame extends JFrame {
    public MainFrame() {
        final JPopupMenu pmMain = new JPopupMenu();
        JMenuItem miOpen = new JMenuItem("打开");
        JMenuItem miSave = new JMenuItem("保存");
        JMenuItem miExit = new JMenuItem("退出");
        pmMain.add(miOpen);
        pmMain.add(miSave);
        pmMain.add(miExit);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    pmMain.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

        setSize(300, 200);
        setTitle("Hello, Swing!");
    }
}

代码中创建JPopupMenu的方式和窗体菜单是类似的,不过上下文菜单的触发比较特殊,它需要绑定到鼠标松开触发的mouseReleased事件上并使用事件对象的isPopupTrigger()方法判断是否是要弹出上下文菜单,当该事件触发时,我们将上下文菜单显示在当前触发组件的鼠标相对位置上。

动态更新控件树

和浏览器中的DHTML页面类似,Swing图形界面的控件树也不是静态的,我们其实可以动态修改图形界面的控件树,不过稍微复杂一点的是Swing中修改控件树涉及线程安全问题和手动触发的控件重绘。

package com.gacfox.demo;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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

public class MainFrame extends JFrame {
    public MainFrame() {
        final JPanel pMain = new JPanel(new FlowLayout());
        JButton btnText = new JButton("Click Me!");
        pMain.add(btnText);

        btnText.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        JButton btnNew = new JButton("Click Me!");
                        pMain.add(btnNew);
                        pMain.revalidate();
                        pMain.repaint();
                    }
                });
            }
        });

        add(pMain);
        setSize(300, 200);
        setLocationByPlatform(true);
        setTitle("Hello, Swing!");
    }
}

上面代码中,我们实现每次点击按钮就在JPanel中新增一个按钮,注意我们对控件树的修改操作是放在事件分发线程中执行的。此外由于Swing是事件驱动绘制的GUI框架(与之相对的是游戏中常用的即时绘制imGUI),每次修改后我们都调用了pMain.revalidate()pMain.repaint()触发了JPanel的重绘,否则可能修改不会及时显示出来。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap