ZetCode-GUI-教程-八-

ZetCode GUI 教程(八)

原文:ZetCode

协议:CC BY-NC-SA 4.0

GroupLayout管理器

原文: http://zetcode.com/tutorials/javaswingtutorial/grouplayout/

GroupLayout管理器是内置的 Swing 管理器。 它是唯一可以创建多平台布局的内置管理器。 所有其他管理器要么非常简单,要么使用固定大小的间隙,不适用于不同平台和屏幕分辨率的用户界面。 除了GroupLayout之外,我们还可以使用第三方MigLayout在 Java 中创建多平台布局。

Tweet

ZetCode 为 Swing 布局管理过程提供了 196 页专门的电子书: Java Swing 布局管理教程

GroupLayout说明

GroupLayout将组件与实际布局分开; 所有组件都可以放置在一个地方,布局可以放置在另一个地方。

GroupLayout管理器独立定义每个大小的布局。 在一个维度上,我们将组件放置在水平轴的旁边。 在另一个维度中,我们沿垂直轴放置组件。 在两种布局中,我们都可以顺序或并行排列组件。 在水平布局中,一行组件称为顺序组,而一列组件称为并行组。 在垂直布局中,一列组件称为顺序组,一排组件称为并行组。

GroupLayout的差距

GroupLayout使用组件之间或组件与边界之间的三种类型的间隙:RELATEDUNRELATEDINDENTEDRELATED用于相关组件,UNRELATED用于不相关,INDENTED用于组件之间的缩进。 这些差距的主要优势在于它们与分辨率无关; 也就是说,它们在不同分辨率的屏幕上的像素大小不同。 其他内置管理器在所有分辨率上错误地使用了固定大小的间隙。

令人惊讶的是,只有三个预定义的间隙。 在高质量的排版系统 LaTeX 中,只有三个垂直空间可用:\smallskip\medskip\bigskip。 在设计 UI 时,少即是多,而仅仅是因为我们可以使用可能不同的间隙大小,字体大小或颜色,但这并不意味着我们应该这样做。

GroupLayout简单示例

使用addComponent()方法将组件添加到布局管理器。 参数是最小,首选和最大大小值。 我们可以传递一些特定的绝对值,也可以提供GroupLayout.DEFAULT_SIZEGroupLayout.PREFERRED_SIZEGroupLayout.DEFAULT_SIZE指示应使用组件的相应大小(例如,对于最小参数,该值由getMinimumSize()方法确定)。 以类似的方式,通过调用组件的getPreferredSize()方法来确定GroupLayout.PREFERRED_SIZE

GroupLayoutSimpleEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.Alignment.LEADING;
import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class GroupLayoutSimpleEx extends JFrame {

    public GroupLayoutSimpleEx() {

        initUI();
    }

    private void initUI() {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        var lbl = new JLabel("Name:");
        var field = new JTextField(15);

        GroupLayout.SequentialGroup sg = gl.createSequentialGroup();

        sg.addComponent(lbl).addPreferredGap(RELATED).addComponent(field,
                GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
                GroupLayout.PREFERRED_SIZE);

        gl.setHorizontalGroup(sg);

        GroupLayout.ParallelGroup pg = gl.createParallelGroup(
                LEADING, false);

        pg.addComponent(lbl).addComponent(field);
        gl.setVerticalGroup(pg);

        gl.setAutoCreateContainerGaps(true);

        pack();

        setTitle("Simple");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new GroupLayoutSimpleEx();
            ex.setVisible(true);
        });
    }
}

在示例中,我们有一个标签和一个文本字段。 文本字段不可扩展。

GroupLayout.SequentialGroup sg = gl.createSequentialGroup();

sg.addComponent(lbl).addPreferredGap(RELATED).addComponent(field,
        GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
        GroupLayout.PREFERRED_SIZE);

addComponent()addPreferredGap()方法都返回组对象。 因此,可以创建一系列方法调用。 我们将文本字段的最大大小更改为GroupLayout.PREFERRED_SIZE,从而使其在水平方向上超出其首选大小不可扩展。 (首选大小和最大大小之间的差异是组件增长的趋势。这适用于遵循这些值的管理器。)将值改回GroupLayout.DEFAULT_SIZE将导致文本字段在水平维度上扩展。

GroupLayout.ParallelGroup pg = gl.createParallelGroup(
        LEADING, false);

在垂直布局中,createParallelGroup()的第二个参数接收为false。 这样我们可以防止文本字段沿垂直方向增长。 通过将addComponent()max参数设置为GroupLayout.PREFERRED_SIZE(在垂直布局中调用),可以实现相同的目的。

GroupLayout simple example

图:GroupLayout简单示例

GroupLayout基线对齐

基线对齐是使组件沿其包含的文本的基线对齐。 以下示例使两个组件沿其基线对齐。

GroupLayoutBaselineEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.Alignment.BASELINE;

public class GroupLayoutBaselineEx extends JFrame {

    private JLabel display;
    private JComboBox box;
    private String[] distros;

    public GroupLayoutBaselineEx() {

        initUI();
    }

    private void initUI() {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        distros = new String[] {"Easy", "Medium", "Hard"};
        box = new JComboBox<>(distros);
        display = new JLabel("Level:");

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(display)
                .addComponent(box,
                        GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE,
                        GroupLayout.PREFERRED_SIZE)
        );

        gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
                .addComponent(box, GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE,
                        GroupLayout.PREFERRED_SIZE)
                .addComponent(display)
        );

        pack();

        setTitle("Baseline alignment");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GroupLayoutBaselineEx();
            ex.setVisible(true);
        });
    }
} 

我们有一个标签和一个组合框。 两个组件都包含文本。 我们将这两个组件沿其文本的基线对齐。

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addComponent(display)
        .addComponent(box,
                GroupLayout.DEFAULT_SIZE,
                GroupLayout.DEFAULT_SIZE,
                GroupLayout.PREFERRED_SIZE)
);

通过将BASELINE参数传递到createParallelGroup()方法可以实现基线对齐。

GroupLayout baseline alignment

图:GroupLayout基线对齐

GroupLayout角按钮示例

下面的示例在窗口的右下角放置两个按钮。 按钮的大小相同。

GroupLayoutCornerButtonsEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingConstants;
import java.awt.Dimension;
import java.awt.EventQueue;

import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class GroupLayoutCornerButtonsEx extends JFrame {

    public GroupLayoutCornerButtonsEx() {

        initUI();
    }

    private void initUI() {

        setPreferredSize(new Dimension(300, 200));

        var cpane = getContentPane();
        var gl = new GroupLayout(cpane);
        cpane.setLayout(gl);

        gl.setAutoCreateGaps(true);
        gl.setAutoCreateContainerGaps(true);

        var okButton = new JButton("OK");
        var closeButton = new JButton("Close");

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(okButton)
                .addComponent(closeButton)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(gl.createParallelGroup()
                        .addComponent(okButton)
                        .addComponent(closeButton))
        );

        gl.linkSize(SwingConstants.HORIZONTAL, okButton, closeButton);

        pack();

        setTitle("Buttons");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GroupLayoutCornerButtonsEx();
            ex.setVisible(true);
        });
    }
}

该示例使用GroupLayout管理器创建角按钮。

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addPreferredGap(RELATED,
                GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(okButton)
        .addComponent(closeButton)
);

在水平布局中,我们添加了一个可拉伸的间隙和两个单个组件。 可伸展的缝隙将两个按钮推向右侧。 间隙是通过addPreferredGap()方法调用创建的。 其参数是间隙的类型,间隙的首选大小和最大大小。 最大值和首选值之间的差异是间隙拉伸的能力。 当两个值相同时,间隙具有固定大小。

gl.setVerticalGroup(gl.createSequentialGroup()
        .addPreferredGap(RELATED,
                GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addGroup(gl.createParallelGroup()
                .addComponent(okButton)
                .addComponent(closeButton))
);

在垂直布局中,我们添加了一个可拉伸的间隙和两个组件的平行组。 同样,该间隙将按钮组推到底部。

gl.linkSize(SwingConstants.HORIZONTAL, okButton, closeButton);

linkSize()方法使两个按钮的大小相同。 我们只需要设置它们的宽度,因为默认情况下它们的高度已经相同。

GroupLayout corner buttons

图:GroupLayout角按钮

GroupLayout 密码示例

在基于表单的应用中可以找到以下布局,该应用由标签和文本字段组成。

GroupLayoutPasswordEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.Alignment.TRAILING;

public class GroupLayoutPasswordEx extends JFrame {

    public GroupLayoutPasswordEx() {

        initUI();
    }

    private void initUI() {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        var serviceLbl = new JLabel("Service:");
        var userNameLbl = new JLabel("User name:");
        var passwordLbl = new JLabel("Password:");

        var field1 = new JTextField(10);
        var field2 = new JTextField(10);
        var field3 = new JTextField(10);

        gl.setAutoCreateGaps(true);
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(TRAILING)
                        .addComponent(serviceLbl)
                        .addComponent(userNameLbl)
                        .addComponent(passwordLbl))
                .addGroup(gl.createParallelGroup()
                        .addComponent(field1)
                        .addComponent(field2)
                        .addComponent(field3))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(serviceLbl)
                        .addComponent(field1))
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(userNameLbl)
                        .addComponent(field2))
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(passwordLbl)
                        .addComponent(field3))
        );

        pack();

        setTitle("Password application");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GroupLayoutPasswordEx();
            ex.setVisible(true);
        });
    }
}

要求是:标签必须在水平方向上右对齐,并且必须使用相应的文本字段垂直对齐其基线。

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addGroup(gl.createParallelGroup(TRAILING)
                .addComponent(serviceLbl)
                .addComponent(userNameLbl)
                .addComponent(passwordLbl))
        .addGroup(gl.createParallelGroup()
                .addComponent(field1)
                .addComponent(field2)
                .addComponent(field3))
);

在水平方向上,布局由包装在顺序组中的两个平行组组成。 标签和字段分别放入其平行的组中。 平行标签组具有GroupLayout.Alignment.TRAILING对齐方式,这使标签正确对齐。

gl.setVerticalGroup(gl.createSequentialGroup()
        .addGroup(gl.createParallelGroup(BASELINE)
                .addComponent(serviceLbl)
                .addComponent(field1))
        .addGroup(gl.createParallelGroup(BASELINE)
                .addComponent(userNameLbl)
                .addComponent(field2))
        .addGroup(gl.createParallelGroup(BASELINE)
                .addComponent(passwordLbl)
                .addComponent(field3))
);

在垂直布局中,我们确保标签与其文本字段对齐至基线。 为此,我们将标签及其对应的字段分组为具有GroupLayout.Alignment.BASELINE对齐方式的平行组。

GroupLayout password example

图:GroupLayout密码示例

在本章中,我们使用内置的GroupLayout管理器来创建布局。

Java Swing 事件

原文: http://zetcode.com/tutorials/javaswingtutorial/swingevents/

所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成,但也可以通过其他方式生成,例如互联网连接,窗口管理器或计时器。

Tweet

在事件模型中,有三个参与者:

  • 事件来源
  • 事件对象
  • 事件监听器

事件源是状态更改的对象。 它生成事件。事件对象(事件)将状态更改封装在事件源中。事件监听器是要通知的对象。 事件源对象将处理事件的任务委托给事件监听器。

Java Swing 工具箱中的事件处理功能非常强大且灵活。 Java 使用事件委托模型。 我们指定发生特定事件时要通知的对象。

事件对象

当应用发生故障时,将创建一个事件对象。 例如,当我们单击按钮或从列表中选择一个项目时。 事件有几种类型,包括ActionEventTextEventFocusEventComponentEvent。 它们每个都是在特定条件下创建的。

事件对象保存有关已发生事件的信息。 在下一个示例中,我们将更详细地分析ActionEvent

EventObjectEx.java

package com.zetcode;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class EventObjectEx extends JFrame {

    private JList mylist;
    private DefaultListModel model;

    public EventObjectEx() {

        initUI();
    }

    private void initUI() {

        model = new DefaultListModel();
        mylist = new JList(model);
        mylist.setBorder(BorderFactory.createEtchedBorder());

        var okBtn = new JButton("OK");
        okBtn.addActionListener(new ClickAction());

        createLayout(okBtn, mylist);

        setTitle("Event object");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1], 250, GroupLayout.PREFERRED_SIZE,
                        GroupLayout.DEFAULT_SIZE)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1], 150, GroupLayout.PREFERRED_SIZE,
                        GroupLayout.DEFAULT_SIZE)
        );

        pack();
    }

    private class ClickAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {

            var formatter = DateTimeFormatter.ISO_TIME;

            var localTime = Instant.ofEpochMilli(e.getWhen()).atZone(
                    ZoneId.systemDefault()).toLocalTime();

            var text = localTime.format(formatter);

            if (!model.isEmpty()) {
                model.clear();
            }

            if (e.getID() == ActionEvent.ACTION_PERFORMED) {
                model.addElement("Event Id: ACTION_PERFORMED");
            }

            model.addElement("Time: " + text);

            var source = e.getSource().getClass().getName();
            model.addElement("Source: " + source);

            var mod = e.getModifiers();

            var buffer = new StringBuffer("Modifiers: ");

            if ((mod & ActionEvent.ALT_MASK) < 0) {
                buffer.append("Alt ");
            }

            if ((mod & ActionEvent.SHIFT_MASK) < 0) {
                buffer.append("Shift ");
            }

            if ((mod & ActionEvent.META_MASK) < 0) {
                buffer.append("Meta ");
            }

            if ((mod & ActionEvent.CTRL_MASK) < 0) {
                buffer.append("Ctrl ");
            }

            model.addElement(buffer);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new EventObjectEx();
            ex.setVisible(true);
        });
    }
}

发生动作时,将调用actionPerformed()方法。 它的参数是ActionEvent对象。

var formatter = DateTimeFormatter.ISO_TIME;

var localTime = Instant.ofEpochMilli(e.getWhen()).atZone(
        ZoneId.systemDefault()).toLocalTime();

var text = localTime.format(formatter);

我们获得事件发生的时间。 getWhen()方法返回以毫秒为单位的时间值。 我们将值转换为LocalTime并使用DateTimeFormatter将其格式化为 ISO 时间。

var source = e.getSource().getClass().getName();
model.addElement("Source: " + source);

在这里,我们将事件源的名称添加到列表中。 在我们的情况下,源是JButton

var mod = e.getModifiers();

我们得到了修饰键。 它是修饰符常量的按位或。

if ((mod & ActionEvent.SHIFT_MASK) > 0)
    buffer.append("Shift ");

在这里,我们确定是否按下了 Shift 键。

Event Object

图:事件对象

事件处理的实现

我们有几种方法可以在 Java Swing 中实现事件处理:

  • 匿名内部类
  • 内部类
  • 派生类

匿名内部类

我们从一个匿名内部类开始。

AnonymousInnerClassEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class AnonymousInnerClassEx extends JFrame {

    public AnonymousInnerClassEx() {

        initUI();
    }

    private void initUI() {

        var closeBtn = new JButton("Close");

        closeBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent event) {
                System.exit(0);
            }
        });

        createLayout(closeBtn);

        setTitle("Anonymous inner class");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(220)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addGap(220)
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new AnonymousInnerClassEx();
            ex.setVisible(true);
        });
    }
}

在此示例中,我们有一个按钮,单击后会关闭窗口。

var closeBtn = new JButton("Close");

“关闭”按钮是事件源。 它将生成事件。

closeBtn.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent event) {
        System.exit(0);
    }
});

在这里,我们使用按钮注册一个动作监听器。 事件被发送到事件目标。 在我们的案例中,事件目标是ActionListener类; 在此代码中,我们使用匿名内部类。

closeBtn.addActionListener((ActionEvent event) -> {
    System.exit(0);
});

使用 lambda 表达式重写的代码。

内部类

在这里,我们使用内部ActionListener类实现示例。

InnerClassEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class InnerClassEx extends JFrame {

    public InnerClassEx() {

        initUI();
    }

    private void initUI() {

        var closeBtn = new JButton("Close");

        var listener = new ButtonCloseListener();
        closeBtn.addActionListener(listener);

        createLayout(closeBtn);

        setTitle("Inner class example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(220)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addGap(220)
        );

        pack();
    }

    private class ButtonCloseListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new InnerClassEx();
            ex.setVisible(true);
        });
    }
}

我们在面板上有一个关闭按钮。 它的监听器在一个命名的内部类中定义。

var listener = new ButtonCloseListener();
closeBtn.addActionListener(listener);

在这里,我们有一个非匿名内部类。

private class ButtonCloseListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
}

在此定义了按钮监听器。

实现监听器的派生类

下面的示例将从组件派生一个类,并在该类内部实现一个动作监听器。

DerivedClassEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class DerivedClassEx extends JFrame {

    public DerivedClassEx() {

        initUI();
    }

    private void initUI() {

        var closeBtn = new MyButton("Close");

        createLayout(closeBtn);

        setTitle("Derived class");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(220)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addGap(220)
        );

        pack();
    }

    private class MyButton extends JButton implements ActionListener {

        public MyButton(String text) {

            super.setText(text);
            addActionListener(this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new DerivedClassEx();
            ex.setVisible(true);
        });
    }
}

在此示例中,我们创建一个派生的MyButton类,该类实现了动作监听器。

var closeButton = new MyButton("Close");

在这里,我们创建自定义MyButton类。

private class MyButton extends JButton implements ActionListener {

    public MyButton(String text) {

        super.setText(text);
        addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
}

MyButton类是从JButton类扩展的。 它实现了ActionListener接口。 这样,事件处理在MyButton类中进行管理。

多种来源

监听器可以插入多个源。 在下一个示例中将对此进行解释。

MultipleSourcesEx.java

package com.zetcode;

import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class MultipleSourcesEx extends JFrame {

    private JLabel statusBar;

    public MultipleSourcesEx() {

        initUI();
    }

    private void initUI() {

        statusBar = new JLabel("Ready");
        statusBar.setBorder(BorderFactory.createEtchedBorder());

        var butListener = new ButtonListener();

        var closeBtn = new JButton("Close");
        closeBtn.addActionListener(butListener);

        var openBtn = new JButton("Open");
        openBtn.addActionListener(butListener);

        var findBtn = new JButton("Find");
        findBtn.addActionListener(butListener);

        var saveBtn = new JButton("Save");
        saveBtn.addActionListener(butListener);

        createLayout(closeBtn, openBtn, findBtn, saveBtn, statusBar);

        setTitle("Multiple Sources");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addComponent(arg[3])
                .addComponent(arg[4], GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addComponent(arg[3])
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(arg[4])
        );

        gl.linkSize(arg[0], arg[1], arg[2], arg[3]);

        pack();
    }

    private class ButtonListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            var o = (JButton) e.getSource();
            var label = o.getText();

            statusBar.setText(" " + label + " button clicked");
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new MultipleSourcesEx();
            ex.setVisible(true);
        });
    }
}

我们创建四个按钮和一个状态栏。 单击按钮后,状态栏将显示一条消息。

var closeBtn = new JButton("Close");
closeBtn.addActionListener(butListener);

var openBtn = new JButton("Open");
openBtn.addActionListener(butListener);
...

每个按钮注册相同的ButtonListener对象。

private class ButtonListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {

        var o = (JButton) e.getSource();
        var label = o.getText();

        statusBar.setText(" " + label + " button clicked");
    }
}

我们确定按下了哪个按钮,并为状态栏创建一条消息。 getSource()方法返回最初发生事件的对象。 该消息是用setText()方法设置的。

Multiple sources

图:多个来源

多监听器

可以为一个事件注册多个监听器。

MultipleListenersEx.java

package com.zetcode;

import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Year;

import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.DEFAULT_SIZE;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class MultipleListenersEx extends JFrame {

    private JLabel statusBar;
    private JSpinner spinner;
    private int count = 0;

    public MultipleListenersEx() {

        initUI();
    }

    private void initUI() {

        statusBar = new JLabel("0");
        statusBar.setBorder(BorderFactory.createEtchedBorder());

        JButton addBtn = new JButton("+");
        addBtn.addActionListener(new ButtonListener1());
        addBtn.addActionListener(new ButtonListener2());

        int currentYear = Year.now().getValue();

        var yearModel = new SpinnerNumberModel(currentYear,
                currentYear - 100,
                currentYear + 100,
                1);

        spinner = new JSpinner(yearModel);
        spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

        createLayout(addBtn, spinner, statusBar);

        setTitle("Multiple Listeners");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[0])
                        .addGap(20)
                        .addComponent(arg[1], DEFAULT_SIZE,
                                DEFAULT_SIZE, PREFERRED_SIZE))
                .addComponent(arg[2], GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(arg[0])
                        .addGap(20)
                        .addComponent(arg[1], DEFAULT_SIZE,
                                DEFAULT_SIZE, PREFERRED_SIZE))
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(arg[2])
        );

        pack();
    }

    private class ButtonListener1 implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            var val = (Integer) spinner.getValue();
            spinner.setValue(++val);
        }
    }

    private class ButtonListener2 implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            statusBar.setText(Integer.toString(++count));
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new MultipleListenersEx();
            ex.setVisible(true);
        });
    }
}

在此示例中,我们有一个按钮,一个微调器和一个状态栏。 对于一个事件,我们使用两个按钮监听器。 一键单击将为微调器组件增加一年并更新状态栏。 状态栏将显示我们单击按钮的次数。

addBtn.addActionListener(new ButtonListener1());
addBtn.addActionListener(new ButtonListener2());

我们注册了两个按钮监听器。

var yearModel = new SpinnerNumberModel(currentYear,
        currentYear - 100,
        currentYear + 100,
        1);

spinner = new JSpinner(yearModel);

在这里,我们创建微调器组件。 我们使用微调器的年度模型。 SpinnerNumberModel参数是初始值,最小值和最大值以及步长。

spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

我们删除了千位分隔符。

private class ButtonListener1 implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {

        var val = (Integer) spinner.getValue();
        spinner.setValue(++val);
    }
}

第一个按钮监听器增加微调器组件的值。

private class ButtonListener2 implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {

        statusBar.setText(Integer.toString(++count));
    }
}

第二个按钮监听器增加状态栏的值。

Multiple Listeners

图:多个监听器

删除监听器

可以使用removeActionListener()方法删除注册的监听器。 下面的示例演示了这一点。

RemoveListenerEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;

public class RemoveListenerEx extends JFrame {

    private JLabel lbl;
    private JButton addBtn;
    private JCheckBox activeBox;
    private ButtonListener buttonlistener;
    private int count = 0;

    public RemoveListenerEx() {

        initUI();
    }

    private void initUI() {

        addBtn = new JButton("+");
        buttonlistener = new ButtonListener();

        activeBox = new JCheckBox("Active listener");
        activeBox.addItemListener((ItemEvent event) -> {
            if (activeBox.isSelected()) {
                addBtn.addActionListener(buttonlistener);
            } else {
                addBtn.removeActionListener(buttonlistener);
            }
        });

        lbl = new JLabel("0");

        createLayout(addBtn, activeBox, lbl);

        setTitle("Remove listener");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[2]))
                .addGap(30)
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1]))
                .addGap(30)
                .addComponent(arg[2])
        );

        pack();
    }

    private class ButtonListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            lbl.setText(Integer.toString(++count));
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new RemoveListenerEx();
            ex.setVisible(true);
        });
    }
}

面板上有三个组件:按钮,复选框和标签。 通过切换复选框,我们可以添加或删除按钮的监听器。

buttonlistener = new ButtonListener();

如果要稍后删除它,我们必须创建一个非匿名监听器。

activeBox.addItemListener((ItemEvent event) -> {
    if (activeBox.isSelected()) {
        addBtn.addActionListener(buttonlistener);
    } else {
        addBtn.removeActionListener(buttonlistener);
    }
});

我们确定是否选中该复选框。 然后,我们添加或删除监听器。

Remove listener

图:删除监听器

移动窗口

以下示例将在屏幕上寻找窗口的位置。

MovingWindowEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

public class MovingWindowEx extends JFrame implements ComponentListener {

    private JLabel labelx;
    private JLabel labely;

    public MovingWindowEx() {

        initUI();
    }

    private void initUI() {

        addComponentListener(this);

        labelx = new JLabel("x: ");
        labelx.setFont(new Font("Serif", Font.BOLD, 14));
        labelx.setBounds(20, 20, 60, 25);

        labely = new JLabel("y: ");
        labely.setFont(new Font("Serif", Font.BOLD, 14));
        labely.setBounds(20, 45, 60, 25);

        createLayout(labelx, labely);

        setTitle("Moving window");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addGap(130)
        );

        pack();
    }

    @Override
    public void componentResized(ComponentEvent e) {
    }

    @Override
    public void componentMoved(ComponentEvent e) {

        var x = e.getComponent().getX();
        var y = e.getComponent().getY();

        labelx.setText("x: " + x);
        labely.setText("y: " + y);
    }

    @Override
    public void componentShown(ComponentEvent e) {
    }

    @Override
    public void componentHidden(ComponentEvent e) {
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new MovingWindowEx();
            ex.setVisible(true);
        });
    }
}

该示例在面板上显示当前窗口坐标。 要获取窗口位置,我们使用ComponentListener

public class MovingWindowExample extends JFrame implements ComponentListener {

主类实现ComponentListener接口。 它必须提供所有方法的实现。

@Override
public void componentResized(ComponentEvent e) {
}

@Override
public void componentMoved(ComponentEvent e) {

    var x = e.getComponent().getX();
    var y = e.getComponent().getY();

    labelx.setText("x: " + x);
    labely.setText("y: " + y);
}

@Override
public void componentShown(ComponentEvent e) {
}

@Override
public void componentHidden(ComponentEvent e) {
}

我们必须创建所有四种方法,即使我们对其中一种方法componentMoved()感兴趣。 其他三种方法为空。

var x = e.getComponent().getX();
var y = e.getComponent().getY();

在这里,我们获得了组件的 x 和 y 位置。

labelx.setText("x: " + x);
labely.setText("y: " + y);

检索到的值设置为标签。

Moving a window

图:移动窗口

适配器

适配器是一种方便的类,它为所有必需的方法提供空的实现。 在前面的代码示例中,即使没有使用它们,我们也必须实现ComponentListener类的所有四个方法。 为了避免不必要的编码,我们可以使用适配器。 然后,我们使用实现我们实际需要的那些方法。 没有用于按钮单击事件的适配器,因为只有一种方法可以实现actionPerformed()。 在有多种方法可以实现的情况下,可以使用适配器。

下面的示例是使用ComponentAdapter重写前一个示例。

AdapterEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

public class AdapterEx extends JFrame {

    private JLabel labelx;
    private JLabel labely;

    public AdapterEx() {

        initUI();
    }

    private void initUI() {

        addComponentListener(new MoveAdapter());

        labelx = new JLabel("x: ");
        labelx.setFont(new Font("Serif", Font.BOLD, 14));

        labely = new JLabel("y: ");
        labely.setFont(new Font("Serif", Font.BOLD, 14));

        createLayout(labelx, labely);

        setTitle("Adapter example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addGap(130)
        );

        pack();
    }

    private class MoveAdapter extends ComponentAdapter {

        @Override
        public void componentMoved(ComponentEvent e) {

            var x = e.getComponent().getX();
            var y = e.getComponent().getY();

            labelx.setText("x: " + x);
            labely.setText("y: " + y);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {
            var ex = new AdapterEx();
            ex.setVisible(true);
        });
    }
}

此示例是对上一个示例的重写。 在这里,我们使用ComponentAdapter

addComponentListener(new MoveAdapter());

在这里,我们注册组件监听器。

private class MoveAdapter extends ComponentAdapter {

    @Override
    public void componentMoved(ComponentEvent e) {

        var x = e.getComponent().getX();
        var y = e.getComponent().getY();

        labelx.setText("x: " + x);
        labely.setText("y: " + y);
    }
}

MoveAdapter内部类中,我们定义componentMoved()方法。 其他所有方法均保留为空。

Java Swing 教程的这一部分专门用于 Swing 事件。 我们介绍了事件源,事件对象,事件监听器,创建事件处理器的几种方法,多个源和监听器,删除监听器以及事件适配器。

基本的 Swing 组件

原文: http://zetcode.com/tutorials/javaswingtutorial/basicswingcomponents/

Swing 组件是应用的基本构建块。 Swing 具有广泛的各种组件,包括按钮,复选框,滑块和列表框。

Tweet

在 Swing 教程的这一部分中,我们将介绍JButtonJLabelJTextFieldJPasswordField

JButton

JButton正在实现按钮。 如果用户单击该动作,则用于触发该动作。

显示文字和图标

JButton可以显示文本,图标或两者。

ImageIconButtonEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;

public class ImageIconButtonEx extends JFrame {

    public ImageIconButtonEx() {

        initUI();
    }

    private void initUI() {

        var saveIcon = new ImageIcon("src/resources/save.png");
        var homeIcon = new ImageIcon("src/resources/home.png");

        var quitBtn = new JButton("Quit");
        var saveBtn = new JButton(saveIcon);
        var homeBtn = new JButton("Home", homeIcon);

        createLayout(quitBtn, saveBtn, homeBtn);

        setTitle("JButtons");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
        );

        gl.linkSize(arg[0], arg[1], arg[2]);

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ImageIconButtonEx();
            ex.setVisible(true);
        });
    }
}

该示例显示了三个按钮:一个显示文本,一个显示图标,一个同时显示文本和图标。

var saveIcon = new ImageIcon("src/main/resources/save.png");

许多组件可以用图标修饰。 为此,我们使用ImageIcon类。

var quitBtn = new JButton("Quit");

JButton构造器将文本作为参数。

var saveBtn = new JButton(saveIcon);

在此JButton构造器中,我们传递一个图标。

JButton homeBtn = new JButton("Home", homeIcon);

此按钮显示文本和图标。

gl.linkSize(arg[0], arg[1], arg[2]);

使用GroupLayoutlinkSize()方法,使按钮大小相同。

JButtons

图:JButtons

带有助记符的JButton

助记符该键与外观的无鼠标修饰符(通常为 Alt)结合使用时,如果焦点位于该按钮的祖先窗口内的某个位置,它将激活该按钮。

ButtonMnemonicEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

public class ButtonMnemonicEx extends JFrame implements ActionListener {

    public ButtonMnemonicEx() {

        initUI();
    }

    private void initUI() {

        var showBtn = new JButton("Show");
        showBtn.addActionListener(this);
        showBtn.setMnemonic(KeyEvent.VK_S);

        createLayout(showBtn);

        setTitle("JButton");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addGap(150)
        );

        pack();
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        JOptionPane.showMessageDialog(this, "Button clicked",
                "Information", JOptionPane.INFORMATION_MESSAGE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ButtonMnemonicEx();
            ex.setVisible(true);
        });
    }
}

此示例中的按钮可以通过单击鼠标或 Alt + S 键盘快捷键来激活。

public class ButtonMnemonicEx extends JFrame implements ActionListener

ButtonMnemonicEx类实现ActionListener; 它必须覆盖actionPerformed()方法,在该方法中,我们将激活按钮后执行的代码放入其中。

var showBtn = new JButton("Show");
showBtn.addActionListener(this);

创建一个新的JButton。 我们使用addActionListener()方法向按钮添加一个动作监听器。

showBtn.setMnemonic(KeyEvent.VK_S);

setMnemonic()设置助记键; 带下划线的"S"字符。

@Override
public void actionPerformed(ActionEvent e) {

    JOptionPane.showMessageDialog(this, "Button clicked",
            "Information", JOptionPane.INFORMATION_MESSAGE);
}

当通过单击鼠标或通过快捷方式激活按钮时,将显示带有JOptionPane.showMessageDialog()的消息对话框。

JLabel

JLabel是显示文本和/或图像的简单组件。 它不响应输入事件。

显示文字

以下示例显示文本。

LabelEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;

public class LabelEx extends JFrame {

    public LabelEx() {

        initUI();
    }

    private void initUI() {

        var lyrics =  "<html>It's way too late to think of<br>" +
                "Someone I would call now<br>" +
                "And neon signs got tired<br>" +
                "Red eye flights help the stars out<br>" +
                "I'm safe in a corner<br>" +
                "Just hours before me<br>" +
                "<br>" +
                "I'm waking with the roaches<br>" +
                "The world has surrendered<br>" +
                "I'm dating ancient ghosts<br>" +
                "The ones I made friends with<br>" +
                "The comfort of fireflies<br>" +
                "Long gone before daylight<br>" +
                "<br>" +
                "And if I had one wishful field tonight<br>" +
                "I'd ask for the sun to never rise<br>" +
                "If God leant his voice for me to speak<br>" +
                "I'd say go to bed, world<br>" +
                "<br>" +
                "I've always been too late<br>" +
                "To see what's before me<br>" +
                "And I know nothing sweeter than<br>" +
                "Champaign from last New Years<br>" +
                "Sweet music in my ears<br>" +
                "And a night full of no fears<br>" +
                "<br>" +
                "But if I had one wishful field tonight<br>" +
                "I'd ask for the sun to never rise<br>" +
                "If God passed a mic to me to speak<br>" +
                "I'd say stay in bed, world<br>" +
                "Sleep in peace</html>";

        var label = new JLabel(lyrics);
        label.setFont(new Font("Serif", Font.PLAIN, 14));
        label.setForeground(new Color(50, 50, 25));

        createLayout(label);

        setTitle("No Sleep");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new LabelEx();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们显示了 Cardigans 的歌曲的歌词。 我们可以在JLabel组件中使用 HTML 标签。 我们使用<br>标签来分隔行。

var label = new JLabel(lyrics);
label.setFont(new Font("Serif", Font.PLAIN, 14));

在这里,我们创建一个标签组件。 我们选择纯衬线字体并将其高度设置为 14px。

pack();

pack()方法将调整窗口大小,以便标签组件以其首选大小显示。

JLabel

图:JLabel

显示图标

JLabel可用于显示图像。

LabelEx2.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;

public class LabelEx2 extends JFrame {

    public LabelEx2() {

        initUI();
    }

    private void initUI() {

        var lbl1 = new JLabel(new ImageIcon("src/resources/cpu.png"));
        var lbl2 = new JLabel(new ImageIcon("src/resources/drive.png"));
        var lbl3 = new JLabel(new ImageIcon("src/resources/laptop.png"));
        var lbl4 = new JLabel(new ImageIcon("src/resources/player.png"));

        createLayout(lbl1, lbl2, lbl3, lbl4);

        setTitle("Icons");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addComponent(arg[3])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addComponent(arg[3])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new LabelEx2();
            ex.setVisible(true);
        });
    }
}

在示例中,我们使用JLabel组件显示四个图标。

var lbl1 = new JLabel(new ImageIcon("src/main/resources/cpu.png"));

JLabelImageIcon作为参数。 图标是固定大小的图像。 ImageIcon从 GIF,JPEG 或 PNG 图像绘制图标。

Displaying icons

图:显示图标

JTextField

JTextField是一个文本组件,允许编辑一行非格式化文本。

JTextFieldEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import java.awt.EventQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JTextFieldEx extends JFrame {

    private JLabel lbl;

    public JTextFieldEx() {

        initUI();
    }

    private void initUI() {

        var field = new JTextField(15);
        lbl = new JLabel();

        field.getDocument().addDocumentListener(new MyDocumentListener());

        createLayout(field, lbl);

        setTitle("JTextField");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class MyDocumentListener implements DocumentListener {

        private String text;

        @Override
        public void insertUpdate(DocumentEvent e) {
            updateLabel(e);
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            updateLabel(e);
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
        }

        private void updateLabel(DocumentEvent e) {

            var doc = e.getDocument();
            int len = doc.getLength();

            try {
                text = doc.getText(0, len);
            } catch (BadLocationException ex) {
                Logger.getLogger(JTextFieldEx.class.getName()).log(
                        Level.WARNING, "Bad location", ex);
            }

            lbl.setText(text);

        }
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0], GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addComponent(arg[1])
                .addGap(150)
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new JTextFieldEx();
            ex.setVisible(true);
        });
    }
}

在示例中,输入JTextField的文本立即显示在标签组件中。

var field = new JTextField(15);

创建了新的JTextField。 该参数是列数。 请注意,此值不会设置字段中允许的字符数。 该值用于计算字段的首选宽度。

field.getDocument().addDocumentListener(new MyDocumentListener());

我们将文档监听器添加到JTextFieldgetDocument()方法获取与编辑器关联的模型。 每个 Swing 组件都有一个模型,用于管理其状态或数据。

@Override
public void insertUpdate(DocumentEvent e) {
    updateLabel(e);
}

@Override
public void removeUpdate(DocumentEvent e) {
    updateLabel(e);
}

insertUpdate()removeUpdate()方法调用updateLabel()方法,该方法从文本字段复制文本并将其设置为标签组件。

@Override
public void changedUpdate(DocumentEvent e) {
}

我们对changeUpdate()方法不感兴趣。 仅在样式化文档中生成此事件。

private void updateLabel(DocumentEvent e) {

    var doc = e.getDocument();
    int len = doc.getLength();

    try {
        text = doc.getText(0, len);
    } catch (BadLocationException ex) {
        Logger.getLogger(JTextFieldEx.class.getName()).log(
                Level.WARNING, "Bad location", ex);
    }

    lbl.setText(text);
}

文档事件的getDocument()方法用于获取正在观察的文本字段的文档。 我们使用文档的getLength()方法获得字符数。 该值用于通过文档的getText()方法复制文本。 最后,使用标签的setText()方法将文本设置为标签。

gl.setVerticalGroup(gl.createSequentialGroup()
        .addComponent(arg[0], GroupLayout.DEFAULT_SIZE, 
                GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addComponent(arg[1])
        .addGap(150)
);

我们不希望JTextField垂直生长; 因此,我们在垂直方向将其最大值设置为GroupLayout.PREFERRED_SIZE

JTextField

图:JTextField

JPasswordField

JPasswordFieldJTextField子类,不显示用户键入的字符。

PasswordEx.java

package com.zetcode;

import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.Arrays;

import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;

public class PasswordEx extends JFrame {

    private JTextField loginField;
    private JPasswordField passField;

    public PasswordEx() {

        initUI();
    }

    private void initUI() {

        var lbl1 = new JLabel("Login");
        var lbl2 = new JLabel("Password");

        loginField = new JTextField(15);
        passField = new JPasswordField(15);

        var submitButton = new JButton("Submit");
        submitButton.addActionListener(new SubmitAction());

        createLayout(lbl1, loginField, lbl2, passField, submitButton);

        setTitle("Login");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private class SubmitAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {

            doSubmitAction();
        }

        private void doSubmitAction() {

            var login = loginField.getText();
            var passwd = passField.getPassword();

            if (!login.isEmpty() && passwd.length != 0) {

                System.out.format("User %s entered %s password%n",
                        login, String.valueOf(passwd));
            }

            Arrays.fill(passwd, '0');
        }
    }

    private void createLayout(Component... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateGaps(true);
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGap(50)
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1])
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
                .addGap(50)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGap(50)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1], GroupLayout.DEFAULT_SIZE,
                                GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                        .addComponent(arg[2])
                        .addComponent(arg[3], GroupLayout.DEFAULT_SIZE,
                                GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(UNRELATED)
                        .addComponent(arg[4]))
                .addGap(50)
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new PasswordEx();
            ex.setVisible(true);
        });
    }
}

该示例具有一个文本字段,一个密码字段和一个按钮。 该按钮将打印用户输入的数据。

passField = new JPasswordField (15);

创建JPasswordField的实例。

var passwd = passField.getPassword();

为了安全起见,密码字段将其值存储为字符数组而不是字符串。 字符数组由getPassword()方法返回。 不推荐使用较早的getText()方法。

Arrays.fill(passwd , '0');

处理完密码后,建议将数组的元素设置为零。

JPasswordField

图:JPasswordField

在 Java Swing 教程的这一部分中,我们介绍了基本的 Swing 组件,包括JButtonJLabelJTextFieldJPasswordField

基本的 Swing 组件 II

原文: http://zetcode.com/tutorials/javaswingtutorial/basicswingcomponentsII/

在 Java Swing 教程的这一章中,我们将继续描述 Java Swing 组件。

Tweet

我们提到以下组件:JCheckBoxJRadioButtonJSliderJComboBoxJProgressBarJToggleButtonJListJTabbedPaneJTextAreaJTextPane

JCheckBox

JCheckBox是带有标签的框,该标签具有两种状态:开和关。 如果选中此复选框,则在复选框中用勾号表示。 复选框可用于在启动时显示或隐藏启动屏幕,切换工具栏的可见性等。

通过JCheckBox可以使用ActionListenerItemListener。 通常使用后一种选项。 ItemListener是用于接收项目事件的接口。 对处理项目事件感兴趣的类,例如观察者,实现此接口。 使用组件的addItemListener()方法向组件注册观察者对象。 发生项目选择事件时,将调用观察者的itemStateChanged()方法。

CheckBoxEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class CheckBoxEx extends JFrame
        implements ItemListener {

    public CheckBoxEx() {

        initUI();
    }

    private void initUI() {

        var cb = new JCheckBox("Show title", true);
        cb.addItemListener(this);

        createLayout(cb);

        setSize(280, 200);
        setTitle("JCheckBox");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    public void itemStateChanged(ItemEvent e) {

        int sel = e.getStateChange();

        if (sel == ItemEvent.SELECTED) {

            setTitle("JCheckBox");
        } else {

            setTitle("");
        }
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new CheckBoxEx();
            ex.setVisible(true);
        });
    }
}

我们的代码示例根据是否选中复选框来显示或隐藏窗口的标题。

public class CheckBoxEx extends JFrame
        implements ItemListener {

我们的应用类实现ItemListener。 这意味着该类必须提供itemStateChanged()方法,在该方法中我们对项目选择事件做出反应。

var checkbox = new JCheckBox("Show title", true);

JCheckBox已创建。 此构造器将文本和复选框的状态作为参数。 最初已选中该复选框。

cb.addItemListener(this);

该应用类被注册为复选框选择事件的观察者。

@Override
public void itemStateChanged(ItemEvent e) {

    int sel = e.getStateChange();

    if (sel == ItemEvent.SELECTED) {

        setTitle("JCheckBox");
    } else {

        setTitle("");
    }
}  

我们调用ItemEventgetStateChange()方法来确定复选框的状态。 ItemEvent是一个语义事件,指示已选择或取消选择一项。 它被发送到注册的观察者。 根据复选框的状态,我们使用setTitle()方法显示或隐藏窗口的标题。

JCheckBox

图:JCheckBox

请注意复选框文本周围的蓝色矩形。 它指示此组件具有键盘焦点。 可以使用 Space 键选择和取消选中该复选框。

JRadioButton

JRadioButton允许用户从一组选项中选择一个独占选项。 它与ButtonGroup组件一起使用。

RadioButtonEx.java

package com.zetcode;

import javax.swing.ButtonGroup;
import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.LayoutStyle;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;

public class RadioButtonEx extends JFrame
        implements ItemListener {

    private JLabel sbar;

    public RadioButtonEx() {

        initUI();
    }

    private void initUI() {

        var lbl = new JLabel("Difficulty");

        var group = new ButtonGroup();

        var rb1 = new JRadioButton("Easy", true);
        var rb2 = new JRadioButton("Medium");
        var rb3 = new JRadioButton("Hard");

        group.add(rb1);
        group.add(rb2);
        group.add(rb3);

        sbar = new JLabel("Selected: Easy");

        rb1.addItemListener(this);
        rb2.addItemListener(this);
        rb3.addItemListener(this);

        createLayout(lbl, rb1, rb2, rb3, sbar);

        setSize(350, 250);
        setTitle("Radio buttons");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    public void itemStateChanged(ItemEvent e) {

        int sel = e.getStateChange();

        if (sel == ItemEvent.SELECTED) {

            var button = (JRadioButton) e.getSource();
            var text = button.getText();

            var sb = new StringBuilder("Selected: ");
            sb.append(text);

            sbar.setText(sb.toString());
        }
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addComponent(arg[3])
                .addComponent(arg[4])
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addComponent(arg[3])
                .addPreferredGap(RELATED,
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(arg[4])
        );
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new RadioButtonEx();
            ex.setVisible(true);
        });
    }
}

该示例具有三个单选按钮。 所选单选按钮的值将显示在状态栏中。

var group = new ButtonGroup();

var rb1 = new JRadioButton("Easy", true);
var rb2 = new JRadioButton("Medium");
var rb3 = new JRadioButton("Hard");

group.add(rb1);
group.add(rb2);
group.add(rb3);

创建三个JRadioButtons并将其放入ButtonGroup中。 预选第一个单选按钮。

rb1.addItemListener(this);
rb2.addItemListener(this);
rb3.addItemListener(this);

所有三个单选按钮共享一个ItemListener

if (sel == ItemEvent.SELECTED) {

当我们选择一个单选按钮时,实际上会触发两个事件:一个事件用于选择,另一个事件用于取消选择。 我们对选择感兴趣。

var button = (JRadioButton) e.getSource();
var text = button.getText();

我们使用getSource()方法获取事件的来源,并获取单选按钮的文本标签。

var sb = new StringBuilder("Selected: ");
sb.append(text);

sbar.setText(sb.toString());

我们构建字符串并将其设置为标签。

JRadioButtons

图:JRadioButtons

JSlider

JSlider是一个组件,使用户可以通过在有限的间隔内滑动旋钮来以图形方式选择一个值。 移动滑块的旋钮,将调用滑块的ChangeListenerstateChanged()方法。

HScale线

JSlider可以选择显示其值范围的刻度线。 刻度线由setMinorTickSpacing()setMajorTickSpacing()setPaintTicks()方法控制。

SliderEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import java.awt.EventQueue;

public class SliderEx extends JFrame {

    private JSlider slider;
    private JLabel lbl;

    public SliderEx() {

        initUI();
    }

    private void initUI() {

        slider = new JSlider(0, 100, 0);
        slider.setMinorTickSpacing(5);
        slider.setMajorTickSpacing(10);
        slider.setPaintTicks(true);

        slider.addChangeListener((ChangeEvent event) -> {

            int value = slider.getValue();
            lbl.setText(Integer.toString(value));
        });

        lbl = new JLabel("...");

        createLayout(slider, lbl);

        setTitle("JSlider");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new SliderEx();
            ex.setVisible(true);
        });
    }
}

在代码示例中,从滑块选择的值显示在标签组件中。

slider = new JSlider(0, 100, 0);

将使用最小值,最大值和当前值作为参数来创建JSlider

slider.setMinorTickSpacing(5);
slider.setMajorTickSpacing(10);

我们将次要刻度线和主要刻度线之间的距离设置为。

slider.setPaintTicks(true);

setPaintTicks()方法确定是否在滑块上绘制刻度线。

slider.addChangeListener((ChangeEvent event) -> {

    int value = slider.getValue();
    lbl.setText(Integer.toString(value));
});

当滑块进行某种更改时,将触发ChangeEvent。 我们使用getValue()方法获得滑块的当前值,使用Integer.toString()将整数转换为字符串,然后使用标签的setText()方法将其设置为标签。

JSlider

图:JSlider

音量控件

第二个示例使用JSlider创建音量控件。

SliderEx2.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import java.awt.EventQueue;

public class SliderEx2 extends JFrame {

    private JSlider slider;
    private JLabel lbl;

    private ImageIcon mute;
    private ImageIcon min;
    private ImageIcon med;
    private ImageIcon max;

    public SliderEx2() {

        initUI();
    }

    private void initUI() {

        loadImages();

        slider = new JSlider(0, 150, 0);

        slider.addChangeListener((ChangeEvent event) -> {

            int value = slider.getValue();

            if (value == 0) {
                lbl.setIcon(mute);
            } else if (value > 0 && value <= 30) {
                lbl.setIcon(min);
            } else if (value > 30 && value < 80) {
                lbl.setIcon(med);
            } else {
                lbl.setIcon(max);
            }
        });

        lbl = new JLabel(mute, JLabel.CENTER);

        createLayout(slider, lbl);

        setTitle("JSlider");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void loadImages() {

        mute = new ImageIcon("src/resources/mute.png");
        min = new ImageIcon("src/resources/min.png");
        med = new ImageIcon("src/resources/med.png");
        max = new ImageIcon("src/resources/max.png");
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new SliderEx2();
            ex.setVisible(true);
        });
    }
}

在代码示例中,我们显示了JSliderJLabel组件。 通过拖动滑块,我们可以更改标签组件上的图标。

slider = new JSlider(0, 150, 0);

这是一个JSlider构造器。 参数为最小值,最大值和当前值。

private void loadImages() {

    mute = new ImageIcon("src/resources/mute.png");
    min = new ImageIcon("src/resources/min.png");
    med = new ImageIcon("src/resources/med.png");
    max = new ImageIcon("src/resources/max.png");
}

loadImages()方法中,我们从磁盘加载图像文件。

slider.addChangeListener((ChangeEvent event) -> {
...
});

我们将ChangeListener添加到滑块。 在监听器内部,我们确定当前的滑块值并相应地更新标签。

JSlider as a volume control

图:JSlider作为音量控件

JComboBox

JComboBox是一个组合了按钮或可编辑字段和下拉列表的组件。 用户可以从下拉列表中选择一个值,该列表应用户的要求出现。 如果使组合框可编辑,则组合框将包含一个可编辑字段,用户可以在其中输入值。

ComboBoxEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import static javax.swing.GroupLayout.Alignment.BASELINE;

public class ComboBoxEx extends JFrame
        implements ItemListener {

    private JLabel display;
    private JComboBox<String> box;
    private String[] distros;

    public ComboBoxEx() {

        initUI();
    }

    private void initUI() {

        distros = new String[]{"Ubuntu", "Redhat", "Arch",
                "Debian", "Mint"};

        box = new JComboBox<>(distros);
        box.addItemListener(this);

        display = new JLabel("Ubuntu");

        createLayout(box, display);

        setTitle("JComboBox");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    @Override
    public void itemStateChanged(ItemEvent e) {

        if (e.getStateChange() == ItemEvent.SELECTED) {
            display.setText(e.getItem().toString());
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ComboBoxEx();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们有一个组合框和一个标签。 组合框包含一个字符串列表,这些字符串表示 Linux 发行版的名称。 从组合框中选择的项目显示在标签中。 组合框使用其ItemListener来检测更改。

distros = new String[] {"Ubuntu", "Redhat", "Arch", 
    "Debian", "Mint"};

JComboBox将保存这些字符串值。

display = new JLabel("Ubuntu");

显示区域是简单的JLabel。 它显示该项目最初显示在组合框中。

box = new JComboBox<>(distros);
box.addItemListener(this);

JComboBox的构造器采用 Linux 发行版的字符串数组。 我们将监听器插入创建的对象。

gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
        .addComponent(arg[0])
        .addComponent(arg[1])
);

在垂直方向上,两个组件将与其文本的基线对齐。

@Override
public void itemStateChanged(ItemEvent e) {

    if (e.getStateChange() == ItemEvent.SELECTED) {
        display.setText(e.getItem().toString());
    }
}    

当用户选择或取消选择一个项目时,将调用itemStateChanged()。 我们检查ItemEvent.SELECTED状态,并将组合框的所选项目设置为标签。

JComboBox

图:JComboBox

JProgressBar

进度条是我们处理冗长的任务时使用的组件。 它具有动画效果,以便用户知道我们的任务正在进行中。 JProgressBar组件提供水平或垂直进度条。 初始和最小值为 0,最大值为 100。

ProgressBarEx.java

package com.zetcode;

import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.Timer;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import static javax.swing.GroupLayout.Alignment.CENTER;

public class ProgressBarEx extends JFrame {

    private Timer timer;
    private JProgressBar progBar;
    private JButton startBtn;
    private final int MAX_VAL = 100;

    public ProgressBarEx() {

        initUI();
    }

    private void initUI() {

        progBar = new JProgressBar();
        progBar.setStringPainted(true);

        startBtn = new JButton("Start");
        startBtn.addActionListener(new ClickAction());

        timer = new Timer(50, new UpdateBarListener());

        createLayout(progBar, startBtn);

        setTitle("JProgressBar");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    private class UpdateBarListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            int val = progBar.getValue();

            if (val >= MAX_VAL) {

                timer.stop();
                startBtn.setText("End");
                return;
            }

            progBar.setValue(++val);
        }
    }

    private class ClickAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {

            if (timer.isRunning()) {

                timer.stop();
                startBtn.setText("Start");

            } else if (!"End".equals(startBtn.getText())) {

                timer.start();
                startBtn.setText("Stop");
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ProgressBarEx();
            ex.setVisible(true);
        });
    }
}

该示例显示一个进度条和一个按钮。 该按钮将启动和停止进度。

progBar = new JProgressBar();
progBar.setStringPainted(true);

在这里,我们创建JProgressBar组件。 最小值为 0,最大值为 100,初始值为 0。这是默认值。 setStringPainted()方法确定进度条是否显示已完成任务的百分比。

timer = new Timer(50, new UpdateBarListener());

计时器对象每 50 毫秒启动一次UpdateBarListener。 在监听器内部,我们检查进度条是否达到最大值。

private class UpdateBarListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {

        int val = progBar.getValue();

        if (val >= MAX_VAL) {

            timer.stop();
            startBtn.setText("End");
            return;
        }

        progBar.setValue(++val);
    }
}

监听器的actionPerformed()方法增加进度条的当前值。 如果达到最大值,则计时器停止计时,并且按钮的标签设置为"End"

private class ClickAction extends AbstractAction {

    @Override
    public void actionPerformed(ActionEvent e) {

        if (timer.isRunning()) {

            timer.stop();
            startBtn.setText("Start");

        } else if (!"End".equals(startBtn.getText())) {

            timer.start();
            startBtn.setText("Stop");
        }
    }
}

该按钮启动或停止计时器。 该按钮的文本会动态更新。 它可以具有"Start"Stop"End"字符串值。

JProgressBar

图:JProgressBar

JToggleButton

JToggleButton是具有两种状态的按钮:已按下和未按下。 我们通过单击在这两种状态之间切换。 在某些情况下此功能非常合适。

ToggleButtonEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.border.LineBorder;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import static javax.swing.GroupLayout.Alignment.CENTER;
import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;

public class ToggleButtonEx extends JFrame
        implements ActionListener {

    private JToggleButton redBtn;
    private JToggleButton greenBtn;
    private JToggleButton blueBtn;
    private JPanel display;

    public ToggleButtonEx() {

        initUI();
    }

    private void initUI() {

        redBtn = new JToggleButton("red");
        redBtn.addActionListener(this);

        greenBtn = new JToggleButton("green");
        greenBtn.addActionListener(this);

        blueBtn = new JToggleButton("blue");
        blueBtn.addActionListener(this);

        display = new JPanel();
        display.setPreferredSize(new Dimension(120, 120));
        display.setBorder(LineBorder.createGrayLineBorder());
        display.setBackground(Color.black);

        createLayout(redBtn, greenBtn, blueBtn, display);

        setTitle("JToggleButton");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1])
                        .addComponent(arg[2]))
                .addPreferredGap(UNRELATED)
                .addComponent(arg[3])
        );

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1])
                        .addComponent(arg[2]))
                .addComponent(arg[3])
        );

        gl.linkSize(redBtn, greenBtn, blueBtn);

        pack();
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        var color = display.getBackground();

        int red = color.getRed();
        int green = color.getGreen();
        int blue = color.getBlue();

        if (e.getActionCommand().equals("red")) {
            if (red == 0) {
                red = 255;
            } else {
                red = 0;
            }
        }

        if (e.getActionCommand().equals("green")) {
            if (green == 0) {
                green = 255;
            } else {
                green = 0;
            }
        }

        if (e.getActionCommand().equals("blue")) {
            if (blue == 0) {
                blue = 255;
            } else {
                blue = 0;
            }
        }

        var setCol = new Color(red, green, blue);
        display.setBackground(setCol);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ToggleButtonEx();
            ex.setVisible(true);
        });
    }
}

该示例具有三个切换按钮和一个面板。 我们将显示面板的背景色设置为黑色。 切换按钮将切换颜色值的红色,绿色和蓝色部分。 背景颜色取决于我们按下的切换按钮。

redBtn = new JToggleButton("red");
redBtn.addActionListener(this);

在这里,我们创建一个切换按钮并为其设置一个动作监听器。

display = new JPanel();
display.setPreferredSize(new Dimension(120, 120));
display.setBorder(LineBorder.createGrayLineBorder());
display.setBackground(Color.black);

这是显示通过切换按钮混合的颜色值的面板。 我们设置其首选大小(默认值非常小),将边界线更改为灰色,并设置初始背景色。

var color = display.getBackground();

int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();

actionPerformed()方法中,我们确定显示背景色的当前红色,绿色和蓝色部分。

if (e.getActionCommand().equals("red")) {
    if (red == 0) {
        red = 255;
    } else {
        red = 0;
    }
}

我们确定切换了哪个按钮,并相应地更新 RGB 值的颜色部分。

var setCol = new Color(red, green, blue);
display.setBackground(setCol);

创建新的颜色,并将显示面板更新为新的颜色。

JToggleButton

图:JToggleButton

JList组件

JList是显示对象列表的组件。 它允许用户选择一项或多项。

ListEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GraphicsEnvironment;

public class ListEx extends JFrame {

    private JLabel label;
    private JScrollPane spane;

    public ListEx() {

        initUI();
    }

    private void initUI() {

        var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        var fonts = ge.getAvailableFontFamilyNames();
        var list = new JList(fonts);

        list.addListSelectionListener(e -> {

            if (!e.getValueIsAdjusting()) {

                var name = (String) list.getSelectedValue();
                var font = new Font(name, Font.PLAIN, 12);

                label.setFont(font);
            }
        });

        spane = new JScrollPane();
        spane.getViewport().add(list);

        label = new JLabel("Aguirre, der Zorn Gottes");
        label.setFont(new Font("Serif", Font.PLAIN, 12));

        createLayout(spane, label);

        setTitle("JList");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])

        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ListEx();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们将显示JListJLabel组件。 列表组件包含我们系统上所有可用字体系列名称的列表。 如果我们从列表中选择一项,则标签将以所选字体显示。

var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
var fonts = ge.getAvailableFontFamilyNames();

在这里,我们获得系统上所有可能的字体系列名称。

var list = new JList(fonts);

我们创建一个JList组件。

list.addListSelectionListener(e -> {
    if (!e.getValueIsAdjusting()) {
        ...
    }
});

列表选择中的事件已分组。 我们收到选择和取消选择项目的事件。 为了仅过滤选择事件,我们使用getValueIsAdjusting()方法。

var name = (String) list.getSelectedValue();
var font = new Font(name, Font.PLAIN, 12);
label.setFont(font);

我们得到所选项目并为标签设置新字体。

spane = new JScrollPane();
spane.getViewport().add(list);

JList可以包含的项目比实际显示在窗口上的项目多。 默认情况下它不可滚动。 我们将列表放入JScrollPane以使其可滚动。

JList component

图:JList

JTabbedPane组件

JTabbedPane是允许用户通过单击选项卡在一组组件之间切换的组件。

TabbedPaneEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import java.awt.EventQueue;

public class TabbedPaneEx extends JFrame {

    public TabbedPaneEx() {

        initUI();
    }

    private void initUI() {

        var tabbedPane = new JTabbedPane();

        tabbedPane.addTab("First", createPanel("First panel"));
        tabbedPane.addTab("Second", createPanel("Second panel"));
        tabbedPane.addTab("Third", createPanel("Third panel"));

        createLayout(tabbedPane);

        setTitle("JTabbedPane");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private JPanel createPanel(String text) {

        var panel = new JPanel();
        var lbl = new JLabel(text);
        panel.add(lbl);

        return panel;
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new TabbedPaneEx();
            ex.setVisible(true);
        });
    }
}

在示例中,我们有一个带有三个选项卡的选项卡式窗格。 每个选项卡都显示一个带有标签的面板。

var tabbedPane = new JTabbedPane();

创建一个新的JTabbedPane

tabbedPane.addTab("First", createPanel("First panel"));

使用addTab()方法,我们创建了一个新标签。 第一个参数是选项卡显示的标题。 第二个参数是单击选项卡时要显示的组件。

JTabbedPane

图:JTabbedPane

JTextArea组件

JTextArea是显示纯文本的多行文本区域。 它是用于处理文本的轻量级组件。 该组件不处理滚动。 对于此任务,我们使用JScrollPane组件。

TextAreaEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.Dimension;
import java.awt.EventQueue;

public class TextAreaEx extends JFrame {

    public TextAreaEx() {

        initUI();
    }

    private void initUI() {

        var area = new JTextArea();
        var spane = new JScrollPane(area);

        area.setLineWrap(true);
        area.setWrapStyleWord(true);

        createLayout(spane);

        setTitle("JTextArea");
        setSize(new Dimension(350, 300));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0])

        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new TextAreaEx();
            ex.setVisible(true);
        });
    }
}

该示例显示了一个简单的JTextArea组件。

var area = new JTextArea(); 

这是JTextArea组件的构造器。

var spane = new JScrollPane(area);

为了使文本可滚动,我们将JTextArea组件放入JScrollPane组件。

area.setLineWrap(true);

如果线条太长而无法容纳文本区域的宽度,则setLineWrap()会对其进行换行。

area.setWrapStyleWord(true);

在这里,我们指定线将如何包装。 在我们的例子中,行将被包裹在单词边界(空白)处。

JTextArea

图:JTextArea

JTextPane组件

JTextPane组件是用于处理文本的更高级的组件。 该组件可以对文本执行一些复杂的格式化操作。 它也可以显示 HTML 文档。

test.html

<!DOCTYPE html>
<html>
<head>
    <title>Simple HTML document</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <h2>A simple HTML document</h2>

    <p>
        <em>JTextPane</em> can display HTML documents.
    </p>

    <br>

    <pre>
    JScrollPane pane = new JScrollPane();
    JTextPane textpane = new JTextPane();

    textpane.setContentType("text/html");
    textpane.setEditable(false);
    </pre>

    <br>
    <p>The Java Swing tutorial, 2018</p>

</body>
</html>

这是我们正在加载到JTextPane组件中的 HTML 代码。 该组件不处理滚动。

TextPaneEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import java.awt.EventQueue;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TextPaneEx extends JFrame {

    private JTextPane textPane;

    public TextPaneEx() {

        initUI();
    }

    private void initUI() {

        textPane = new JTextPane();
        var spane = new JScrollPane(textPane);

        textPane.setContentType("text/html");
        textPane.setEditable(false);

        loadFile();

        createLayout(spane);

        setTitle("JTextPane");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
        );

        pack();
    }

    private void loadFile() {

        try {
            var curDir = System.getProperty("user.dir") + "/";
            textPane.setPage("File:///" + curDir + "test.html");
        } catch (IOException ex) {
            Logger.getLogger(this.getName()).log(Level.SEVERE,
                    "Failed to load file", ex);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new TextPaneEx();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们显示JTextPane组件并加载 HTML 文档。 HTML 文档是从当前工作目录加载的。 当我们使用 IDE 时,它是一个项目目录。 该示例显示了组件的格式化函数。

var textpane = new JTextPane(); 

textpane.setContentType("text/html");
textpane.setEditable(false);

我们创建一个JTextPane组件,将该组件的内容设置为 HTML 文档并禁用编辑。

private void loadFile() {

    try {
        var curDir = System.getProperty("user.dir") + "/";
        textPane.setPage("File:///" + curDir + "test.html");
    } catch (IOException ex) {
        Logger.getLogger(this.getName()).log(Level.SEVERE,
                "Failed to load file", ex);
    }
}

在这里,我们确定用户的当前工作目录。 我们将 HTML 文档加载到窗格中。

JTextPane component

图:JTextPane

在本章中,我们继续介绍基本的 Swing 组件,包括JCheckBoxJRadioButtonJSliderJComboBoxJProgressBarJToggleButtonJListJTabbedPaneJTextAreaJTextPane

Java Swing 对话框

http://zetcode.com/tutorials/javaswingtutorial/swingdialogs/

对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。

在 Java Swing 中,我们可以创建两种对话框:标准对话框和自定义对话框。自定义对话框由程序员创建。 它们基于JDialog类。标准对话框是 Swing 工具箱中可用的预定义对话框,例如JColorChooserJFileChooser。 这些是用于常见编程任务的对话框,例如显示文本,接收输入,加载和保存文件。 它们节省了程序员的时间,并增强了一些标准行为。

对话框有两种基本类型:模态对话框和非模态对话框。模态对话框阻止输入到其他顶级窗口。非模态对话框允许输入其他窗口。 打开文件对话框是模态对话框的一个很好的例子。 选择要打开的文件时,不应进行其他操作。 典型的非模态对话框是查找文本对话框。 能够在文本控件中移动光标并定义从何处开始查找特定文本很方便。

MessageDialog

消息对话框是向用户提供信息的简单对话框。 消息对话框是使用JOptionPane.showMessageDialog()方法创建的。

MessageDialogsEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.DEFAULT_SIZE;

public class MessageDialogsEx extends JFrame {

    private JPanel pnl;

    public MessageDialogsEx() {

        initUI();
    }

    private void initUI() {

        pnl = (JPanel) getContentPane();

        var warBtn = new JButton("Warning");
        var errBtn = new JButton("Error");
        var queBtn = new JButton("Question");
        var infBtn = new JButton("Information");

        warBtn.addActionListener(event -> JOptionPane.showMessageDialog(pnl,
                "A deprecated call!", "Warning", JOptionPane.WARNING_MESSAGE));

        errBtn.addActionListener(event -> JOptionPane.showMessageDialog(pnl,
                "Could not open file!", "Error", JOptionPane.ERROR_MESSAGE));

        queBtn.addActionListener(event -> JOptionPane.showMessageDialog(pnl,
                "Are you sure to quit?", "Question", JOptionPane.QUESTION_MESSAGE));

        infBtn.addActionListener(event -> JOptionPane.showMessageDialog(pnl,
                "Download completed.", "Information", 
                JOptionPane.INFORMATION_MESSAGE));

        createLayout(warBtn, errBtn, queBtn, infBtn);

        setTitle("Message dialogs");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[2]))
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[1])
                        .addComponent(arg[3]))
                .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1]))
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[2])
                        .addComponent(arg[3]))
                .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE)
        );

        gl.linkSize(arg[0], arg[1], arg[2], arg[3]);

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var md = new MessageDialogsEx();
            md.setVisible(true);
        });
    }
}

该示例显示了错误,警告,问题和信息消息对话框。

var warBtn = new JButton("Warning");
var errBtn = new JButton("Error");
var queBtn = new JButton("Question");
var infBtn = new JButton("Information");

这四个按钮显示四个不同的消息对话框。

errBtn.addActionListener(event -> JOptionPane.showMessageDialog(pnl,
        "Could not open file!", "Error", JOptionPane.ERROR_MESSAGE));

要创建消息对话框,我们调用JOptionPane类的静态showMessageDialog()方法。 我们提供对话框的父项,消息文本,标题和消息类型。 消息类型是以下常量之一:

  • ERROR_MESSAGE
  • WARNING_MESSAGE
  • QUESTION_MESSAGE
  • INFORMATION_MESSAGE

显示的图标取决于此常数。

Question message dialog

图:问题消息对话框

自定义对话框

在下面的示例中,我们创建一个简单的自定义对话框。 它是有关在许多 GUI 应用中找到的对话框的示例,通常位于“帮助”菜单中。

CustomDialogEx.java

package com.zetcode;

import javax.swing.Box;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import static javax.swing.GroupLayout.Alignment.CENTER;

class AboutDialog extends JDialog {

    public AboutDialog(Frame parent) {
        super(parent);

        initUI();
    }

    private void initUI() {

        var icon = new ImageIcon("src/resources/notes.png");
        var imgLabel = new JLabel(icon);

        var textLabel = new JLabel("Notes, 1.23");
        textLabel.setFont(new Font("Serif", Font.BOLD, 13));

        var okBtn = new JButton("OK");
        okBtn.addActionListener(event -> dispose());

        createLayout(textLabel, imgLabel, okBtn);

        setModalityType(ModalityType.APPLICATION_MODAL);

        setTitle("About Notes");
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setLocationRelativeTo(getParent());
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup(CENTER)
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
                .addGap(200)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGap(30)
                .addComponent(arg[0])
                .addGap(20)
                .addComponent(arg[1])
                .addGap(20)
                .addComponent(arg[2])
                .addGap(30)
        );

        pack();
    }
}

public class CustomDialogEx extends JFrame
        implements ActionListener {

    public CustomDialogEx() {

        initUI();
    }

    private void initUI() {

        createMenuBar();

        setTitle("Simple Dialog");
        setSize(350, 250);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createMenuBar() {

        var menubar = new JMenuBar();

        var fileMenu = new JMenu("File");
        fileMenu.setMnemonic(KeyEvent.VK_F);

        var helpMenu = new JMenu("Help");
        helpMenu.setMnemonic(KeyEvent.VK_H);

        var aboutMemuItem = new JMenuItem("About");
        aboutMemuItem.setMnemonic(KeyEvent.VK_A);
        helpMenu.add(aboutMemuItem);

        aboutMemuItem.addActionListener(this);

        menubar.add(fileMenu);
        menubar.add(Box.createGlue());
        menubar.add(helpMenu);
        setJMenuBar(menubar);
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        showAboutDialog();
    }

    private void showAboutDialog() {

        var aboutDialog = new AboutDialog(this);
        aboutDialog.setVisible(true);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new CustomDialogEx();
            ex.setVisible(true);
        });
    }
}

从“帮助”菜单中,我们可以弹出一个小对话框。 该对话框显示文本,图标和按钮。

class AboutDialog extends JDialog {

自定义对话框基于JDialog类。

setModalityType(ModalityType.APPLICATION_MODAL);

setModalityType()方法设置对话框的模态类型。 ModalityType.APPLICATION_MODAL阻止来自同一应用的所有顶级窗口的输入。 在我们的例子中,在对话框的生存期内,应用框架的输入被阻止。

setLocationRelativeTo(getParent());

setLocationRelativeTo()方法将对话框窗口居中在框架窗口的区域上方。

setDefaultCloseOperation(DISPOSE_ON_CLOSE);

setDefaultCloseOperation()设置用户单击窗口的“关闭”按钮时发生的情况。 该对话框将被隐藏和处置。

private void showAboutDialog() {

    var aboutDialog = new AboutDialog(this);
    aboutDialog.setVisible(true);
}

对话框窗口使用setVisible()方法显示在屏幕上。

Custom dialog

图:自定义对话框

JFileChooser

JFileChooser是用于从文件系统中选择文件的标准对话框。

FileChooserEx.java

package com.zetcode;

import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import static javax.swing.GroupLayout.DEFAULT_SIZE;

public class FileChooserEx extends JFrame {

    private JPanel panel;
    private JTextArea area;

    public FileChooserEx() {

        initUI();
    }

    private void initUI() {

        panel = (JPanel) getContentPane();
        area = new JTextArea();

        var spane = new JScrollPane();
        spane.getViewport().add(area);

        var toolbar = createToolBar();

        createLayout(toolbar, spane);

        setTitle("JFileChooser");
        setSize(400, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private JToolBar createToolBar() {

        var openIcon = new ImageIcon("src/resources/document-open.png");

        var toolbar = new JToolBar();
        var openBtn = new JButton(openIcon);

        openBtn.addActionListener(new OpenFileAction());

        toolbar.add(openBtn);

        return toolbar;
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0], DEFAULT_SIZE, DEFAULT_SIZE,
                        Short.MAX_VALUE)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[1]))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(4)
                .addComponent(arg[1])
        );

        pack();
    }

    public String readFile(File file) {

        String content = "";

        try {
            content = new String(Files.readAllBytes(Paths.get(
                    file.getAbsolutePath())));
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(this,
                    "Could not read file", "Error", JOptionPane.ERROR_MESSAGE);
        }

        return content;
    }

    private class OpenFileAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {

            var fileChooser = new JFileChooser();
            var filter = new FileNameExtensionFilter("Java files", "java");
            fileChooser.addChoosableFileFilter(filter);

            int ret = fileChooser.showDialog(panel, "Open file");

            if (ret == JFileChooser.APPROVE_OPTION) {

                var file = fileChooser.getSelectedFile();
                var text = readFile(file);

                area.setText(text);
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new FileChooserEx();
            ex.setVisible(true);
        });
    }
}

该代码示例将演示如何使用JFileChooser将文件内容加载到文本区域组件中。

var fileChooser = new JFileChooser();

这是文件选择器对话框的构造器。

var filter = new FileNameExtensionFilter("Java files", "java");
fileChooser.addChoosableFileFilter(filter);

在这里,我们定义文件过滤器。 在本例中,我们将具有扩展名为.java的 Java 文件。 我们还有默认的“所有文件”选项。

int ret = fileChooser.showDialog(panel, "Open file");

showDialog()方法在屏幕上显示对话框。 单击“是”或“确定”按钮时,将返回JFileChooser.APPROVE_OPTION

if (ret == JFileChooser.APPROVE_OPTION) {

    var file = fileChooser.getSelectedFile();
    var text = readFile(file);

    area.setText(text);
}

在这里,我们获得所选文件的名称。 我们读取文件的内容并将文本设置到文本区域中。

JFileChooser dialog

图:JFileChooser对话框

JColorChooser

JColorChooser是用于选择颜色的标准对话框。

ColorChooserEx.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import java.awt.Color;
import java.awt.EventQueue;

import static javax.swing.GroupLayout.DEFAULT_SIZE;

public class ColorChooserEx extends JFrame {

    private JPanel colourPanel;

    public ColorChooserEx() {

        initUI();
    }

    private void initUI() {

        colourPanel = new JPanel();
        colourPanel.setBackground(Color.WHITE);

        var toolbar = createToolBar();

        createLayout(toolbar, colourPanel);

        setTitle("JColorChooser");
        setSize(400, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private JToolBar createToolBar() {

        var openIcon = new ImageIcon("src/resources/colourdlg.png");

        var toolbar = new JToolBar();
        var openBtn = new JButton(openIcon);

        openBtn.addActionListener(e -> {

            var color = JColorChooser.showDialog(colourPanel,
                    "Choose colour", Color.white);
            colourPanel.setBackground(color);
        });

        toolbar.add(openBtn);

        return toolbar;
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(arg[0], DEFAULT_SIZE, DEFAULT_SIZE,
                        Short.MAX_VALUE)
                .addGroup(gl.createSequentialGroup()
                        .addGap(30)
                        .addComponent(arg[1])
                        .addGap(30))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(30)
                .addComponent(arg[1])
                .addGap(30)
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ColorChooserEx();
            ex.setVisible(true);
        });
    }
}

在示例中,我们有一个白色面板。 我们将通过从JColorChooser中选择一种颜色来更改面板的背景颜色。

var color = JColorChooser.showDialog(colourPanel,
        "Choose colour", Color.white);
colourPanel.setBackground(color);

此代码显示颜色选择器对话框。 showDialog()方法返回所选的颜色值。 我们将colourPanel's背景更改为新选择的颜色。

在 Java Swing 教程的这一部分中,我们介绍了对话框。

Java Swing 模型架构

http://zetcode.com/tutorials/javaswingtutorial/swingmodels/

Swing 工程师创建了 Swing 工具箱,实现了修改后的 MVC 设计模式。 这样可以有效地处理数据,并在运行时使用可插入的外观。

传统的 MVC 模式将应用分为三个部分:模型,视图和控制器。 该模型表示应用中的数据。 视图是数据的视觉表示。 最后,控制器处理并响应事件(通常是用户操作),并可以调用模型上的更改。 这个想法是通过引入一个中间组件:控制器,将数据访问和业务逻辑与数据表示和用户交互分开。

Swing 工具箱使用修改后的 MVC 设计模式。 对于视图和控制器,它只有一个 UI 对象。 有时将这种修改后的 MVC 称为可分离模型架构。

在 Swing 工具箱中,每个组件都有其模型,甚至包括按钮之类的基本组件。 Swing 工具箱中有两种模型:

  • 状态模型
  • 数据模型

状态模型处理组件的状态。 例如,模型会跟踪组件是处于选中状态还是处于按下状态。 数据模型处理它们使用的数据。 列表组件保留其显示的项目列表。

对于 Swing 开发者来说,这意味着他们通常需要获取模型实例才能操纵组件中的数据。 但是也有例外。 为了方便起见,有一些方法可以返回数据,而无需程序员访问模型。

public int getValue() { 
    return getModel().getValue(); 
}

一个示例是JSlider组件的getValue()方法。 开发者无需直接使用模型。 而是在后台进行对模型的访问。 在如此简单的情况下直接使用模型将是一个过大的杀伤力。 因此,Swing 提供了一些便捷的方法,如上一个。

要查询模型的状态,我们有两种通知:

  • 轻量级通知
  • 状态通知

轻量级通知使用ChangeListener类。 对于来自组件的所有通知,我们只有一个事件(ChangeEvent)。对于更复杂的组件,将使用状态通知。对于此类通知,我们具有不同类型的事件。例如,JList组件具有ListDataEventListSelectionEvent

如果我们不为组件设置模型,则会创建一个默认模型。 例如,按钮组件具有DefaultButtonModel模型。

public JButton(String text, Icon icon) {
  // Create the model
  setModel(new DefaultButtonModel());

  // initialize
  init(text, icon);
}

查看JButton.java源文件,我们发现默认模型是在构建组件时创建的。

按钮模型

该模型用于各种按钮,例如按钮,复选框,单选框和菜单项。 以下示例说明了JButton的模型。 因为没有数据可以与按钮关联,所以我们只能管理按钮的状态。

ButtonModelEx.java

package com.zetcode;

import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;

public class ButtonModelEx extends JFrame {

    private JButton okBtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JLabel armedLbl;
    private JCheckBox checkBox;

    public ButtonModelEx() {

        initUI();
    }

    private void initUI() {

        okBtn = new JButton("OK");
        okBtn.addChangeListener(new DisabledChangeListener());
        checkBox = new JCheckBox();
        checkBox.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");
        armedLbl = new JLabel("Armed: false");

        createLayout(okBtn, checkBox, enabledLbl, pressedLbl, armedLbl);

        setTitle("ButtonModel");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[0])
                        .addGap(80)
                        .addComponent(arg[1]))
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1]))
                .addGap(40)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
        );

        pack();
    }

    private class DisabledChangeListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {

            var model = (DefaultButtonModel) okBtn.getModel();

            if (model.isEnabled()) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            if (model.isArmed()) {
                armedLbl.setText("Armed: true");
            } else {
                armedLbl.setText("Armed: false");
            }

            if (model.isPressed()) {
                pressedLbl.setText("Pressed: true");
            } else {
                pressedLbl.setText("Pressed: false");
            }
        }
    }

    private class CheckBoxAction extends AbstractAction {

        public CheckBoxAction() {
            super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            if (okBtn.isEnabled()) {
                okBtn.setEnabled(false);
            } else {
                okBtn.setEnabled(true);
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ButtonModelEx();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们有一个按钮,一个复选框和三个标签。 标签代表按钮的三个属性:按下,禁用或布防状态。

okbtn.addChangeListener(new DisabledChangeListener());

我们使用ChangeListener来监听按钮状态的变化。

var model = (DefaultButtonModel) okBtn.getModel();

在这里,我们获得默认的按钮模型。

if (model.isEnabled()) {
    enabledLbl.setText("Enabled: true");
} else {
    enabledLbl.setText("Enabled: false");
}

我们查询模型是否启用了按钮。 标签会相应更新。

if (okBtn.isEnabled()) {
    okBtn.setEnabled(false);
} else {
    okBtn.setEnabled(true);
}

该复选框启用或禁用该按钮。 要启用“确定”按钮,我们调用setEnabled()方法。 因此,我们更改了按钮的状态。 模型在哪里? 答案就在AbstractButton.java文件中。

public void setEnabled(boolean b) {
    if (!b && model.isRollover()) {
        model.setRollover(false);
    } 
    super.setEnabled(b);
    model.setEnabled(b);
}

答案是,Swing 工具箱在内部与模型一起使用。 setEnabled()是程序员的另一种便捷方法。

ButtonModel

图:ButtonModel

自定义ButtonModel

在前面的示例中,我们使用了默认按钮模型。 在下面的代码示例中,我们将使用我们自己的按钮模型。

CustomButtonModelEx.java

package com.zetcode;

import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;

public class CustomButtonModelEx extends JFrame {

    private JButton okBtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JLabel armedLbl;
    private JCheckBox checkBox;

    public CustomButtonModelEx() {

        initUI();
    }

    private void initUI() {

        okBtn = new JButton("OK");
        checkBox = new JCheckBox();
        checkBox.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");
        armedLbl  = new JLabel("Armed: false");

        var model = new OkButtonModel();
        okBtn.setModel(model);

        createLayout(okBtn, checkBox, enabledLbl, pressedLbl, armedLbl);

        setTitle("Custom button model");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[0])
                        .addGap(80)
                        .addComponent(arg[1]))
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1]))
                .addGap(40)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
        );

        pack();
    }

    private class OkButtonModel extends DefaultButtonModel {

        @Override
        public void setEnabled(boolean b) {
            if (b) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            super.setEnabled(b);
        }

        @Override
        public void setArmed(boolean b) {
            if (b) {
                armedLbl.setText("Armed: true");
            } else {
                armedLbl.setText("Armed: false");
            }

            super.setArmed(b);
        }

        @Override
        public void setPressed(boolean b) {
            if (b) {
                pressedLbl.setText("Pressed: true");
            } else {
                pressedLbl.setText("Pressed: false");
            }

            super.setPressed(b);
        }
    }

    private class CheckBoxAction extends AbstractAction {

        public CheckBoxAction() {
            super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okBtn.isEnabled()) {
                okBtn.setEnabled(false);
            } else {
                okBtn.setEnabled(true);
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new CustomButtonModelEx();
            ex.setVisible(true);
        });
    }
}

本示例与上一个示例具有相同的作用。 区别在于我们不使用变更监听器,而使用自定义按钮模型。

var model = new OkButtonModel();
okBtn.setModel(model);

我们为按钮设置自定义模型。

private class OkButtonModel extends DefaultButtonModel {
...
}

我们创建一个自定义按钮模型并覆盖必要的方法。

@Override
public void setEnabled(boolean b) {
    if (b) {
        enabledLbl.setText("Enabled: true");
    } else {
        enabledLbl.setText("Enabled: false");
    }

    super.setEnabled(b);
}

我们重写setEnabled()方法,并在其中添加一些功能。 我们一定不要忘记调用父方法来继续进行处理。

JList模型

几个组件具有两个模型。 JList是其中之一。 它具有以下模型:ListModelListSelectionModelListModel处理数据,ListSelectionModel处理列表的选择状态。 以下示例使用两种模型。

ListModelsEx.java

package com.zetcode;

import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import static javax.swing.GroupLayout.Alignment.CENTER;

public class ListModelsEx extends JFrame {

    private DefaultListModel<String> model;
    private JList<String> myList;
    private JButton remAllBtn;
    private JButton addBtn;
    private JButton renBtn;
    private JButton delBtn;

    public ListModelsEx() {

        initUI();
    }

    private void createList() {

        model = new DefaultListModel<>();
        model.addElement("Amelie");
        model.addElement("Aguirre, der Zorn Gottes");
        model.addElement("Fargo");
        model.addElement("Exorcist");
        model.addElement("Schindler's myList");

        myList = new JList<>(model);
        myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        myList.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {

                if (e.getClickCount() == 2) {

                    int index = myList.locationToIndex(e.getPoint());
                    var item = model.getElementAt(index);
                    var text = JOptionPane.showInputDialog("Rename item", item);

                    String newItem;

                    if (text != null) {
                        newItem = text.trim();
                    } else {
                        return;
                    }

                    if (!newItem.isEmpty()) {

                        model.remove(index);
                        model.add(index, newItem);

                        var selModel = myList.getSelectionModel();
                        selModel.setLeadSelectionIndex(index);
                    }
                }
            }
        });
    }

    private void createButtons() {

        remAllBtn = new JButton("Remove All");
        addBtn = new JButton("Add");
        renBtn = new JButton("Rename");
        delBtn = new JButton("Delete");

        addBtn.addActionListener(e -> {

            var text = JOptionPane.showInputDialog("Add a new item");
            String item;

            if (text != null) {
                item = text.trim();
            } else {
                return;
            }

            if (!item.isEmpty()) {

                model.addElement(item);
            }
        });

        delBtn.addActionListener(event -> {

            var selModel = myList.getSelectionModel();

            int index = selModel.getMinSelectionIndex();

            if (index >= 0) {
                model.remove(index);
            }
        });

        renBtn.addActionListener(e -> {

            var selModel = myList.getSelectionModel();
            int index = selModel.getMinSelectionIndex();

            if (index == -1) {
                return;
            }

            var item = model.getElementAt(index);
            var text = JOptionPane.showInputDialog("Rename item", item);
            String newItem;

            if (text != null) {
                newItem = text.trim();
            } else {
                return;
            }

            if (!newItem.isEmpty()) {

                model.remove(index);
                model.add(index, newItem);
            }
        });

        remAllBtn.addActionListener(e -> model.clear());
    }

    private void initUI() {

        createList();
        createButtons();

        var scrollPane = new JScrollPane(myList);
        createLayout(scrollPane, addBtn, renBtn, delBtn, remAllBtn);

        setTitle("JList models");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[1])
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
        );

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
                .addComponent(arg[0])
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[1])
                        .addComponent(arg[2])
                        .addComponent(arg[3])
                        .addComponent(arg[4]))
        );

        gl.linkSize(addBtn, renBtn, delBtn, remAllBtn);

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ListModelsEx();
            ex.setVisible(true);
        });
    }
}

该示例显示了一个列表组件和四个按钮。 这些按钮控制列表组件中的数据。 该示例更大,因为我们在那里进行了一些其他检查。 例如,我们不允许在列表组件中输入空格。

model = new DefaultListModel<>();
model.addElement("Amelie");
model.addElement("Aguirre, der Zorn Gottes");
model.addElement("Fargo");
...

我们创建一个默认列表模型,并向其中添加元素。

myList = new JList<>(model);
myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

我们创建一个列表组件。 构造器的参数是我们创建的模型。 我们使列表进入单选模式。

if (text != null) {
    item = text.trim();
} else {
    return;
}

if (!item.isEmpty()) {

    model.addElement(item);
}

我们仅添加不等于null且不为空的项目,例如包含至少一个非空格字符的项目。 在列表中添加空格或空值没有意义。

var selModel = myList.getSelectionModel();

int index = selModel.getMinSelectionIndex();

if (index >= 0) {
    model.remove(index);
}

这是我们按下删除按钮时运行的代码。 为了从列表中删除一个项目,必须选择它-我们必须找出当前选择的项目。 为此,我们调用getSelectionModel()方法。 我们使用getMinSelectionIndex()获取选定的索引,并使用remove()方法删除该项目。

在此示例中,我们使用了两种列表模型。 我们调用列表数据模型的add()remove()clear()方法来处理我们的数据。 并且我们使用了一个列表选择模型,以便找出所选项目。

List models

图:列表模型

文件模型

文档模型是从视觉表示中分离数据的一个很好的例子。 在JTextPane组件中,我们有一个StyledDocument用于设置文本数据的样式。

DocumentModelEx.java

package com.zetcode;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.BorderLayout;
import java.awt.EventQueue;

public class DocumentModelEx extends JFrame {

    private StyledDocument sdoc;
    private JTextPane textPane;

    public DocumentModelEx() {

        initUI();
    }

    private void initUI() {

        createToolbar();

        var panel =  new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        textPane = new JTextPane();
        sdoc = textPane.getStyledDocument();
        initStyles(textPane);

        panel.add(new JScrollPane(textPane));
        add(panel);
        pack();

        setTitle("Document Model");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createToolbar() {

        var toolbar = new JToolBar();

        var bold = new ImageIcon("src/main/resources/bold.png");
        var italic = new ImageIcon("src/main/resources/italic.png");
        var strike = new ImageIcon("src/main/resources/strike.png");
        var underline = new ImageIcon("src/main/resources/underline.png");

        var boldBtn = new JButton(bold);
        var italBtn = new JButton(italic);
        var striBtn = new JButton(strike);
        var undeBtn = new JButton(underline);

        toolbar.add(boldBtn);
        toolbar.add(italBtn);
        toolbar.add(striBtn);
        toolbar.add(undeBtn);

        add(toolbar, BorderLayout.NORTH);

        boldBtn.addActionListener(e -> sdoc.setCharacterAttributes(
                textPane.getSelectionStart(),
                textPane.getSelectionEnd() - textPane.getSelectionStart(),
                textPane.getStyle("Bold"), false));

        italBtn.addActionListener(e -> sdoc.setCharacterAttributes(
                textPane.getSelectionStart(),
                textPane.getSelectionEnd() - textPane.getSelectionStart(),
                textPane.getStyle("Italic"), false));

        striBtn.addActionListener(e -> sdoc.setCharacterAttributes(
                textPane.getSelectionStart(),
                textPane.getSelectionEnd() - textPane.getSelectionStart(),
                textPane.getStyle("Strike"), false));

        undeBtn.addActionListener(e -> sdoc.setCharacterAttributes(
                textPane.getSelectionStart(),
                textPane.getSelectionEnd() - textPane.getSelectionStart(),
                textPane.getStyle("Underline"), false));
    }

    private void initStyles(JTextPane textPane) {

        var style = textPane.addStyle("Bold", null);
        StyleConstants.setBold(style, true);

        style = textPane.addStyle("Italic", null);
        StyleConstants.setItalic(style, true);

        style = textPane.addStyle("Underline", null);
        StyleConstants.setUnderline(style, true);

        style = textPane.addStyle("Strike", null);
        StyleConstants.setStrikeThrough(style, true);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new DocumentModelEx();
            ex.setVisible(true);
        });
    }
}

该示例具有一个文本窗格和一个工具栏。 在工具栏中,我们有四个按钮可以更改文本的属性。

sdoc = textpane.getStyledDocument();

在这里,我们获得样式化的文档,该文档是文本窗格组件的模型。

var style = textpane.addStyle("Bold", null);
StyleConstants.setBold(style, true);

样式是一组文本属性,例如颜色和大小。 在这里,我们为文本窗格组件注册了一个粗体样式。 可以随时检索已注册的样式。

doc.setCharacterAttributes(textpane.getSelectionStart(), 
    textpane.getSelectionEnd() - textpane.getSelectionStart(),
    textpane.getStyle("Bold"), false);

在这里,我们更改文本的属性。 参数是选择的偏移量和长度,样式和布尔值替换。 偏移量是我们应用粗体文本的开头。 我们通过减去选择结束值和选择开始值来获得长度值。 布尔值false表示我们不会用新样式替换旧样式,而是将它们合并。 这意味着如果文本带有下划线,并且我们将其设为粗体,则结果为带下划线的粗体文本。

Document model

图:文档模型

在本章中,我们提到了 Swing 模型。

Swing 中的拖放

原文: http://zetcode.com/tutorials/javaswingtutorial/draganddrop/

在计算机图形用户界面中,拖放是单击虚拟对象并将其拖动到其他位置或另一个虚拟对象上的动作(或支持以下动作)。 通常,它可用于调用多种动作,或在两个抽象对象之间创建各种类型的关联。

Tweet

拖放

拖放操作使用户可以直观地完成复杂的事情。

通常,我们可以拖放两件事:数据或某些图形对象。 如果将图像从一个应用拖到另一个应用,则会拖放二进制数据。 如果我们在 Firefox 中拖动选项卡并将其移动到另一个位置,则将拖放图形组件。

Drag and drop

图:Swing 中的拖放

开始拖动操作的组件必须注册一个DragSource对象。 DropTarget是负责在拖放操作中接受拖放的对象。 Transferable封装正在传输的数据。 传输的数据可以是各种类型。 DataFlavor对象提供有关正在传输的数据的信息。

几个 Swing 组件已经内置了对拖放操作的支持。 在这种情况下,我们使用TransferHandler来管理拖放功能。 在没有内置支持的情况下,我们必须从头开始创建所有内容。

Swing 文本拖放示例

我们将演示一个简单的拖放示例。 我们将使用内置的拖放支持。 我们利用了TransferHandler类。

SimpeDnD.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.TransferHandler;
import java.awt.EventQueue;

public class SimpleDnD extends JFrame {

    private JTextField field;
    private JButton button;

    public SimpleDnD() {

        initUI();
    }

    private void initUI() {

        setTitle("Simple Drag & Drop");

        button = new JButton("Button");
        field = new JTextField(15);

        field.setDragEnabled(true);
        button.setTransferHandler(new TransferHandler("text"));

        createLayout(field, button);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createParallelGroup(GroupLayout.Alignment.BASELINE)
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new SimpleDnD();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们有一个文本字段和一个按钮。 我们可以将文本从字段中拖放到按钮上。

field.setDragEnabled(true);

文本字段具有内置的拖动支持。 我们必须启用它。

button.setTransferHandler(new TransferHandler("text"));

TransferHandler是负责在组件之间传输数据的类。 构造器将属性名称作为参数。

Swing 图标拖放

一些 Java Swing 组件没有内置的拖动支持。 JLabel组件就是这样的组件。 我们必须自己编写拖动功能。

以下示例显示了如何拖放图标。 在前面的示例中,我们使用了text属性。 这次我们使用icon属性。

IconDnD.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.TransferHandler;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class IconDnD extends JFrame {

    public IconDnD() {

        initUI();
    }

    private void initUI() {

        var icon1 = new ImageIcon("src/resources/sad.png");
        var icon2 = new ImageIcon("src/resources/plain.png");
        var icon3 = new ImageIcon("src/resources/smile.png");

        var label1 = new JLabel(icon1, JLabel.CENTER);
        var label2 = new JLabel(icon2, JLabel.CENTER);
        var label3 = new JLabel(icon3, JLabel.CENTER);

        var listener = new DragMouseAdapter();
        label1.addMouseListener(listener);
        label2.addMouseListener(listener);
        label3.addMouseListener(listener);

        var button = new JButton(icon2);
        button.setFocusable(false);

        label1.setTransferHandler(new TransferHandler("icon"));
        label2.setTransferHandler(new TransferHandler("icon"));
        label3.setTransferHandler(new TransferHandler("icon"));
        button.setTransferHandler(new TransferHandler("icon"));

        createLayout(label1, label2, label3, button);

        setTitle("Icon Drag & Drop");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private class DragMouseAdapter extends MouseAdapter {

        public void mousePressed(MouseEvent e) {

            var c = (JComponent) e.getSource();
            var handler = c.getTransferHandler();
            handler.exportAsDrag(c, e, TransferHandler.COPY);
        }
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup(GroupLayout.Alignment.CENTER)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(arg[0])
                        .addGap(30)
                        .addComponent(arg[1])
                        .addGap(30)
                        .addComponent(arg[2])
                )
                .addComponent(arg[3], GroupLayout.DEFAULT_SIZE,
                        GroupLayout.DEFAULT_SIZE, Integer.MAX_VALUE)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(arg[0])
                        .addComponent(arg[1])
                        .addComponent(arg[2]))
                .addGap(30)
                .addComponent(arg[3])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new IconDnD();
            ex.setVisible(true);
        });
    }
}

在代码示例中,我们有两个标签和一个按钮。 每个组件都显示一个图标。 这两个标签启用拖动手势,按钮接受放置手势。

label1.setTransferHandler(new TransferHandler("icon"));
label2.setTransferHandler(new TransferHandler("icon"));
label3.setTransferHandler(new TransferHandler("icon"));

默认情况下,标签的拖动支持未启用。 我们为这两个标签注册了一个自定义鼠标适配器。

label1.setTransferHandler(new TransferHandler("icon"));
label2.setTransferHandler(new TransferHandler("icon"));
label3.setTransferHandler(new TransferHandler("icon"));
button.setTransferHandler(new TransferHandler("icon"));

每个组件都有一个用于图标属性的TransferHandler类。 拖动源和拖动目标也需要TransferHandler

var c = (JComponent) e.getSource();
var handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.COPY);

这些代码行启动拖动支持。 我们得到了拖动源。 在我们的例子中,它是一个标签实例。 我们获取其传输处理器对象,并最终通过exportAsDrag()方法调用来启动拖动支持。

Icon drag & drop example

图:图标 drag & drop example

Swing JList放置示例

某些组件没有默认的放置支持。 其中之一是JList。 这是有充分的理由的。 我们不知道是将数据插入一行,还是插入两行或更多行。 因此,我们必须手动实现对列表组件的拖放支持。

下面的示例将逗号或空格分隔的文本插入JList组件的行中。 否则,文本将进入一行。

ListDnD.java

package com.zetcode;

import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;

public class ListDnD extends JFrame {

    private JTextField field;
    private DefaultListModel model;

    public ListDnD() {

        initUI();
    }

    private void initUI() {

        var scrollPane = new JScrollPane();
        scrollPane.setPreferredSize(new Dimension(180, 150));

        model = new DefaultListModel();
        var myList = new JList(model);

        myList.setDropMode(DropMode.INSERT);
        myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        myList.setTransferHandler(new ListHandler());

        field = new JTextField(15);
        field.setDragEnabled(true);

        scrollPane.getViewport().add(myList);

        createLayout(field, scrollPane);

        setTitle("ListDrop");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private class ListHandler extends TransferHandler {

        public boolean canImport(TransferSupport support) {

            if (!support.isDrop()) {
                return false;
            }

            return support.isDataFlavorSupported(DataFlavor.stringFlavor);
        }

        public boolean importData(TransferSupport support) {

            if (!canImport(support)) {
                return false;
            }

            var transferable = support.getTransferable();
            String line;

            try {
                line = (String) transferable.getTransferData(DataFlavor.stringFlavor);
            } catch (Exception e) {
                return false;
            }

            var dl = (JList.DropLocation) support.getDropLocation();
            int index = dl.getIndex();

            String[] data = line.split("[,\\s]");

            for (String item : data) {

                if (!item.isEmpty())
                    model.add(index++, item.trim());
            }

            return true;
        }
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        gl.setVerticalGroup(gl.createParallelGroup(GroupLayout.Alignment.BASELINE)
                .addComponent(arg[0])
                .addComponent(arg[1])
        );

        pack();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {

            var ex = new ListDnD();
            ex.setVisible(true);
        });
    }
}

在示例中,我们有一个文本字段和一个列表组件。 可以将文本字段中的文本拖放到列表中。 如果文本用逗号或空格字符逗号分隔,则单词将分成几行。 如果不是,则将文本插入一行。

myList.setDropMode(DropMode.INSERT);

在这里,我们指定放置模式。 DropMode.INSERT指定我们将要在列表组件中插入新项目。 如果选择DropMode.INSERT,则将新项目拖放到现有项目上。

myList.setTransferHandler(new ListHandler());

我们设置了一个自定义的传输处理器类。

field.setDragEnabled(true);

我们为文本字段组件启用了拖动支持。

public boolean canImport(TransferSupport support) {

    if (!support.isDrop()) {
        return false;
    }

    return support.isDataFlavorSupported(DataFlavor.stringFlavor);
}

此方法测试放置操作的适用性。 我们过滤掉剪贴板粘贴操作,仅允许字符串放置操作。 如果该方法返回 false,则取消放置操作。

public boolean importData(TransferSupport support) {
...
}

importData()方法将数据从剪贴板或拖放操作传输到放置位置。

var transferable = support.getTransferable();

Transferable是数据捆绑在一起的类。

line = (String) transferable.getTransferData(DataFlavor.stringFlavor);

我们检索我们的数据。

var dl = (JList.DropLocation) support.getDropLocation();
int index = dl.getIndex();

我们得到该列表的放置位置。 我们检索将在其中插入数据的索引。

String[] data = line.split("[,\\s]");

for (String item : data) {

    if (!item.isEmpty())
        model.add(index++, item.trim());
}

我们将文本分为几部分,然后将其插入一行或多行中。

JList drop example

图:JList放置示例

前面的示例使用了具有内置拖放支持的组件。 接下来,我们将从头开始创建拖放功能。

Swing 拖动手势

在以下示例中,我们检查了一个简单的拖动手势。 我们处理创建拖动手势所需的几个类。 DragSourceDragGestureEventDragGestureListenerTransferable

DragGesture.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;

public class DragGesture extends JFrame implements
        DragGestureListener, Transferable {

    public DragGesture() {

        initUI();
    }

    private void initUI() {

        var redPanel = new JPanel();
        redPanel.setBackground(Color.red);
        redPanel.setPreferredSize(new Dimension(120, 120));

        var ds = new DragSource();

        ds.createDefaultDragGestureRecognizer(redPanel,
                DnDConstants.ACTION_COPY, this);

        createLayout(redPanel);

        setTitle("Drag Gesture");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public void dragGestureRecognized(DragGestureEvent event) {

        var cursor = Cursor.getDefaultCursor();

        if (event.getDragAction() == DnDConstants.ACTION_COPY) {

            cursor = DragSource.DefaultCopyDrop;
        }

        event.startDrag(cursor, this);
    }

    public Object getTransferData(DataFlavor flavor) {

        return null;
    }

    public DataFlavor[] getTransferDataFlavors() {

        return new DataFlavor[0];
    }

    public boolean isDataFlavorSupported(DataFlavor flavor) {

        return false;
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGap(50)
                .addComponent(arg[0])
                .addGap(50)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGap(50)
                .addComponent(arg[0])
                .addGap(50)
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new DragGesture();
            ex.setVisible(true);
        });
    }
}

这个简单的示例演示了拖动手势。 当我们单击组件并在按下按钮时移动鼠标指针时,将创建拖动手势。 该示例说明了如何为组件创建DragSource

public class DragGesture extends JFrame implements 
   DragGestureListener, Transferable {

DragGesture实现两个接口。 DragGestureListener监听拖动手势。 Transferable处理用于传输操作的数据。 在该示例中,我们将不会传输任何数据; 我们仅演示拖动手势。 Transferable接口的三种必要方法未实现。

var ds = new DragSource();

ds.createDefaultDragGestureRecognizer(redPanel,
        DnDConstants.ACTION_COPY, this);

在这里,我们创建一个DragSource对象并将其注册到面板。 DragSource是负责启动拖放操作的实体。 createDefaultDragGestureRecognizer()将拖动源和DragGestureListener与特定组件相关联。

public void dragGestureRecognized(DragGestureEvent event) {

}

dragGestureRecognized()方法响应拖动手势。

var cursor = Cursor.getDefaultCursor();

if (event.getDragAction() == DnDConstants.ACTION_COPY) {
    cursor = DragSource.DefaultCopyDrop;
}

event.startDrag(cursor, this);

DragGestureEventstartDrag()方法最终开始拖动操作。 我们指定两个参数:游标类型和Transferable对象。

public Object getTransferData(DataFlavor flavor) {

    return null;
}

public DataFlavor[] getTransferDataFlavors() {

    return new DataFlavor[0];
}

public boolean isDataFlavorSupported(DataFlavor flavor) {

    return false;
}

实现Transferable接口的对象必须实现这三种方法。 还没有功能。

一个复杂的拖放示例

在下面的示例中,我们创建一个复杂的拖放示例。 我们创建拖动源,放置目标和可移动对象。

ComplexDnD.java

package com.zetcode;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;

public class ComplexDnD extends JFrame
        implements DragGestureListener {

    private JPanel leftPanel;

    public ComplexDnD() {

        initUI();
    }

    private void initUI() {

        var colourBtn = new JButton("Choose Color");
        colourBtn.setFocusable(false);

        leftPanel = new JPanel();
        leftPanel.setBackground(Color.red);
        leftPanel.setPreferredSize(new Dimension(100, 100));

        colourBtn.addActionListener(event -> {

            var color = JColorChooser.showDialog(this, "Choose Color", Color.white);
            leftPanel.setBackground(color);
        });

        var rightPanel = new JPanel();
        rightPanel.setBackground(Color.white);
        rightPanel.setPreferredSize(new Dimension(100, 100));

        var mtl = new MyDropTargetListener(rightPanel);

        var ds = new DragSource();
        ds.createDefaultDragGestureRecognizer(leftPanel,
                DnDConstants.ACTION_COPY, this);

        createLayout(colourBtn, leftPanel, rightPanel);

        setTitle("Complex drag and drop example");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public void dragGestureRecognized(DragGestureEvent event) {

        var cursor = Cursor.getDefaultCursor();
        var panel = (JPanel) event.getComponent();

        var color = panel.getBackground();

        if (event.getDragAction() == DnDConstants.ACTION_COPY) {
            cursor = DragSource.DefaultCopyDrop;
        }

        event.startDrag(cursor, new TransferableColor(color));
    }

    private class MyDropTargetListener extends DropTargetAdapter {

        private final DropTarget dropTarget;
        private final JPanel panel;

        public MyDropTargetListener(JPanel panel) {
            this.panel = panel;

            dropTarget = new DropTarget(panel, DnDConstants.ACTION_COPY,
                    this, true, null);
        }

        public void drop(DropTargetDropEvent event) {

            try {

                var tr = event.getTransferable();
                var col = (Color) tr.getTransferData(TransferableColor.colorFlavor);

                if (event.isDataFlavorSupported(TransferableColor.colorFlavor)) {

                    event.acceptDrop(DnDConstants.ACTION_COPY);
                    this.panel.setBackground(col);
                    event.dropComplete(true);
                    return;
                }

                event.rejectDrop();
            } catch (Exception e) {

                e.printStackTrace();
                event.rejectDrop();
            }
        }
    }

    private void createLayout(JComponent... arg) {

        var pane = getContentPane();
        var gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(30)
                .addComponent(arg[1])
                .addGap(30)
                .addComponent(arg[2])
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addComponent(arg[1])
                .addComponent(arg[2])
        );

        pack();
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ComplexDnD();
            ex.setVisible(true);
        });
    }
}

class TransferableColor implements Transferable {

    protected static final DataFlavor colorFlavor =
            new DataFlavor(Color.class, "A Color Object");

    protected static final DataFlavor[] supportedFlavors = {
            colorFlavor,
            DataFlavor.stringFlavor,
    };

    private final Color color;

    public TransferableColor(Color color) {

        this.color = color;
    }

    public DataFlavor[] getTransferDataFlavors() {

        return supportedFlavors;
    }

    public boolean isDataFlavorSupported(DataFlavor flavor) {

        return flavor.equals(colorFlavor) ||
                flavor.equals(DataFlavor.stringFlavor);
    }

    public Object getTransferData(DataFlavor flavor)
            throws UnsupportedFlavorException {

        if (flavor.equals(colorFlavor)) {
            return color;
        } else if (flavor.equals(DataFlavor.stringFlavor)) {
            return color.toString();
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
    }
}

该代码示例显示一个按钮和两个面板。 该按钮显示一个颜色选择器对话框,并为第一个面板设置颜色。 可以将颜色拖到第二个面板中。

此示例增强了前一个示例。 我们将添加放置目标和自定义可转移对象。

var mtl = new MyDropTargetListener(rightPanel);

我们在右侧面板中注册放置目标监听器。

event.startDrag(cursor, new TransferableColor(color));

startDrag()方法具有两个参数。 游标和Transferable对象。

public MyDropTargetListener(JPanel panel) {
    this.panel = panel;

    dropTarget = new DropTarget(panel, DnDConstants.ACTION_COPY, 
        this, true, null);
}

MyDropTargetListener中,我们创建一个放置目标对象。

var tr = event.getTransferable();
var col = (Color) tr.getTransferData(TransferableColor.colorFlavor);

if (event.isDataFlavorSupported(TransferableColor.colorFlavor)) {

    event.acceptDrop(DnDConstants.ACTION_COPY);
    this.panel.setBackground(color);
    event.dropComplete(true);
    return;
}

我们得到正在传输的数据。 在我们的情况下,它是一个颜色对象。 在这里,我们设置右侧面板的颜色。

event.rejectDrop();

如果不满足拖放操作的条件,我们将拒绝它。

protected static DataFlavor colorFlavor =
    new DataFlavor(Color.class, "A Color Object");

TransferableColor中,我们创建一个新的DataFlavor对象。

protected static DataFlavor[] supportedFlavors = {
    colorFlavor,
    DataFlavor.stringFlavor,
};

在这里,我们指定了我们支持的数据类型。 在我们的情况下,这是一个自定义的颜色味道和预定义的DataFlavor.stringFlavor

public Object getTransferData(DataFlavor flavor)
        throws UnsupportedFlavorException {

    if (flavor.equals(colorFlavor)) {
        return color;
    } else if (flavor.equals(DataFlavor.stringFlavor)) {
        return color.toString();
    } else {
        throw new UnsupportedFlavorException(flavor);
    }
}

getTransferData()返回一个具有特定数据风味的对象。

Java Swing 教程的这一部分专门用于 Swing 的拖放操作。

Windows API 中的日期和时间

原文: http://zetcode.com/gui/winapi/datetime/

在 Windows API 教程的这一部分中,我们将使用日期和时间。 ZetCode 的文章处理 ANSI C 中的日期和时间。

SYSTEMTIME结构用于 Windows API 中的日期和时间。 时间可以是协调世界时(UTC)或本地时间。 它具有以下成员:

WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;

SYSTEMTIME结构用GetSystemTime()函数或GetLocalTime()函数填充。 然后,我们可以访问结构的成员以获取当前日期或时间。

FILETIME结构包含一个 64 位的值,该值表示自 1601 年 1 月 1 日(UTC)起 100 纳秒间隔的数量。 使用此值,我们可以计算 Windows API 纪元或日期时间差。

DWORD dwLowDateTime;
DWORD dwHighDateTime;

FILETIME结构具有两个成员:dwLowDateTime是文件时间的低阶部分,dwHighDateTime是文件时间的高阶部分。 为了从两个成员中获得单个值,我们利用了LARGE_INTEGER并集。

FileTimeToSystemTime()SystemTimeToFileTime()函数用于在两个结构之间进行转换。

当地时间

本地时间定义为用户所在时区中的当前时间。

localtime.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    SYSTEMTIME lt = {0};

    GetLocalTime(&lt);

    wprintf(L"The local time is: %02d:%02d:%02d\n", 
        lt.wHour, lt.wMinute, lt.wSecond);

    return 0;
}

程序将打印本地时间。

SYSTEMTIME lt = {0};

我们声明SYSTEMTIME结构。 通过调用特定的时间函数来填充此结构的成员。

GetLocalTime(&lt);

GetLocalTime()检索当前的本地日期和时间。 它用当前日期&时间值填充SYSTEMTIME结构的成员。

wprintf(L"The local time is: %02d:%02d:%02d\n", 
  lt.wHour, lt.wMinute, lt.wSecond);

我们以hh:mm:ss格式打印当前本地时间。

C:\Users\Jano\Documents\Pelles C Projects\timedate\LocalTime>LocalTime.exe
The local time is: 20:20:07

这是示例输出。

UTC 时间

我们的星球是一个球体。 它绕其轴旋转。 地球向东旋转。 因此,太阳在不同位置的不同时间升起。 地球大约每 24 小时旋转一次。 因此,世界被划分为 24 个时区。 在每个时区,都有一个不同的本地时间。 夏令时通常会进一步修改此本地时间。

实际需要一个全球时间。 全球时间可以避免时区和夏令时的混淆。 UTC(世界标准时间)被选为主要时间标准。 UTC 用于航空,天气预报,飞行计划,空中交通管制通关和地图。 与当地时间不同,UTC 不会随季节变化而变化。

Windows API 具有GetSystemTime()函数以获取 UTC 时间。

utctime.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    SYSTEMTIME st = {0};

    GetSystemTime(&st);

    wprintf(L"The UTC time is: %02d:%02d:%02d\n", 
        st.wHour, st.wMinute, st.wSecond);

    return 0;
}

在示例中,我们计算 UTC 时间。

SYSTEMTIME st = {0};

UTC 时间将存储在SYSTEMTIME结构中。

GetSystemTime(&st);

我们使用GetSystemTime()函数检索 UTC 时间。

wprintf(L"The UTC time is: %02d:%02d:%02d\n", 
  st.wHour, st.wMinute, st.wSecond);

UTC 时间以hh:mm:ss格式打印到控制台。

C:\Users\Jano\Documents\Pelles C Projects\timedate\UtcTime>UtcTime.exe
The UTC time is: 19:25:20

这是 UTC 时间的输出。

算术

不建议对SYSTEMTIME结构中的值进行算术运算以获得相对时间。 相反,我们将SYSTEMTIME结构转换为FILETIME结构,将所得的FILETIME结构复制到ULARGE_INTEGER结构,并对ULARGE_INTEGER值使用常规的 64 位算术。 最后,我们将FILETIME结构转换回SYSTEMTIME结构。

arithmetic.c

#include <windows.h>
#include <wchar.h>

#define NSECS 60*60*3

int wmain(void) {

    SYSTEMTIME st = {0};
    FILETIME ft = {0};

    GetLocalTime(&st);

    wprintf(L"%02d/%02d/%04d %02d:%02d:%02d\n",
        st.wDay, st.wMonth, st.wYear, st.wHour, st.wMinute, st.wSecond);

    SystemTimeToFileTime(&st, &ft);

    ULARGE_INTEGER u = {0};

    memcpy(&u, &ft, sizeof(u));
    u.QuadPart += NSECS * 10000000LLU;
    memcpy(&ft, &u, sizeof(ft));

    FileTimeToSystemTime(&ft, &st);

    wprintf(L"%02d/%02d/%04d %02d:%02d:%02d\n",
        st.wDay, st.wMonth, st.wYear, st.wHour, st.wMinute, st.wSecond);

    return 0;
}

在示例中,我们将三个小时添加到当前本地时间值。

#define NSECS 60*60*3

三个小时以秒表示。

GetLocalTime(&st);

使用GetLocalTime()函数,我们可以获取当前本地时间。

SystemTimeToFileTime(&st, &ft);

我们调用SystemTimeToFileTime()函数将SYSTEMTIME结构转换为FILETIME结构。

ULARGE_INTEGER u = {0};

ULARGE_INTEGER结构已创建。

memcpy(&u, &ft, sizeof(u));
u.QuadPart += NSECS * 10000000LLU;
memcpy(&ft, &u, sizeof(ft));

我们向ULARGE_INTEGER结构的QuadPart成员添加了三个小时。 成员以 100 纳秒刻度表示; 因此,我们将NSECS乘以10000000LLU

FileTimeToSystemTime(&ft, &st);

我们将FILETIME结构转换回SYSTEMTIME结构。

C:\Users\Jano\Documents\Pelles C Projects\timedate\Arithmetic>Arithmetic.exe
01/02/2016 13:28:13
01/02/2016 16:28:13

这是Arithmetic.exe的示例输出。 已将三个小时正确地添加到当前本地时间。

日期

GetLocalTime()函数还用于确定当前日期。

today.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    SYSTEMTIME st = {0};

    GetLocalTime(&st);

    wprintf(L"Today is: %d-%02d-%02d\n", st.wYear, st.wMonth, st.wDay);

    return 0;
}

上面的程序打印今天的日期。

SYSTEMTIME st = {0};

我们声明一个SYSTEMTIME结构。

GetLocalTime(&st);

我们用当前的本地时间和日期值填充SYSTEMTIME成员。

wprintf(L"Today is: %d-%02d-%02d\n", st.wYear, st.wMonth, st.wDay);

当前日期将打印到控制台。 我们选择了公历大端日期格式。

C:\Users\Jano\Documents\Pelles C Projects\timedate\Today>Today.exe
Today is: 2016-01-30

这是Today.exe程序的输出。

格式化日期

GetDateFormatEx()函数将日期格式化为指定语言环境的日期字符串。

date_format.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    PDWORD cChars = NULL;
    HANDLE std = GetStdHandle(STD_OUTPUT_HANDLE);   

    if (std == INVALID_HANDLE_VALUE) {
        wprintf(L"Cannot retrieve standard output handle %d\n", 
            GetLastError());
        return 1;
    }

    SYSTEMTIME lt = {0};
    GetLocalTime(&lt);

    wchar_t buf[128] = {0};

    int r = GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_LONGDATE, 
                &lt, NULL, buf, sizeof(buf)/sizeof(buf[0]), NULL);

    if (r == 0) {

        wprintf(L"GetDateFormatEx function failed %d\n", 
            GetLastError());

        CloseHandle(std);

        return 1;
    }

    WriteConsoleW(std, buf, wcslen(buf), cChars, NULL);

    r = CloseHandle(std);

    if (r == 0) {

        wprintf(L"Cannot close console handle %d\n", 
            GetLastError());
        return 1;    
    }

    CloseHandle(std);

    return 0;
}

程序以本地化格式打印当前本地时间。

SYSTEMTIME lt = {0};
GetLocalTime(&lt);

检索当地时间。

int r = GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_LONGDATE, 
			&lt, NULL, buf, sizeof(buf)/sizeof(buf[0]), NULL);

GetDateFormatEx()以区域和语言选项中指定的默认语言环境格式化日期。 日期以长日期格式打印。

WriteConsoleW(std, buf, wcslen(buf), cChars, NULL);

日期将打印到控制台。

C:\Users\Jano\Documents\Pelles C Projects\timedate\DateFormat>DateFormat.exe
1\. februára 2016

程序以斯洛伐克语打印日期。

确定闰年

闰年是包含额外一天的年份。 日历中额外一天的原因是天文日历年与日历年之间的差异。 日历年正好是 365 天,而天文学年(地球绕太阳公转的时间)是 365.25 天。 相差 6 个小时,这意味着在四年的时间里,我们一天中都没有。 因为我们希望日历与季节同步,所以每四年将 2 月增加一天。 (有例外。)在公历中,February 年的 2 月有 29 天,而不是通常的 28 天。该年持续 366 天,而不是通常的 365 天。

leapyear.c

#include <windows.h>
#include <stdbool.h>
#include <wchar.h>

bool isLeapYear(int);

int wmain(void) {

    // Assume year >= 1582 in the Gregorian calendar.
    int years[] = { 2000, 2002, 2004, 2008, 2012, 2016, 2020,
        1900, 1800, 1600 };

    int size = sizeof(years) / sizeof(int);

    for (int i=0; i<size; i++) {

        if (isLeapYear(years[i])) {

            wprintf(L"%ld is a leap year\n", years[i]);
        } else {

            wprintf(L"%ld is not a leap year\n", years[i]);
        }
    }

    return 0;
}

bool isLeapYear(int year) {

    if (year % 4 != 0) {

        return false;
    } else if (year % 400 == 0) {

        return true;
    } else if (year % 100 == 0) {

        return false;
    } else {

        return true;
    }
}

我们有很多年。 我们检查所有年份是否为闰年。 没有内置函数可以检查闰年。 我们创建了一个自定义的isLeapYear()函数。

// Assume year >= 1582 in the Gregorian calendar.
int years[] = { 2000, 2002, 2004, 2008, 2012, 2016, 2020,
    1900, 1800, 1600 };

这是我们要检查的年份。 年份必须在公历中。

for (int i=0; i<size; i++) {

    if (isLeapYear(years[i])) {

        wprintf(L"%ld is a leap year\n", years[i]);
    } else {

        wprintf(L"%ld is not a leap year\n", years[i]);
    }
}

使用 for 循环,我们遍历数组。 我们使用isLeapYear()函数检查年份是否为闰年。

bool isLeapYear(int year) {

    if (year % 4 != 0) {

        return false;
    } else if (year % 400 == 0) {

        return true;
    } else if (year % 100 == 0) {

        return false;
    } else {

        return true;
    }
}

这是确定闰年的功能。 年是 4 的整数倍。年份是 100 的整数倍,则不是闰年,除非它也是 400 的整数倍,在这种情况下,它也是闰年。

C:\Users\Jano\Documents\Pelles C Projects\timedate\LeapYear>LeapYear.exe
2000 is a leap year
2002 is not a leap year
2004 is a leap year
2008 is a leap year
2012 is a leap year
2016 is a leap year
2020 is a leap year
1900 is not a leap year
1800 is not a leap year
1600 is a leap year

LeapYear.exe程序的输出。

正常运行时间

GetTickCount()函数可用于获取计算机的正常运行时间。 它检索自系统启动以来经过的毫秒数。

DWORD WINAPI GetTickCount(void);

该函数返回DWORD值,因此返回的最大天数为 49.7。 为了克服这个限制,我们可以使用GetTickCount64()。 从 Windows Vista 开始,该函数可用。

uptime.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {  

    DWORD tc = GetTickCount();

    short seconds = tc / 1000 % 60; 
    short minutes = tc / 1000 / 60 % 60; 
    short hours = tc / 1000 / 60 / 60 % 24; 
    short days = tc / 1000 / 60 / 60 / 24 % 7;  
    short weeks = tc / 1000 / 60 / 60 / 24 / 7 % 52; 

    wprintf(L"Computer has been running for: ");

    if (weeks > 0 && weeks != 1) {

        wprintf(L"%hi weeks ", weeks);
    } else if (weeks == 1) {

        wprintf(L"1 week ");
    }

    if (days > 0 && days != 1) {

        wprintf(L"%hi days ", days);
    } else if (days == 1) {

        wprintf(L"1 day ");
    }

    if (hours > 0 && hours != 1) {

        wprintf(L"%hi hours ", hours);
    } else if (hours == 1) {

        wprintf(L"1 hour ");
    }

    if (minutes > 0 && minutes != 1) {

        wprintf(L"%hi minutes ", minutes); 
    } else if (minutes == 1) {

        wprintf(L"1 minute ");
    }

    wprintf(L"and %hi seconds\n", seconds);

    return 0;
}

该程序将打印计算机的正常运行时间。 我们使用GetTickCount()函数。 如果计算机运行的时间少于 49.71 天或 4294967296 ms,它将正常工作。 之后,DWORD值溢出。

DWORD tc = GetTickCount();

我们得到计算机正在运行的毫秒数。 DWORD变量可以存储的最大数量为ULONG_MAX

short seconds = tc / 1000 % 60; 
short minutes = tc / 1000 / 60 % 60; 
short hours = tc / 1000 / 60 / 60 % 24; 
short days = tc / 1000 / 60 / 60 / 24 % 7;  
short weeks = tc / 1000 / 60 / 60 / 24 / 7 % 52; 

我们计算秒,分钟,小时,天和周。

if (weeks > 0 && weeks != 1) {

  wprintf(L"%hi weeks ", weeks);
} else if (weeks == 1) {

  wprintf(L"1 week ");
}

如果计算机正在运行一个或多个星期,则可以在控制台上打印week变量或"1 week"字符串。

C:\winapi\examples2\datetime\Uptime>Uptime.exe
Computer has been running for: 3 hours 31 minutes and 7 seconds

样本输出。

星期几

SYSTEMTIME结构的wDayOfWeek成员存储星期几。 值是1..7,其中 1 是星期日,2 星期一,... 7 星期六。

dayofweek.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    SYSTEMTIME st = {0};

    wchar_t *dn[] = { L"Sunday", L"Monday", L"Tuesday", 
        L"Wednesday", L"Thursday", L"Friday", L"Saturday" };

    GetLocalTime(&st);
    wprintf(L"Today is %ls\n", dn[st.wDayOfWeek]);

    return 0;
}

该代码将星期几显示在控制台上。

wchar_t *dn[] = { L"Sunday", L"Monday", L"Tuesday", 
  L"Wednesday", L"Thursday", L"Friday", L"Saturday" };

我们将日期名称存储在字符串数组中。

GetLocalTime(&st);
wprintf(L"Today is %ls\n", dn[st.wDayOfWeek]);

这些行检索并打印星期几。

C:\Users\Jano\Documents\Pelles C Projects\timedate\DayOfWeek>DayOfWeek.exe
Today is Sunday

这是输出。

纪元

纪元是选择作为特定纪元起源的时间瞬间。 例如,在西方基督教国家,时期从耶稣出生(据信出生)的第 0 天开始。 另一个例子是法国共和党日历,使用了十二年。 这个时期是 1792 年 9 月 22 日宣布的共和纪元的开始,即宣布成立第一共和国并废除君主制的那一天。 电脑也有自己的纪元。 最受欢迎的时间之一是 Unix 时间。 Unix 纪元是 1970 年 1 月 1 日 UTC 时间 00:00:00(或1970-01-01T00:00:00Z ISO8601)。 计算机中的日期和时间是根据自该计算机或平台的定义时期以来经过的秒数或时钟滴答数确定的。

Windows 操作系统有几个时期。 Microsoft Excel,MS SQL Server 或 FAT32 文件系统具有不同的时间纪元。 Windows API 纪元是 UTC 1601 年 1 月 1 日 00:00:00。 选择此日期的原因是接受公历 400 周年。 周年纪念日是在 Windows NT 设计之初。 FILETIME结构包含一个 64 位的值,该值表示自 1601 年 1 月 1 日(UTC)起 100 纳秒间隔的数量。

windows_epoch.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    FILETIME ft = {0};

    GetSystemTimeAsFileTime(&ft);

    LARGE_INTEGER li = {0};    

    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;

    long long int hns = li.QuadPart;

    wprintf(L"%lli hundreds of nanoseconds have elapsed " 
        "since Windows API epoch\n", hns);

    return 0;
}

该代码示例计算从 Windows API 纪元到现在为止经过的 100 纳秒间隔的数量。

FILETIME ft = {0};

我们声明FILETIME结构。 它有两个成员。 dwLowDateTime保留文件时间的低位部分。 dwHighDateTime保留文件时间的高位部分。

LARGE_INTEGER li = {0};

LARGE_INTEGER是一个联合,可帮助我们将FILETIME结构的成员转换为 100 纳秒的间隔。

li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;

FILETIME结构的值将复制到大整数联合成员。

long long int hns = li.QuadPart;

QuadPart成员存储从LowPartHighPart成员确定的数百纳秒数。 它是一个巨大的数字,存储为 64 位整数。

wprintf(L"%lli hundreds of nanoseconds have elapsed " 
  "since Windows API epoch\n", hns);

该值将打印到控制台。

C:\Users\Jano\Documents\Pelles C Projects\timedate\WindowsEpoch>WindowsEpoch.exe
130987330019489987 hundreds of nanoseconds have elapsed since Windows API epoch

这是示例输出。

以下示例将 Windows API 时间转换为 Unix 时间。

unix_time.c

#include <windows.h>
#include <wchar.h>

#define WINDOWS_TICKS_PER_SEC 10000000
#define EPOCH_DIFFERENCE 11644473600LL

long long WindowsTicksToUnixSeconds(long long);

int wmain(void) {

    FILETIME ft = {0};

    GetSystemTimeAsFileTime(&ft);

    LARGE_INTEGER li = {0};    

    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;

    long long int hns = li.QuadPart;

    wprintf(L"Windows API time: %lli\n", hns);

    long long int utm = WindowsTicksToUnixSeconds(hns);

    wprintf(L"Unix time: %lli\n", utm);

    return 0;
}

long long int WindowsTicksToUnixSeconds(long long windowsTicks) {

     return (windowsTicks / WINDOWS_TICKS_PER_SEC - EPOCH_DIFFERENCE);
}

该示例显示 Windows API 时间和 Unix 控制台时间。

#define EPOCH_DIFFERENCE 11644473600LL

两个时期之间的差是11644473600LL。 请注意,闰秒是 1972 年引入的,因此我们没有将它们考虑在内。

long long int WindowsTicksToUnixSeconds(long long windowsTicks) {

     return (windowsTicks / WINDOWS_TICKS_PER_SEC - EPOCH_DIFFERENCE);
}

该函数将 Windows 刻度转换为 Unix 时间秒。

C:\Users\Jano\Documents\Pelles C Projects\timedate\UnixTime>UnixTime.exe
Windows API time: 130987431026414297
Unix time: 1454269502

这是UnixTime.exe示例的输出。

直到 XMas 的天数

Windows API 没有任何函数来计算两天之间的差异。 我们必须自己做数学。

days_to_xmas.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    FILETIME ft1 = {0};
    FILETIME ft2 = {0};
    SYSTEMTIME st = {0};  
    LARGE_INTEGER li1 = {0};    
    LARGE_INTEGER li2 = {0}; 

    st.wYear = 2016;
    st.wMonth = 12;
    st.wDay = 25;

    int r = SystemTimeToFileTime(&st, &ft1);

    if (r == 0) {

        wprintf(L"Failed to convert system time to file time\n (%d)", 
            GetLastError());
        return 1;
    }

    GetSystemTimeAsFileTime(&ft2);

    li1.LowPart = ft1.dwLowDateTime;
    li1.HighPart = ft1.dwHighDateTime;

    li2.LowPart = ft2.dwLowDateTime;
    li2.HighPart = ft2.dwHighDateTime;

    long long int dif = li1.QuadPart - li2.QuadPart;

    int days2xmas = dif / 10000000L / 60 / 60 / 24;

    if (days2xmas == 1) {

        wprintf(L"There is one day until Christmas\n", days2xmas);
    } else if (days2xmas == 0) {

        wprintf(L"Today is Chritmas\n");
    } else {

        wprintf(L"There are %d days until Christmas\n", days2xmas);
    }

    return 0;
}

该代码示例计算直到圣诞节的天数。

FILETIME ft1 = {0};
FILETIME ft2 = {0};
SYSTEMTIME st = {0};  
LARGE_INTEGER li1 = {0};    
LARGE_INTEGER li2 = {0}; 

我们需要FILETIMESYSTEMTIME结构和LARGE_INTEGER并集来进行计算。

st.wYear = 2016;
st.wMonth = 12;
st.wDay = 25;

SYSTEMTIME结构填充了圣诞节的值。

int r = SystemTimeToFileTime(&st, &ft1);

圣诞节的系统时间将转换为文件时间。

GetSystemTimeAsFileTime(&ft2);

我们使用GetSystemTimeAsFileTime()函数将当前日期作为文件时间。

li1.LowPart = ft1.dwLowDateTime;
li1.HighPart = ft1.dwHighDateTime;

li2.LowPart = ft2.dwLowDateTime;
li2.HighPart = ft2.dwHighDateTime;

我们用文件时间的低位和高位部分填充两个并集。

long long int dif = li1.QuadPart - li2.QuadPart;

计算两个日期之间的差。

int days2xmas = dif / 10000000L / 60 / 60 / 24;

差异以 100 纳秒表示。 此值转换为天。

C:\Users\Jano\Documents\Pelles C Projects\timedate\DaysToXmas>DaysToXmas.exe
There are 328 days until Christmas

在 2016 年 1 月 31 日,我们获得了此输出。

比较时间

CompareFileTime()函数可用于比较两个文件时间。 当指定的第一次时间较早时,该函数返回 -1。 当两次相等时,它返回 0。 当第一次晚于第二个文件时间时,它将返回 1。

compare_time.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    SYSTEMTIME st1 = {0};
    SYSTEMTIME st2 = {0};
    FILETIME ft1 = {0};
    FILETIME ft2 = {0};

    st1.wYear = 2015;
    st1.wMonth = 4;
    st1.wDay = 12;

    st2.wYear = 2015;
    st2.wMonth = 5;
    st2.wDay = 12;

    int r1 = SystemTimeToFileTime(&st1, &ft1);

    if (r1 == 0) {

        wprintf(L"Failed to convert system time to file time\n (%d)", 
            GetLastError());
        return 1;
    }

    int r2 = SystemTimeToFileTime(&st2, &ft2);

    if (r2 == 0) {

        wprintf(L"Failed to convert system time to file time\n (%d)", 
            GetLastError());
        return 1;
    }

    short ct = CompareFileTime(&ft1, &ft2);

    if (ct == -1) {

        wprintf(L"4/12/2015 comes before 5/12/2015\n");
    } else if (ct == 0) {

        wprintf(L"4/12/2015 is equal to 5/12/2015\n");
    } else if (ct == 1) {

        wprintf(L"4/12/2015 comes after 5/12/2015\n");
    }

    return 0;
}

我们有两个时间值。 我们使用CompareFileTime()来确定哪个时间更早。

st1.wYear = 2015;
st1.wMonth = 4;
st1.wDay = 12;

st2.wYear = 2015;
st2.wMonth = 5;
st2.wDay = 12;

两次定义。

int r1 = SystemTimeToFileTime(&st1, &ft1);

if (r1 == 0) {

	wprintf(L"Failed to convert system time to file time\n (%d)", 
		GetLastError());
	return 1;
}

int r2 = SystemTimeToFileTime(&st2, &ft2);

if (r2 == 0) {

	wprintf(L"Failed to convert system time to file time\n (%d)", 
		GetLastError());
	return 1;
}

使用SystemTimeToFileTime()函数调用将系统时间转换为文件时间。

short ct = CompareFileTime(&ft1, &ft2);

将两个文件时间与CompareFileTime()函数进行比较。

if (ct == -1) {

	wprintf(L"4/12/2015 comes before 5/12/2015\n");
} else if (ct == 0) {

	wprintf(L"4/12/2015 is equal to 5/12/2015\n");
} else if (ct == 1) {

	wprintf(L"4/12/2015 comes after 5/12/2015\n");
}

根据返回的值,我们将消息打印到控制台。

C:\Users\Jano\Documents\Pelles C Projects\timedate\CompareTime>CompareTime.exe
4/12/2015 comes before 5/12/2015

这是CompareTime.exe程序的输出。

时区

时区是一个使用相同标准时间的区域。 世界上有 24 个时区。

UTC = local time + bias

偏差是 UTC 时间与本地时间之间的差异(以分钟为单位)。

检索时区

GetTimeZoneInformation()用于获取时区信息。 信息存储在TIME_ZONE_INFORMATION结构中。

get_time_zone.c

#include <windows.h>
#include <wchar.h>

int wmain(void) {

    TIME_ZONE_INFORMATION tzi = {0};

    int r = GetTimeZoneInformation(&tzi);

    if (r == TIME_ZONE_ID_INVALID) {

        wprintf(L"Failed to get time zone %d", GetLastError());
        return 1;
    }

    wprintf(L"Time zone: %ls\n", tzi.StandardName);
    wprintf(L"The bias is: %ld minutes\n", tzi.Bias);

    return 0;
}

该示例显示用户的时区。

TIME_ZONE_INFORMATION tzi = {0};

TIME_ZONE_INFORMATION结构存储时区的设置。

int r = GetTimeZoneInformation(&tzi);

GetTimeZoneInformation()函数检索当前时区设置。

wprintf(L"Time zone: %ls\n", tzi.StandardName);

TIME_ZONE_INFORMATION结构的StandardName成员存储我们时区的名称。

wprintf(L"The bias is: %ld minutes\n", tzi.Bias);

我们打印偏差值。

C:\Users\Jano\Documents\Pelles C Projects\timedate\GetTimeZone>GetTimeZone.exe
Time zone: Central Europe Standard Time
The bias is: -60 minutes

我们的时区是中欧标准时间(CEST),偏差是 -60 分钟。

将当地时间转换为世界时间

TzSpecificLocalTimeToSystemTime()函数将本地时间转换为 UTC 时间。 该函数考虑了夏令时(DST)对于要转换的当地时间是否有效。

localtime_to_universaltime.c

#include <windows.h>
#include <wchar.h>

int wmain(void) { 

    SYSTEMTIME lt = {0};
    GetLocalTime(&lt);

    TIME_ZONE_INFORMATION tzi = {0};
    GetTimeZoneInformation(&tzi);

    SYSTEMTIME utm = {0};

    int r = TzSpecificLocalTimeToSystemTime(&tzi, &lt, &utm);

    if (r == 0) {

        wprintf(L"Failed to convert local time to system time %d\n)", 
            GetLastError());
        return 1;
    }

    wprintf(L"Date: %d/%d/%d\n", lt.wMonth, lt.wDay, lt.wYear);

    wprintf(L"The local time is: %02d:%02d:%02d\n", 
        lt.wHour, lt.wMinute, lt.wSecond);

    wprintf(L"The universal time is: %02d:%02d:%02d\n", 
        utm.wHour, utm.wMinute, utm.wSecond);

    return 0;
}

该示例将本地时间转换为世界时间。

SYSTEMTIME lt = {0};
GetLocalTime(&lt);

当前的本地时间通过GetLocalTime()函数获取。

TIME_ZONE_INFORMATION tzi = {0};
GetTimeZoneInformation(&tzi);

时区设置由GetTimeZoneInformation()函数确定。

int r = TzSpecificLocalTimeToSystemTime(&tzi, &lt, &utm);

TzSpecificLocalTimeToSystemTime()将夏令时考虑在内,将当地时间转换为世界时间。

wprintf(L"The local time is: %02d:%02d:%02d\n", 
	lt.wHour, lt.wMinute, lt.wSecond);

将本地时间打印到控制台。

wprintf(L"The universal time is: %02d:%02d:%02d\n", 
	utm.wHour, utm.wMinute, utm.wSecond);

世界时间打印到控制台。

C:\Users\Jano\Documents\Pelles C Projects\timedate\LocalTimeToUniversalTime>LocalTimeToUniversalTime.exe
Date: 2/1/2016
The local time is: 11:39:48
The universal time is: 10:39:48

在 2016 年 2 月 1 日的 CEST 时区,我们得到了上述输出。

在 Windows API 教程的这一部分中,我们使用日期&时间。

Swing 中的绘图

原文: http://zetcode.com/tutorials/javaswingtutorial/painting/

Swing 的绘图系统能够渲染向量图形,图像和轮廓基于字体的文本。

Tweet

当我们想要更改或增强现有的小部件,或者要从头开始创建自定义小部件时,应用中需要绘图。要进行绘图,我们使用 Swing 工具箱提供的绘图 API。

绘图是在paintComponent()方法中完成的。 在绘图过程中,我们使用Graphics2D对象。

Swing 2D 向量图形

有两种不同的计算机图形:向量图形和栅格图形。 栅格图形将图像表示为像素的集合。 向量图形是使用诸如点,线,曲线或多边形之类的几何图元来表示图像。 这些基元是使用数学方程式创建的。

两种类型的计算机图形都有优点和缺点。 向量图形优于栅格的优点是:

  • 较小的大小
  • 无限放大的能力
  • 移动,缩放,填充或旋转不会降低图像质量

基本类型

  • 线
  • 折线
  • 多边形
  • 圆圈
  • 椭圆
  • 样条

Swing 绘制点

最简单的图形原语是点。 它是窗口上的一个点。 在 Swing 中没有方法可以画点。 要画点,我们使用drawLine()方法。 我们使用一分两次。

PointsEx.java

package com.zetcode;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Random;

class DrawPanel extends JPanel {

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g;

        g2d.setColor(Color.blue);

        for (int i = 0; i <= 1000; i++) {

            var size = getSize();
            var insets = getInsets();

            int w = size.width - insets.left - insets.right;
            int h = size.height - insets.top - insets.bottom;

            var r = new Random();
            int x = Math.abs(r.nextInt()) % w;
            int y = Math.abs(r.nextInt()) % h;
            g2d.drawLine(x, y, x, y);
        }
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class PointsEx extends JFrame {

    public PointsEx() {

        initUI();
    }

    private void initUI() {

        var drawPanel = new DrawPanel();
        add(drawPanel);

        setSize(350, 250);
        setTitle("Points");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new PointsEx();
            ex.setVisible(true);
        });
    }
}

有一点很难观察到。 因此,我们在面板表面上随机绘制了 1000 个点。

class DrawPanel extends JPanel {

我们正在自定义绘图面板上绘图,该面板是JPanel组件。 绘图面板稍后将添加到JFrame组件。

@Override
public void paintComponent(Graphics g) {

    super.paintComponent(g);
    doDrawing(g);
}

自定义绘图是在paintComponent()方法内部执行的,我们将其覆盖。 super.paintComponent()方法调用父类的方法。 准备用于绘图的组件需要做一些必要的工作。 实际图形委托给doDrawing()方法。

var g2d = (Graphics2D) g;

Swing 中的绘制是在Graphics2D对象上完成的。

g2d.setColor(Color.blue);

我们将点涂成蓝色。

var size = getSize();
var insets = getInsets();

窗口的大小包括边框和标题栏。 我们不在那画。

int w =  size.width - insets.left - insets.right;
int h =  size.height - insets.top - insets.bottom;

在这里,我们计算面积,在此我们将有效地绘制点。

var r = new Random();
int x = Math.abs(r.nextInt()) % w;
int y = Math.abs(r.nextInt()) % h;

我们得到一个上面计算出的区域大小范围内的随机数。

g2d.drawLine(x, y, x, y);

在这里,我们指出了这一点。 如前所述,我们使用drawLine()方法。 我们两次指定相同的点。

Points

图:点

Swing 绘制线

线是简单的图形基元。 它使用两点绘制。

LinesEx.java

package com.zetcode;

import java.awt.BasicStroke;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

class DrawPanel extends JPanel {

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g;

        float[] dash1 = {2f, 0f, 2f};
        float[] dash2 = {1f, 1f, 1f};
        float[] dash3 = {4f, 0f, 2f};
        float[] dash4 = {4f, 4f, 1f};

        g2d.drawLine(20, 40, 250, 40);

        var bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f);

        var bs2 = new BasicStroke(1, BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_ROUND, 1.0f, dash2, 2f);

        var bs3 = new BasicStroke(1, BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_ROUND, 1.0f, dash3, 2f);

        var bs4 = new BasicStroke(1, BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_ROUND, 1.0f, dash4, 2f);

        g2d.setStroke(bs1);
        g2d.drawLine(20, 80, 250, 80);

        g2d.setStroke(bs2);
        g2d.drawLine(20, 120, 250, 120);

        g2d.setStroke(bs3);
        g2d.drawLine(20, 160, 250, 160);

        g2d.setStroke(bs4);
        g2d.drawLine(20, 200, 250, 200);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class LinesEx extends JFrame {

    public LinesEx() {

        initUI();
    }

    private void initUI() {

        var drawPanel = new DrawPanel();
        add(drawPanel);

        setSize(280, 270);
        setTitle("Lines");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new LinesEx();
            ex.setVisible(true);
        });
    }
}

在示例中,我们绘制了五条线。 第一行使用默认值绘制。 其他将具有不同的粗细。 使用BasicStroke类创建描边。 它为图形基元的轮廓定义了一组基本的渲染属性。

float[] dash1 = { 2f, 0f, 2f };

在这里,我们创建一个在描边对象中使用的笔划线。

var bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT, 
    BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f )

这段代码创建了一个笔画。 描边定义线宽,端盖,线连接,斜接限制,笔划线和笔划线阶段。

Lines

图:直线

Swing 绘制矩形

要绘制矩形,我们使用drawRect()方法。 要使用当前颜色填充矩形,我们使用fillRect()方法。

RectanglesEx.java

package com.zetcode;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

class DrawPanel extends JPanel {

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g;

        g2d.setColor(new Color(212, 212, 212));
        g2d.drawRect(10, 15, 90, 60);
        g2d.drawRect(130, 15, 90, 60);
        g2d.drawRect(250, 15, 90, 60);
        g2d.drawRect(10, 105, 90, 60);
        g2d.drawRect(130, 105, 90, 60);
        g2d.drawRect(250, 105, 90, 60);
        g2d.drawRect(10, 195, 90, 60);
        g2d.drawRect(130, 195, 90, 60);
        g2d.drawRect(250, 195, 90, 60);

        g2d.setColor(new Color(125, 167, 116));
        g2d.fillRect(10, 15, 90, 60);

        g2d.setColor(new Color(42, 179, 231));
        g2d.fillRect(130, 15, 90, 60);

        g2d.setColor(new Color(70, 67, 123));
        g2d.fillRect(250, 15, 90, 60);

        g2d.setColor(new Color(130, 100, 84));
        g2d.fillRect(10, 105, 90, 60);

        g2d.setColor(new Color(252, 211, 61));
        g2d.fillRect(130, 105, 90, 60);

        g2d.setColor(new Color(241, 98, 69));
        g2d.fillRect(250, 105, 90, 60);

        g2d.setColor(new Color(217, 146, 54));
        g2d.fillRect(10, 195, 90, 60);

        g2d.setColor(new Color(63, 121, 186));
        g2d.fillRect(130, 195, 90, 60);

        g2d.setColor(new Color(31, 21, 1));
        g2d.fillRect(250, 195, 90, 60);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class RectanglesEx extends JFrame {

    public RectanglesEx() {

        initUI();
    }

    private void initUI() {

        var drawPanel = new DrawPanel();
        add(drawPanel);

        setSize(360, 300);
        setTitle("Rectangles");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new RectanglesEx();
            ex.setVisible(true);
        });
    }
}

在示例中,我们绘制了九个彩色矩形。

g2d.setColor(new Color(212, 212, 212));
g2d.drawRect(10, 15, 90, 60);
...

我们将矩形轮廓的颜色设置为柔和的灰色,以免干扰填充颜色。 要绘制矩形的轮廓,我们使用drawRect()方法。 前两个参数是 x 和 y 值。 第三和第四是宽度和高度。

g2d.fillRect(10, 15, 90, 60);

为了用颜色填充矩形,我们使用fillRect()方法。

Rectangles

图:矩形

Swing 使用纹理

纹理是应用于形状的位图图像。 要在 Java 2D 中使用纹理,我们使用TexturePaint类。

TexturesEx.java

package com.zetcode;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

class DrawingPanel extends JPanel {

    private BufferedImage slate;
    private BufferedImage java;
    private BufferedImage pane;

    public DrawingPanel() {

        loadImages();
    }

    private void loadImages() {

        try {

            slate = ImageIO.read(new File("src/resources/slate.png"));
            java = ImageIO.read(new File("src/resources/java.png"));
            pane = ImageIO.read(new File("src/resources/pane.png"));

        } catch (IOException ex) {

            JOptionPane.showMessageDialog(this,
                    "Could not load images", "Error", JOptionPane.ERROR_MESSAGE);
            System.exit(1);
        }
    }

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g.create();

        var slateTp = new TexturePaint(slate, new Rectangle(0, 0, 90, 60));
        var javaTp = new TexturePaint(java, new Rectangle(0, 0, 90, 60));
        var paneTp = new TexturePaint(pane, new Rectangle(0, 0, 90, 60));

        g2d.setPaint(slateTp);
        g2d.fillRect(10, 15, 90, 60);

        g2d.setPaint(javaTp);
        g2d.fillRect(130, 15, 90, 60);

        g2d.setPaint(paneTp);
        g2d.fillRect(250, 15, 90, 60);

        g2d.dispose();
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

class TexturesEx extends JFrame {

    public TexturesEx() {

        initUI();
    }

    private void initUI() {

        var drawingPanel = new DrawingPanel();
        add(drawingPanel);

        setTitle("Textures");
        setSize(360, 120);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new TexturesEx();
            ex.setVisible(true);
        });
    }
}

在代码示例中,我们用三个不同的纹理填充三个矩形。

private BufferedImage slate;
private BufferedImage java;
private BufferedImage pane;

BufferedImage是存储在内存中的像素矩形。 它是 Swing 中最重要的图像类型之一。 许多 Swing 方法都返回BufferedImage以供使用。

slate = ImageIO.read(new File("src/resources/slate.png"));

在这里,我们使用ImageIO.read()方法将图像读取到缓冲图像中。 它接受File对象并返回BufferedImage

slateTp = new TexturePaint(slate, new Rectangle(0, 0, 90, 60));

我们从缓冲图像中创建一个TexturePaint类。

g2d.setPaint(slateTp);
g2d.fillRect(10, 15, 90, 60);

我们用纹理填充一个矩形。

Textures

图:纹理

Swing 使用渐变

在计算机图形学中,渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在 2D 绘图程序和绘图程序中,渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。

GradientsEx.java

package com.zetcode;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

class DrawPanel extends JPanel {

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g;

        var gp1 = new GradientPaint(5, 5,
                Color.red, 20, 20, Color.black, true);

        g2d.setPaint(gp1);
        g2d.fillRect(20, 20, 300, 40);

        var gp2 = new GradientPaint(5, 25,
                Color.yellow, 20, 2, Color.black, true);

        g2d.setPaint(gp2);
        g2d.fillRect(20, 80, 300, 40);

        var gp3 = new GradientPaint(5, 25,
                Color.green, 2, 2, Color.black, true);

        g2d.setPaint(gp3);
        g2d.fillRect(20, 140, 300, 40);

        var gp4 = new GradientPaint(25, 25,
                Color.blue, 15, 25, Color.black, true);

        g2d.setPaint(gp4);
        g2d.fillRect(20, 200, 300, 40);

        var gp5 = new GradientPaint(0, 0,
                Color.orange, 0, 20, Color.black, true);

        g2d.setPaint(gp5);
        g2d.fillRect(20, 260, 300, 40);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        doDrawing(g);
    }
}

public class GradientsEx extends JFrame {

    public GradientsEx() {

        initUI();
    }

    private void initUI() {

        var drawPanel = new DrawPanel();
        add(drawPanel);

        setSize(350, 350);
        setTitle("Gradients");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new GradientsEx();
            ex.setVisible(true);
        });
    }
}

我们的代码示例展示了五个带有渐变的矩形。

var gp4 = new GradientPaint(25, 25, 
    Color.blue, 15, 25, Color.black, true);

要使用渐变,我们使用 Java Swing 的GradientPaint类。 通过操纵颜色值和起点,终点,我们可以获得不同类型的渐变。

g2d.setPaint(gp5);

调用setPaint()方法激活渐变。

Gradients

图:渐变

Swing 绘制文字

使用drawString()方法进行绘制。 我们指定我们要绘制的字符串以及文本在窗口区域中的位置。

DrawingTextEx.java

package com.zetcode;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

class DrawPanel extends JPanel {

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g;

        var rh = new RenderingHints(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        rh.put(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);

        g2d.setRenderingHints(rh);

        var font = new Font("URW Chancery L", Font.BOLD, 21);
        g2d.setFont(font);

        g2d.drawString("Not marble, nor the gilded monuments", 20, 30);
        g2d.drawString("Of princes, shall outlive this powerful rhyme;", 20, 60);
        g2d.drawString("But you shall shine more bright in these contents",
                20, 90);
        g2d.drawString("Than unswept stone, besmear'd with sluttish time.",
                20, 120);
        g2d.drawString("When wasteful war shall statues overturn,", 20, 150);
        g2d.drawString("And broils root out the work of masonry,", 20, 180);
        g2d.drawString("Nor Mars his sword, nor war's quick "
                + "fire shall burn", 20, 210);
        g2d.drawString("The living record of your memory.", 20, 240);
        g2d.drawString("'Gainst death, and all oblivious enmity", 20, 270);
        g2d.drawString("Shall you pace forth; your praise shall still "
                + "find room", 20, 300);
        g2d.drawString("Even in the eyes of all posterity", 20, 330);
        g2d.drawString("That wear this world out to the ending doom.", 20, 360);
        g2d.drawString("So, till the judgment that yourself arise,", 20, 390);
        g2d.drawString("You live in this, and dwell in lovers' eyes.", 20, 420);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class DrawingTextEx extends JFrame {

    public DrawingTextEx() {

        initUI();
    }

    private void initUI() {

        var drawPanel = new DrawPanel();
        add(drawPanel);

        setSize(500, 470);
        setTitle("Sonnet55");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new DrawingTextEx();
            ex.setVisible(true);
        });
    }
}

在我们的示例中,我们在面板组件上绘制了十四行诗。

var rh = new RenderingHints(
    RenderingHints.KEY_ANTIALIASING,
    RenderingHints.VALUE_ANTIALIAS_ON);

rh.put(RenderingHints.KEY_RENDERING, 
    RenderingHints.VALUE_RENDER_QUALITY);

g2d.setRenderingHints(rh);

这段代码是为了使我们的文本看起来更好。 我们使用RenderingHints应用称为抗锯齿的技术。

var font = new Font("URW Chancery L", Font.BOLD, 21);
g2d.setFont(font);

我们为文本选择特定的字体。

g2d.drawString("Not marble, nor the gilded monuments", 20, 30);

这是绘制文本的代码。

Swing 绘制图像

工具包最重要的功能之一就是显示图像的能力。 图像是像素数组。 每个像素代表给定位置的颜色。 我们可以使用JLabel之类的组件来显示图像,也可以使用 Java 2D API 绘制图像。

DrawImageEx.java

package com.zetcode;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;

class DrawPanel extends JPanel {

    private Image myImage;

    public DrawPanel() {

        initPanel();
    }

    private void initPanel() {

        loadImage();
        var dm = new Dimension(myImage.getWidth(null), myImage.getHeight(null));
        setPreferredSize(dm);
    }

    private void loadImage() {

        myImage = new ImageIcon("src/resources/icesid.jpg").getImage();
    }

    private void doDrawing(Graphics g) {

        var g2d = (Graphics2D) g;

        g2d.drawImage(myImage, 0, 0, null);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class DrawImageEx extends JFrame {

    public DrawImageEx() {

        initUI();
    }

    private void initUI() {

        var drawPanel = new DrawPanel();
        add(drawPanel);

        setTitle("Image");
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new DrawImageEx();
            ex.setVisible(true);
        });
    }
}

本示例将在面板上绘制图像。 图像适合JFrame窗口。

private void initPanel() {

    loadImage();
    var dm = new Dimension(img.getWidth(null), img.getHeight(null));
    setPreferredSize(dm);
}

initPanel()方法中,我们称为loadImage()方法。 我们确定图像大小并设置面板组件的首选大小。 这将与pack()方法一起显示完全适合窗口的图像。

private void loadImage() {

    myImage = new ImageIcon("src/resources/icesid.jpg").getImage();
}

该方法从磁盘加载映像。 我们使用ImageIcon类。 此类简化了 Java Swing 中图像的处理。

g2d.drawImage(this.img, 0, 0, null);

使用drawImage()方法绘制图像。

在本章中,我们使用 Java Swing 进行了一些绘图。 请访问 ZetCode 的 Java 2D 教程,以获取有关在 Swing 中绘图的其他信息。

Java Swing 中的可调整大小的组件

原文: http://zetcode.com/tutorials/javaswingtutorial/resizablecomponent/

在 Java Swing 教程的这一部分中,我们将创建一个可调整大小的组件。

可调整大小的组件

在创建图表时,经常使用可调整大小的组件。 常见的可调整大小的组件是电子表格应用中的图表。 可以将图表移到应用的表格小部件上并调整大小。

Tweet

为了创建可以在面板上自由拖动的组件,我们使用启用了绝对定位的面板。 在我们的示例中,我们将创建一个可以在父窗口上自由移动并调整大小的组件。

当可调整大小的组件具有焦点时,将在其可调整大小的组件的边框上绘制八个小矩形。 矩形用作拖动点,我们可以在其中绘制组件并开始调整大小。

ResizableComponentEx.java

package com.zetcode;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class ResizableComponentEx extends JFrame {

    private Resizable res;

    public ResizableComponentEx() {

        initUI();
    }

    private void initUI() {

        var pnl = new JPanel(null);
        add(pnl);

        var area = new JPanel();
        area.setBackground(Color.white);

        res = new Resizable(area);
        res.setBounds(50, 50, 200, 150);
        pnl.add(res);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent me) {

                requestFocus();
                res.repaint();
            }
        });

        setSize(350, 300);
        setTitle("Resizable component");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var ex = new ResizableComponentEx();
            ex.setVisible(true);
        });
    }
}

ResizableComponentEx设置面板和组件。

var pnl = new JPanel(null);

我们对可调整大小的组件使用绝对定位。 通过将null提供给JPanel的构造器,我们创建了具有绝对定位的面板。

addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent me) {

        requestFocus();
        res.repaint();
    }
});

如果我们在父面板上(即在可调整大小的组件外部)按下,我们将抓住焦点并重新绘制该组件。 边框上的矩形将消失。

ResizableBorder.java

package com.zetcode;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.SwingConstants;
import javax.swing.border.Border;

public class ResizableBorder implements Border {

    private int dist = 8;

    int locations[] = {
            SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
            SwingConstants.EAST, SwingConstants.NORTH_WEST,
            SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
            SwingConstants.SOUTH_EAST
    };

    int cursors[] = {
            Cursor.N_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR,
            Cursor.E_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
            Cursor.SW_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
    };

    public ResizableBorder(int dist) {
        this.dist = dist;
    }

    @Override
    public Insets getBorderInsets(Component component) {
        return new Insets(dist, dist, dist, dist);
    }

    @Override
    public boolean isBorderOpaque() {
        return false;
    }

    @Override
    public void paintBorder(Component component, Graphics g, int x, int y,
                            int w, int h) {

        g.setColor(Color.black);
        g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);

        if (component.hasFocus()) {

            for (int i = 0; i < locations.length; i++) {

                var rect = getRectangle(x, y, w, h, locations[i]);

                g.setColor(Color.WHITE);
                g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
                g.setColor(Color.BLACK);
                g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
            }
        }
    }

    private Rectangle getRectangle(int x, int y, int w, int h, int location) {

        switch (location) {

            case SwingConstants.NORTH:
                return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);

            case SwingConstants.SOUTH:
                return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist, dist);

            case SwingConstants.WEST:
                return new Rectangle(x, y + h / 2 - dist / 2, dist, dist);

            case SwingConstants.EAST:
                return new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist, dist);

            case SwingConstants.NORTH_WEST:
                return new Rectangle(x, y, dist, dist);

            case SwingConstants.NORTH_EAST:
                return new Rectangle(x + w - dist, y, dist, dist);

            case SwingConstants.SOUTH_WEST:
                return new Rectangle(x, y + h - dist, dist, dist);

            case SwingConstants.SOUTH_EAST:
                return new Rectangle(x + w - dist, y + h - dist, dist, dist);
        }
        return null;
    }

    public int getCursor(MouseEvent me) {

        var c = me.getComponent();
        int w = c.getWidth();
        int h = c.getHeight();

        for (int i = 0; i < locations.length; i++) {

            var rect = getRectangle(0, 0, w, h, locations[i]);

            if (rect.contains(me.getPoint())) {
                return cursors[i];
            }
        }

        return Cursor.MOVE_CURSOR;
    }
}

ResizableBorder负责绘制组件的边框并确定要使用的光标的类型。

int locations[] = {
    SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
    SwingConstants.EAST, SwingConstants.NORTH_WEST,
    SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
    SwingConstants.SOUTH_EAST
};

这些是绘制矩形的位置。 这些位置也是抓取点,可以在其中抓取组件并调整其大小。

g.setColor(Color.black);
g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);

paintBorder()方法中,我们绘制了可调整大小组件的边框。 上面的代码绘制了组件的外边界。

if (component.hasFocus()) {

    for (int i = 0; i < locations.length; i++) {

        var rect = getRectangle(x, y, w, h, locations[i]);

        g.setColor(Color.WHITE);
        g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
        g.setColor(Color.BLACK);
        g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
    }
}

仅当可调整大小的组件当前具有焦点时才绘制八个矩形。

private Rectangle getRectangle(int x, int y, int w, int h, int location) {

    switch (location) {

        case SwingConstants.NORTH:
            return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);

        case SwingConstants.SOUTH:
            return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist, dist);
...
}

getRectangle()方法返回矩形的坐标。

public int getCursor(MouseEvent me) {

    var c = me.getComponent();
    int w = c.getWidth();
    int h = c.getHeight();

    for (int i = 0; i < locations.length; i++) {

        var rect = getRectangle(0, 0, w, h, locations[i]);

        if (rect.contains(me.getPoint())) {
            return cursors[i];
        }
    }

    return Cursor.MOVE_CURSOR;
}

getCursor()方法获取相关抓点的光标类型。

Resizable.java

package com.zetcode;

import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.MouseEvent;

public class Resizable extends JComponent {

    public Resizable(Component comp) {
        this(comp, new ResizableBorder(8));
    }

    public Resizable(Component comp, ResizableBorder border) {

        setLayout(new BorderLayout());
        add(comp);
        addMouseListener(resizeListener);
        addMouseMotionListener(resizeListener);
        setBorder(border);
    }

    private void resize() {

        if (getParent() != null) {
            getParent().revalidate();
        }
    }

    MouseInputListener resizeListener = new MouseInputAdapter() {

        @Override
        public void mouseMoved(MouseEvent me) {

            if (hasFocus()) {

                var resizableBorder = (ResizableBorder) getBorder();
                setCursor(Cursor.getPredefinedCursor(resizableBorder.getCursor(me)));
            }
        }

        @Override
        public void mouseExited(MouseEvent mouseEvent) {
            setCursor(Cursor.getDefaultCursor());
        }

        private int cursor;
        private Point startPos = null;

        @Override
        public void mousePressed(MouseEvent me) {

            var resizableBorder = (ResizableBorder) getBorder();
            cursor = resizableBorder.getCursor(me);
            startPos = me.getPoint();

            requestFocus();
            repaint();
        }

        @Override
        public void mouseDragged(MouseEvent me) {

            if (startPos != null) {

                int x = getX();
                int y = getY();
                int w = getWidth();
                int h = getHeight();

                int dx = me.getX() - startPos.x;
                int dy = me.getY() - startPos.y;

                switch (cursor) {

                    case Cursor.N_RESIZE_CURSOR:

                        if (!(h - dy < 50)) {
                            setBounds(x, y + dy, w, h - dy);
                            resize();
                        }
                        break;

                    case Cursor.S_RESIZE_CURSOR:

                        if (!(h + dy < 50)) {
                            setBounds(x, y, w, h + dy);
                            startPos = me.getPoint();
                            resize();
                        }
                        break;

                    case Cursor.W_RESIZE_CURSOR:

                        if (!(w - dx < 50)) {
                            setBounds(x + dx, y, w - dx, h);
                            resize();
                        }
                        break;

                    case Cursor.E_RESIZE_CURSOR:

                        if (!(w + dx < 50)) {
                            setBounds(x, y, w + dx, h);
                            startPos = me.getPoint();
                            resize();
                        }
                        break;

                    case Cursor.NW_RESIZE_CURSOR:
                        if (!(w - dx < 50) && !(h - dy < 50)) {
                            setBounds(x + dx, y + dy, w - dx, h - dy);
                            resize();
                        }
                        break;

                    case Cursor.NE_RESIZE_CURSOR:

                        if (!(w + dx < 50) && !(h - dy < 50)) {
                            setBounds(x, y + dy, w + dx, h - dy);
                            startPos = new Point(me.getX(), startPos.y);
                            resize();
                        }
                        break;

                    case Cursor.SW_RESIZE_CURSOR:

                        if (!(w - dx < 50) && !(h + dy < 50)) {
                            setBounds(x + dx, y, w - dx, h + dy);
                            startPos = new Point(startPos.x, me.getY());
                            resize();
                        }
                        break;

                    case Cursor.SE_RESIZE_CURSOR:

                        if (!(w + dx < 50) && !(h + dy < 50)) {
                            setBounds(x, y, w + dx, h + dy);
                            startPos = me.getPoint();
                            resize();
                        }
                        break;

                    case Cursor.MOVE_CURSOR:

                        var bounds = getBounds();
                        bounds.translate(dx, dy);
                        setBounds(bounds);
                        resize();
                }

                setCursor(Cursor.getPredefinedCursor(cursor));
            }
        }

        @Override
        public void mouseReleased(MouseEvent mouseEvent) {
            startPos = null;
        }
    };
}

Resizable类表示正在调整大小并在窗口上移动的组件。

private void resize() {

    if (getParent() != null) {
        getParent().revalidate();
    }
}

调整组件大小后,将调用resize()方法。 revalidate()方法导致重画组件。

MouseInputListener resizeListener = new MouseInputAdapter() {

    @Override
    public void mouseMoved(MouseEvent me) {

        if (hasFocus()) {

            var border = (ResizableBorder) getBorder();
            setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
        }
    }
...
}

当我们将光标悬停在抓取点上时,我们将更改光标类型。 仅当组件具有焦点时,光标类型才会更改。

@Override
public void mousePressed(MouseEvent me) {

    var resizableBorder = (ResizableBorder) getBorder();
    cursor = resizableBorder.getCursor(me);
    startPos = me.getPoint();

    requestFocus();
    repaint();
}

如果单击可调整大小的组件,则将更改光标,获得拖动的起点,将焦点放在该组件上,然后重新绘制它。

int x = getX();
int y = getY();
int w = getWidth();
int h = getHeight();

int dx = me.getX() - startPos.x;
int dy = me.getY() - startPos.y;

mouseDragged()方法中,我们确定光标的 x 和 y 坐标以及组件的宽度和高度。 我们计算鼠标拖动事件期间的距离。

case Cursor.N_RESIZE_CURSOR:

    if (!(h - dy < 50)) {
        setBounds(x, y + dy, w, h - dy);
        resize();
    }
    break;

对于所有大小调整,我们确保该组件不小于 50px。 否则,我们可以将其减小到最终将其隐藏的程度。 setBounds()方法重新定位组件并调整其大小。

Resizable component

图:可调整大小的组件

在 Java Swing 教程的这一部分中,我们创建了一个可调整大小的组件。

Java Swing 中的益智游戏

原文: http://zetcode.com/tutorials/javaswingtutorial/puzzle/

在本章中,我们将使用 Java Swing 创建一个简单的益智游戏。 来源可从作者的 Github 仓库中获得。

Tweet

这个小游戏的目的是形成一张图片。 通过单击包含图像的按钮来移动它们。 只能移动与空按钮相邻的按钮。

在这个游戏中,我们学习了如何将图像裁剪成多个部分。

PuzzleEx.java

package com.zetcode;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class MyButton extends JButton {

    private boolean isLastButton;

    public MyButton() {

        super();

        initUI();
    }

    public MyButton(Image image) {

        super(new ImageIcon(image));

        initUI();
    }

    private void initUI() {

        isLastButton = false;
        BorderFactory.createLineBorder(Color.gray);

        addMouseListener(new MouseAdapter() {

            @Override
            public void mouseEntered(MouseEvent e) {
                setBorder(BorderFactory.createLineBorder(Color.yellow));
            }

            @Override
            public void mouseExited(MouseEvent e) {
                setBorder(BorderFactory.createLineBorder(Color.gray));
            }
        });
    }

    public void setLastButton() {

        isLastButton = true;
    }

    public boolean isLastButton() {

        return isLastButton;
    }
}

public class PuzzleEx extends JFrame {

    private JPanel panel;
    private BufferedImage source;
    private BufferedImage resized;
    private Image image;
    private MyButton lastButton;
    private int width, height;

    private List<MyButton> buttons;
    private List<Point> solution;

    private final int NUMBER_OF_BUTTONS = 12;
    private final int DESIRED_WIDTH = 300;

    public PuzzleEx() {

        initUI();
    }

    private void initUI() {

        solution = new ArrayList<>();

        solution.add(new Point(0, 0));
        solution.add(new Point(0, 1));
        solution.add(new Point(0, 2));
        solution.add(new Point(1, 0));
        solution.add(new Point(1, 1));
        solution.add(new Point(1, 2));
        solution.add(new Point(2, 0));
        solution.add(new Point(2, 1));
        solution.add(new Point(2, 2));
        solution.add(new Point(3, 0));
        solution.add(new Point(3, 1));
        solution.add(new Point(3, 2));

        buttons = new ArrayList<>();

        panel = new JPanel();
        panel.setBorder(BorderFactory.createLineBorder(Color.gray));
        panel.setLayout(new GridLayout(4, 3, 0, 0));

        try {
            source = loadImage();
            int h = getNewHeight(source.getWidth(), source.getHeight());
            resized = resizeImage(source, DESIRED_WIDTH, h,
                    BufferedImage.TYPE_INT_ARGB);

        } catch (IOException ex) {
            JOptionPane.showMessageDialog(this, "Could not load image", "Error",
                    JOptionPane.ERROR_MESSAGE);
        }

        width = resized.getWidth(null);
        height = resized.getHeight(null);

        add(panel, BorderLayout.CENTER);

        for (int i = 0; i < 4; i++) {

            for (int j = 0; j < 3; j++) {

                image = createImage(new FilteredImageSource(resized.getSource(),
                        new CropImageFilter(j * width / 3, i * height / 4,
                                (width / 3), height / 4)));

                var button = new MyButton(image);
                button.putClientProperty("position", new Point(i, j));

                if (i == 3 && j == 2) {

                    lastButton = new MyButton();
                    lastButton.setBorderPainted(false);
                    lastButton.setContentAreaFilled(false);
                    lastButton.setLastButton();
                    lastButton.putClientProperty("position", new Point(i, j));
                } else {

                    buttons.add(button);
                }
            }
        }

        Collections.shuffle(buttons);
        buttons.add(lastButton);

        for (int i = 0; i < NUMBER_OF_BUTTONS; i++) {

            var btn = buttons.get(i);
            panel.add(btn);
            btn.setBorder(BorderFactory.createLineBorder(Color.gray));
            btn.addActionListener(new ClickAction());
        }

        pack();

        setTitle("Puzzle");
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private int getNewHeight(int w, int h) {

        double ratio = DESIRED_WIDTH / (double) w;
        int newHeight = (int) (h * ratio);
        return newHeight;
    }

    private BufferedImage loadImage() throws IOException {

        var bimg = ImageIO.read(new File("src/resources/icesid.jpg"));

        return bimg;
    }

    private BufferedImage resizeImage(BufferedImage originalImage, int width,
                                      int height, int type) {

        var resizedImage = new BufferedImage(width, height, type);
        var g = resizedImage.createGraphics();
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();

        return resizedImage;
    }

    private class ClickAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {

            checkButton(e);
            checkSolution();
        }

        private void checkButton(ActionEvent e) {

            int lidx = 0;

            for (MyButton button : buttons) {
                if (button.isLastButton()) {
                    lidx = buttons.indexOf(button);
                }
            }

            var button = (JButton) e.getSource();
            int bidx = buttons.indexOf(button);

            if ((bidx - 1 == lidx) || (bidx + 1 == lidx)
                    || (bidx - 3 == lidx) || (bidx + 3 == lidx)) {
                Collections.swap(buttons, bidx, lidx);
                updateButtons();
            }
        }

        private void updateButtons() {

            panel.removeAll();

            for (JComponent btn : buttons) {

                panel.add(btn);
            }

            panel.validate();
        }
    }

    private void checkSolution() {

        var current = new ArrayList<Point>();

        for (JComponent btn : buttons) {
            current.add((Point) btn.getClientProperty("position"));
        }

        if (compareList(solution, current)) {
            JOptionPane.showMessageDialog(panel, "Finished",
                    "Congratulation", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    public static boolean compareList(List ls1, List ls2) {

        return ls1.toString().contentEquals(ls2.toString());
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var puzzle = new PuzzleEx();
            puzzle.setVisible(true);
        });
    }
}

我们使用的是冰河世纪电影中 Sid 角色的图像。 我们缩放图像并将其切成 12 张。 这些片段由JButton组件使用。 最后一块没有使用; 我们有一个空按钮。 您可以下载一些相当大的图片并在游戏中使用它。

addMouseListener(new MouseAdapter() {

    @Override
    public void mouseEntered(MouseEvent e) {
        setBorder(BorderFactory.createLineBorder(Color.yellow));
    }

    @Override
    public void mouseExited(MouseEvent e) {
        setBorder(BorderFactory.createLineBorder(Color.gray));

    }
});

当我们将鼠标指针悬停在按钮上时,其边框变为黄色。

public boolean isLastButton() {

    return isLastButton;
}

有一个按钮称为最后一个按钮。 它是没有图像的按钮。 其他按钮与此空间交换空间。

private final int DESIRED_WIDTH = 300;

我们用来形成的图像被缩放以具有所需的宽度。 使用getNewHeight()方法,我们可以计算新的高度,并保持图像的比例。

solution.add(new Point(0, 0));
solution.add(new Point(0, 1));
solution.add(new Point(0, 2));
solution.add(new Point(1, 0));
...

解决方案数组列表存储形成图像的按钮的正确顺序。 每个按钮由一个Point标识。

panel.setLayout(new GridLayout(4, 3, 0, 0));

我们使用GridLayout存储我们的组件。 布局由 4 行和 3 列组成。

image = createImage(new FilteredImageSource(resized.getSource(),
        new CropImageFilter(j * width / 3, i * height / 4,
                (width / 3), height / 4)));

CropImageFilter用于从已调整大小的图像源中切出矩形。 它旨在与FilteredImageSource对象结合使用,以生成现有图像的裁剪版本。

button.putClientProperty("position", new Point(i, j));

按钮由其position客户端属性标识。 这是包含按钮在图片中正确的行和列的位置的点。 这些属性用于确定窗口中按钮的顺序是否正确。

if (i == 3 && j == 2) {

    lastButton = new MyButton();
    lastButton.setBorderPainted(false);
    lastButton.setContentAreaFilled(false);
    lastButton.setLastButton();
    lastButton.putClientProperty("position", new Point(i, j));
} else {

    buttons.add(button);
}

没有图像的按钮称为最后一个按钮。 它放置在右下角网格的末端。 该按钮与被单击的相邻按钮交换位置。 我们使用setLastButton()方法设置其isLastButton标志。

Collections.shuffle(buttons);
buttons.add(lastButton);

我们随机重新排列buttons列表的元素。 最后一个按钮,即没有图像的按钮,被插入到列表的末尾。 它不应该被改组,它总是在我们开始益智游戏时结束。

for (int i = 0; i < NUMBER_OF_BUTTONS; i++) {

    var btn = buttons.get(i);
    panel.add(btn);
    btn.setBorder(BorderFactory.createLineBorder(Color.gray));
    btn.addActionListener(new ClickAction());
}    

buttons列表中的所有组件都放置在面板上。 我们在按钮周围创建一些灰色边框,并添加一个点击动作监听器。

private int getNewHeight(int w, int h) {

    double ratio = DESIRED_WIDTH / (double) w;
    int newHeight = (int) (h * ratio);
    return newHeight;
}

getNewHeight()方法根据所需的宽度计算图像的高度。 图像的比例保持不变。 我们使用这些值缩放图像。

private BufferedImage loadImage() throws IOException {

    var bimg = ImageIO.read(new File("src/resources/icesid.jpg"));

    return bimg;
}

从磁盘加载了 JPG 图像。 ImageIOread()方法返回BufferedImage,这是 Swing 处理图像的重要类。

private BufferedImage resizeImage(BufferedImage originalImage, int width,
        int height, int type) throws IOException {

    var resizedImage = new BufferedImage(width, height, type);
    var g = resizedImage.createGraphics();
    g.drawImage(originalImage, 0, 0, width, height, null);
    g.dispose();

    return resizedImage;
}

通过创建具有新大小的新BufferedImage来调整原始图像的大小。 我们将原始图像绘制到此新的缓冲图像中。

private void checkButton(ActionEvent e) {

    int lidx = 0;

    for (MyButton button : buttons) {
        if (button.isLastButton()) {
            lidx = buttons.indexOf(button);
        }
    }

    var button = (JButton) e.getSource();
    int bidx = buttons.indexOf(button);

    if ((bidx - 1 == lidx) || (bidx + 1 == lidx)
            || (bidx - 3 == lidx) || (bidx + 3 == lidx)) {
        Collections.swap(buttons, bidx, lidx);
        updateButtons();
    }
}

按钮存储在数组列表中。 然后,此列表将映射到面板的网格。 我们得到最后一个按钮和被单击按钮的索引。 如果它们相邻,则使用Collections.swap()进行交换。

private void updateButtons() {

    panel.removeAll();

    for (JComponent btn : buttons) {

        panel.add(btn);
    }

    panel.validate();
}

updateButtons()方法将列表映射到面板的网格。 首先,使用removeAll()方法删除所有组件。 for循环用于通过buttons列表,将重新排序的按钮添加回面板的布局管理器。 最后,validate()方法实现了新的布局。

private void checkSolution() {

    var current = new ArrayList<Point>();

    for (JComponent btn : buttons) {
        current.add((Point) btn.getClientProperty("position"));
    }

    if (compareList(solution, current)) {
        JOptionPane.showMessageDialog(panel, "Finished",
                "Congratulation", JOptionPane.INFORMATION_MESSAGE);
    }
}

通过将正确排序的按钮的点列表与包含窗口中按钮顺序的当前列表进行比较,来完成解决方案检查。 如果解决方案出现,则会显示一个消息对话框。

Puzzle

图:益智游戏

这是益智游戏。

{% raw %}

俄罗斯方块

http://zetcode.com/tutorials/javaswingtutorial/thetetrisgame/

在本章中,我们将在 Java Swing 中创建一个俄罗斯方块游戏克隆。

俄罗斯方块

俄罗斯方块游戏是有史以来最受欢迎的计算机游戏之一。 原始游戏是由俄罗斯程序员 Alexey Pajitnov 于 1985 年设计和编程的。此后,几乎所有版本的几乎所有计算机平台上都可以使用俄罗斯方块。

俄罗斯方块被称为下降块益智游戏。 在这个游戏中,我们有七个不同的形状,称为 tetrominoes:S 形,Z 形,T 形,L 形,线形,MirroredL 形和方形。 这些形状中的每一个都形成有四个正方形。 形状从板上掉下来。 俄罗斯方块游戏的目的是移动和旋转形状,以使其尽可能地适合。 如果我们设法形成一行,则该行将被破坏并得分。 我们玩俄罗斯方块游戏,直到达到顶峰。

Tetrominoes

图:Tetrominoes

开发

我们的俄罗斯方块游戏没有图像,我们使用 Swing 绘图 API 绘制四面体。 每个计算机游戏的背后都有一个数学模型。 俄罗斯方块也是如此。

游戏背后的一些想法。

  • 我们使用Timer类创建游戏周期
  • 绘制四方块
  • 形状以正方形为单位移动(不是逐个像素移动)
  • 从数学上讲,棋盘是简单的数字列表

游戏经过简化,因此更易于理解。 游戏启动后立即开始。 我们可以通过按 p 键暂停游戏。 空格键将俄罗斯方块放到底部。 d 键将棋子下降一行。 (它可以用来加快下降速度。)游戏以恒定速度运行,没有实现加速。 分数是我们已删除的行数。

com/zetcode/Tetris.java

package com.zetcode;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;

/*
Java Tetris game clone

Author: Jan Bodnar
Website: http://zetcode.com
 */
public class Tetris extends JFrame {

    private JLabel statusbar;

    public Tetris() {

        initUI();
    }

    private void initUI() {

        statusbar = new JLabel(" 0");
        add(statusbar, BorderLayout.SOUTH);

        var board = new Board(this);
        add(board);
        board.start();

        setTitle("Tetris");
        setSize(200, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    JLabel getStatusBar() {

        return statusbar;
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> {

            var game = new Tetris();
            game.setVisible(true);
        });
    }
}

Tetris中,我们设置了游戏。 我们创建一个玩游戏的棋盘。 我们创建一个状态栏。

board.start();

start()方法启动俄罗斯方块游戏。

com/zetcode/Shape.java

package com.zetcode;

import java.util.Random;

public class Shape {

    protected enum Tetrominoe {
        NoShape, ZShape, SShape, LineShape,
        TShape, SquareShape, LShape, MirroredLShape
    }

    private Tetrominoe pieceShape;
    private int coords[][];
    private int[][][] coordsTable;

    public Shape() {

        coords = new int[4][2];
        setShape(Tetrominoe.NoShape);
    }

    void setShape(Tetrominoe shape) {

        coordsTable = new int[][][]{
                {{0, 0}, {0, 0}, {0, 0}, {0, 0}},
                {{0, -1}, {0, 0}, {-1, 0}, {-1, 1}},
                {{0, -1}, {0, 0}, {1, 0}, {1, 1}},
                {{0, -1}, {0, 0}, {0, 1}, {0, 2}},
                {{-1, 0}, {0, 0}, {1, 0}, {0, 1}},
                {{0, 0}, {1, 0}, {0, 1}, {1, 1}},
                {{-1, -1}, {0, -1}, {0, 0}, {0, 1}},
                {{1, -1}, {0, -1}, {0, 0}, {0, 1}}
        };

        for (int i = 0; i < 4; i++) {

            System.arraycopy(coordsTable[shape.ordinal()], 0, coords, 0, 4);
        }

        pieceShape = shape;
    }

    private void setX(int index, int x) {
        coords[index][0] = x;
    }

    private void setY(int index, int y) {
        coords[index][1] = y;
    }

    int x(int index) {
        return coords[index][0];
    }

    int y(int index) {
        return coords[index][1];
    }

    Tetrominoe getShape() {
        return pieceShape;
    }

    void setRandomShape() {

        var r = new Random();
        int x = Math.abs(r.nextInt()) % 7 + 1;

        Tetrominoe[] values = Tetrominoe.values();
        setShape(values[x]);
    }

    public int minX() {

        int m = coords[0][0];

        for (int i = 0; i < 4; i++) {

            m = Math.min(m, coords[i][0]);
        }

        return m;
    }

    int minY() {

        int m = coords[0][1];

        for (int i = 0; i < 4; i++) {

            m = Math.min(m, coords[i][1]);
        }

        return m;
    }

    Shape rotateLeft() {

        if (pieceShape == Tetrominoe.SquareShape) {
            return this;
        }

        var result = new Shape();
        result.pieceShape = pieceShape;

        for (int i = 0; i < 4; ++i) {

            result.setX(i, y(i));
            result.setY(i, -x(i));
        }

        return result;
    }

    Shape rotateRight() {

        if (pieceShape == Tetrominoe.SquareShape) {
            return this;
        }

        var result = new Shape();
        result.pieceShape = pieceShape;

        for (int i = 0; i < 4; ++i) {

            result.setX(i, -y(i));
            result.setY(i, x(i));
        }

        return result;
    }
}

Shape提供有关俄罗斯方块的信息。

protected enum Tetrominoe {
    NoShape, ZShape, SShape, LineShape,
    TShape, SquareShape, LShape, MirroredLShape
}

Tetrominoe拥有七个俄罗斯方块形状名称,而空的形状称为NoShape

public Shape() {

    coords = new int[4][2];
    setShape(Tetrominoe.NoShape);
}

这是Shape类的构造器。 coords数组保存俄罗斯方块的实际坐标。

coordsTable = new int[][][] {
   { { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 } },
   { { 0, -1 },  { 0, 0 },   { -1, 0 },  { -1, 1 } },
   { { 0, -1 },  { 0, 0 },   { 1, 0 },   { 1, 1 } },
   { { 0, -1 },  { 0, 0 },   { 0, 1 },   { 0, 2 } },
   { { -1, 0 },  { 0, 0 },   { 1, 0 },   { 0, 1 } },
   { { 0, 0 },   { 1, 0 },   { 0, 1 },   { 1, 1 } },
   { { -1, -1 }, { 0, -1 },  { 0, 0 },   { 0, 1 } },
   { { 1, -1 },  { 0, -1 },  { 0, 0 },   { 0, 1 } }
};

coordsTable数组保存我们的俄罗斯方块的所有可能的坐标值。 这是一个模板,所有零件均从该模板获取其坐标值。

for (int i = 0; i < 4; i++) {

    System.arraycopy(coordsTable[shape.ordinal()], 0, coords, 0, 4);
}

在这里,我们将一行坐标值从coordsTable复制到俄罗斯方块的coords数组。 请注意ordinal()方法的使用。 在 C++ 中,枚举类型本质上是一个整数。 与 C++ 不同,Java 枚举是完整类,ordinal()方法返回枚举类型在枚举对象中的当前位置。

Coordinates

图:坐标

该图像有助于进一步了解坐标值。 coords数组保存俄罗斯方块的坐标。 下图说明了旋转的 S 形。 它具有以下坐标:(-1,1),(-1,0),(0,0)和(0,-1)。

Shape rotateRight() {

    if (pieceShape == Tetrominoe.SquareShape) {
        return this;
    }

    var result = new Shape();
    result.pieceShape = pieceShape;

    for (int i = 0; i < 4; ++i) {

        result.setX(i, -y(i));
        result.setY(i, x(i));
    }

    return result;
}

此代码将棋子向右旋转。 正方形不必旋转。 这就是为什么在Tetrominoe.SquareShape的情况下我们仅将引用返回给当前对象的原因。 查看上一张图片将有助于进一步了解旋转情况。

com/zetcode/Board.java

package com.zetcode;

import com.zetcode.Shape.Tetrominoe;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

public class Board extends JPanel implements ActionListener {

    private final int BOARD_WIDTH = 10;
    private final int BOARD_HEIGHT = 22;

    private Timer timer;
    private boolean isFallingFinished = false;
    private boolean isStarted = false;
    private boolean isPaused = false;
    private int numLinesRemoved = 0;
    private int curX = 0;
    private int curY = 0;
    private JLabel statusbar;
    private Shape curPiece;
    private Tetrominoe[] board;

    public Board(Tetris parent) {

        initBoard(parent);
    }

    private void initBoard(Tetris parent) {

        setFocusable(true);
        curPiece = new Shape();
        int DELAY = 400;
        timer = new Timer(DELAY, this);
        timer.start();

        statusbar =  parent.getStatusBar();
        board = new Tetrominoe[BOARD_WIDTH * BOARD_HEIGHT];
        addKeyListener(new TAdapter());
        clearBoard();
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        if (isFallingFinished) {

            isFallingFinished = false;
            newPiece();
        } else {

            oneLineDown();
        }
    }

    private int squareWidth() { return (int) getSize().getWidth() / BOARD_WIDTH; }
    private int squareHeight() { return (int) getSize().getHeight() / BOARD_HEIGHT; }
    private Tetrominoe shapeAt(int x, int y) { return board[(y * BOARD_WIDTH) + x]; }

    void start()  {

        if (isPaused) {
            return;
        }

        isStarted = true;
        isFallingFinished = false;
        numLinesRemoved = 0;
        clearBoard();

        newPiece();
        timer.start();
    }

    private void pause()  {

        if (!isStarted) {
            return;
        }

        isPaused = !isPaused;

        if (isPaused) {

            timer.stop();
            statusbar.setText("paused");
        } else {

            timer.start();
            statusbar.setText(String.valueOf(numLinesRemoved));
        }

        repaint();
    }

    private void doDrawing(Graphics g) {

        var size = getSize();
        int boardTop = (int) size.getHeight() - BOARD_HEIGHT * squareHeight();

        for (int i = 0; i < BOARD_HEIGHT; ++i) {

            for (int j = 0; j < BOARD_WIDTH; ++j) {

                Tetrominoe shape = shapeAt(j, BOARD_HEIGHT - i - 1);

                if (shape != Tetrominoe.NoShape)
                    drawSquare(g, j * squareWidth(),
                            boardTop + i * squareHeight(), shape);
            }
        }

        if (curPiece.getShape() != Tetrominoe.NoShape) {

            for (int i = 0; i < 4; ++i) {

                int x = curX + curPiece.x(i);
                int y = curY - curPiece.y(i);

                drawSquare(g, x * squareWidth(),
                        boardTop + (BOARD_HEIGHT - y - 1) * squareHeight(),
                        curPiece.getShape());
            }
        }
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }

    private void dropDown() {

        int newY = curY;

        while (newY > 0) {

            if (!tryMove(curPiece, curX, newY - 1)) {
                break;
            }

            --newY;
        }

        pieceDropped();
    }

    private void oneLineDown()  {

        if (!tryMove(curPiece, curX, curY - 1)) {
            pieceDropped();
        }
    }

    private void clearBoard() {

        for (int i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; ++i) {
            board[i] = Tetrominoe.NoShape;
        }
    }

    private void pieceDropped() {

        for (int i = 0; i < 4; ++i) {

            int x = curX + curPiece.x(i);
            int y = curY - curPiece.y(i);
            board[(y * BOARD_WIDTH) + x] = curPiece.getShape();
        }

        removeFullLines();

        if (!isFallingFinished) {
            newPiece();
        }
    }

    private void newPiece()  {

        curPiece.setRandomShape();
        curX = BOARD_WIDTH / 2 + 1;
        curY = BOARD_HEIGHT - 1 + curPiece.minY();

        if (!tryMove(curPiece, curX, curY)) {

            curPiece.setShape(Tetrominoe.NoShape);
            timer.stop();
            isStarted = false;
            statusbar.setText("game over");
        }
    }

    private boolean tryMove(Shape newPiece, int newX, int newY) {

        for (int i = 0; i < 4; ++i) {

            int x = newX + newPiece.x(i);
            int y = newY - newPiece.y(i);

            if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
                return false;

            if (shapeAt(x, y) != Tetrominoe.NoShape)
                return false;
        }

        curPiece = newPiece;
        curX = newX;
        curY = newY;

        repaint();

        return true;
    }

    private void removeFullLines() {

        int numFullLines = 0;

        for (int i = BOARD_HEIGHT - 1; i >= 0; --i) {

            boolean lineIsFull = true;

            for (int j = 0; j < BOARD_WIDTH; ++j) {

                if (shapeAt(j, i) == Tetrominoe.NoShape) {
                    lineIsFull = false;
                    break;
                }
            }

            if (lineIsFull) {

                ++numFullLines;

                for (int k = i; k < BOARD_HEIGHT - 1; ++k) {
                    for (int j = 0; j < BOARD_WIDTH; ++j)
                        board[(k * BOARD_WIDTH) + j] = shapeAt(j, k + 1);
                }
            }
        }

        if (numFullLines > 0) {

            numLinesRemoved += numFullLines;
            statusbar.setText(String.valueOf(numLinesRemoved));
            isFallingFinished = true;
            curPiece.setShape(Tetrominoe.NoShape);
            repaint();
        }
    }

    private void drawSquare(Graphics g, int x, int y, Tetrominoe shape)  {

        Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102),
                new Color(102, 204, 102), new Color(102, 102, 204),
                new Color(204, 204, 102), new Color(204, 102, 204),
                new Color(102, 204, 204), new Color(218, 170, 0)
        };

        var color = colors[shape.ordinal()];

        g.setColor(color);
        g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);

        g.setColor(color.brighter());
        g.drawLine(x, y + squareHeight() - 1, x, y);
        g.drawLine(x, y, x + squareWidth() - 1, y);

        g.setColor(color.darker());
        g.drawLine(x + 1, y + squareHeight() - 1,
                x + squareWidth() - 1, y + squareHeight() - 1);
        g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
                x + squareWidth() - 1, y + 1);

    }

    class TAdapter extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {

            if (!isStarted || curPiece.getShape() == Tetrominoe.NoShape) {
                return;
            }

            int keycode = e.getKeyCode();

            if (keycode == 'P') {
                pause();
                return;
            }

            if (isPaused) {
                return;
            }

            switch (keycode) {

                case KeyEvent.VK_LEFT:
                    tryMove(curPiece, curX - 1, curY);
                    break;

                case KeyEvent.VK_RIGHT:
                    tryMove(curPiece, curX + 1, curY);
                    break;

                case KeyEvent.VK_DOWN:
                    tryMove(curPiece.rotateRight(), curX, curY);
                    break;

                case KeyEvent.VK_UP:
                    tryMove(curPiece.rotateLeft(), curX, curY);
                    break;

                case KeyEvent.VK_SPACE:
                    dropDown();
                    break;

                case KeyEvent.VK_D:
                    oneLineDown();
                    break;
            }
        }
    }
}

游戏逻辑位于Board中。

private final int BOARD_WIDTH = 10;
private final int BOARD_HEIGHT = 22;

BOARD_WIDTHBOARD_HEIGHT常数定义电路板的大小。

...
private boolean isFallingFinished = false;
private boolean isStarted = false;
private boolean isPaused = false;
private int numLinesRemoved = 0;
private int curX = 0;
private int curY = 0;
...

我们初始化一些重要的变量。 isFallingFinished变量确定俄罗斯方块形状是否已完成下降,然后我们需要创建一个新形状。 numLinesRemoved计算行数,到目前为止我们已经删除了行数。 curXcurY变量确定下降的俄罗斯方块形状的实际位置。

setFocusable(true);

我们必须显式调用setFocusable()方法。 从现在开始,开发板具有键盘输入。

int DELAY = 400;

DELAY常数定义游戏的速度。

timer = new Timer(DELAY, this);
timer.start();

在指定的延迟后,Timer对象将触发一个或多个操作事件。 在我们的情况下,计时器每DELAY ms 调用一次actionPerformed()方法。

@Override
public void actionPerformed(ActionEvent e) {

    if (isFallingFinished) {

        isFallingFinished = false;
        newPiece();
    } else {

        oneLineDown();
    }
}

actionPerformed()方法检查下降是否已完成。 如果是这样,将创建一个新作品。 如果不是,下降的俄罗斯方块下降了一行。

doDrawing()方法内部,我们在板上绘制了所有对象。 这幅画有两个步骤。

for (int i = 0; i < BOARD_HEIGHT; ++i) {

    for (int j = 0; j < BOARD_WIDTH; ++j) {

        Tetrominoe shape = shapeAt(j, BOARD_HEIGHT - i - 1);

        if (shape != Tetrominoe.NoShape)
            drawSquare(g, j * squareWidth(),
                    boardTop + i * squareHeight(), shape);
    }
}

在第一步中,我们绘制掉落到板底部的所有形状或形状的其余部分。 所有正方形都记在板数组中。 我们使用shapeAt()方法访问它。

if (curPiece.getShape() != Tetrominoe.NoShape) {

    for (int i = 0; i < 4; ++i) {

        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);

        drawSquare(g, 0 + x * squareWidth(),
                   boardTop + (BOARD_HEIGHT - y - 1) * squareHeight(),
                   curPiece.getShape());
    }
}

在第二步中,我们绘制实际的下降部分。

private void dropDown() {

    int newY = curY;

    while (newY > 0) {

        if (!tryMove(curPiece, curX, newY - 1)) {
            break;
        }

        --newY;
    }

    pieceDropped();
}

如果我们按 Space 键,则该片段将落到底部。 我们只是简单地尝试将一块下降到另一条俄罗斯方块下降的底部或顶部。 当俄罗斯方块结束下降时,将调用pieceDropped()

private void clearBoard() {

    for (int i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; ++i) {
        board[i] = Tetrominoe.NoShape;
    }
}

clearBoard()方法用空的NoShapes填充电路板。

private void pieceDropped() {

    for (int i = 0; i < 4; ++i) {

        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);
        board[(y * BOARD_WIDTH) + x] = curPiece.getShape();
    }

    removeFullLines();

    if (!isFallingFinished) {
        newPiece();
    }
}

pieceDropped()方法将下降的片段放入board数组。 棋盘再次保持了所有碎片的正方形和已经落下的碎片的剩余部分。 当一块完成落下时,就该检查是否可以去除板上的一些线了。 这是removeFullLines()方法的工作。 然后,我们尝试创建一个新作品。

private void newPiece()  {

    curPiece.setRandomShape();
    curX = BOARD_WIDTH / 2 + 1;
    curY = BOARD_HEIGHT - 1 + curPiece.minY();

    if (!tryMove(curPiece, curX, curY)) {

        curPiece.setShape(Tetrominoe.NoShape);
        timer.stop();
        isStarted = false;
        statusbar.setText("game over");
    }
}

newPiece()方法创建一个新的俄罗斯方块。 作品获得了新的随机形状。 然后,我们计算初始curXcurY值。 如果我们不能移动到初始位置,则游戏结束。 我们加油。 计时器停止。 我们将游戏放在状态栏上的字符串上方。

private boolean tryMove(Shape newPiece, int newX, int newY) {

    for (int i = 0; i < 4; ++i) {

        int x = newX + newPiece.x(i);
        int y = newY - newPiece.y(i);

        if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
            return false;
        }

        if (shapeAt(x, y) != Tetrominoe.NoShape) {
            return false;
        }
    }

    curPiece = newPiece;
    curX = newX;
    curY = newY;

    repaint();

    return true;
}

tryMove()方法尝试移动俄罗斯方块。 如果该方法已达到板边界或与已经跌落的俄罗斯方块相邻,则返回false

int numFullLines = 0;

for (int i = BOARD_HEIGHT - 1; i >= 0; --i) {

    boolean lineIsFull = true;

    for (int j = 0; j < BOARD_WIDTH; ++j) {

        if (shapeAt(j, i) == Tetrominoe.NoShape) {
            lineIsFull = false;
            break;
        }
    }

    if (lineIsFull) {

        ++numFullLines;

        for (int k = i; k < BOARD_HEIGHT - 1; ++k) {
            for (int j = 0; j < BOARD_WIDTH; ++j)
                board[(k * BOARD_WIDTH) + j] = shapeAt(j, k + 1);
        }
    }
}

removeFullLines()方法内部,我们检查board的所有行中是否有完整的行。 如果至少有一条实线,则将其删除。 找到整条线后,我们增加计数器。 我们将整行上方的所有行向下移动一行。 这样我们就破坏了整个生产线。 注意,在我们的俄罗斯方块游戏中,我们使用了所谓的朴素重力。 这意味着正方形可能会漂浮在空白间隙上方。

每个俄罗斯方块都有四个正方形。 每个正方形都使用drawSquare()方法绘制。 俄罗斯方块有不同的颜色。

g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);

正方形的左侧和顶部以较亮的颜色绘制。 类似地,底部和右侧用较深的颜色绘制。 这是为了模拟 3D 边缘。

我们使用键盘控制游戏。 控制机制通过KeyAdapter实现。 这是一个覆盖keyPressed()方法的内部类。

case KeyEvent.VK_LEFT:
    tryMove(curPiece, curX - 1, curY);
    break;

如果按向左箭头键,则尝试将下降片向左移动一个正方形。

Tetris

图:俄罗斯方块

这是俄罗斯方块游戏。

{% endraw %}

JavaFX 教程

原文: http://zetcode.com/gui/javafx/

这是一个 JavaFX 教程。 JavaFX 教程适合初学者和中级 Java 开发者。 阅读本教程后,您将能够开发非平凡的 JavaFX 应用。

目录

JavaFX

JavaFX 是用于开发和交付可在各种设备上运行的互联网富应用(RIA)的软件平台。 JavaFX 是用于 Java 平台的下一代 GUI 工具包。

相关教程

Java Swing 教程涵盖了 Swing。 高级 Java Swing 电子书涵盖了高级 Java Swing 主题。 Java SWT 教程涵盖了第三方 SWT 工具包。 Java 2D 游戏教程Java 2D 教程进一步增强了您对 Java 图形编程的了解。 Java 教程教授 Java 语言的基础知识。

JavaFX 简介

原文: http://zetcode.com/gui/javafx/intro/

这是 JavaFX 入门教程。 本教程的目的是帮助您开始使用 JavaFX。 该教程已在 Linux 上创建并测试。

关于 JavaFX

JavaFX 是用于开发和交付可在多种设备上运行的互联网富应用(RIA)的软件平台。 JavaFX 是用于 Java 平台的下一代 GUI 工具包。 它与 Java SE Runtime Environment(JRE)和 Java Development Kit(JDK)的最新版本完全集成。

JavaFX 具有以下主要部分:

  • Prism
  • Glass 窗口工具包
  • 媒体引擎
  • 网页引擎

Prism 是用于 2D 和 3D 图形的高性能图形引擎。Glass 窗口工具箱是一个依赖于平台的层,将 JavaFX 连接到本机操作系统。 它提供本机操作系统服务,例如管理窗口,事件,计时器和表面。媒体引擎提供了用于创建媒体应用的工具,这些应用允许在支持的平台上的桌面窗口或网页内播放媒体。Web 引擎是一种网络浏览器引擎,支持 HTML5,CSS,JavaScript,DOM 和 SVG。

JavaFX 应用性能分析

Application是 JavaFX 程序的主要类。 每个 JavaFX 程序必须扩展Application类。 其start()方法是应用的主要入口点; 这是系统准备就绪后要调用的第一个方法。 JavaFX 应用中不需要main()方法; 当在某些情况下无法启动应用时,它可以用作备用。

JavaFX 应用由StageScene组成。 Stage是顶级容器,是应用的主窗口。 (对于嵌入在 Web 浏览器中的应用,它是主要的矩形区域。)SceneStage可视内容的容器。 Scene's内容被组织在场景图中。 这两个术语反映了从桌面应用到更通用的富互联网应用的转变。

场景图

场景图是节点的分层树,代表应用用户界面的所有可视元素。 场景图中的单个元素称为节点。 每个节点都是分支节点或叶节点。 分支节点可以包含其他节点(它们的子节点)。 叶节点不包含其他节点。 树中的第一个节点称为根节点; 根节点没有父节点。

节点的具体实现包括图形基元,控件,布局管理器,图像或媒体。 可以通过修改节点属性来操纵场景。 这样,我们可以为节点设置动画,应用效果,进行变换或更改其不透明度。

构建 JavaFX 应用

NetBeans IDE 具有 JavaFX 项目类别。 可通过菜单栏中的文件,新建项目或通过 Ctrl + Shift + N 键盘快捷键访问该文件。

JavaFX project category in NetBeans

图:NetBeans 中的 JavaFX 项目类别

首次申请

在本节中,我们将介绍一个简单的 JavaFX 应用。

FirstEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;

/**
 * ZetCode Java SWT tutorial
 *
 * This program shows a label control in
 * the middle of the main window.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class FirstEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Scene scene = new Scene(root, 300, 250);

        Label lbl = new Label("Simple JavaFX application.");
        lbl.setFont(Font.font("Serif", FontWeight.NORMAL, 20));
        root.getChildren().add(lbl);

        stage.setTitle("Simple application");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例在应用窗口的中间显示了一个文本。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;

基本的 JavaFX 类,集合和属性驻留在javafx包中。

public class FirstEx extends Application {

Application是 JavaFX 程序的主要类。

@Override
public void start(Stage stage) {

    initUI(stage);
}

Applicationstart()方法被覆盖。 start()方法是 JavaFX 程序的主要入口点。 它接收Stage作为其唯一参数。 (Stage是主应用窗口或区域。)用户界面是通过initUI()方法构建的。

StackPane root = new StackPane();

StackPane是用于组织节点的容器。 它使用一个简单的布局管理器,将其内容节点放置在从后到前的单个栈中。 在我们的情况下,我们只想将单个节点居中。

Scene scene = new Scene(root, 300, 250);

Scene是场景图中所有内容的容器。 它以根节点为第一个参数。 StackPane是此场景图中的根节点。 接下来的两个参数指定场景的宽度和高度。

Label lbl = new Label("Simple JavaFX application.");
lbl.setFont(Font.font("Serif", FontWeight.NORMAL, 20));

创建一个Label控件,并使用setFont()方法设置其字体。 Label是不可编辑的文本控件。

root.getChildren().add(lbl);

标签控件被添加到StackPane中。 getChildren()方法返回窗格的子级列表。

stage.setTitle("Simple application");

StagesetTitle()方法为主窗口设置标题。

stage.setScene(scene);

使用setScene()方法将场景添加到舞台。

stage.show();

show()方法在屏幕上显示窗口。

public static void main(String[] args) {
    launch(args);
}

不需要传统的main()方法。 它仅在 JavaFX 启动无法正常工作的情况下用作备用。

First JavaFX application

图:第一个 JavaFX 应用

Swing 和 SWT

Swing 是 Java 的第一个主要 GUI 工具包。 它是一个健壮且灵活的 GUI 库。 Swing 在企业应用中很流行。 创建 JavaFX 的动机之一是很难使 Swing 适应用户界面的新趋势。 因此,决定将 JavaFX 创建为一个全新的工具箱。

标准窗口小部件工具箱(SWT)是 Java 的第三方 GUI 库。 SWT 使用 Windows API 或 GTK+ 之类的本地 GUI API 通过 Java 本机接口(JNI)创建其小部件。 与 Swing 和 JavaFX 不同,SWT 不是 JDK 的一部分。 它可以作为外部 JAR 文件使用。 SWT 最初是由 IBM 公司开发的。 现在,它是一个由 Eclipse 社区维护的开源项目。

这是 JavaFX 的简介。

JavaFX 首个程序

原文: http://zetcode.com/gui/javafx/firstprograms/

在本章中,我们将创建一些基本的 JavaFX 程序。

退出按钮

在下面的示例中,我们有一个Button控件。 当我们单击按钮时,应用终止。 按下并释放按钮时,将发送ActionEvent

QuitButtonEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program has a Quit button. Clicking
 * on the button terminates the application.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class QuitButtonEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Button btn = new Button();
        btn.setText("Quit");
        btn.setOnAction((ActionEvent event) -> {
            Platform.exit();
        });

        HBox root = new HBox();
        root.setPadding(new Insets(25));
        root.getChildren().add(btn);

        Scene scene = new Scene(root, 280, 200);

        stage.setTitle("Quit button");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Button控件位于窗口的左上角。 事件处理器将添加到按钮。

Button btn = new Button();
btn.setText("Quit");

实例化Button控件。 setText()方法设置按钮的标签。

btn.setOnAction((ActionEvent event) -> {
    Platform.exit();
});

setOnAction()方法设置按钮的动作,每当触发按钮时都会调用该动作。 上面的代码创建了一个匿名事件处理器。 Platform.exit()终止应用。

HBox root = new HBox();
root.setPadding(new Insets(25));

HBox是将其子级布置在单个水平行中的窗格。 setPadding()方法在窗格内容周围创建填充。 (默认填充为Insets.EMPTY。)这样,按钮和窗口边框的边缘之间会留有一些空间。

root.getChildren().add(btn);

该按钮将添加到HBox窗格。

Quit button

图:退出按钮

工具提示

任何节点都可以显示工具提示。 Tooltip是常见的 UI 元素,通常用于显示有关场景图中节点的其他信息。 当我们将鼠标指针悬停在节点上时,将显示该图标。

TooltipEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a tooltip for 
 * a button control.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class TooltipEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();
        root.setPadding(new Insets(20));

        Button btn = new Button("Button");
        Tooltip tooltip = new Tooltip("Button control");
        Tooltip.install(btn, tooltip);

        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Tooltip");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们将工具提示设置为按钮控件。

Button btn = new Button("Button");

实例化Button控件。

Tooltip tooltip = new Tooltip("Button control");
Tooltip.install(btn, tooltip);

创建一个Tooltip并将其设置为通过Tooltipinstall()方法设置的按钮。

Tooltip

图:工具提示

助记符

助记符是激活支持助记符的控件的快捷键。 例如,它们可以与标签,按钮或菜单项一起使用。

助记符是通过在控件的标签上添加_字符来创建的。 它使下一个字符成为助记符。 字符与无鼠标修饰符(通常为 Alt )结合在一起。 选择的字符带有下划线,但是可以以平台特定的方式强调。 在某些平台上,仅在按下无鼠标修饰符后才对字符加下划线。

MnemonicEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a mnemonic for 
 * a button control.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class MnemonicEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();
        root.setPadding(new Insets(20));

        Button btn = new Button("_Button");
        btn.setOnAction((ActionEvent event) -> {
            System.out.println("Button fired");
        });

        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Mnemonic");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

我们为按钮控件设置了一个助记符。 可以使用 Alt + B 键盘快捷键激活。

Button btn = new Button("_Button");

在按钮的标签中,_字符位于B字符之前; 因此,B字符带有下划线,并包含在键盘快捷键中。

btn.setOnAction((ActionEvent event) -> {
    System.out.println("Button fired");
});

触发按钮后,它将消息发送到控制台。

目前,有三种激活按钮的方式:单击鼠标左键, Alt + B 快捷方式以及空格键 按钮具有焦点)。

设置控件样式

JavaFX 中的控件可以使用 CSS 设置样式。

text.css

#root {-fx-background-color: linear-gradient(gray, darkgray); }
#text {-fx-fill:linear-gradient(orange, orangered); }  

此 CSS 文件为根节点和Text节点创建样式。

StylingTextEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program styles a Text control with CSS.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class StylingTextEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();
        root.setPadding(new Insets(20));

        Text text = new Text("ZetCode");
        text.setFont(Font.font("Serif", FontWeight.BOLD, 76));

        text.setId("text");
        root.setId("root");

        root.getChildren().addAll(text);

        Scene scene = new Scene(root);
        scene.getStylesheets().add(this.getClass().getResource("text.css")
                .toExternalForm());

        stage.setTitle("Styling text");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例为根节点创建背景渐变颜色,并为Text控件创建线性渐变填充。

Text text = new Text("ZetCode");
text.setFont(Font.font("Serif", FontWeight.BOLD, 76));

创建一个Text控件。 较大的粗体衬线字体设置为控件。

text.setId("text");
root.setId("root");

节点由其 ID 标识,该 ID 由setId()方法设置。

scene.getStylesheets().add(this.getClass().getResource("text.css")
        .toExternalForm());

样式表已添加到Scene中。

Styled Text control

图:样式文本控件

在本章中,我们创建了一些简单的 JavaFX 程序。

JavaFX 布局窗格

原文: http://zetcode.com/gui/javafx/layoutpanes/

JavaFX 教程的这一部分涵盖了节点的布局管理。 我们提到了以下布局窗格:FlowPaneHBoxBorderPaneAnchorPaneGridPaneMigPane。 另外,我们展示了如何使用Pane在绝对坐标中定位节点。

布局窗格是用于在 JavaFX 应用的场景图中灵活,动态地排列 UI 控件的容器。 调整窗口大小时,布局窗格会自动调整其位置和大小。

JavaFX 具有以下内置布局窗格:

  • FlowPane – 在环绕在窗格边界上的流中布置其子项。
  • HBox – 将其内容节点水平排列在一行中。
  • VBox – 将其内容节点垂直排列在单个列中。
  • AnchorPane – 将节点锚定到窗格的顶部,底部,左侧或中心。
  • BorderPane – 将其内容节点布置在顶部,底部,右侧,左侧或中央区域。
  • StackPane – 将其内容节点放置在从后到前的单个栈中。
  • TilePane – 将其内容节点放置在大小统​​一的布局单元或图块中。
  • GridPane – 将其内容节点放置在行和列的网格中。

为了创建更复杂的布局,可以在 JavaFX 应用中嵌套不同的容器。 除了GridPane之外,内置的布局管理器是非常基本的,不适用于更复杂的应用。 更复杂的布局应使用GridPane或第三方MigPane

绝对布局

Pane节点可用于在绝对坐标中定位节点。 复杂的布局应始终使用布局管理器创建; 绝对布局用于特定情况(例如,定位图或图像)。

AbsoluteLayoutEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program positions three shapes
 * using absolute coordinates.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class AbsoluteLayoutEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Rectangle rect = new Rectangle(25, 25, 50, 50);
        rect.setFill(Color.CADETBLUE);

        Line line = new Line(90, 40, 230, 40);
        line.setStroke(Color.BLACK);

        Circle circle = new Circle(130, 130, 30);
        circle.setFill(Color.CHOCOLATE);

        root.getChildren().addAll(rect, line, circle);

        Scene scene = new Scene(root, 250, 220, Color.WHITESMOKE);

        stage.setTitle("Absolute layout");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

此示例显示了三种形状:矩形,直线和圆形。 使用绝对坐标定位形状。

Pane root = new Pane();

实例化了Pane节点。 要在绝对坐标中定位节点,我们使用Pane节点。

Rectangle rect = new Rectangle(25, 25, 50, 50);

创建一个Rectangle形状。 前两个参数是 x 和 y 坐标,后两个参数是矩形的宽度和高度。 左上角的矩形从其父节点的x = 25y = 25开始。

Line line = new Line(90, 40, 230, 40);
line.setStroke(Color.BLACK);

Circle circle = new Circle(130, 130, 30);
circle.setFill(Color.CHOCOLATE);

LineCircle形状在其构造器中采用绝对坐标值。 线的颜色通过setStroke()方法更改,圆内部的颜色通过setFill()方法更改。

root.getChildren().addAll(rect, line, circle);

所有这三个形状都添加到根节点。

Absolute positioning

图:绝对定位

FlowPane

FlowPane将节点放置在行或列中,当所有节点都无法显示时,将其包裹起来。 流窗格的默认方向为水平。 FlowPane的用法非常有限。

FlowPaneEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a FlowPane to position 
 * twenty buttons.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class FlowPaneEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        FlowPane root = new FlowPane(Orientation.HORIZONTAL, 5, 5);
        root.setPadding(new Insets(5));

        for (int i=1; i<=20; i++) {
            root.getChildren().add(new Button(String.valueOf(i)));
        }

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("FlowPane");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们在FlowPane中放置了二十个按钮。 如果无法将所有按钮都显示在一行中,则将它们包装在其他行中。

FlowPane root = new FlowPane(Orientation.HORIZONTAL, 5, 5);

将创建水平FlowPane。 第二个和第三个参数指定窗格中节点之间的水平和垂直间隙。

root.setPadding(new Insets(5));

setPadding()方法在窗格周围设置一些空间。

for (int i=1; i<=20; i++) {
    root.getChildren().add(new Button(String.valueOf(i)));
}

二十个按钮将添加到流窗格中。 这些按钮显示整数值。

FlowPane

图:FlowPane

HBox

HBox将其子级布置在单个水平行中。 该窗格与其他布局管理器一起使用以创建布局。 它适合于进行基本布局。

RowOfButtonsEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program shows four buttons in 
 * a right-aligned, horizontal row with a HBox.
 * 
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class RowOfButtonsEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox(5);
        root.setPadding(new Insets(10));
        root.setAlignment(Pos.BASELINE_RIGHT);

        Button prevBtn = new Button("Previous");
        Button nextBtn = new Button("Next");
        Button cancBtn = new Button("Cancel");
        Button helpBtn = new Button("Help");

        root.getChildren().addAll(prevBtn, nextBtn, cancBtn, helpBtn);

        Scene scene = new Scene(root);
        stage.setTitle("Row of buttons");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例在一行中显示了四个按钮。 该行右对齐。 按钮之间有一些空间。

HBox root = new HBox(5);

HBox窗格以一定的间距创建。

root.setPadding(new Insets(10));

我们在HBox周围创建一些填充

root.setAlignment(Pos.BASELINE_RIGHT);

setAlignment()方法将节点右对齐。

root.getChildren().addAll(prevBtn, nextBtn, cancBtn, helpBtn);

这些按钮将添加到容器中。

A row of buttons created with a HBox

图:使用HBox创建的一排按钮

BorderPane

BorderPane将子项放在顶部,左侧,右侧,底部和中央位置。 它可以用来创建经典外观的应用布局。

BorderPaneEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program places five labels into
 * the BorderPane's five areas.
 * 
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

class MyLabel extends Label {

    public MyLabel(String text) {
        super(text);

        setAlignment(Pos.BASELINE_CENTER);
    }
}

public class BorderPaneEx extends Application {

    private BorderPane root;
    private final int SIZE = 60;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        root = new BorderPane();

        root.setTop(getTopLabel());
        root.setBottom(getBottomLabel());
        root.setLeft(getLeftLabel());
        root.setRight(getRightLabel());
        root.setCenter(getCenterLabel());

        Scene scene = new Scene(root, 350, 300);

        stage.setTitle("BorderPane");
        stage.setScene(scene);
        stage.show();
    }

    private Label getTopLabel() {

        Label lbl = new MyLabel("Top");
        lbl.setPrefHeight(SIZE);
        lbl.prefWidthProperty().bind(root.widthProperty());
        lbl.setStyle("-fx-border-style: dotted; -fx-border-width: 0 0 1 0;"
                + "-fx-border-color: gray; -fx-font-weight: bold");        

        return lbl;
    }

    private Label getBottomLabel() {

        Label lbl = new MyLabel("Bottom");
        lbl.setPrefHeight(SIZE);
        lbl.prefWidthProperty().bind(root.widthProperty());
        lbl.setStyle("-fx-border-style: dotted; -fx-border-width: 1 0 0 0;"
                + "-fx-border-color: gray; -fx-font-weight: bold");

        return lbl;
    }

    private Label getLeftLabel() {

        Label lbl = new MyLabel("Left");
        lbl.setPrefWidth(SIZE);
        lbl.prefHeightProperty().bind(root.heightProperty().subtract(2*SIZE));
        lbl.setStyle("-fx-border-style: dotted; -fx-border-width: 0 1 0 0;"
                + "-fx-border-color: gray; -fx-font-weight: bold");

        return lbl;
    }

    private Label getRightLabel() {

        Label lbl = new MyLabel("Right");
        lbl.setPrefWidth(SIZE);
        lbl.prefHeightProperty().bind(root.heightProperty().subtract(2*SIZE));
        lbl.setStyle("-fx-border-style: dotted; -fx-border-width: 0 0 0 1;"
                + "-fx-border-color: gray; -fx-font-weight: bold");

        return lbl;
    }

    private Label getCenterLabel() {

        Label lbl = new MyLabel("Center");
        lbl.setStyle("-fx-font-weight: bold");
        lbl.prefHeightProperty().bind(root.heightProperty().subtract(2*SIZE));
        lbl.prefWidthProperty().bind(root.widthProperty().subtract(2*SIZE));

        return lbl;
    }    

    public static void main(String[] args) {
        launch(args);
    }
}

该示例将五个标签放置在五个BorderPane's区域中。

root.setTop(getTopLabel());
root.setBottom(getBottomLabel());
root.setLeft(getLeftLabel());
root.setRight(getRightLabel());
root.setCenter(getCenterLabel());

使用setTop()setBottom()setLeft()setRight()setCenter()方法定位节点。

Label lbl = new MyLabel("Top");
lbl.setPrefHeight(SIZE);

在这里,我们使用setPrefHeight()方法增加顶部标签的首选高度。 优选的高度是最初显示标签的高度。

lbl.prefWidthProperty().bind(root.widthProperty());

BorderPane尊重其子级的首选大小。 如果是标签,则其大小足以显示其文本。 我们将标签的首选width属性绑定到窗格的相应属性。 这样,标签将从窗格的左到右放大。

lbl.setStyle("-fx-border-style: dotted; -fx-border-width: 0 0 1 0;"
        + "-fx-border-color: gray; -fx-font-weight: bold");   

我们更改标签的样式以便清楚地看到其边界。

BorderPane

图:BorderPane

AnchorPane

AnchorPane将子节点的边缘锚定到与锚定窗格的边缘偏移的位置。 如果锚定窗格设置了边框或填充,则将从这些插图的内部边缘开始测量偏移。 AnchorPane是一个简单的布局窗格,必须与其他布局窗格一起使用才能创建有意义的布局。

CornerButtonsEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program shows two buttons in the
 * bottom-right corner of the window. It uses
 * an AnchorPane and an HBox.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class CornerButtonsEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        AnchorPane root = new AnchorPane();

        Button okBtn = new Button("OK");
        Button closeBtn = new Button("Close");
        HBox hbox = new HBox(5, okBtn, closeBtn);

        root.getChildren().addAll(hbox);

        AnchorPane.setRightAnchor(hbox, 10d);
        AnchorPane.setBottomAnchor(hbox, 10d);

        Scene scene = new Scene(root, 300, 200);

        stage.setTitle("Corner buttons");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用AnchorPaneHBox将两个按钮放置在窗口的右下角。

AnchorPane root = new AnchorPane();

AnchorPane是场景图的根节点。

Button okBtn = new Button("OK");
Button closeBtn = new Button("Close");
HBox hbox = new HBox(5, okBtn, closeBtn);

这两个按钮位于HBox中。 我们使用一个构造器,将直接放置按钮对象。

root.getChildren().addAll(hbox);

hbox已添加到锚定窗格。

AnchorPane.setRightAnchor(hbox, 10d);

setRightAnchor()方法将hbox锚定到窗格的右边缘。 第二个参数给出了相对于边缘的一些偏移。

AnchorPane.setBottomAnchor(hbox, 10d);

setBottomAnchor()方法将hbox锚定到窗格的底部边缘。

Corner buttons

图:角按钮

GridPane

GridPane将其节点放入行和列的网格中。 节点可以跨越多行或多列。 GridPane是最灵活的内置布局窗格。

setGridLinesVisible()可以显示布局网格的线条,这使我们可以直观地调试布局。

NewFolderEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a NewFolder layout with 
 * a GridPane.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class NewFolderEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        GridPane root = new GridPane();
        root.setHgap(8);
        root.setVgap(8);
        root.setPadding(new Insets(5));

        ColumnConstraints cons1 = new ColumnConstraints();
        cons1.setHgrow(Priority.NEVER);
        root.getColumnConstraints().add(cons1);

        ColumnConstraints cons2 = new ColumnConstraints();
        cons2.setHgrow(Priority.ALWAYS);

        root.getColumnConstraints().addAll(cons1, cons2);

        RowConstraints rcons1 = new RowConstraints();
        rcons1.setVgrow(Priority.NEVER);

        RowConstraints rcons2 = new RowConstraints();
        rcons2.setVgrow(Priority.ALWAYS);  

        root.getRowConstraints().addAll(rcons1, rcons2);

        Label lbl = new Label("Name:");
        TextField field = new TextField();
        ListView view = new ListView();
        Button okBtn = new Button("OK");
        Button closeBtn = new Button("Close");

        GridPane.setHalignment(okBtn, HPos.RIGHT);

        root.add(lbl, 0, 0);
        root.add(field, 1, 0, 3, 1);
        root.add(view, 0, 1, 4, 2);
        root.add(okBtn, 2, 3);
        root.add(closeBtn, 3, 3);

        Scene scene = new Scene(root, 280, 300);

        stage.setTitle("New folder");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

此示例的布局由标签,文本字段,列表视图和两个按钮组成。

GridPane root = new GridPane();

创建GridPane的实例。

root.setHgap(8);
root.setVgap(8);

这两种方法在节点之间创建了水平和垂直间隙。

ColumnConstraints cons1 = new ColumnConstraints();
cons1.setHgrow(Priority.NEVER);
root.getColumnConstraints().add(cons1);

ColumnConstraints cons2 = new ColumnConstraints();
cons2.setHgrow(Priority.ALWAYS);

root.getColumnConstraints().addAll(cons1, cons2);

在布局中,我们需要使第二列可扩展。 默认情况下,网格窗格以其首选大小显示其子级,并且在窗口放大时不会放大它们。 我们创建列约束,将第二列的水平增长优先级设置为Priority.ALWAYS。 (没有特定的方法可以执行此操作。)最后,这会使文本字段和列表视图控件随着窗口的放大而沿水平方向增长。

RowConstraints rcons1 = new RowConstraints();
rcons1.setVgrow(Priority.NEVER);

RowConstraints rcons2 = new RowConstraints();
rcons2.setVgrow(Priority.ALWAYS);  

root.getRowConstraints().addAll(rcons1, rcons2);

以类似的方式,使第二行可增长。 通过使第二列和第二行可增长,列表视图将在两个方向上都增长,从而占用了大部分客户区。

Label lbl = new Label("Name:");
TextField field = new TextField();
ListView view = new ListView();
Button okBtn = new Button("OK");
Button closeBtn = new Button("Close");

将创建五个控件。

GridPane.setHalignment(okBtn, HPos.RIGHT);

setHalignment()方法使okBtn右对齐。

root.add(lbl, 0, 0);

标签控件将添加到网格。 add()方法的前两个参数是列和行索引。 索引从零开始。

root.add(field, 1, 0, 3, 1);

重载的add()方法还指定列和行跨度。 文本字段转到第二列和第一行。 它跨越三列和一行。

New folder

图:新文件夹

MigPane

MigPane是一个非常强大的第三方布局管理器。 它使用MigLayout管理器,可用于 Swing,SWT 和 JavaFX。 强烈建议考虑这位管理器。

MigPane JARs

图:MigPane JAR

要使用MigPane,必须将 JAR 包含到项目库中。 用于源代码和 javadoc 的 JAR 是可选的。

MigPane使用字符串约束进行布局。 有四种约束:常规约束,列约束,行约束和控制约束。 MigPane中有几种布局模式。 网格模式是默认模式,并且是可用模式中最灵活的一种。

MigLayoutWindowsEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
import org.tbee.javafx.scene.layout.MigPane;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a Windows layout with 
 * a MigPane.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class MigLayoutWindowsEx extends Application {

    MigPane root;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        root = new MigPane("", "[grow][]", "[][][grow][]");
        Scene scene = new Scene(root);

        Label lbl = new Label("Windows");
        Button actBtn = new Button("Activate");
        Button closeBtn = new Button("Close");
        Button okBtn = new Button("OK");
        Button helpBtn = new Button("Help");
        ListView listView = new ListView();

        createLayout(lbl, listView, actBtn, closeBtn, helpBtn, okBtn);

        stage.setTitle("Windows");
        stage.setScene(scene);
        stage.show();
    }

    private void createLayout(Control...arg) {

        root.add(arg[0], "wrap");
        root.add(arg[1], "w 200, h 200, span 2 2, grow");
        root.add(arg[2], "wrap");
        root.add(arg[3], "top, wrap");
        root.add(arg[4]);
        root.add(arg[5], "skip");    
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用六个控件,四个按钮,一个标签和一个列表视图。

root = new MigPane("", "[grow][]", "[][][grow][]");

MigPane构造器的三个字符串指定常规约束,列约束和行约束。 [grow][]约束指定有两列,第一列是可增长的。 同样,[][][grow][]约束告诉MigPane有四行,第三行是可增长的。 如果将debug约束放入常规约束中,则可以直观地调试布局。

createLayout(lbl, listView, actBtn, closeBtn, helpBtn, okBtn);

布局的创建委托给createLayout()方法。

root.add(arg[0], "wrap");

标签控件进入第一行和第一列。 可以(但不是必需)明确指定单元格索引。 wrap约束开始一个新行。

root.add(arg[1], "w 200, h 200, span 2 2, grow");

wh约束指定列表视图控件的初始宽度和高度。 最佳实践是,只有布局管理器才能设置其组件的大小。 换句话说,直接在控件上调用方法行setMinSize()是一种不好的做法。 span约束使控件跨越两列和两行。 最后,grow约束使控件在调整窗口大小时向两个方向扩展。

root.add(arg[2], "wrap");

第三个控件是“激活”按钮。 它在列表视图旁边。 放置此控件后,我们开始新的一行。

root.add(arg[3], "top, wrap");

“关闭”按钮在列表视图旁边,在“激活”按钮下面。 top约束将按钮对齐到其单元格的顶部。

root.add(arg[4]);

我们使列表视图跨两行。 将前一个按钮放入两行后,下一个按钮会自动进入列表视图下方。

root.add(arg[5], "skip");  

最后一个按钮跳过一列。 因此它被放置在第三列和第四行。

Windows layout created with a MigPane

图:窗口 layout created with a MigPane

在 JavaFX 教程的这一部分中,我们提到了布局窗格。

基本的 JavaFX 控件

原文: http://zetcode.com/gui/javafx/controls/

控件是应用的基本构建块。 Control是场景图中的一个可由用户操纵的节点。 它以对用户一致且可预测的方式支持常见的用户交互。 JavaFX 具有广泛的内置控件。 在本章中,我们涵盖五个控件:LabelCheckBoxChoiceBoxSliderProgressBar。 还简要提到了ImageViewTextField控件。

Label

Label是不可编辑的文本控件。 标签可以使用省略号或截断符来调整字符串的大小以使其适合。

LabelEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program shows lyrics in a Label
 * control.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class LabelEx extends Application {

    String lyrics = "It's way too late to think of\n"
            + "Someone I would call now\n"
            + "And neon signs got tired\n"
            + "Red eye flights help the stars out\n"
            + "I'm safe in a corner\n"
            + "Just hours before me\n"
            + "\n"
            + "I'm waking with the roaches\n"
            + "The world has surrendered\n"
            + "I'm dating ancient ghosts\n"
            + "The ones I made friends with\n"
            + "The comfort of fireflies\n"
            + "Long gone before daylight\n"
            + "\n"
            + "And if I had one wishful field tonight\n"
            + "I'd ask for the sun to never rise\n"
            + "If God leant his voice for me to speak\n"
            + "I'd say go to bed, world\n"
            + "\n"
            + "I've always been too late\n"
            + "To see what's before me\n"
            + "And I know nothing sweeter than\n"
            + "Champaign from last New Years\n"
            + "Sweet music in my ears\n"
            + "And a night full of no fears\n"
            + "\n"
            + "But if I had one wishful field tonight\n"
            + "I'd ask for the sun to never rise\n"
            + "If God passed a mic to me to speak\n"
            + "I'd say stay in bed, world\n"
            + "Sleep in peace";

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();
        root.setPadding(new Insets(10));

        Label lbl = new Label(lyrics);
        root.getChildren().add(lbl);

        Scene scene = new Scene(root);

        stage.setTitle("No sleep");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例显示了 Cardigans 的歌曲的歌词。

String lyrics = "It's way too late to think of\n"
        + "Someone I would call now\n"
        + "And neon signs got tired\n"
        + "Red eye flights help the stars out\n"
...        

该字符串由多行文本组成。

HBox root = new HBox();
root.setPadding(new Insets(10));

标签控件放置在HBox中。 我们在盒子周围放了一些填充物。

Label lbl = new Label(lyrics);

创建一个Label控件。 它以字符串作为唯一参数。

root.getChildren().add(lbl);

标签已添加到容器中。

labelFor属性

labelFor属性指定在按下助记符时将键盘焦点发送到的节点。

LabelForEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses the labelFor property to 
 * send focus to a specified text field.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class LabelForEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        GridPane root = new GridPane();
        root.setVgap(10);
        root.setHgap(5);
        root.setPadding(new Insets(10));

        Label lbl1 = new Label("_Name:");
        Label lbl2 = new Label("_Address:");
        Label lbl3 = new Label("_Occupation:");

        TextField field1 = new TextField();
        TextField field2 = new TextField();
        TextField field3 = new TextField();

        lbl1.setLabelFor(field1);
        lbl1.setMnemonicParsing(true);
        lbl2.setLabelFor(field2);
        lbl2.setMnemonicParsing(true);
        lbl3.setLabelFor(field3);
        lbl3.setMnemonicParsing(true);

        root.add(lbl1, 0, 0);
        root.add(field1, 2, 0);
        root.add(lbl2, 0, 1);
        root.add(field2, 2, 1);
        root.add(lbl3, 0, 2);
        root.add(field3, 2, 2);        

        GridPane.setHalignment(lbl1, HPos.RIGHT);
        GridPane.setHalignment(lbl2, HPos.RIGHT);
        GridPane.setHalignment(lbl3, HPos.RIGHT);

        Scene scene = new Scene(root);

        stage.setTitle("TextField");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用labelFor属性和助记符将焦点转移到指定的文本字段。

GridPane root = new GridPane();
root.setVgap(10);
root.setHgap(5);
root.setPadding(new Insets(10));

我们的应用是一个典型的基于表单的程序。 GridPane非常适合此。 我们在控件周围以及控件之间设置了一些空间。

Label lbl1 = new Label("_Name:");
Label lbl2 = new Label("_Address:");
Label lbl3 = new Label("_Occupation:");

创建了三个Labels。 下划线字符位于助记键之前。

TextField field1 = new TextField();
TextField field2 = new TextField();
TextField field3 = new TextField();

TextField是用于编辑单行未格式化文本的控件。 每个文本字段都放置在一个标签控件旁边。

lbl1.setLabelFor(field1);

setLabelFor()设置按下助记符时将焦点转移到的目标节点。

lbl1.setMnemonicParsing(true);

默认情况下,未为标签设置助记符。 我们必须使用setMnemonicParsing()方法启用它们。

The labelFor property

图:labelFor属性

在某些平台上,必须按无鼠标修饰符(通常为 Alt )以显示下划线。 在图中,通过按 Alt + A 将焦点转移到中间文本字段。

CheckBox

CheckBox是三态选择控制框,在选中时显示对勾或勾号。 默认情况下,控件具有两种状态:选中和未选中。 setAllowIndeterminate()使能第三种状态:不确定。

CheckBoxEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program presents the 
 * CheckBox control.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class CheckBoxEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();
        root.setPadding(new Insets(10, 0, 0, 10));

        CheckBox cbox = new CheckBox("Show title");
        cbox.setSelected(true);

        cbox.setOnAction((ActionEvent event) -> {
            if (cbox.isSelected()) {
                stage.setTitle("CheckBox");
            } else {
                stage.setTitle("");
            }
        });

        root.getChildren().add(cbox);

        Scene scene = new Scene(root, 300, 200);

        stage.setTitle("CheckBox");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例根据是否选中该复选框来显示或隐藏窗口的标题。

CheckBox cbox = new CheckBox("Show title");

创建一个CheckBox控件。 指定的文本为其标签。

cbox.setSelected(true);

由于默认情况下窗口的标题是可见的,因此我们使用setSelected()方法检查控件。

cbox.setOnAction((ActionEvent event) -> {
    if (cbox.isSelected()) {
        stage.setTitle("CheckBox");
    } else {
        stage.setTitle("");
    }
});

使用setOnAction()方法,设置复选框的操作,该操作在触发复选框时被调用。 我们用isSelected()方法确定其状态。 根据当前状态,我们使用setTitle()方法显示或隐藏窗口标题。

CheckBox

图:CheckBox

请注意复选框文本周围的蓝色矩形。 它表示此控件具有键盘焦点。 可以使用 Space 键选择和取消选中该复选框。

滑杆

Slider是一种控件,它使用户可以通过在有限间隔内滑动旋钮来以图形方式选择一个值。 滑块可以选择显示刻度线和标签,以指示不同的滑块位置值。

SliderEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a Slider control to 
 * manipulate the images of an ImageView.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class SliderEx extends Application {

    private ImageView iview;
    private Image muteImg;
    private Image minImg;
    private Image maxImg;
    private Image medImg;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(15));

        loadImages();

        iview = new ImageView(muteImg);

        Slider slider = new Slider(0, 100, 0);
        slider.valueProperty().addListener(new MyChangeListener());

        Scene scene = new Scene(root);

        root.getChildren().addAll(slider, iview);

        stage.setTitle("Slider");
        stage.setScene(scene);
        stage.show();
    }

    private void loadImages() {

        muteImg = new Image("file:mute.png");
        minImg = new Image("file:min.png");
        maxImg = new Image("file:max.png");
        medImg = new Image("file:med.png");
    }

    private class MyChangeListener implements ChangeListener<Number> {

        @Override
        public void changed(ObservableValue<? extends Number> observable,
                Number oldValue, Number newValue) {

            Double value = newValue.doubleValue();

            if (value == 0) {
                iview.setImage(muteImg);
            } else if (value > 0 && value <= 30) {
                iview.setImage(minImg);
            } else if (value > 30 && value < 80) {
                iview.setImage(medImg);
            } else {
                iview.setImage(maxImg);
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在代码示例中,我们显示了SliderImageView控件。 通过拖动滑块的旋钮,我们可以更改标签控件上的图像。

root.setAlignment(Pos.CENTER);

滑块和图像视图在行中居中。

iview = new ImageView(muteImg);

ImageView显示加载了Image类的图像。

Slider slider = new Slider(0, 100, 0);

将使用指定的最小值,最大值和当前值创建一个Slider控件。

slider.valueProperty().addListener(new MyChangeListener());

监听器已添加到滑块的值更改中。

Double value = newValue.doubleValue();

if (value == 0) {
    iview.setImage(muteImg);
} else if (value > 0 && value <= 30) {
    iview.setImage(minImg);
} else if (value > 30 && value < 80) {
    iview.setImage(medImg);
} else {
    iview.setImage(maxImg);
}

基于滑块的当前值,我们将适当的图像设置为图像视图。

private void loadImages() {

    muteImg = new Image("file:mute.png");
    minImg = new Image("file:min.png");
    maxImg = new Image("file:max.png");
    medImg = new Image("file:med.png");
}

loadImages()方法从磁盘加载图像。

Slider

图:Slider

选择框

ChoiceBox用于向用户显示一小组预定义的选项。 当用户单击该框时,将显示一个选择列表。 一次只能选择一个选项。 未显示此列表时,将显示当前选择的选项。 ChoiceBox项目选择由SelectionModel处理。

ChoiceBoxEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a ChoiceBox. The chosen
 * item is shown in a label.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class ChoiceBoxEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        VBox root = new VBox(35);
        root.setPadding(new Insets(10));

        Label lbl = new Label();

        ChoiceBox chbox = new ChoiceBox(FXCollections.observableArrayList(
                "Ubuntu", "Redhat", "Arch", "Debian", "Mint"));

        SingleSelectionModel model = chbox.getSelectionModel();

        model.selectedItemProperty().addListener((ObservableValue observable, 
                Object oldValue, Object newValue) -> {

            lbl.setText(newValue.toString());
        });

        root.getChildren().addAll(chbox, lbl);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("ChoiceBox");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在我们的示例中,我们有一个选择框和一个标签。 选择框包含一个字符串列表,这些字符串表示 Linux 发行版的名称。 从选择框中选择的项目显示在标签中。

Label lbl = new Label();

Label显示了从选择框中选择的当前项目。

ChoiceBox chbox = new ChoiceBox(FXCollections.observableArrayList(
        "Ubuntu", "Redhat", "Arch", "Debian", "Mint"));

创建了ChoiceBox。 它以可观察的数组列表作为参数。

SingleSelectionModel model = chbox.getSelectionModel();

model.selectedItemProperty().addListener((ObservableValue observable, 
        Object oldValue, Object newValue) -> {

    lbl.setText(newValue.toString());
});

要实现监听器,我们需要使用getSelectionModel()方法获得选择模型。 该模型包含可观察的selectedItem属性。 在处理器方法内部,我们获取选定的值并将其设置为标签。

ChoiceBox

图:ChoiceBox

进度条

ProgressBar是一个控件,用于指示带有完成条的特定任务的处理。

ProgressBarEx.java

package com.zetcode;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program presents the ProgressBar control.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class ProgressBarEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox(15);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        ProgressBar pbar = new ProgressBar(0);
        pbar.setPrefWidth(150);

        KeyFrame frame1 = new KeyFrame(Duration.ZERO, 
                new KeyValue(pbar.progressProperty(), 0));

        KeyFrame frame2 = new KeyFrame(Duration.seconds(3), 
                new KeyValue(pbar.progressProperty(), 1));        

        Timeline task = new Timeline(frame1, frame2);

        Button btn = new Button("Start");
        btn.setOnAction((ActionEvent actionEvent) -> {
            task.playFromStart();
        });

        root.getChildren().addAll(pbar, btn);

        Scene scene = new Scene(root);

        stage.setTitle("ProgressBar");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例由进度条和按钮组成。 该按钮将启动进度条,并对其进行动画处理几秒钟。

ProgressBar pbar = new ProgressBar(0);

构造器使用给定的进度值创建一个新的ProgressBar

KeyFrame frame1 = new KeyFrame(Duration.ZERO, 
        new KeyValue(pbar.progressProperty(), 0));

KeyFrame frame2 = new KeyFrame(Duration.seconds(3), 
        new KeyValue(pbar.progressProperty(), 1));        

Timeline task = new Timeline(frame1, frame2);

此代码创建一个简单的动画任务。 动画由两个帧组成。 动画属性定义为KeyValues

Button btn = new Button("Start");
btn.setOnAction((ActionEvent actionEvent) -> {
    task.playFromStart();
});

触发后,该按钮调用playFromStart()方法,该方法从初始位置开始向前播放动画。

ProgressBar

图:ProgressBar

在 JavaFX 教程的这一部分中,我们介绍了基本的 JavaFX 控件。

基本 JavaFX 控件 II

原文: http://zetcode.com/gui/javafx/controlsII/

在本章中,我们将继续介绍基本的 JavaFX 控件。 我们提出了DatePickerMenuBarColorPickerRadioButtonTabPane控件。

日期选择器

DatePicker是用于选择日期的控件。

DatePickerEx.java

package com.zetcode;

import java.time.LocalDate;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program shows a date chosen from 
 * a DatePicker in a label.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class DatePickerEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        VBox root = new VBox(15);
        root.setPadding(new Insets(10));        

        Label lbl = new Label("...");

        DatePicker datePicker = new DatePicker();

        datePicker.setOnAction(e -> {
            LocalDate date = datePicker.getValue();
            lbl.setText(date.toString());
        });

        root.getChildren().addAll(datePicker, lbl);

        Scene scene = new Scene(root, 350, 200);

        stage.setTitle("Date picker");
        stage.setScene(scene);
        stage.show();        
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用DatePicker控件选择和显示日期。 日期显示在标签控件中。

DatePicker datePicker = new DatePicker();

创建一个DatePicker控件的实例。

datePicker.setOnAction(e -> {
    LocalDate date = datePicker.getValue();
    lbl.setText(date.toString());
});

getValue()方法将选择的日期作为LocalDate返回。 所选日期通过setText()方法设置到标签控件。

DatePicker

图:DatePicker

菜单栏

MenuBarMenu对象组成,这些对象包含MenuItem对象,即应用的命令。 传统上,它位于应用窗口的顶部。

MenuBarEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a MenuBar with one
 * menu and four menu items.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class MenuBarEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();

        MenuBar mbar = new MenuBar();
        mbar.prefWidthProperty().bind(stage.widthProperty());

        MyMenuHandler handler = new MyMenuHandler();

        Menu fileMenu = new Menu("File");
        mbar.getMenus().add(fileMenu);

        MenuItem nmi = new MenuItem("New");
        nmi.setOnAction(handler);
        fileMenu.getItems().add(nmi);

        MenuItem omi = new MenuItem("Open");
        omi.setOnAction(handler);
        fileMenu.getItems().add(omi);

        MenuItem smi = new MenuItem("Save");
        smi.setOnAction(handler);
        fileMenu.getItems().add(smi);

        fileMenu.getItems().add(new SeparatorMenuItem());

        MenuItem emi = new MenuItem("Exit");
        emi.setOnAction((ActionEvent event) -> {
            Platform.exit();
        });

        fileMenu.getItems().add(emi);

        root.getChildren().add(mbar);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("MenuBar");
        stage.setScene(scene);
        stage.show();
    }

    private class MyMenuHandler implements EventHandler<ActionEvent> {

        @Override
        public void handle(ActionEvent event) {

            doShowMessageDialog(event);
        }

        private void doShowMessageDialog(ActionEvent event) {

            MenuItem mi = (MenuItem) event.getSource();
            String item = mi.getText();
            Alert alert = new Alert(AlertType.INFORMATION);
            alert.setTitle("Information dialog");
            alert.setHeaderText("Menu item selection information");
            alert.setContentText(item + " menu item selected");

            alert.showAndWait();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例在菜单栏中包含一个菜单。 菜单包含四个菜单项和一个分隔符。

MenuBar mbar = new MenuBar();
mbar.prefWidthProperty().bind(stage.widthProperty());

MenuBar控件已创建。 在水平框内,它足够大以显示其单个菜单。 通过将其绑定到舞台的widthProperty,菜单栏从左向右拉伸。

MyMenuHandler handler = new MyMenuHandler();

将创建一个菜单处理器。 它由三个菜单项共享。

Menu fileMenu = new Menu("File");
mbar.getMenus().add(fileMenu);

文件Menu已创建并添加到菜单栏。

MenuItem nmi = new MenuItem("New");
nmi.setOnAction(handler);
fileMenu.getItems().add(nmi);

新建MenuItem已创建并添加到“文件”菜单中。 菜单项的处理器是通过setOnAction()方法设置的。

fileMenu.getItems().add(new SeparatorMenuItem());

SeparatorMenuItem是水平分隔符,用于在视觉上分隔相关菜单项。

emi.setOnAction((ActionEvent event) -> {
    Platform.exit();
});

退出菜单项通过Platform.exit()方法调用终止应用。

private class MyMenuHandler implements EventHandler<ActionEvent> {

    @Override
    public void handle(ActionEvent event) {

        doShowMessageDialog(event);
    }
...
}

选择带有此处理器的菜单项时,将调用EventHandlerhandle()方法。 该方法调用doShowMessageDialog()方法,该方法显示一个消息对话框。

private void doShowMessageDialog(ActionEvent event) {

    MenuItem mi = (MenuItem) event.getSource();
    String item = mi.getText();
    Alert alert = new Alert(AlertType.INFORMATION);
    alert.setTitle("Information dialog");
    alert.setHeaderText("Menu item selection information");
    alert.setContentText(item + " menu item selected");

    alert.showAndWait();
}

doShowMessageDialog()方法使用Alert控件创建一个信息对话框。 从事件源,我们确定菜单项的名称,该菜单项用于创建内容文本。

MenuBar

图:MenuBar

颜色选择器

ColorPicker是用于选择颜色值的内置对话框。 它允许用户从标准调色板中选择一种颜色或定义一种自定义颜色。

ColorPickerEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses the ColorPicker 
 * dialog to choose a colour value.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class ColorPickerEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox(25);
        root.setAlignment(Pos.BASELINE_CENTER);
        root.setPadding(new Insets(10));

        Text txt = new Text("ZetCode");

        Font font = Font.font(20);
        txt.setFont(font);

        ColorPicker cp = new ColorPicker();
        cp.setOnAction((ActionEvent event) -> {
            txt.setFill(cp.getValue());
        });

        root.getChildren().addAll(cp, txt);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("ColorPicker");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们有一个ColorPicker和一个Text控件。 从颜色选择器中选择的颜色用于设置文本控件的前景色。

Text txt = new Text("ZetCode");

Font font = Font.font(20);
txt.setFont(font);

创建一个Text控件。 我们扩大其字体以获得更好的可见性。

ColorPicker cp = new ColorPicker();
cp.setOnAction((ActionEvent event) -> {
    txt.setFill(cp.getValue());
});

创建ColorPicker并设置事件处理器。 使用ColorPickergetValue()方法检索当前选择的颜色。 使用setFill()方法可以更改文本控件的前景色。

ColorPicker

图:ColorPicker

RadioButton

RadioButton通常用于创建互斥的项目系列。 当放置在ToggleGroup中时,只能选择一个RadioButton。 选择RadioButton时,将发送ActionEvent

RadioButtonEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program presents the RadioButton
 * control.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class RadioButtonEx extends Application {

    private final double BORDER = 10d;
    private Label lbl2;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        AnchorPane root = new AnchorPane();

        VBox vbox = new VBox(10);
        vbox.setPadding(new Insets(10));

        Label lbl1 = new Label("Difficulty");

        lbl2 = new Label("");
        lbl2.setStyle("-fx-background-color:wheat; -fx-padding: 0 0 0 5");
        lbl2.prefWidthProperty().bind(stage.widthProperty().subtract(2*BORDER));

        ToggleGroup tg =  new ToggleGroup();
        tg.selectedToggleProperty().addListener(new MyToggleListener());

        RadioButton rb1 = new RadioButton("Easy");
        rb1.setToggleGroup(tg);
        rb1.setSelected(true);

        RadioButton rb2 = new RadioButton("Medium");
        rb2.setToggleGroup(tg);

        RadioButton rb3 = new RadioButton("Hard");
        rb3.setToggleGroup(tg);

        vbox.getChildren().addAll(lbl1, rb1, rb2, rb3);

        root.getChildren().addAll(vbox, lbl2);

        AnchorPane.setTopAnchor(vbox, BORDER);
        AnchorPane.setBottomAnchor(lbl2, BORDER);
        AnchorPane.setLeftAnchor(lbl2, BORDER);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("RadioButton");
        stage.setScene(scene);
        stage.show();
    }

    private class MyToggleListener implements ChangeListener<Toggle> {

        @Override
        public void changed(ObservableValue<? extends Toggle> observable, 
                Toggle oldValue, Toggle newValue) {

            RadioButton rb = (RadioButton) newValue;
            String txt = rb.getText();
            lbl2.setText(txt);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例具有三个单选按钮。 通过将它们放在切换组中,一次只能选择其中之一。

Label lbl1 = new Label("Difficulty");

此标签提供对单选按钮的描述。

lbl2 = new Label("");
lbl2.setStyle("-fx-background-color:wheat; -fx-padding: 0 0 0 5");
lbl2.prefWidthProperty().bind(stage.widthProperty().subtract(2*BORDER));    

该标签显示当前选中的单选按钮的文本标签。 其样式是使用setStyle()方法定制的。 标签将被放大以达到舞台宽度减去指定边框的宽度。

ToggleGroup tg =  new ToggleGroup();
tg.selectedToggleProperty().addListener(new MyToggleListener());

创建ToggleGroup并将监听器添加到其selectedToggleProperty

RadioButton rb1 = new RadioButton("Easy");

创建一个RadioButton控件。

rb1.setToggleGroup(tg);

setToggleGroup()方法将单选按钮设置为切换组。

rb1.setSelected(true);

setSelected()选择单选按钮。

private class MyToggleListener implements ChangeListener<Toggle> {

    @Override
    public void changed(ObservableValue<? extends Toggle> observable, 
            Toggle oldValue, Toggle newValue) {

        RadioButton rb = (RadioButton) newValue;
        String txt = rb.getText();
        lbl2.setText(txt);
    }
}

在监听器对象内部,我们使用getText()方法获取单选按钮的文本标签,并使用setText()方法将其设置为标签。

RadioButton

图:RadioButton

TabPane

TabPane是允许在一组Tabs之间切换的控件。 一次只显示一个标签。 TabPane中的选项卡可以位于窗口的四个侧面中的任何一个。 默认面是顶面。

TabPaneEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program presents the TabPane control.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class TabPaneEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        TabPane tabPane = new TabPane();

        Tab tab1 = new Tab();
        tab1.setText("Rectangle");
        tab1.setContent(new Rectangle(100, 100, Color.LIGHTSTEELBLUE));

        Tab tab2 = new Tab();
        tab2.setText("Line");
        tab2.setContent(new Line(0, 0, 100, 100));  

        Tab tab3 = new Tab();
        tab3.setText("Circle");
        tab3.setContent(new Circle(0, 0, 50));         

        tabPane.getSelectionModel().select(1);
        tabPane.getTabs().addAll(tab1, tab2, tab3);

        root.getChildren().add(tabPane);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("TabPane");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例包含带有三个选项卡的TabPane控件。 每个选项卡均包含几何形状。 应用启动时,将选择第二个选项卡。

TabPane tabPane = new TabPane();

创建一个TabPane控件。

Tab tab1 = new Tab();
tab1.setText("Rectangle");
tab1.setContent(new Rectangle(100, 100, Color.LIGHTSTEELBLUE));

创建了Tab。 它的文本标签是用setText()方法设置的。 内容通过setContent()方法设置。

tabPane.getSelectionModel().select(1);

TabPane's选择模型处理选项卡的选择。 模型的select()方法选择第二个选项卡。

tabPane.getTabs().addAll(tab1, tab2, tab3);

选项卡将插入选项卡窗格。 使用getTabs()方法检索选项卡的内部列表。

TabPane

图:TabPane

在本章中,我们将继续介绍基本的 JavaFX 控件。

Windows API 中的一个窗口

原文: http://zetcode.com/gui/winapi/window/

窗口是应用显示输出并从用户接收输入的屏幕矩形区域。 一切都是 Windows 中的一个窗口。 至少从程序员的角度来看。 一个主窗口,一个按钮,一个静态文本甚至一个图标; 都是窗口。 静态文本只是窗口的一种特殊类型,桌面区域也是如此。

wWinMain()函数

每个 Windows UI 应用必须至少具有两个函数:WinMain函数和窗口过程。WinMain函数是 Windows UI 应用的入口点。 它初始化应用,在屏幕上显示应用窗口,然后进入主循环。 在我们的示例中,我们使用wWinMain()函数原型,该原型用于创建 Unicode UI 程序。

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    PWSTR pCmdLine, int nCmdShow);

hInstance是实例的句柄。 它是一个 32 位数字,用于标识我们在 OS 环境中的程序实例。 Windows 在程序开始执行时给出此数字。 hPrevInstance参数始终为NULL; 它是 16 位 Windows 的传统。 Windows 程序也可以从命令行启动。 给定的参数存储在pCmdLine参数中。 nCmdShow值指定窗口的显示方式:最小化,最大化或隐藏。

wWinMain()函数在收到WM_QUIT消息时终止。

注册窗口类

在创建窗口之前,我们必须在 Windows 中注册其类。 许多控件的窗口类已经注册。 因此,当我们创建按钮或静态文本时,我们不需要为其注册窗口类。 要注册窗口类,我们必须创建并填充WNDCLASS结构。 我们设置窗口样式,额外的分配字节,窗口类名称,程序实例的句柄,背景画笔,可选菜单名称,窗口过程,光标的句柄和图标。 然后调用RegisterClassW()函数。

创建一个窗口

通过调用CreateWindowW()函数创建窗口。

HWND CreateWindowW(LPCWSTR lpClassName, LPCWSTR lpWindowName, 
  DWORD dwStyle, int x, int y, int nWidth, int nHeight, 
  HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); 

lpClassName唯一标识窗口。 它是我们注册窗口的名称。 lpWindowName是窗口名称。 它的效果取决于上下文-它可以是父窗口中的窗口标题,也可以是子窗口中的标签(如按钮或静态文本)。 可以使用多种样式创建 Windows。 为此,我们有dwStyle参数。 xy指定窗口的初始水平和垂直位置。 nWidthnHeight指定窗口的宽度和高度。 hWndParent是父窗口的句柄。 对于没有父级的窗口,我们使用NULL。 对于父窗口,hMenu是菜单的可选句柄,对于子窗口,hMenu是控件标识符。 hInstance是程序实例的句柄。 lpParam是最后一个参数,它是在WM_CREATE消息期间传递到窗口的可选值。 CreateWindowW()函数将句柄返回到新创建的窗口。

消息内容

WinMain()函数创建一个消息循环。 在应用的生命周期中,这是一个无休止的循环。 消息循环是一种程序结构,它等待并分派程序中的事件或消息。 Windows 使用消息进行通信。消息是一个整数值,用于标识特定事件-单击按钮,调整窗口大小或关闭应用。 一次可以创建多个消息。 不能同时处理所有消息。 因此,它们存储在消息队列中。 消息进入消息队列并等待直到被处理。 GetMessage()函数从消息队列中检索消息。 DispatchMessage()函数将消息调度到窗口过程。 如果应用获得了字符输入,则在循环中包含TranslateMessage()函数。

窗口过程

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

每个窗口都有一个关联的窗口过程。 它是接收消息的函数。 hwnd是要接收消息的窗口的句柄。 uMsg是消息。 wParamlParam参数提供其他消息信息。 这些参数的值取决于消息类型。 消息来自用户或操作系统。 我们对消息做出反应,或者我们调用默认的窗口过程来提供默认的处理。 大多数消息发送到默认的窗口过程。 默认的窗口过程称为DefWindowProcW()。 使用与常规窗口过程相同的参数来调用它。

一个简单的窗口

以下示例显示了骨架 Windows 应用。

simplewindow.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    PWSTR pCmdLine, int nCmdShow) {

    MSG  msg;    
    HWND hwnd;
    WNDCLASSW wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.lpszClassName = L"Window";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpszMenuName  = NULL;
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassW(&wc);
    hwnd = CreateWindowW(wc.lpszClassName, L"Window",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 350, 250, NULL, NULL, hInstance, NULL);  

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {

        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    switch(msg) {

      case WM_DESTROY:

          PostQuitMessage(0);
          break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

我们将逐步解释示例代码。

#include <windows.h>

这是 C 编程语言的头文件。 它包含 API 中的所有函数声明,所有公共宏和所有数据类型。 通过链接必要的库kernel32.libuser32.libgdi32.lib并包含<windows.h>头文件,将 Windows API 添加到 C 编程项目中。

wc.style = CS_HREDRAW | CS_VREDRAW;

我们在这里设置窗口样式。 CS_HREDRAWCS_VREDRAW标志意味着只要对窗口的高度或宽度进行移动或大小调整,就会重新绘制整个窗口。

wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;

在我们的示例中,我们不使用其他字节。 因此,我们将成员设置为零。 这两个属性最常见的用法是窗口子类化。

wc.lpszClassName = L"Window";

Window 是此特定窗口类型的类名。 创建窗口时,我们将使用此类名称。 L字符在宽字符串之前。

wc.hInstance = hInstance;

我们设置程序的实例。

wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);

在这里,我们设置背景画笔。 它是用来绘制窗口客户区域的颜色。

wc.lpszMenuName  = NULL;

在我们的示例中,我们不创建菜单。

wc.lpfnWndProc = WndProc;

我们为窗口类提供了窗口过程。

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

我们为应用设置光标。 我们使用LoadCursor()函数从系统资源中加载光标。 IDC_ARROW是标准箭头光标的值。

wc.hIcon   = LoadIcon(NULL, IDI_APPLICATION);

我们为应用设置图标。 使用LoadIcon()函数从系统资源中检索图标。 IDI_APPLICATION是默认应用图标的值。

RegisterClassW(&wc);

我们向系统注册窗口类。

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

这两行显示了屏幕上的窗口。 nCmdShow指定我们如何在屏幕上显示窗口。

while (GetMessage(&msg, NULL, 0, 0)) {

  DispatchMessage(&msg);
}

这是消息循环。 我们使用GetMessage()函数从消息队列中接收消息,并使用DispatchMessage()函数将它们分发到窗口过程。

return (int) msg.wParam;

在应用结束时,退出代码将返回到系统。

switch(msg) {

  case WM_DESTROY:

    PostQuitMessage(0);
    break;
}

return DefWindowProcW(hwnd, msg, wParam, lParam);

在窗口过程中,我们对WM_DESTROY消息作出反应。 PostQuitMessage()WM_QUIT消息发送到消息队列。 使用DefWindowProcW()函数将所有其他消息发送到默认处理。

A window

图:窗口

在 Windows API 教程的这一部分中,我们创建了一个基本窗口。

JavaFX 事件

原文: http://zetcode.com/gui/javafx/events/

GUI 应用是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件是由用户(单击鼠标),应用(计时器)或系统(时钟)生成的。

事件是有关更改的通知。 它将状态更改封装在事件源中。 应用中已注册的事件过滤器和事件处理器将接收事件并提供响应。

JavaFX 中的每个事件都具有三个属性:

  • 事件来源
  • 事件目标
  • 事件类型

事件源是状态更改的对象; 它产生事件。事件目标是事件的目的地。事件类型为相同Event类的事件提供了额外的分类。

事件源对象将处理事件的任务委托给事件处理器。 当事件发生时,事件源创建一个事件对象,并将其发送到每个注册的处理器。

事件处理器

EventHandler处理特定类或类型的事件。 事件处理器设置为事件源。 它具有handle()方法,在该方法中,我们将为响应生成的事件而调用的代码放入其中。

EventHandlerEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses two EventHandlers for
 * two different Events.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class EventHandlerEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();

        ContextMenu conMenu = new ContextMenu();
        MenuItem noopMi = new MenuItem("No op");
        MenuItem exitMi = new MenuItem("Exit");
        conMenu.getItems().addAll(noopMi, exitMi);

        exitMi.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                Platform.exit();
            }
        });

        root.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                if (event.isSecondaryButtonDown()) {
                    conMenu.show(root, event.getScreenX(), 
                            event.getScreenY());
                }
            }
        });        

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("EventHandler");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例将两个EventHandlers用于两个不同的Events

ContextMenu conMenu = new ContextMenu();

ContextMenu是一个包含菜单项列表的弹出控件。

MenuItem noop = new MenuItem("No op");
MenuItem exit = new MenuItem("Exit");
conMenu.getItems().addAll(noop, exit);

将创建两个MenuItems并将其添加到上下文菜单。

exitMi.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        Platform.exit();
    }
});

使用setOnAction()方法,我们为ActionEvent设置了一个事件处理器。 EventHandlerhandle()方法以Platform.exit()方法退出应用。

root.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        if (event.isSecondaryButtonDown()) {
            conMenu.show(root, event.getScreenX(), 
                    event.getScreenY());
        }
    }
});  

使用setOnMousePressed()方法,我们为MouseEvent设置了一个事件处理器。 当我们单击第二个鼠标按钮(通常是右按钮)时,上下文菜单将显示在屏幕上。 它显示在鼠标单击的 x 和 y 坐标下方。

事件属性

以下程序探讨了MouseEvent的属性。 这是由于用户与鼠标交互而发生的事件。

EventSourceEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program explores the properties of 
 * an event.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class EventSourceEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Rectangle rect = new Rectangle(30, 30, 80, 80);
        rect.setOnMouseClicked(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent e) {

                System.out.println(e.getSource());
                System.out.println(e.getTarget());
                System.out.println(e.getEventType());
                System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
                System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
            }
        });

        root.getChildren().addAll(rect);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Event properties");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们有一个矩形。 我们将事件处理器添加到鼠标单击的事件类型。

rect.setOnMouseClicked(new EventHandler<MouseEvent>() {

    @Override
    public void handle(MouseEvent e) {

        ...
    }
});

setOnMouseClicked()将事件处理器添加到鼠标单击的事件类型。 处理器是一个匿名内部类。 当在矩形上检测到鼠标单击时,将调用其handle()方法。

System.out.println(e.getSource());
System.out.println(e.getTarget());
System.out.println(e.getEventType());

这三个是通用属性,可用于所有事件。 getSource()方法返回最初发生事件的对象。 getTarget()方法返回此事件的事件目标。 在我们的例子中,事件源和事件目标是相同的-矩形。 getEventType()方法返回MouseEvent的事件类型。 在我们的情况下,它返回MOUSE_CLICKED值。

System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());

这四个属性特定于此事件。 我们打印相对于场景和屏幕的鼠标单击的 x 和 y 坐标。

Lambda 表达式

从 JDK 8 开始,可以使用 lambda 表达式替换匿名内部类。

rect.setOnMouseClicked((MouseEvent e) -> {

    System.out.println(e.getSource());
    System.out.println(e.getTarget());
    System.out.println(e.getEventType());
    System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
    System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
});

这是使用 lambda 表达式重写的上一个示例中的事件处理代码。

通用处理器

在下一个示例中,我们创建一个监听所有类型事件的通用事件处理器。

GenericHandlerEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program adds a generic event 
 * handler to a button control.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class GenericHandlerEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Button btn = new Button("Button");
        btn.addEventHandler(EventType.ROOT, new GenericHandler());

        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Generic handler");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    private class GenericHandler implements EventHandler<Event> {

        @Override
        public void handle(Event event) {
            System.out.println(event.getEventType());
        }
    }
}

本示例具有一个按钮控件。 通用处理器已插入按钮。

Button btn = new Button("Button");
btn.addEventHandler(EventType.ROOT, new GenericHandler());

addEventHandler()方法将事件处理器注册到指定事件类型的按钮节点。 EventType.ROOT代表所有事件类型。

private class GenericHandler implements EventHandler<Event> {

    @Override
    public void handle(Event event) {
        System.out.println(event.getEventType());
    }
}

处理器使用其handle()方法将事件类型打印到控制台。

多种来源

可以将单个事件处理器添加到多个源。 可以使用getSource()方法确定事件的来源。

MultipleSourcesEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program plugs an EventHandler to multiple
 * controls.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class MultipleSourcesEx extends Application {

    private Label lbl;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        AnchorPane root = new AnchorPane();

        VBox vbox = new VBox(5);

        Button btn1 = new Button("Close");
        Button btn2 = new Button("Open");
        Button btn3 = new Button("Find");
        Button btn4 = new Button("Save");

        MyButtonHandler mbh = new MyButtonHandler();

        btn1.setOnAction(mbh);
        btn2.setOnAction(mbh);
        btn3.setOnAction(mbh);
        btn4.setOnAction(mbh);

        vbox.getChildren().addAll(btn1, btn2, btn3, btn4);

        lbl = new Label("Ready");

        AnchorPane.setTopAnchor(vbox, 10d);
        AnchorPane.setLeftAnchor(vbox, 10d);
        AnchorPane.setBottomAnchor(lbl, 10d);
        AnchorPane.setLeftAnchor(lbl, 10d);

        root.getChildren().addAll(vbox, lbl);

        Scene scene = new Scene(root, 350, 200);

        stage.setTitle("Multiple sources");
        stage.setScene(scene);
        stage.show();
    }

    private class MyButtonHandler implements EventHandler<ActionEvent> {

        @Override
        public void handle(ActionEvent event) {

            Button btn = (Button) event.getSource();
            lbl.setText(String.format("Button %s fired", btn.getText()));
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例有四个按钮和一个标签。 一个事件处理器将添加到所有四个按钮。 触发按钮的名称显示在标签中。

Button btn1 = new Button("Close");
Button btn2 = new Button("Open");
Button btn3 = new Button("Find");
Button btn4 = new Button("Save");

这四个按钮将共享一个事件处理器。

MyButtonHandler mbh = new MyButtonHandler();

创建一个MyButtonHandler的实例。 它作为内部命名类实现。

btn1.setOnAction(mbh);
btn2.setOnAction(mbh);
btn3.setOnAction(mbh);
btn4.setOnAction(mbh);

使用setOnAction()方法将处理器添加到四个不同的按钮。

private class MyButtonHandler implements EventHandler<ActionEvent> {

    @Override
    public void handle(ActionEvent event) {

        Button btn = (Button) event.getSource();
        lbl.setText(String.format("Button %s fired", btn.getText()));
    }
}

MyButtonHandlerhandle()方法内部,我们确定事件的来源并使用来源的文本标签构建消息。 该消息通过setText()方法设置为标签控件。

Multiple sources

图:多个来源

java.util.Timer

java.util.Timer计划任务以供将来在后台线程中执行。 TimerTask是可以计划为一次性执行或由计时器重复执行的任务。

TimerEx.java

package com.zetcode;

import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Spinner;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a java.util.Timer to 
 * schedule a task.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class TimerEx extends Application {

    int delay = 0;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox(10);
        root.setPadding(new Insets(10));

        Timer timer = new java.util.Timer();

        Spinner spinner = new Spinner(1, 60, 5);
        spinner.setPrefWidth(80);        

        Button btn = new Button("Show message");
        btn.setOnAction(event -> {

            delay = (int) spinner.getValue();
            timer.schedule(new MyTimerTask(), delay*1000);
        });

        root.getChildren().addAll(btn, spinner);

        stage.setOnCloseRequest(event -> {
            timer.cancel();
        });

        Scene scene = new Scene(root);

        stage.setTitle("Timer");
        stage.setScene(scene);
        stage.show();
    }

    private class MyTimerTask extends TimerTask {

        @Override
        public void run() {

            Platform.runLater(() -> {
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("Information dialog");
                alert.setHeaderText("Time elapsed information");

                String contxt;

                if (delay == 1) {
                    contxt = "1 second has elapsed";
                } else {
                    contxt = String.format("%d seconds have elapsed", 
                            delay);
                }

                alert.setContentText(contxt);
                alert.showAndWait();
            });
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例有两个控件:一个按钮和一个微调器。 该按钮将启动计时器,延迟后将显示一个消息对话框。 延迟由微调控件选择。

Timer timer = new java.util.Timer();

创建java.util.Timer的实例。

Spinner spinner = new Spinner(1, 60, 5);

Spinner控件用于选择延迟量。 它的参数是最小值,最大值和当前值。 该值以毫秒为单位。

btn.setOnAction(event -> {

    delay = (int) spinner.getValue();
    timer.schedule(new MyTimerTask(), delay*1000);
});

在按钮的事件处理器中,我们使用getValue()方法获取微调框的当前值,并使用计时器的schedule()方法安排任务。

stage.setOnCloseRequest(event -> {
    timer.cancel();
});

当使用计时器的cancel()方法终止应用时,我们将取消计时器。

private class MyTimerTask extends TimerTask {

    @Override
    public void run() {

        Platform.runLater(() -> {
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("Information dialog");
            alert.setHeaderText("Time elapsed information");

            String contxt;

            if (delay == 1) {
                contxt = "1 second has elapsed";
            } else {
                contxt = String.format("%d seconds have elapsed", 
                        delay);
            }

            alert.setContentText(contxt);
            alert.showAndWait();
        });
    }
}

runLater()方法在 JavaFX 应用线程上执行任务。 我们显示一个消息对话框,通知您经过的时间。

Time elapsed

图:经过的时间

移动窗口

以下示例显示了应用窗口在屏幕上的位置。

MovingWindowEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program shows the screen coordinates
 * of the application window in two labels.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class MovingWindowEx extends Application {

    int x = 0;
    int y = 0;
    Label lblx;
    Label lbly;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        VBox root = new VBox(10);
        root.setPadding(new Insets(10));

        String txt1 = String.format("x: %d", x);
        lblx = new Label(txt1);

        String txt2 = String.format("y: %d", y);
        lbly = new Label(txt2);        

        root.getChildren().addAll(lblx, lbly);

        stage.xProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable, 
                    Number oldValue, Number newValue) {

                doChange(newValue);
            }

            private void doChange(Number newValue) {

                x = newValue.intValue();
                updateXLabel();                
            }

        });

        stage.yProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable, 
                    Number oldValue, Number newValue) {

                doChange(newValue);
            }

            private void doChange(Number newValue) {

                y = newValue.intValue();
                updateYLabel();                
            }

        });        

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Moving window");
        stage.setScene(scene);
        stage.show();
    }

    private void updateXLabel() {

        String txt = String.format("x: %d", x);
        lblx.setText(txt);
    }

    private void updateYLabel() {

        String txt = String.format("y: %d", y);
        lbly.setText(txt);        
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例显示了两个标签控件中的当前窗口坐标。 为了获得窗口位置,我们监听舞台的xPropertyyProperty的变化。

String txt1 = String.format("x: %d", x);
lblx = new Label(txt1);

String txt2 = String.format("y: %d", y);
lbly = new Label(txt2);  

这两个标签显示了应用窗口左上角的 x 和 y 坐标。

stage.xProperty().addListener(new ChangeListener<Number>() {

    @Override
    public void changed(ObservableValue<? extends Number> observable, 
            Number oldValue, Number newValue) {

        doChange(newValue);
    }

    private void doChange(Number newValue) {

        x = newValue.intValue();
        updateXLabel();                
    }

});

xProperty将舞台的水平位置存储在屏幕上。 我们添加一个ChangeListener来监听属性的更改。 每次修改属性时,我们都会检索新值并更新标签。

Moving a window

图:移动窗口

JavaFX 教程的这一部分专门讨论 JavaFX 事件。

JavaFX 效果

原文: http://zetcode.com/gui/javafx/effects/

JavaFX 包含javafx.scene.effect包,该包具有执行各种视觉效果的一组或类。 在本章中,我们创建DropShadowReflectionLightingGaussianBlurSepiaTonePerspectiveTransform效果。 我们还将展示如何组合多种效果。

通过setEffect()方法将效果应用于节点的effectProperty

阴影

DropShadow是一种高级效果,可使用指定的颜色,半径和偏移量在内容后面渲染阴影。

DropShadowEx.java

package com.zetcod;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program applies a DropShadow effect
 * on a Rectangle.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class DropShadowEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Rectangle rect = new Rectangle(0, 0, 100, 100);
        rect.setFill(Color.GREENYELLOW);

        DropShadow ds = new DropShadow(15, Color.DARKGREEN);

        rect.setEffect(ds);

        root.getChildren().add(rect);

        Scene scene = new Scene(root, 250, 200, Color.WHITESMOKE);

        stage.setTitle("DropShadow");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例在矩形周围创建阴影。

Rectangle rect = new Rectangle(0, 0, 100, 100);
rect.setFill(Color.GREENYELLOW);

构造一个绿色黄色矩形。

DropShadow ds = new DropShadow(15, Color.DARKGREEN);

创建DropShadow效果。 构造器接受半径和颜色。

rect.setEffect(ds);

通过setEffect()方法应用效果。

DropShadow

图:DropShadow

反射

Reflection是一种将输入的反射版本呈现在实际输入内容之下的效果。

ReflectionEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.Reflection;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program applies a Reflection effect
 * on a Text node.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class ReflectionEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Text text = new Text();
        text.setText("ZetCode");
        text.setFill(Color.STEELBLUE);
        text.setFont(Font.font("Serif", FontWeight.BOLD, 60));

        Reflection ref = new Reflection();
        text.setEffect(ref);

        root.getChildren().add(text);        

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Reflection");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

本示例在Text节点上应用Reflection效果。

Text text = new Text();
text.setText("ZetCode");
text.setFill(Color.STEELBLUE);
text.setFont(Font.font("Serif", FontWeight.BOLD, 60));

创建一个Text控件。 它的油漆是STEELBLUE。 字体变为粗体和放大。

Reflection ref = new Reflection();
text.setEffect(ref);

将创建默认的Reflection并将其应用于文本控件。

Reflection

图:反射

灯光

Lighting模拟照在给定内容上的光源,该光源可用于为平面对象提供更逼真的三维外观。 Light源的setAzimuth()方法设置方位角-光源的方向角。

LightingEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program applies a Lighting effect on 
 * a Text control. The azimuth of the light is 
 * controlled by a Slider.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class LightingEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        VBox root = new VBox(30);
        root.setPadding(new Insets(10));

        DoubleProperty azimuth = new SimpleDoubleProperty(0);        

        Light.Distant light = new Light.Distant();
        light.setAzimuth(0);

        Lighting lighting = new Lighting(light);
        lighting.setSurfaceScale(5.0);

        Text text = new Text();
        text.setText("ZetCode");
        text.setFill(Color.LIGHTSKYBLUE);
        text.setFont(Font.font(null, FontWeight.BOLD, 60));

        Slider slider = new Slider(1, 360, 0);
        azimuth.bind(slider.valueProperty());        

        slider.valueProperty().addListener(event -> {
            light.setAzimuth(azimuth.get());
            lighting.setLight(light);
            text.setEffect(lighting);
        });        

        text.setEffect(lighting);

        root.getChildren().addAll(slider, text);

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Lighting");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

本示例对Text控件应用Lighting效果。 光的方位角由Slider控制。

Light.Distant light = new Light.Distant();
light.setAzimuth(0);

创建一个Light源。

Lighting lighting = new Lighting(light);

该行使用指定的光源创建Lighting的新实例。

Text text = new Text();
text.setText("ZetCode");
text.setFill(Color.LIGHTSKYBLUE);
text.setFont(Font.font(null, FontWeight.BOLD, 60));

这是在其上设置了Lighting效果的Text控件。

Slider slider = new Slider(1, 360, 0);
azimuth.bind(slider.valueProperty());        

slider.valueProperty().addListener(event -> {
    light.setAzimuth(azimuth.get());
    lighting.setLight(light);
    text.setEffect(lighting);
});  

Slider控件管理光源的方位角。

Lighting

图:Lighting

高斯模糊

GaussianBlur是使用具有可配置半径的高斯卷积核的模糊效果。

GaussianBlurEx.java

package com.zetcode;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program applies a GaussianBlur effect on 
 * a Text control. The radius of the blur is 
 * controlled by a Slider.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class GaussianBlurEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        VBox root = new VBox(30);
        root.setPadding(new Insets(10));

        DoubleProperty radius = new SimpleDoubleProperty(0);

        Text blurredText = new Text("Inception");
        blurredText.setFont(Font.font(38));        

        Slider slider = new Slider(1, 20, 1);
        radius.bind(slider.valueProperty());        

        slider.valueProperty().addListener(event -> {
            blurredText.setEffect(new GaussianBlur(radius.get()));
        });

        root.getChildren().addAll(slider, blurredText);

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Blur effect");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

本示例对Text控件应用GaussianBlur效果。 模糊的半径由Slider控制。

Text blurredText = new Text("Inception");
blurredText.setFont(Font.font(38)); 

模糊效果将应用于此文本控件。

Slider slider = new Slider(1, 20, 1);
radius.bind(slider.valueProperty());        

slider.valueProperty().addListener(event -> {
    blurredText.setEffect(new GaussianBlur(radius.get()));
});

Slider控件管理GaussianBlur效果的radius属性。

GaussianBlur

图:GaussianBlur

棕褐色调

SepiaTone是产生棕褐色调效果的滤镜,类似于古董照片。

SepiaToneEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.effect.Effect;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program applies a SepiaTone effect
 * on an Image when a mouse pointer is over
 * the image.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class SepiaToneEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Image image = new Image("file:mushroom.png");

        ImageView iw = new ImageView(image);

        SepiaTone sepia = new SepiaTone();
        iw.effectProperty().bind(
                Bindings
                    .when(iw.hoverProperty())
                        .then((Effect) sepia)
                        .otherwise((Effect) null)
        );

        iw.setCache(true);
        iw.setCacheHint(CacheHint.SPEED);  

        root.getChildren().add(iw);

        Scene scene = new Scene(root);

        stage.setTitle("SepiaTone");
        scene.setFill(Color.WHITESMOKE);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

当鼠标指针悬停在图像上时,该示例在Image上应用SepiaTone效果。

Image image = new Image("file:mushroom.png");
ImageView iw = new ImageView(image);

我们从磁盘加载Image并创建一个ImageView控件。

SepiaTone sepia = new SepiaTone();
iw.effectProperty().bind(
        Bindings
            .when(iw.hoverProperty())
                .then((Effect) sepia)
                .otherwise((Effect) null)
);

当鼠标指针位于ImageView控件的边界上时,将设置SepiaTone效果。

iw.setCache(true);
iw.setCacheHint(CacheHint.SPEED);  

出于性能原因,将缓存节点渲染。

透视变换

PerspectiveTransform提供输入内容的非仿射变换。 它通常用于在二维内容上创建三维效果。

PerspectiveEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a chessboard 
 * with a PerspectiveTransform effect.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class PerspectiveEx extends Application {

    private final int SIZE = 50;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Pane board = new Pane();

        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {

                Rectangle r = new Rectangle(col * SIZE, row*SIZE, 
                        SIZE, SIZE);

                if ((col+row) % 2 == 0) {
                    r.setFill(Color.WHITE);
                } else {
                    r.setFill(Color.BLACK);
                }

                board.getChildren().add(r);
            }
        }

        PerspectiveTransform e = new PerspectiveTransform();
        e.setUlx(30);     // Upper-left point
        e.setUly(170);
        e.setUrx(370);    // Upper-right point
        e.setUry(170);
        e.setLlx(0);      // Lower-left point
        e.setLly(300); 
        e.setLrx(400);    // Lower-right point
        e.setLry(300);
        board.setEffect(e);

        root.getChildren().add(board);

        Scene scene = new Scene(root, Color.WHITESMOKE);

        stage.setTitle("ChessBoard");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例形成具有PerspectiveTransform效果的棋盘。

for (int row = 0; row < 8; row++) {
    for (int col = 0; col < 8; col++) {

        Rectangle r = new Rectangle(col * SIZE, row*SIZE, 
                SIZE, SIZE);

        if ((col+row) % 2 == 0) {
            r.setFill(Color.WHITE);
        } else {
            r.setFill(Color.BLACK);
        }

        board.getChildren().add(r);
    }
}

此代码产生 64 个矩形。 矩形具有黑色和白色。

PerspectiveTransform e = new PerspectiveTransform();
e.setUlx(30);     // Upper-left point
e.setUly(170);
e.setUrx(370);    // Upper-right point
e.setUry(170);
e.setLlx(0);      // Lower-left point
e.setLly(300); 
e.setLrx(400);    // Lower-right point
e.setLry(300);
board.setEffect(e);

实例化一个PerspectiveTransform并将其应用于该节点。 我们提供四个角点的 x 和 y 坐标。 这些点形成一个矩形,在其中渲染效果。

Chessboard

图:Chessboard

组合效果

可以组合效果。 如果已经设置了一种效果,则setEffect()方法将替换一种效果。 为了组合多种效果,我们使用EffectsetInput()方法。

CombiningEffectsEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.effect.Reflection;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program combines a Reflection effect 
 * with a Lighting effect on a Text node.
 *
 * Author: Jan Bodnar
 * Website: zetcode.com
 * Last modified: June 2015
 */

public class CombiningEffectsEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        Light.Distant light = new Light.Distant();
        light.setAzimuth(50);

        Lighting lighting = new Lighting();
        lighting.setLight(light);
        lighting.setSurfaceScale(5);

        Text text = new Text();
        text.setText("ZetCode");
        text.setFill(Color.CADETBLUE);
        text.setFont(Font.font(null, FontWeight.BOLD, 60));

        Reflection ref = new Reflection();
        ref.setInput(lighting);
        text.setEffect(ref);

        root.getChildren().add(text);

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Combining effects");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例程序在Text节点上结合了Reflection效果和Lighting效果。

Light.Distant light = new Light.Distant();
light.setAzimuth(50);

Lighting lighting = new Lighting();
lighting.setLight(light);
lighting.setSurfaceScale(5.0);

这些行创建Lighting效果。

Text text = new Text();
text.setText("ZetCode");
text.setFill(Color.CADETBLUE);
text.setFont(Font.font(null, FontWeight.BOLD, 60));

创建一个Text控件。 字体是放大和粗体。 文本的颜色是CADETBLUE

Reflection ref = new Reflection();
ref.setInput(lighting);

构造了Reflection效果。 使用setInput()方法将其与照明效果结合在一起。

text.setEffect(ref);

效果的最终组合通过setEffect()方法应用于节点。

Combining effects

图:组合效果

在本章中,我们创建了几种视觉效果。

JavaFX 动画

原文: http://zetcode.com/gui/javafx/animation/

在本章中,我们将使用 JavaFX 中的动画。 我们使用AnimationTimerTransitionTimeline创建动画。

动画是连续的图像,使人产生了运动的幻觉。 但是,动画不仅限于运动。 随着时间的推移更改节点的背景也被视为动画。

JavaFX 提供了三种创建动画的基本工具:

  • 动画计时器
  • 过渡
  • 时间线

AnimationTimer是创建动画的最简单工具。 这是一个基本计时器。 在动画的每一帧中都会调用其handle()方法。 Transition是定义动画的基本高级框架。 动画由interpolate()方法的frac值控制。 Timeline是用于制作高级动画的最复杂的工具。 Timeline动画由KeyFrames定义,该动画概述了沿Timeline插补的一组变量在指定时间点的节点目标值。 动画属性由KeyValues定义。

动画类

Animation是 JavaFX 中定义高级动画的基本类。 TransitionTimeline都扩展了Animation。 动画以play()playFromStart()方法开始,以stop()方法结束。 可以通过调用pause()方法暂停动画,然后下一个play()调用从暂停的位置继续播放动画。 rate属性定义了预期播放动画的方向和速度。 delay属性指定动画的初始延迟量。 动画可以循环运行; cycleCount属性中定义了循环数,cycleDuration指示了循环的持续时间。 可以使用autoReverseProperty以交替的周期反转动画。

动画计时器

AnimationTimer允许创建一个定时器,该定时器在活动时在每个帧中被调用。 这是一个抽象类; 因此,我们需要创建一个扩展它的自定义类。 在每个帧中调用的handle()方法都必须重写。 AnimationTimerstart()方法启动计时器,stop()方法停止计时器。

AnimationTimerEx.java

package com.zetcode;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses AnimationTimer to 
 * create an animation.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class AnimationTimerEx extends Application {

    private double opacity = 1;
    private Label lbl;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        StackPane root = new StackPane();

        lbl = new Label("JavaFX");
        lbl.setFont(Font.font(48));
        root.getChildren().add(lbl);

        AnimationTimer timer = new MyTimer();
        timer.start();

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("AnimationTimer");
        stage.setScene(scene);
        stage.show();
    }

    private class MyTimer extends AnimationTimer {

        @Override
        public void handle(long now) {

            doHandle();
        }

        private void doHandle() {

            opacity -= 0.01;
            lbl.opacityProperty().set(opacity);

            if (opacity <= 0) {
                stop();
                System.out.println("Animation stopped");
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用AnimationTimer在节点上创建淡出效果。

lbl = new Label("JavaFX");
lbl.setFont(Font.font(48));
root.getChildren().add(lbl);

我们的动画更改了此Label控件的属性。

AnimationTimer timer = new MyTimer();
timer.start();

创建一个AnimationTimer并调用其start()方法。

private class MyTimer extends AnimationTimer {

    @Override
    public void handle(long now) {

        doHandle();
    }
...
}

我们创建AnimationTimer的具体子类并覆盖其handle()方法。

private void doHandle() {

    opacity -= 0.01;
    lbl.opacityProperty().set(opacity);

    if (opacity <= 0) {
        stop();
        System.out.println("Animation stopped");
    }
}

doHandle()方法中,我们减小opacity变量并更新opacityProperty。 如果opacity达到最小值,则使用stop()方法停止计时器。

渐隐过渡

Transition动画最适合计划的动画。 Transition具有具体的类,可用于创建可以并行或顺序执行的各种动画; 例如FadeTransitionPathTransitionRotateTransitionScaleTransition

FadeTransition创建一个跨越其持续时间的淡入淡出效果动画。 这是通过定期更新节点的opacity变量来完成的。

FadeTransitionEx.java

package com.zetcode;

import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a FadeTransition. A rectangle
 * fades out after we click into its area.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class FadeTransitionEx extends Application {

    private FadeTransition ft;
    private Rectangle rect;

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Group root = new Group();

        rect = new Rectangle(20, 20, 150, 150);
        rect.setOnMouseClicked(new RectClickHandler());

        ft = new FadeTransition(Duration.millis(5000), rect);
        ft.setFromValue(1.0);
        ft.setToValue(0.0);

        root.getChildren().add(rect);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Fading transition");
        stage.setScene(scene);
        stage.show();        
    }

    private class RectClickHandler implements EventHandler<MouseEvent> {

        @Override
        public void handle(MouseEvent event) {

            doHandle();
        }

        private void doHandle() {

            Double opa = rect.getOpacity();

            if (opa.intValue() == 0) {
                return;
            }

            Animation.Status as = ft.getStatus();

            if (as == Animation.Status.RUNNING) {
                return;
            }

            if (as == Animation.Status.STOPPED) {
                ft.play();
            }            
        }

    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

本示例使用FadeTransition在矩形上创建淡出效果。 在矩形区域内单击鼠标后开始动画。

rect = new Rectangle(20, 20, 150, 150);
rect.setOnMouseClicked(new RectClickHandler());

鼠标单击的处理器设置为矩形。

ft = new FadeTransition(Duration.millis(5000), rect);

创建了FadeTransition。 它的第一个参数是过渡的持续时间。 第二个参数是更新其opacity参数的节点。

ft.setFromValue(1.0);
ft.setToValue(0.0);

setFromValue()设置不透明度的开始值,setToValue()设置不透明度的结束值。

Double opa = rect.getOpacity();

当前的不透明度值是通过getOpacity()方法确定的。

if (opa.intValue() == 0) {
    return;
}

矩形淡出后,我们取消了鼠标单击。

Animation.Status as = ft.getStatus();

if (as == Animation.Status.RUNNING) {
    return;
}

if (as == Animation.Status.STOPPED) {
    ft.play();
}  

getStatus()方法确定转换的状态。 如果状态为Animation.Status.STOPPED,我们将使用play()方法开始转换。

路径转换

PathTransition沿路径创建动画。 通过更新节点的translateXtranslateY变量来完成沿路径的转换。 请注意,我们必须使用支持元素绝对定位的节点。

PathTransitionEx.java

package com.zetcode;

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a PathTransition to move 
 * a circle along a path.
 * 
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class PathTransitionEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Path path = new Path();
        path.getElements().add(new MoveTo(20, 120));
        path.getElements().add(new CubicCurveTo(180, 60, 250, 340, 420, 240));

        Circle circle = new Circle(20, 120, 10);
        circle.setFill(Color.CADETBLUE);

        PathTransition ptr = new PathTransition();

        ptr.setDuration(Duration.seconds(6));
        ptr.setDelay(Duration.seconds(2));
        ptr.setPath(path);
        ptr.setNode(circle);
        ptr.setCycleCount(2);
        ptr.setAutoReverse(true);
        ptr.play();     

        root.getChildren().addAll(path, circle);

        Scene scene = new Scene(root, 450, 300);

        stage.setTitle("PathTransition");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用PathTransition沿路径移动圆。 动画在初始延迟 2 秒后开始。 它由两个周期组成。 动画受到尊敬; 也就是说,圆从起点到终点再返回。

Pane root = new Pane();

我们使用Pane作为我们的根节点。 它支持动画所需的绝对定位。

Path path = new Path();
path.getElements().add(new MoveTo(20, 120));
path.getElements().add(new CubicCurveTo(180, 60, 250, 340, 420, 240));

在这里,我们定义了动画对象将沿其移动的Path

Circle circle = new Circle(20, 120, 10);
circle.setFill(Color.CADETBLUE);

这个圆是动画中的运动对象。

PathTransition ptr = new PathTransition();

创建一个PathTransition对象。

ptr.setDuration(Duration.seconds(6));

setDuration()方法设置动画的持续时间。

ptr.setDelay(Duration.seconds(2));

setDelay()方法设置动画的初始延迟。

ptr.setPath(path);
ptr.setNode(circle);

setPath()方法设置路径,setNode()设置动画的目标节点。

ptr.setCycleCount(2);

我们的动画有两个循环。 循环次数通过setCycleCount()方法设置。

ptr.setAutoReverse(true);

使用setAutoReverse()方法,我们可以反转动画的方向。 圆会移回到起始位置。

ptr.play();

最后,play()方法开始播放动画。

PathTransition

图:PathTransition

并行转换

ParallelTransition并行播放Animations的列表。

ParallelTransitionEx.java

package com.zetcode;

import javafx.animation.FillTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.RotateTransition;
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a parallel 
 * transition animation.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class ParallelTransitionEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Rectangle rect = new Rectangle(50, 50, 30, 30);
        rect.setArcHeight(10);
        rect.setArcWidth(10);
        rect.setFill(Color.CADETBLUE);

        RotateTransition rottr
                = new RotateTransition(Duration.millis(2000), rect);
        rottr.setByAngle(180);
        rottr.setCycleCount(2);
        rottr.setAutoReverse(true);

        ScaleTransition sctr = new ScaleTransition(Duration.millis(2000), 
                rect);
        sctr.setByX(2);
        sctr.setByY(2);
        sctr.setCycleCount(2);
        sctr.setAutoReverse(true);

        FillTransition fltr = new FillTransition(Duration.millis(2000), 
                rect, Color.CADETBLUE, Color.STEELBLUE);
        fltr.setCycleCount(2);
        fltr.setAutoReverse(true);

        root.getChildren().add(rect);

        ParallelTransition ptr = new ParallelTransition();
        ptr.getChildren().addAll(rottr, sctr, fltr);

        ptr.play();    

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("ParallelTransition");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例并行播放三个过渡。 有一个旋转,缩放和背景颜色变化的矩形对象。

RotateTransition rottr
        = new RotateTransition(Duration.millis(2000), rect);
rottr.setByAngle(180);
rottr.setCycleCount(2);
rottr.setAutoReverse(true);

RotateTransition将矩形旋转指定角度。 旋转以两个周期发生并反转。

ScaleTransition sctr = new ScaleTransition(Duration.millis(2000), 
        rect);
sctr.setByX(2);
sctr.setByY(2);

ScaleTransition将矩形放大和缩小 2 倍。

FillTransition fltr = new FillTransition(Duration.millis(2000), 
        rect, Color.CADETBLUE, Color.STEELBLUE);

FillTransition将矩形的填充颜色从一种颜色值更改为另一种颜色值。

ParallelTransition ptr = new ParallelTransition();
ptr.getChildren().addAll(rottr, sctr, fltr);

ptr.play(); 

三种类型的过渡放置在ParallelTransition中,它们并行(即同时)播放它们。

顺序转换

SequentialTransition按顺序播放Animations的列表。

SequentialTransitionEx.java

package com.zetcode;

import javafx.animation.FillTransition;
import javafx.animation.RotateTransition;
import javafx.animation.ScaleTransition;
import javafx.animation.SequentialTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program plays three transitions in
 * a sequential order.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class SequentialTransitionEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Rectangle rect = new Rectangle(50, 50, 30, 30);
        rect.setArcHeight(10);
        rect.setArcWidth(10);
        rect.setFill(Color.CADETBLUE);

        RotateTransition rottr
                = new RotateTransition(Duration.millis(2000), rect);
        rottr.setByAngle(180);
        rottr.setCycleCount(2);
        rottr.setAutoReverse(true);

        ScaleTransition sctr = new ScaleTransition(Duration.millis(2000), 
                rect);
        sctr.setByX(2);
        sctr.setByY(2);
        sctr.setCycleCount(2);
        sctr.setAutoReverse(true);

        FillTransition fltr = new FillTransition(Duration.millis(2000), 
                rect, Color.CADETBLUE, Color.STEELBLUE);
        fltr.setCycleCount(2);
        fltr.setAutoReverse(true);

        root.getChildren().add(rect);

        SequentialTransition str = new SequentialTransition();
        str.getChildren().addAll(rottr, sctr, fltr);

        str.play();         

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("SequentialTransition");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例按顺序播放三个过渡-一个接一个。

SequentialTransition str = new SequentialTransition();
str.getChildren().addAll(rottr, sctr, fltr);

str.play();

这三个转换被添加到SequentialTransition中。

时间线

Timeline是使用 JavaFX 创建动画的最复杂的工具。 动画用KeyFrames定义,其中包含更改的节点的属性。 这些属性封装在KeyValues中。 Timeline内插属性的更改。

TimelineEx.java

package com.zetcode;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.Lighting;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program uses a Timeline to 
 * move a rectangle.
 * 
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class TimelineEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Rectangle rect = new Rectangle(20, 20, 60, 60);
        rect.setEffect(new Lighting());
        rect.setFill(Color.CADETBLUE);

        Timeline tl = new Timeline();

        tl.setCycleCount(2);
        tl.setAutoReverse(true);

        KeyValue kv = new KeyValue(rect.translateXProperty(), 200);
        KeyFrame kf = new KeyFrame(Duration.millis(2000), kv);
        tl.getKeyFrames().addAll(kf);

        tl.play();

        root.getChildren().addAll(rect);

        Scene scene = new Scene(root, 350, 250);

        stage.setTitle("Timeline");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用Timeline为矩形动画。

Rectangle rect = new Rectangle(20, 20, 60, 60);
rect.setEffect(new Lighting());
rect.setFill(Color.CADETBLUE);

此矩形是动画中的移动对象。

Timeline tl = new Timeline();

创建一个Timeline对象。

tl.setCycleCount(2);
tl.setAutoReverse(true);

动画由两个循环组成,并且被反转。 矩形前后移动。

KeyValue kv = new KeyValue(rect.translateXProperty(), 200);

KeyValue包含translateX属性,该属性会随着时间变化为 200。

KeyFrame kf = new KeyFrame(Duration.millis(2000), kv);

实例化了KeyFrame。 第一个参数是其持续时间,第二个参数是KeyValue。 动画持续 2 秒钟,其translateX属性更改为 200。

tl.getKeyFrames().addAll(kf);

关键帧将添加到帧列表。

顺序时间线动画

我们没有在时间轴中定义所有关键帧。 我们定义一些帧,其余的帧被插值。 关键帧在指定的时间点为时间轴内插的一组变量提供目标值。 为了顺序执行关键帧,我们利用了SequentialTransition类。

SequentialTimelineEx.java

package com.zetcode;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * ZetCode JavaFX tutorial
 *
 * This program creates a sequential Timeline
 * animation.
 * 
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class SequentialTimelineEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Circle c = new Circle(50, 100, 10);
        c.setFill(Color.CADETBLUE);

        KeyValue kv1 = new KeyValue(c.scaleXProperty(), 4);
        KeyValue kv2 = new KeyValue(c.scaleYProperty(), 4);
        KeyFrame kf1 = new KeyFrame(Duration.millis(3000), kv1, kv2);

        Timeline scale = new Timeline();
        scale.getKeyFrames().add(kf1);        

        KeyValue kv3 = new KeyValue(c.centerXProperty(), 250);
        KeyFrame kf2 = new KeyFrame(Duration.millis(5000), kv3);

        Timeline move = new Timeline();
        move.getKeyFrames().add(kf2);        

        KeyValue kv4 = new KeyValue(c.scaleXProperty(), 1);
        KeyValue kv5 = new KeyValue(c.scaleYProperty(), 1);
        KeyFrame kf3 = new KeyFrame(Duration.millis(3000), kv4, kv5);

        Timeline scale2 = new Timeline();
        scale2.getKeyFrames().add(kf3);           

        SequentialTransition seqtr = new SequentialTransition(scale, 
                move, scale2);
        seqtr.play();

        root.getChildren().add(c);

        Scene scene = new Scene(root, 300, 250);

        stage.setTitle("Sequential Timeline animation");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例创建顺序时间轴动画。 动画由三个Timelines组成,它们与SequentialTransition顺序执行。

Circle c = new Circle(50, 100, 10);
c.setFill(Color.CADETBLUE);

此动画中的移动对象是Circle

KeyValue kv1 = new KeyValue(c.scaleXProperty(), 4);
KeyValue kv2 = new KeyValue(c.scaleYProperty(), 4);
KeyFrame kf1 = new KeyFrame(Duration.millis(3000), kv1, kv2);

Timeline scale = new Timeline();
scale.getKeyFrames().add(kf1); 

这是第一个Timeline。 它会在三秒钟的时间内放大圆圈。

KeyValue kv3 = new KeyValue(c.centerXProperty(), 250);
KeyFrame kf2 = new KeyFrame(Duration.millis(5000), kv3);

Timeline move = new Timeline();
move.getKeyFrames().add(kf2);   

第二个Timeline将圆圈向前移动。 动画的这一部分持续五秒钟。

KeyValue kv4 = new KeyValue(c.scaleXProperty(), 1);
KeyValue kv5 = new KeyValue(c.scaleYProperty(), 1);
KeyFrame kf3 = new KeyFrame(Duration.millis(3000), kv4, kv5);

Timeline scale2 = new Timeline();
scale2.getKeyFrames().add(kf3);   

第三个Timeline按比例缩小圆圈。

SequentialTransition seqtr = new SequentialTransition(scale, 
        move, scale2);
seqtr.play();

这三个时间线放置在SequentialTransition中。 时间轴依次播放。

在本章中,我们介绍了 JavaFX 动画。

JavaFX 画布

原文: http://zetcode.com/gui/javafx/canvas/

Canvas是可以使用GraphicsContext提供的一组图形命令绘制的图像。 它是进行绘图的高级工具。

GraphicsContext用于使用缓冲区向Canvas发出绘图调用。

简单的线条

在第一个示例中,我们绘制了简单的线条。 线是基本的图形基元。 需要两个坐标才能形成一条线。

SimpleLinesEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program draws three lines which
 * form a rectangle.
 * 
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class SimpleLinesEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(300, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawLines(gc);

        root.getChildren().add(canvas);

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Lines");
        stage.setScene(scene);
        stage.show();
    }

    private void drawLines(GraphicsContext gc) {

        gc.beginPath();
        gc.moveTo(30.5, 30.5);
        gc.lineTo(150.5, 30.5);
        gc.lineTo(150.5, 150.5);
        gc.lineTo(30.5, 30.5);
        gc.stroke();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例绘制了形成矩形的三条线。

Canvas canvas = new Canvas(300, 300);

Canvas的宽度和高度指定了将画布绘制命令渲染到的图像的大小。 所有绘图操作都被裁剪到该图像的边界。

GraphicsContext gc = canvas.getGraphicsContext2D();

getGraphicsContext2D()返回与画布关联的GraphicsContext

drawLines(gc);

该图形委托给drawLines()方法。

gc.beginPath();

线图元表示为路径元素。 beginPath()方法开始一个新路径。

gc.moveTo(30.5, 30.5);

moveTo()方法将当前路径的起点移动到指定的坐标。

gc.lineTo(150.5, 30.5);
gc.lineTo(150.5, 150.5);
gc.lineTo(30.5, 30.5);

lineTo()方法将线段添加到当前路径。

gc.stroke();

stroke()方法使用当前的描边绘图描边路径。

Lines

图:直线

描边和填充

描边用于绘制形状的轮廓。 填充用于绘制形状的内部。

StrokeFillEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program draws an outline of a circle 
 * and fills an interior of a circle.
 * 
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class StrokeFillEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(300, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        doDrawing(gc);

        root.getChildren().add(canvas);    

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Stroke and fill");
        stage.setScene(scene);
        stage.show();
    }

    private void doDrawing(GraphicsContext gc) {

        gc.setStroke(Color.FORESTGREEN.brighter());
        gc.setLineWidth(5);
        gc.strokeOval(30, 30, 80, 80);        
        gc.setFill(Color.FORESTGREEN);
        gc.fillOval(130, 30, 80, 80);
    }    

    public static void main(String[] args) {
        launch(args);
    }
}

该示例绘制了圆的轮廓并填充了圆的内部。

gc.setStroke(Color.FORESTGREEN.brighter());

setStroke()方法设置当前的笔触绘图属性。 默认颜色是黑色。 GraphicsContext的笔触方法使用该属性。

gc.setLineWidth(5);

setLineWidth()设置当前线宽。

gc.strokeOval(130, 30, 80, 80);

strokeOval()方法使用当前的描边绘图描边椭圆。

gc.setFill(Color.FORESTGREEN);

setFill()方法设置当前的填充涂料属性。 默认颜色是黑色。 GraphicsContext的填充方法使用该属性。

gc.fillOval(30, 30, 80, 80);

fillOval()使用当前的填充颜料填充椭圆形。

Stroke and fill

图:描边和填充

颜色

Color类用于处理 JavaFX 中的颜色。 有许多预定义的颜色。 可以使用 RGB 或 HSB 颜色模型创建自定义颜色值。

ColoursEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program draws six circles in six 
 * different colours.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class ColoursEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(300, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawShapes(gc);

        root.getChildren().add(canvas);         

        Scene scene = new Scene(root, 280, 200, Color.WHITESMOKE);

        stage.setTitle("Colours");
        stage.setScene(scene);
        stage.show();
    }

    private void drawShapes(GraphicsContext gc) {

        gc.setFill(Color.CADETBLUE);
        gc.fillOval(30, 30, 50, 50);

        gc.setFill(Color.DARKRED);
        gc.fillOval(110, 30, 50, 50);

        gc.setFill(Color.STEELBLUE);
        gc.fillOval(190, 30, 50, 50);    

        gc.setFill(Color.BURLYWOOD);
        gc.fillOval(30, 110, 50, 50); 

        gc.setFill(Color.LIGHTSEAGREEN);
        gc.fillOval(110, 110, 50, 50);  

        gc.setFill(Color.CHOCOLATE);
        gc.fillOval(190, 110, 50, 50);          
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用预定义的颜色值绘制六个圆。

gc.setFill(Color.CADETBLUE);

预定义的Color.CADETBLUE颜色设置为当前填充。

gc.fillOval(30, 30, 50, 50);

圆形对象的内部填充有当前的fill属性。

Colours

图:颜色

渐变

在计算机图形学中,渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在绘图和绘图程序中,渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。 有两种类型的渐变:线性渐变和径向渐变。

线性渐变

线性渐变是沿直线平滑混合颜色。 它由LinearGradient类定义。

LinearGradientEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program draws a linear gradient.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */
public class LinearGradientEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(300, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        doDrawing(gc);

        root.getChildren().add(canvas);    

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Linear gradient");
        stage.setScene(scene);
        stage.show();
    }

    private void doDrawing(GraphicsContext gc) {

        Stop[] stops1 = new Stop[] { new Stop(0.2, Color.BLACK), 
            new Stop(0.5, Color.RED), new Stop(0.8, Color.BLACK)};
        LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, 
                CycleMethod.NO_CYCLE, stops1);
        gc.setFill(lg1);
        gc.fillRect(50, 30, 200, 180);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们用线性渐变填充矩形。

Stop[] stops1 = new Stop[] { new Stop(0.2, Color.BLACK), 
    new Stop(0.5, Color.RED), new Stop(0.8, Color.BLACK)};

我们定义渐变的停止点。 它们指定如何沿渐变分布颜色。

LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, 
        CycleMethod.NO_CYCLE, stops1);

前四个参数指定渐变绘制所沿的线。 第五个参数是比例参数,它设置坐标是否与该渐变填充的形状成比例。 第六个参数设置渐变的循环方法。 最后一个参数为停止点。

LinearGradient

图:LinearGradient

径向渐变

径向渐变是圆和焦点之间颜色或阴影的平滑混合。 径向渐变由RadialGradient类定义。

RadialGradientEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program draws a radial gradient.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */
public class RadialGradientEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(300, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        doDrawing(gc);

        root.getChildren().add(canvas);    

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Radial gradient");
        stage.setScene(scene);
        stage.show();
    }

    private void doDrawing(GraphicsContext gc) {

        Stop[] stops1 = new Stop[] { new Stop(0, Color.RED), 
            new Stop(1, Color.BLACK)};
        RadialGradient lg1 = new RadialGradient(0, 0, 0.5, 0.5, 0.8, true, 
                CycleMethod.NO_CYCLE, stops1);
        gc.setFill(lg1);
        gc.fillOval(30, 30, 150, 150);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用径向渐变填充圆。

Stop[] stops1 = new Stop[] { new Stop(0, Color.RED), 
    new Stop(1, Color.BLACK)};

我们定义渐变的终止值。

RadialGradient lg1 = new RadialGradient(0, 0, 0.5, 0.5, 0.8, true, 
        CycleMethod.NO_CYCLE, stops1);

创建了一个径向渐变。 前两个参数是聚焦角和聚焦距离。 接下来的两个参数是渐变圆的圆心的 x 和 y 坐标。 第五个参数是定义颜色渐变范围的圆的半径。

RadialGradient

图:RadialGradient

形状

矩形,椭圆形,弧形是基本的几何形状。 GraphicsContext包含用于绘制这些形状的轮廓和内部的方法。

ShapesEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program paints six different
 * shapes.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class ShapesEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(320, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawShapes(gc);

        root.getChildren().add(canvas);        

        Scene scene = new Scene(root, 300, 200, Color.WHITESMOKE);

        stage.setTitle("Shapes");
        stage.setScene(scene);
        stage.show();
    }

    private void drawShapes(GraphicsContext gc) {

        gc.setFill(Color.GRAY);

        gc.fillOval(30, 30, 50, 50);
        gc.fillOval(110, 30, 80, 50);
        gc.fillRect(220, 30, 50, 50);
        gc.fillRoundRect(30, 120, 50, 50, 20, 20);
        gc.fillArc(110, 120, 60, 60, 45, 180, ArcType.OPEN);
        gc.fillPolygon(new double[]{220, 270, 220}, 
                new double[]{120, 170, 170}, 3);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用图形上下文的fill方法绘制了六个不同的形状。

gc.setFill(Color.GRAY);

形状涂成灰色。

gc.fillOval(30, 30, 50, 50);
gc.fillOval(110, 30, 80, 50);

fillOval()方法绘制一个圆和一个椭圆。 前两个参数是 x 和 y 坐标。 第三个和第四个参数是椭圆的宽度和高度。

gc.fillRect(220, 30, 50, 50);

fillRect()使用当前的填充颜料填充矩形。

gc.fillRoundRect(30, 120, 50, 50, 20, 20);

fillRoundRect()绘制一个矩形,其角是圆形的。 该方法的最后两个参数是矩形角的圆弧宽度和圆弧高度。

gc.fillArc(110, 120, 60, 60, 45, 180, ArcType.OPEN);

fillArc()方法使用当前的填充涂料填充圆弧。 最后三个参数是起始角度,角度扩展和闭合类型。

gc.fillPolygon(new double[]{220, 270, 220}, 
        new double[]{120, 170, 170}, 3);

fillPolygon()方法使用当前设置的填充涂料用给定的点填充多边形。 在我们的例子中,它绘制了一个直角三角形。 第一个参数是包含多边形点的 x 坐标的数组,第二个参数是包含多边形点的 y 坐标的数组。 最后一个参数是形成多边形的点数。

Colurs

图:颜色

星形

可以使用strokePolygon()fillPolygon()方法绘制更复杂的形状。 下一个示例绘制一个星形。

StarShapeEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program draws a Star shape on 
 * a Canvas.
 * 
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class StarShapeEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(300, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawStarShape(gc);

        root.getChildren().add(canvas);

        Scene scene = new Scene(root, 300, 250, Color.WHITESMOKE);

        stage.setTitle("Star");
        stage.setScene(scene);
        stage.show();
    }

    private void drawStarShape(GraphicsContext gc) {

        double xpoints[] = {10, 85, 110, 135, 210, 160,
            170, 110, 50, 60};
        double ypoints[] = {85, 75, 10, 75, 85, 125,
            190, 150, 190, 125};
        gc.strokePolygon(xpoints, ypoints, xpoints.length);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例绘制了星形的轮廓。 形状由十个坐标组成。

double xpoints[] = {10, 85, 110, 135, 210, 160, 
    170, 110, 50, 60};
double ypoints[] = {85, 75, 10, 75, 85, 125, 
    190, 150, 190, 125};

这些是形状的 x 和 y 坐标。

gc.strokePolygon(xpoints, ypoints, xpoints.length);

使用strokePolygon()方法绘制形状。

Star shape

图:星星 shape

透明矩形

透明性是指能够透视材料的质量。 在计算机图形学中,我们可以使用 alpha 合成来实现透明效果。 Alpha 合成是将图像与背景组合以创建部分透明外观的过程。

TransparentRectanglesEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * ZetCode JavaFX tutorial
 *
 * This program draws ten rectangles with different
 * levels of transparency.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: June 2015
 */

public class TransparentRectanglesEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        Pane root = new Pane();

        Canvas canvas = new Canvas(600, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawRectangles(gc);

        root.getChildren().add(canvas);

        Scene scene = new Scene(root, 600, 100, Color.WHITESMOKE);

        stage.setTitle("Transparent rectangles");
        stage.setScene(scene);
        stage.show();
    }

    private void drawRectangles(GraphicsContext gc) {

        for (int i = 1; i <= 10; i++) {

            float alpha = i * 0.1f;

            gc.setFill(Color.FORESTGREEN);
            gc.setGlobalAlpha(alpha);
            gc.fillRect(50 * i, 20, 40, 40);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例绘制了十个具有不同透明度级别的矩形。

float alpha = i * 0.1f;

在每个for周期中计算一个 alpha 值。

gc.setGlobalAlpha(alpha);

setGlobalAlpha()方法设置当前状态的全局 alpha。

Transparent rectangles

图:透明矩形

在本章中,我们在Canvas节点上执行了绘制操作。

JavaFX 图表

原文: http://zetcode.com/gui/javafx/charts/

在 JavaFX 教程的这一部分中,我们将使用图表。 在 JavaFX 中,只需添加几行代码即可构建图表。

在以下示例中,我们创建一个折线图,一个面积图,一个散点图,一个条形图和一个饼图。

折线图

折线图是一种基本类型的图表,它将信息显示为由直线段连接的一系列数据点。 JavaFX 中的折线图是使用javafx.scene.chart.LineChart创建的。

LineChartEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program creates a line chart.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */

public class LineChartEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();

        Scene scene = new Scene(root, 450, 330);

        NumberAxis xAxis = new NumberAxis();
        xAxis.setLabel("Age");

        NumberAxis yAxis = new NumberAxis();
        yAxis.setLabel("Salary (€)");

        LineChart lineChart = new LineChart(xAxis, yAxis);
        lineChart.setTitle("Average salary per age");

        XYChart.Series data = new XYChart.Series();
        data.setName("2016");

        data.getData().add(new XYChart.Data(18, 567));
        data.getData().add(new XYChart.Data(20, 612));
        data.getData().add(new XYChart.Data(25, 800));
        data.getData().add(new XYChart.Data(30, 980));
        data.getData().add(new XYChart.Data(40, 1410));
        data.getData().add(new XYChart.Data(50, 2350));

        lineChart.getData().add(data);

        root.getChildren().add(lineChart);

        stage.setTitle("LineChart");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们有一个折线图,显示每个年龄段的平均工资。

NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Age");

NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Salary (€)");

NumberAxis创建两个轴。 setLabel()方法设置轴的描述。

LineChart lineChart = new LineChart(xAxis, yAxis);
lineChart.setTitle("Average salary per age");

LineChart创建折线图。 setTitle()方法为图表设置标题。

XYChart.Series data = new XYChart.Series();
data.setName("2016");

XYChart.Series为图表提供数据系列。 数据系列是数据点的列表。 每个数据点包含一个 x 值和一个 y 值。 setName()方法为系列命名。 (一个图表中可能有多个系列。)

data.getData().add(new XYChart.Data(18, 567));
data.getData().add(new XYChart.Data(20, 612));
data.getData().add(new XYChart.Data(25, 800));
data.getData().add(new XYChart.Data(30, 980));
data.getData().add(new XYChart.Data(40, 1410));
data.getData().add(new XYChart.Data(50, 2350));

我们将数据添加到数据系列中。 XYChart.Data是一个单独的数据项,其中包含 2 个轴图的数据。

lineChart.getData().add(data);

数据被插入到图表中。

LineChart

图:LineChart

区域图

区域图以图形方式显示随时间变化的定量数据。

AreaChart.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program creates an area chart.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */
public class AreaChartEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();

        Scene scene = new Scene(root, 490, 350);

        CategoryAxis xAxis = new CategoryAxis();
        xAxis.setLabel("Time");

        NumberAxis yAxis = new NumberAxis();
        yAxis.setLabel("Thousand bbl/d");

        AreaChart areaChart = new AreaChart(xAxis, yAxis);
        areaChart.setTitle("Oil consumption");

        XYChart.Series data = new XYChart.Series();

        data.getData().add(new XYChart.Data("2004", 82502));
        data.getData().add(new XYChart.Data("2005", 84026));
        data.getData().add(new XYChart.Data("2006", 85007));
        data.getData().add(new XYChart.Data("2007", 86216));
        data.getData().add(new XYChart.Data("2008", 85559));
        data.getData().add(new XYChart.Data("2009", 84491));
        data.getData().add(new XYChart.Data("2010", 87672));
        data.getData().add(new XYChart.Data("2011", 88575));
        data.getData().add(new XYChart.Data("2012", 89837));
        data.getData().add(new XYChart.Data("2013", 90701));

        areaChart.getData().add(data);
        areaChart.setLegendVisible(false);

        root.getChildren().add(areaChart);

        stage.setTitle("AreaChart");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例显示了一个区域图,该区域图按年份显示了世界原油消耗量。

AreaChart areaChart = new AreaChart(xAxis, yAxis);
areaChart.setTitle("Oil consumption");

使用AreaChart创建面积图。

CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Time");

CategoryAxis适用于字符串类别。 我们在此轴上显示年份字符串。

AreaChart

图:AreaChart

散点图

散点图是在水平和垂直轴上绘制的一组点。

ScatterChartEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program creates a scatter chart.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */
public class ScatterChartEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();

        CategoryAxis xAxis = new CategoryAxis();

        NumberAxis yAxis = new NumberAxis("USD/kg", 30, 50, 2);

        ScatterChart scatterChart = new ScatterChart(xAxis, yAxis);

        XYChart.Series data = new XYChart.Series();

        data.getData().add(new XYChart.Data("Mar 14", 43));
        data.getData().add(new XYChart.Data("Nov 14", 38.5));
        data.getData().add(new XYChart.Data("Jan 15", 41.8));
        data.getData().add(new XYChart.Data("Mar 15", 37));
        data.getData().add(new XYChart.Data("Dec 15", 33.7));
        data.getData().add(new XYChart.Data("Feb 16", 39.8));

        scatterChart.getData().add(data);
        scatterChart.setLegendVisible(false);

        Scene scene = new Scene(root, 450, 330);

        root.getChildren().add(scatterChart);

        stage.setTitle("Gold price");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们使用ScatterChart显示黄金价格。

CategoryAxis xAxis = new CategoryAxis();

x 轴是用于显示日期的CategoryAxis

NumberAxis yAxis = new NumberAxis("USD/kg", 30, 50, 2);

y 轴是用于显示黄金价格的NumberAxis。 构造器的参数为​​:轴标签,下限,上限和刻度单位。

XYChart.Series data = new XYChart.Series();

data.getData().add(new XYChart.Data("Mar 14", 43));
...

使用XYChart.Series创建一系列数据,并使用XYChart.Data创建其数据项。

ScatterChart

图:ScatterChart

条形图

条形图显示带有矩形条的分组数据,其长度与它们所代表的值成比例。 条形图可以垂直或水平绘制。

BarChartEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program creates a bar chart.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */
public class BarChartEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();  

        Scene scene = new Scene(root, 480, 330);
        CategoryAxis xAxis = new CategoryAxis();

        NumberAxis yAxis = new NumberAxis();
        yAxis.setLabel("Gold medals");

        BarChart barChart = new BarChart(xAxis, yAxis);
        barChart.setTitle("Olympic gold medals in London");

        XYChart.Series data = new XYChart.Series();

        data.getData().add(new XYChart.Data("USA", 46));
        data.getData().add(new XYChart.Data("China", 38));
        data.getData().add(new XYChart.Data("UK", 29));
        data.getData().add(new XYChart.Data("Russia", 22));
        data.getData().add(new XYChart.Data("South Korea", 13));
        data.getData().add(new XYChart.Data("Germany", 11));

        barChart.getData().add(data);
        barChart.setLegendVisible(false);

        root.getChildren().add(barChart);

        stage.setTitle("BarChart");
        stage.setScene(scene);
        stage.show();

    }

    public static void main(String[] args) {
        launch(args);
    }
}

在示例中,我们使用条形图显示了 2012 年伦敦奥运会每个国家/地区的奥运金牌数量。

BarChart barChart = new BarChart(xAxis, yAxis);

使用BarChart创建条形图。

AreaChart

图:AreaChart

饼形图

饼图是一种圆图,分为多个切片以说明数值比例。

PieChartEx.java

package com.zetcode;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/*
 * ZetCode JavaFX tutorial
 *
 * This program creates a pie chart.
 *
 * Author: Jan Bodnar 
 * Website: zetcode.com 
 * Last modified: August 2016
 */
public class PieChartEx extends Application {

    @Override
    public void start(Stage stage) {

        initUI(stage);
    }

    private void initUI(Stage stage) {

        HBox root = new HBox();

        Scene scene = new Scene(root, 450, 330);

        ObservableList<PieChart.Data> pieChartData
                = FXCollections.observableArrayList(
                        new PieChart.Data("Apache", 52),
                        new PieChart.Data("Nginx", 31),
                        new PieChart.Data("IIS", 12),
                        new PieChart.Data("LiteSpeed", 2),
                        new PieChart.Data("Google server", 1),
                        new PieChart.Data("Others", 2));

        PieChart pieChart = new PieChart(pieChartData);
        pieChart.setTitle("Web servers market share (2016)");

        root.getChildren().add(pieChart);        

        stage.setTitle("PieChart");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

该示例使用饼图来显示 Web 服务器的市场份额。

ObservableList<PieChart.Data> pieChartData
        = FXCollections.observableArrayList(
                new PieChart.Data("Apache", 52),
                new PieChart.Data("Nginx", 31),
                new PieChart.Data("IIS", 12),
                new PieChart.Data("LiteSpeed", 2),
                new PieChart.Data("Google server", 1),
                new PieChart.Data("Others", 2));

饼图数据项是使用PieChart.Data创建的。

PieChart pieChart = new PieChart(pieChartData);

使用PieChart类创建一个饼图。

PieChart

图:PieChart

在本章中,我们在 JavaFX 中创建了LineChartAreaChartScatterChartBarChartPieChartJFreechart 教程显示了如何在流行的 JFreechart 库中创建图表。

wxWidgets 教程

原文: http://zetcode.com/gui/wxwidgets/

这是针对 C++ 编程语言的 wxWidgets 教程。 wxWidgets 是用于创建 C++ GUI 应用的跨平台工具包或框架。 阅读完本教程后,您将能够编写非平凡的 wxWidgets 应用。

目录

wxWidgets

wxWidgets 是用于创建 C++ 应用的 GUI(图形用户界面)工具箱。 它是一个开放源代码,成熟且跨平台的工具箱。 wxWidgets 应用可在所有主要的 OS 平台,Windows,Unix 和 Mac OS 上运行。

Tweet

相关教程

wxPython 教程涵盖了 Python 语言与 wxWidgets 库的绑定。 Qt4 教程介绍了 Qt4 库。

wxWidgets 简介

原文: http://zetcode.com/gui/wxwidgets/introduction/

本教程将向您介绍使用 wxWidgets 工具包进行编程。

wxWidgets

wxWidgets 是用于创建 C++ 应用的图形用户界面(GUI)工具包。 它是一个开源的,成熟的,跨平台的工具箱。 wxWidgets 应用可在所有主要的 OS 平台上运行,包括 Windows,Unix 和 Mac OS。 该项目由 Julian Smart 于 1992 年发起。wxWidgets 不仅仅是工具包。 它提供了各种各样的类来处理流,数据库,线程,联机帮助或应用设置。 wxWidgets 包含大量的小部件。 wxWidgets 周围的社区围绕他们的网站分组。

C++ 编程语言

C++ 编程语言是使用最广泛的编程语言之一。 它被用于许多著名的桌面应用中,例如 MS Office,Macromedia Flash,Firefox,Photoshop 或 3D Max。 C++ 在 PC 游戏世界中也占主导地位。 它是最困难的编程语言之一。 另一方面,当今的 C++ 编程与 10 年前的编程不同。 有许多工具和库可简化编程。

编程语言

当前有几种广泛使用的编程语言。 以下列表基于 TIOBE 编程社区索引。 数字是从 2017 年 9 月开始的。我们可以看到,C++ 仍然属于世界上最受欢迎的编程语言。

位置 语言 份额
1 Java 12.7%
2 C 7.4%
3 C++ 5.6%
4 C# 4.8%
5 Python 2.9%
6 PHP 2.2%
7 JavaScript 2.0%
8 Visual Basic .NET 1.98%
9 Perl 1.95%
10 Ruby 1.93%

Java 是使用最广泛的编程语言。 Java 在创建便携式移动应用,对各种设备进行编程以及创建企业应用方面表现出色。 每四个应用都使用 C/C++ 进行编程。 它们是创建操作系统和各种桌面应用的标准配置。 C/C++ 是使用最广泛的系统编程语言。

C# 是 Microsoft .NET 平台的主要编程语言。

PHP 在网络上占主导地位。 Java 主要由大型组织使用,而 PHP 由较小的公司和个人使用。 PHP 用于创建动态 Web 应用。

Visual Basic .NET 是另一种流行的.NET 编程语言。 它代表了 RAD(快速应用开发)的流行。

Perl,Python 和 Ruby 是使用最广泛的脚本语言。 他们有很多相似之处。 他们是紧密的竞争对手。

多平台编程

如今,多平台编程已成为流行语。 大多数语言和库都希望是多平台的。 wxWidgets 从一开始就是作为多平台工具创建的。 大多数开发者在这些选项中进行选择。 如果可能的话,他们会上网。 或者他们可以使用 Qt,wxWidgets,Swing 或 SWT。 Qt 库是最接近 wxWidgets 的竞争对手。

安装 wxWidgets

以下说明对基于 Debian 的 Linux 有效。

$ sudo apt-get install build-essential

如果我们还没有 C++ 编译器,请安装build-essential包。

$ sudo apt-get install libwxgtk3.0-dev

这将安装 wxWidgets 包。

这是 wxWidgets 的简介。

wxWidgets 助手类

原文: http://zetcode.com/gui/wxwidgets/helperclasses/

wxWidgets 由一大堆帮助程序类组成,它们可以帮助程序员完成工作。 这些包括用于处理字符串,文件,XML 文件,流,数据库或网络的类。 在这里,我们将只显示整个湖面的一小滴。

wxWidgets 库可用于创建控制台和 GUI 应用。 在本章中,我们将说明基于控制台的应用中的一些帮助程序类。

控制台

这是一个简单的控制台应用。 该应用将一些文本放入控制台窗口。

console.cpp

#include <wx/string.h>

int main(int argc, char **argv)
{
  wxPuts(wxT("A wxWidgets console application"));
}

A wxWidgets console application

这是输出。

wxString

wxString是代表字符串的类。

在下面的示例中,我们定义了三个wxStrings。 我们使用加法运算创建一个字符串。

addition.cpp

#include <wx/string.h>

int main(int argc, char **argv)
{
  wxString str1 = wxT("Linux");
  wxString str2 = wxT("Operating");
  wxString str3 = wxT("System");

  wxString str = str1 + wxT(" ") + str2 + wxT(" ") + str3;

  wxPuts(str);
}

Linux Operating System

这是输出。

Printf()方法用于格式化字符串。

formatted.cpp

#include <wx/string.h>

int main(int argc, char **argv)
{

  int flowers = 21;

  wxString str;
  str.Printf(wxT("There are %d red roses."), flowers);

  wxPuts(str);
}

There are 21 red roses.

这是输出。

下面的示例检查一个字符串是否包含另一个字符串。 为此,我们有一个Contains()方法。

contains.cpp

#include <wx/string.h>

int main(int argc, char **argv)
{

  wxString str = wxT("The history of my life");

  if (str.Contains(wxT("history"))) {
      wxPuts(wxT("Contains!"));
  }

  if (!str.Contains(wxT("plain"))) {
      wxPuts(wxT("Does not contain!"));
  }

}

Contains!
Does not contain!

这是输出。

Len()方法返回字符串中的字符数。

length.cpp

#include <wx/string.h>

int main(int argc, char **argv)
{
  wxString str = wxT("The history of my life");
  wxPrintf(wxT("The string has %d characters\n"), str.Len());
}

The string has 22 characters

这是输出。

MakeLower()MakeUpper()方法使字符小写和大写。

cases.cpp

#include <wx/string.h>

int main(int argc, char **argv)
{
  wxString str = wxT("The history of my life");

  wxPuts(str.MakeLower());
  wxPuts(str.MakeUpper());
}

the history of my life
THE HISTORY OF MY LIFE

这是输出。

实用函数

wxWidgets 具有几个方便的工具函数,用于执行进程,获取主用户目录或获取 OS 名称。

在下面的示例中,我们执行ls命令。 为此,我们具有wxShell()函数(仅 Unix)。

shell.cpp

#include <wx/string.h>
#include <wx/utils.h>

int main(int argc, char **argv)
{

  wxShell(wxT("ls -l"));

}

total 40
-rwxr-xr-x 1 vronskij vronskij  9028 2007-09-06 22:10 basic
-rw-r--r-- 1 vronskij vronskij    95 2007-09-06 22:09 basic.cpp
-rw-r--r-- 1 vronskij vronskij   430 2007-09-06 00:07 basic.cpp~
-rwxr-xr-x 1 vronskij vronskij 11080 2007-09-05 23:17 console
-rw-r--r-- 1 vronskij vronskij   500 2007-09-05 23:17 console.cpp
-rw-r--r-- 1 vronskij vronskij   485 2007-09-05 23:16 console.cpp~

这是输出。

接下来,我们将获得主用户目录,操作系统名称,用户名,主机名和总可用内存。

system.cpp

#include <wx/string.h>
#include <wx/utils.h>

int main(int argc, char **argv)
{
  wxPuts(wxGetHomeDir());
  wxPuts(wxGetOsDescription());
  wxPuts(wxGetUserName());
  wxPuts(wxGetFullHostName());

  long mem = wxGetFreeMemory().ToLong();

  wxPrintf(wxT("Memory: %ld\n"), mem);
}

/home/vronskij
Linux 2.6.20-16-generic i686
jan bodnar
spartan
Memory: 741244928

这是输出。

时间日期

在 wxWidgets 中,我们有几个用于处理日期&时间的类。

该示例以各种格式显示当前日期或时间。

datetime.cpp

#include <wx/datetime.h>

int main(int argc, char **argv)
{
  wxDateTime now = wxDateTime::Now();

  wxString date1 = now.Format();
  wxString date2 = now.Format(wxT("%X"));
  wxString date3 = now.Format(wxT("%x"));

  wxPuts(date1);
  wxPuts(date2);
  wxPuts(date3);
}

Fri Sep  7 21:28:38 2007
21:28:38
09/07/07

这是输出。

接下来,我们将显示不同城市的当前时间。

datetime2.cpp

#include <wx/datetime.h>

int main(int argc, char **argv)
{
  wxDateTime now = wxDateTime::Now();

  wxPrintf(wxT("   Tokyo: %s\n"), now.Format(wxT("%a %T"), 
      wxDateTime::GMT9).c_str());
  wxPrintf(wxT("  Moscow: %s\n"), now.Format(wxT("%a %T"), 
      wxDateTime::MSD).c_str());
  wxPrintf(wxT("Budapest: %s\n"), now.Format(wxT("%a %T"), 
      wxDateTime::CEST).c_str());
  wxPrintf(wxT("  London: %s\n"), now.Format(wxT("%a %T"), 
      wxDateTime::WEST).c_str());
  wxPrintf(wxT("New York: %s\n"), now.Format(wxT("%a %T"), 
      wxDateTime::EDT).c_str());
}

   Tokyo: Sat 05:42:24
  Moscow: Sat 00:42:24
Budapest: Fri 22:42:24
  London: Fri 22:42:24
New York: Fri 16:42:24

这是输出。

以下示例显示了如何将日期范围添加到日期/时间。 我们将当前时间增加一个月。

datespan.cpp

#include <wx/datetime.h>

int main(int argc, char **argv)
{
  wxDateTime now = wxDateTime::Now();
  wxString date1 = now.Format(wxT("%B %d %Y"));
  wxPuts(date1);

  wxDateSpan span(0, 1);
  wxDateTime then = now.Add(span);

  wxString date2 = then.Format(wxT("%B %d %Y"));
  wxPuts(date2);

}

September 07 2007
October 07 2007

这是输出。

文件

wxWidgets 有几个类可简化文件的处理。 与使用流相反,这是对文件的低级别访问。

在下面的示例中,我们使用wxFile类创建一个新文件并将数据写入其中。 我们还将测试文件是否打开。 请注意,当我们创建文件时,它会自动保持打开状态。

createfile.cpp

#include <wx/file.h>

int main(int argc, char **argv)
{

  wxString str = wxT("You make me want to be a better man.\n");

  wxFile file;
  file.Create(wxT("quote"), true);

  if (file.IsOpened())
      wxPuts(wxT("the file is opened"));

  file.Write(str);
  file.Close();

  if (!file.IsOpened())
      wxPuts(wxT("the file is not opened"));
}

$ ls qoute
ls: qoute: No such file or directory

$ ./createfile 
the file is opened
the file is not opened

$ cat quote
You make me want to be a better man.

这是输出。

wxTextFile是一个简单的类,允许逐行处理文本文件。 与wxFile类相比,使用此类更容易。

在下一个示例中,我们将打印文件中的行数,第一行和最后一行,最后将读取并显示文件的内容。

readfile.cpp

#include <wx/textfile.h>

int main(int argc, char **argv)
{

  wxTextFile file(wxT("test.c"));

  file.Open();

  wxPrintf(wxT("Number of lines: %d\n"), file.GetLineCount());
  wxPrintf(wxT("First line: %s\n"), file.GetFirstLine().c_str());
  wxPrintf(wxT("Last line: %s\n"), file.GetLastLine().c_str());

  wxPuts(wxT("-------------------------------------"));

  wxString s;

  for ( s = file.GetFirstLine(); !file.Eof(); 
      s = file.GetNextLine() )
  {
       wxPuts(s);
  }

  file.Close();
}

Number of lines: 8
First line: #include <glib.h>
Last line: }
-------------------------------------
#include <glib.h>
#include <glib/gstdio.h>

int main() {

g_mkdir("/home/vronskij/test", S_IRWXU);

}

这是输出。

wxDir类允许我们枚举文件和目录。

在以下示例中,我们将打印当前工作目录中可用的所有文件和目录。

dir.cpp

#include <wx/dir.h>
#include <wx/filefn.h>

int main(int argc, char **argv)
{

  wxDir dir(wxGetCwd());

  wxString file;

  bool cont = dir.GetFirst(&file, wxEmptyString,
      wxDIR_FILES | wxDIR_DIRS);

  while (cont) {
      wxPuts(file);
      cont = dir.GetNext(&file);
  }
}

$ ./dir
dir
temp
console
basic.cpp
basic
quote
createfile
console.cpp
basic.cpp~
test.c
console.cpp~

这是输出。

在本章中,我们介绍了一些 wxWidgets 帮助器类。

wxWidgets 中的第一个程序

原文: http://zetcode.com/gui/wxwidgets/firstprograms/

在本章中,我们将介绍创建 wxWidgets 应用所需的基础知识。 我们将创建第一个简单示例,展示如何显示图标。 接下来,我们将创建一个简单的示例来演示事件的用法。 最后,我们将看到小部件如何在 wxWidgets 应用中进行通信。

一个简单的应用

首先,我们创建非常基本的 wxWidgets 程序。

simple.h

#include <wx/wx.h>

class Simple : public wxFrame
{
public:
    Simple(const wxString& title);

};

simple.cpp

#include "simple.h"

Simple::Simple(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
{
  Centre();
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "simple.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
    Simple *simple = new Simple(wxT("Simple"));
    simple->Show(true);

    return true;
}

这个非常基本的示例在屏幕上显示了一个小窗口。 窗口居中。

Centre();

此方法使窗口在屏幕上水平和垂直居中。

IMPLEMENT_APP(MyApp)

实现该应用的代码隐藏在此宏的后面。 这是复制和粘贴代码,我们通常不必关心。

g++ main.cpp main.h simple.cpp simple.h  `wx-config --cxxflags --libs` -o simple

要在 Unix 上编译示例,请运行以上命令。

Simple

图:简单

应用图标

在此示例中,我们为应用提供一个图标。 在窗口的左上角显示小图标已成为一种标准。 图标是程序的图形标识。

icon.h

#include <wx/wx.h>

class Icon : public wxFrame
{
public:
    Icon(const wxString& title);

};

icon.cpp

#include "icon.h"

Icon::Icon(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
{
  SetIcon(wxIcon(wxT("web.xpm")));
  Centre();
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "icon.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
    Icon *icon = new Icon(wxT("Icon"));
    icon->Show(true);

    return true;
}

在我们的示例中,我们显示了一个小的 Web 图标。

SetIcon(wxIcon(wxT("web.xpm")));

显示应用图标仅需一行代码。 XPM(X PixMap)是 ASCII 图像格式。

Icon

图:图标

一个简单的按钮

在下面的示例中,我们在框架小部件上创建一个按钮。 我们将展示如何创建一个简单的事件处理器。

button.h

#include <wx/wx.h>

class Button : public wxFrame
{
public:
    Button(const wxString& title);

    void OnQuit(wxCommandEvent & event);
};

button.cpp

#include "button.h"

Button::Button(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
{
  wxPanel *panel = new wxPanel(this, wxID_ANY);

  wxButton *button = new wxButton(panel, wxID_EXIT, wxT("Quit"), 
      wxPoint(20, 20));
  Connect(wxID_EXIT, wxEVT_COMMAND_BUTTON_CLICKED, 
      wxCommandEventHandler(Button::OnQuit));
  button->SetFocus();
  Centre();
}

void Button::OnQuit(wxCommandEvent & WXUNUSED(event))
{
    Close(true);
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "button.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    Button *btnapp = new Button(wxT("Button"));
    btnapp->Show(true);

    return true;
}

wxPanel *panel = new wxPanel(this, wxID_ANY);

首先,我们创建一个wxPanel小部件。 它将放置在wxFrame小部件内。

wxButton *button = new wxButton(panel, wxID_EXIT, wxT("Quit"), wxPoint(20, 20));

我们创建一个wxButton小部件。 它放在面板上。 我们为按钮使用预定义的wxID_EXIT ID。 这将导致在按钮上显示一个小的退出图标。 按钮的标签为"Quit"。 手动将按钮定位在x = 20y = 20坐标处。 坐标系的起点在左上角。

Connect(wxID_EXIT, wxEVT_COMMAND_BUTTON_CLICKED, 
    wxCommandEventHandler(Button::OnQuit));

如果单击按钮,将生成wxEVT_COMMAND_BUTTON_CLICKED事件。 我们将事件连接到Button类的OnQuit()方法。 因此,当我们单击按钮时,将调用OnQuit()方法。

button->SetFocus();

我们将键盘焦点设置为按钮。 因此,如果我们按Enter键,则单击该按钮。

Close(true);

OnQuit()方法内部,我们称为Close()方法。 这将终止我们的应用。

Button

图:按钮

小部件通信

了解小部件如何在应用中进行通信非常重要。 请遵循下一个示例。

Panels.h

#include <wx/wx.h>
#include <wx/panel.h>

class LeftPanel : public wxPanel
{
public:
    LeftPanel(wxPanel *parent);

    void OnPlus(wxCommandEvent & event);
    void OnMinus(wxCommandEvent & event);

    wxButton *m_plus;
    wxButton *m_minus;
    wxPanel *m_parent;
    int count;

};

class RightPanel : public wxPanel
{
public:
    RightPanel(wxPanel *parent);

    void OnSetText(wxCommandEvent & event);

    wxStaticText *m_text;

};

const int ID_PLUS = 101;
const int ID_MINUS = 102;

Panels.cpp

#include <wx/stattext.h>
#include "Communicate.h"

LeftPanel::LeftPanel(wxPanel * parent)
       : wxPanel(parent, -1, wxPoint(-1, -1), wxSize(-1, -1), wxBORDER_SUNKEN)
{
  count = 0;
  m_parent = parent;
  m_plus = new wxButton(this, ID_PLUS, wxT("+"), 
      wxPoint(10, 10));
  m_minus = new wxButton(this, ID_MINUS, wxT("-"), 
      wxPoint(10, 60));
  Connect(ID_PLUS, wxEVT_COMMAND_BUTTON_CLICKED, 
      wxCommandEventHandler(LeftPanel::OnPlus));
  Connect(ID_MINUS, wxEVT_COMMAND_BUTTON_CLICKED, 
      wxCommandEventHandler(LeftPanel::OnMinus));
}

void LeftPanel::OnPlus(wxCommandEvent & WXUNUSED(event))
{
  count++;

  Communicate *comm = (Communicate *) m_parent->GetParent();
  comm->m_rp->m_text->SetLabel(wxString::Format(wxT("%d"), count));
}

void LeftPanel::OnMinus(wxCommandEvent & WXUNUSED(event))
{
  count--;

  Communicate *comm = (Communicate *) m_parent->GetParent();
  comm->m_rp->m_text->SetLabel(wxString::Format(wxT("%d"), count));
}

RightPanel::RightPanel(wxPanel * parent)
       : wxPanel(parent, wxID_ANY, wxDefaultPosition, 
         wxSize(270, 150), wxBORDER_SUNKEN)
{
    m_text = new wxStaticText(this, -1, wxT("0"), wxPoint(40, 60));
}

Communicate.h

#include "Panels.h"
#include <wx/wxprec.h>

class Communicate : public wxFrame
{
public:
    Communicate(const wxString& title);

    LeftPanel *m_lp;
    RightPanel *m_rp;
    wxPanel *m_parent;

};

Communicate.cpp

#include "Communicate.h"

Communicate::Communicate(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(290, 150))
{
  m_parent = new wxPanel(this, wxID_ANY);

  wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);

  m_lp = new LeftPanel(m_parent);
  m_rp = new RightPanel(m_parent);

  hbox->Add(m_lp, 1, wxEXPAND | wxALL, 5);
  hbox->Add(m_rp, 1, wxEXPAND | wxALL, 5);

  m_parent->SetSizer(hbox);

  this->Centre();
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "Communicate.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    Communicate *communicate = new Communicate(wxT("Widgets communicate"));
    communicate->Show(true);

    return true;
}

在我们的示例中,我们有两个面板。 左右面板。 左侧面板有两个按钮。 右侧面板有一个静态文本。 这些按钮更改静态文本中显示的数字。 问题是,我们如何抓住指向静态文本的指针?

m_parent = parent;

在这里,我们将指针保存到LeftPanel的父窗口小部件。 这是一个wxPanel小部件。

Communicate *comm = (Communicate *) m_parent->GetParent();
comm->m_rp->m_text->SetLabel(wxString::Format(wxT("%d"), count));

这两行是示例中最重要的行。 显示了如何访问放置在不同面板上的静态文本小部件。 首先,我们获得左右两个面板的父面板。 该父窗口小部件具有指向右侧面板的指针。 右面板上有一个指向静态文本的指针。

Widgets communicate

图:小部件通信

在 wxWidgets 教程的这一部分中,我们创建了一些简单的程序。

wxWidgets 中的菜单和工具栏

原文: http://zetcode.com/gui/wxwidgets/menustoolbars/

菜单栏是 GUI 应用中最可见的部分之一。 它是位于各个菜单中的一组命令。 在控制台应用中,您必须记住所有这些神秘命令,在这里,我们将大多数命令分组为逻辑部分。 有公认的标准可以进一步减少学习新应用的时间。 要在 wxWidgets 中实现菜单栏,我们需要三个类:wxMenuBarwxMenuwxMenuItem

简单菜单示例

在 wxWidgets 中创建菜单栏非常简单。

menu.h

#include <wx/wx.h>
#include <wx/menu.h>

class SimpleMenu : public wxFrame
{
public:
    SimpleMenu(const wxString& title);

    void OnQuit(wxCommandEvent& event);

    wxMenuBar *menubar;
    wxMenu *file;

};

menu.cpp

#include "menu.h"

SimpleMenu::SimpleMenu(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{

  menubar = new wxMenuBar;
  file = new wxMenu;
  file->Append(wxID_EXIT, wxT("&Quit"));
  menubar->Append(file, wxT("&File"));
  SetMenuBar(menubar);

  Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED,
      wxCommandEventHandler(SimpleMenu::OnQuit));
  Centre();

}

void SimpleMenu::OnQuit(wxCommandEvent& WXUNUSED(event))
{
  Close(true);
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "menu.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    SimpleMenu *menu = new SimpleMenu(wxT("Simple Menu"));
    menu->Show(true);

    return true;
}

menubar = new wxMenuBar;

首先,我们创建一个菜单栏对象。

file = new wxMenu;

接下来,我们创建一个菜单对象。

file->Append(wxID_EXIT, wxT("&Quit"));

我们将菜单项添加到菜单对象中。 第一个参数是菜单项的 ID。 第二个参数是菜单项的名称。 在这里,我们没有明确创建一个wxMenuItem。 它是通过Append()方法在后台创建的。 稍后,我们将手动创建wxMenuItem

menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);

之后,我们将菜单添加到菜单栏中。 &字符创建一个加速键。 带下划线的&后面的字符。 这样,可以通过 Alt + F 快捷方式访问菜单。 最后,我们调用SetMenuBar()方法。 该方法属于wxFrame小部件。 它设置菜单栏。

Simle menu example

图:简单菜单 example

子菜单

每个菜单也可以有一个子菜单。 这样,我们可以将类似的命令分组。 例如,我们可以将隐藏或显示各种工具栏(例如个人栏,地址栏,状态栏或导航栏)的命令放置在称为工具栏的子菜单中。 在菜单中,我们可以使用分隔符来分隔命令。 这是一条简单的线。 通常的做法是使用单个分隔符将命令(例如新建,打开,保存)与命令(例如打印,打印预览)分开。 在我们的示例中,我们将看到如何创建子菜单和菜单分隔符。

menu.h

#include <wx/wx.h>
#include <wx/menu.h>

class SubMenu : public wxFrame
{
public:
  SubMenu(const wxString& title);

  void OnQuit(wxCommandEvent & event);

  wxMenuBar *menubar;
  wxMenu *file;
  wxMenu *imp;
  wxMenuItem *quit;

};

menu.cpp

#include "menu.h"

SubMenu::SubMenu(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{

  menubar = new wxMenuBar;
  file = new wxMenu;

  file->Append(wxID_ANY, wxT("&New"));
  file->Append(wxID_ANY, wxT("&Open"));
  file->Append(wxID_ANY, wxT("&Save"));
  file->AppendSeparator();

  imp = new wxMenu;
  imp->Append(wxID_ANY, wxT("Import newsfeed list..."));
  imp->Append(wxID_ANY, wxT("Import bookmarks..."));
  imp->Append(wxID_ANY, wxT("Import mail..."));

  file->AppendSubMenu(imp, wxT("I&mport"));

  quit = new wxMenuItem(file, wxID_EXIT, wxT("&Quit\tCtrl+W"));
  file->Append(quit);

  menubar->Append(file, wxT("&File"));
  SetMenuBar(menubar);

  Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, 
      wxCommandEventHandler(SubMenu::OnQuit));
  Centre();

}

void SubMenu::OnQuit(wxCommandEvent& WXUNUSED(event))
{
  Close(true);
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "menu.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    SubMenu *smenu = new SubMenu(wxT("Submenu"));
    smenu->Show(true);

    return true;
}

我们在文件菜单中创建了一个子菜单。 这是一个导入子菜单,可以在 Opera Web 浏览器中看到。

file->AppendSeparator();

创建菜单分隔线,并调用AppendSeparator()方法。

imp = new wxMenu;
imp->Append(wxID_ANY, wxT("Import newsfeed list..."));
imp->Append(wxID_ANY, wxT("Import bookmarks..."));
imp->Append(wxID_ANY, wxT("Import mail..."));

file->AppendSubMenu(imp, wxT("I&mport"));

子菜单的创建类似于普通菜单。 它附有AppendSubMenu()方法。

Submenu

图:子菜单

工具栏

菜单将我们可以在应用中使用的所有命令分组。 使用工具栏可以快速访问最常用的命令。

virtual wxToolBar* wxFrame::CreateToolBar(long style = wxTB_DEFAULT_STYLE,
    wxWindowID id = wxID_ANY, const wxString & name = wxToolBarNameStr)

要创建工具栏,我们调用框架窗口小部件的CreateToolBar()方法。

一个简单的工具栏

我们的第一个示例将创建一个简单的工具栏。

toolbar.h

#include <wx/wx.h>

class Toolbar : public wxFrame
{
public:
    Toolbar(const wxString& title);

    void OnQuit(wxCommandEvent& event);
};

toolbar.cpp

#include "toolbar.h"

Toolbar::Toolbar(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, 
                 wxDefaultPosition, wxSize(300, 250)) {

    wxImage::AddHandler(new wxPNGHandler);

    wxBitmap exit(wxT("exit.png"), wxBITMAP_TYPE_PNG);

    wxToolBar *toolbar = CreateToolBar();
    toolbar->AddTool(wxID_EXIT, wxT("Exit application"), exit);
    toolbar->Realize();

    Connect(wxID_EXIT, wxEVT_COMMAND_TOOL_CLICKED, 
        wxCommandEventHandler(Toolbar::OnQuit));
}

void Toolbar::OnQuit(wxCommandEvent& WXUNUSED(event)) {

    Close(true);
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp {

    public:
        virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "toolbar.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit() {

    Toolbar *toolbar = new Toolbar(wxT("Toolbar"));
    toolbar->Show(true);

    return true;
}

在我们的示例中,我们创建了一个工具栏和一个工具按钮。 单击工具栏按钮将终止应用。

wxToolBar *toolbar = CreateToolBar();

我们创建一个工具栏。

toolbar->AddTool(wxID_EXIT, wxT("Exit application"), exit);

我们将工具添加到工具栏。

toolbar->Realize();

添加工具后,我们将调用Realize()方法。

Toolbar

图:工具栏

工具栏

如果我们要拥有多个工具栏,则必须以其他方式创建它们,例如除了调用CreateToolbar()方法。

toolbars.h

#include <wx/wx.h>

class Toolbar : public wxFrame {

    public:
        Toolbar(const wxString& title);

        void OnQuit(wxCommandEvent& event);

        wxToolBar *toolbar1;
        wxToolBar *toolbar2;
};

toolbars.cpp

#include "toolbars.h"

Toolbar::Toolbar(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, 
                 wxDefaultPosition, wxSize(300, 250)) {

    wxImage::AddHandler(new wxPNGHandler);

    wxBitmap exit(wxT("exit.png"), wxBITMAP_TYPE_PNG);
    wxBitmap newb(wxT("new.png"), wxBITMAP_TYPE_PNG);
    wxBitmap open(wxT("open.png"), wxBITMAP_TYPE_PNG);
    wxBitmap save(wxT("save.png"), wxBITMAP_TYPE_PNG);

    wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);

    toolbar1 = new wxToolBar(this, wxID_ANY);
    toolbar1->AddTool(wxID_ANY, wxT("New"), newb);
    toolbar1->AddTool(wxID_ANY, wxT("Open"), open);
    toolbar1->AddTool(wxID_ANY, wxT(""), save);
    toolbar1->Realize();

    toolbar2 = new wxToolBar(this, wxID_ANY);
    toolbar2->AddTool(wxID_EXIT, wxT("Exit application"), exit);
    toolbar2->Realize();

    vbox->Add(toolbar1, 0, wxEXPAND);
    vbox->Add(toolbar2, 0, wxEXPAND);

    SetSizer(vbox);

    Connect(wxID_EXIT, wxEVT_COMMAND_TOOL_CLICKED, 
        wxCommandEventHandler(Toolbar::OnQuit));
}

void Toolbar::OnQuit(wxCommandEvent& WXUNUSED(event)) {

    Close(true);
}

main.h

#include <wx/wx.h>

class MyApp : public wxApp {

    public:
        virtual bool OnInit();
};

main.cpp

#include "main.h"
#include "toolbars.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit() {

    Toolbar *toolbar = new Toolbar(wxT("Toolbar"));
    toolbar->Show(true);

    return true;
}

在我们的示例中,我们创建了两个水平工具栏。 我们将它们放置在垂直包装机上。

toolbar1 = new wxToolBar(this, wxID_ANY);
...
toolbar2 = new wxToolBar(this, wxID_ANY);

在这里,我们创建两个工具栏。

vbox->Add(toolbar1, 0, wxEXPAND);
vbox->Add(toolbar2, 0, wxEXPAND);

在这里,我们将它们添加到垂直框大小调整器中。

Toolbars

图:工具栏 s

在 wxWidgets 教程的这一部分中,我们介绍了菜单和工具栏。

UI 的第一步

原文: http://zetcode.com/gui/winapi/firststeps/

在 Windows API 教程的这一部分中,我们将创建一些简单的 UI 示例。

简单的程序

这是一个非常简单的程序。 它将弹出一个小对话框。

simple.c

#include <windows.h>

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow) {

    MessageBoxW(NULL, L"First Program", L"First", MB_OK);

    return 0;
}

屏幕上会显示一个小对话框。 它具有标题,消息和“确定”按钮。

#include <windows.h>

我们包括基本的函数声明,常量,数据类型和结构。

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow)

wWinMain()函数是我们应用的入口。

MessageBoxW(NULL, L"First Program", L"First", MB_OK);

MessageBoxW()函数显示一个简单的消息框。 第一个参数是所有者窗口。 在我们的情况下,该对话框没有所有者。 接下来的两个参数提供消息文本和标题。 最后一个参数定义消息对话框的类型。 MB_OK值使对话框具有一个“确定”按钮。

Simple message box

图:简单 message box

使窗口居中

在下一个代码示例中,我们将窗口置于屏幕中央。 SetWindowPos()函数更改子项,弹出窗口或顶级窗口的大小,位置和 Z 顺序。

BOOL WINAPI SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int x, int y,
    int cx, int cy, UINT uFlags);

第一个参数是窗口的句柄。 第二个参数是窗口的句柄,该窗口的句柄以 Z 顺序或特殊标志位于定位的窗口之前。 例如,HWND_BOTTOM标志将窗口置于 Z 顺序的底部,HWND_TOP标志置于 Z 顺序的顶部。 xy参数是客户端坐标中窗口左侧和顶部的新位置。 cxcy是窗口的新宽度和高度大小,以像素为单位。 最后一个参数是大小和位置标志的组合。 例如SWP_NOMOVE保留当前位置(忽略xy参数)或SWP_NOSIZE保留当前大小(忽略cxcy参数)。

centering.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CenterWindow(HWND);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR pCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Center";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Center",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 250, 150, 0, 0, hInstance, 0);  

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam) {

    switch(msg) {

        case WM_CREATE: 

            CenterWindow(hwnd);
            break;      

        case WM_DESTROY: 

            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CenterWindow(HWND hwnd) {

    RECT rc = {0};

    GetWindowRect(hwnd, &rc);
    int win_w = rc.right - rc.left;
    int win_h = rc.bottom - rc.top;

    int screen_w = GetSystemMetrics(SM_CXSCREEN);
    int screen_h = GetSystemMetrics(SM_CYSCREEN);

    SetWindowPos(hwnd, HWND_TOP, (screen_w - win_w)/2, 
        (screen_h - win_h)/2, 0, 0, SWP_NOSIZE);
}

为了使窗口在屏幕上居中,我们需要确定窗口和屏幕的大小。

case WM_CREATE: 

    CenterWindow(hwnd);
    break;  

我们在WM_CREATE消息期间调用用户定义的CenterWindow()函数。

GetWindowRect(hwnd, &rc) ;

使用GetWindowRect()函数,我们检索指定窗口的边界矩形的大小。

int win_w = rc.right - rc.left;
int win_h = rc.bottom - rc.top;

计算窗口的宽度和高度。

int screen_w = GetSystemMetrics(SM_CXSCREEN)
int screen_h = GetSystemMetrics(SM_CYSCREEN);

通过GetSystemMetrics()函数,我们可以确定屏幕的宽度和高度。

SetWindowPos(hwnd, HWND_TOP, (screen_w - win_w)/2, 
    (screen_h - win_h)/2, 0, 0, SWP_NOSIZE);

我们使用SetWindowPos()函数将应用窗口放置在屏幕中央。

热键

在以下示例中,我们显示了如何注册热键。 热键是用于执行特定操作的组合键。 热键已通过RegisterHotKey()函数注册。

BOOL WINAPI RegisterHotKey(HWND hWnd, int id, UINT fsModifiers, UINT vk);

第一个参数是窗口的句柄,该窗口将接收由热键生成的WM_HOTKEY消息。 第二个参数是热键的 ID。 第三个参数由修饰符组成; 必须将这些键与vk参数指定的键组合在一起才能生成WM_HOTKEY消息。 改性剂的实例包括MOD_ALTMOD_CONTROL。 最后一个参数是热键的虚拟键代码。

hotkey.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CenterWindow(HWND);

#define ID_HOTKEY 1

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    HWND hwnd;
    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Application";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    hwnd = CreateWindowW(wc.lpszClassName, L"Hot key",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 270, 170, 0, 0, 0, 0);  

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
        WPARAM wParam, LPARAM lParam) {

    switch(msg) {

        case WM_CREATE:

              RegisterHotKey(hwnd, ID_HOTKEY, MOD_CONTROL, 0x43);
              break;

        case WM_HOTKEY:

            if ((wParam) == ID_HOTKEY) {

                CenterWindow(hwnd);
            }

            break;

        case WM_DESTROY:

            UnregisterHotKey(hwnd, ID_HOTKEY);
            PostQuitMessage(0);            
            break; 
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CenterWindow(HWND hwnd) {

    RECT rc = {0};

    GetWindowRect(hwnd, &rc);
    int win_w = rc.right - rc.left;
    int win_h = rc.bottom - rc.top;

    int screen_w = GetSystemMetrics(SM_CXSCREEN);
    int screen_h = GetSystemMetrics(SM_CYSCREEN);

    SetWindowPos(hwnd, HWND_TOP, (screen_w - win_w)/2, 
        (screen_h - win_h)/2, 0, 0, SWP_NOSIZE);
}

在示例中,我们注册了 Ctrl + C 热键。 它将窗口居中在屏幕上。

case WM_CREATE:

      RegisterHotKey(hwnd, ID_HOTKEY, MOD_CONTROL, 0x43);
      break;

在创建窗口的过程中,我们使用RegisterHotKey()函数注册了 Ctrl + C 热键。

case WM_HOTKEY:

    if ((wParam) == ID_HOTKEY) {

        CenterWindow(hwnd);
    }

    break;

调用热键时会生成WM_HOTKEY消息。 我们通过检查wParam参数来识别我们的热键,然后调用CenterWindow()函数。

case WM_DESTROY:

    UnregisterHotKey(hwnd, ID_HOTKEY);
    PostQuitMessage(0);            
    break; 

当窗口被破坏时,我们使用UnregisterHotKey()函数取消注册热键。 MSDN 尚不清楚是否必须调用此函数。

更多窗口

从特定的窗口类创建一个窗口。 窗口类定义了几个窗口共有的一组行为。 一些类已经在系统中预定义。 自定义窗口类必须注册。 之后,我们可以创建此新窗口类的窗口。 使用CreateWindowW()函数创建一个窗口。 它的第一个参数是窗口类名称。

每个窗口都有一个窗口过程。 当用户与窗口交互时,此函数由 OS 调用。 在下面的示例中,我们创建三个窗口:一个父窗口和两个子窗口。

morewindows.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK PanelProc(HWND, UINT, WPARAM, LPARAM);

void RegisterRedPanelClass(void);
void RegisterBluePanelClass(void);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {
    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Windows";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Windows",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 250, 180, 0, 0, hInstance, 0);  

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {    

    switch(msg) {

        case WM_CREATE:

            RegisterRedPanelClass();

            CreateWindowW(L"RedPanelClass", NULL, 
                          WS_CHILD | WS_VISIBLE,
                          20, 20, 80, 80,
                          hwnd, (HMENU) 1, NULL, NULL);

            RegisterBluePanelClass();

            CreateWindowW(L"BluePanelClass", NULL, 
                          WS_CHILD | WS_VISIBLE,
                          120, 20, 80, 80,
                          hwnd, (HMENU) 2, NULL, NULL);
            break;

        case WM_DESTROY:

            PostQuitMessage(0);
            return 0; 
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK PanelProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    switch(msg) {

        case WM_LBUTTONUP:

            MessageBeep(MB_OK);
            break;    
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void RegisterRedPanelClass(void) {

    HBRUSH hbrush = CreateSolidBrush(RGB(255, 0, 0));

    WNDCLASSW rwc = {0};

    rwc.lpszClassName = L"RedPanelClass";
    rwc.hbrBackground = hbrush;
    rwc.lpfnWndProc   = PanelProc;
    rwc.hCursor       = LoadCursor(0, IDC_ARROW);
    RegisterClassW(&rwc); 
}

void RegisterBluePanelClass(void) {

    HBRUSH hbrush = CreateSolidBrush(RGB(0, 0, 255));

    WNDCLASSW rwc = {0};

    rwc.lpszClassName = L"BluePanelClass";
    rwc.hbrBackground = hbrush;
    rwc.lpfnWndProc   = PanelProc;
    rwc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&rwc);
}

我们有一个带有两个子窗口的应用窗口。 两个子窗口具有蓝色和红色背景。

HBRUSH hbrush = CreateSolidBrush(RGB(255, 0, 0));
...
rwc.hbrBackground = hbrush;

要创建彩色窗口背景,我们通过调用CreateSolidBrush()函数来创建自定义的实心画笔。 要指定颜色,我们使用RGB宏。 众所周知,可以通过组合红色,绿色和蓝色来创建任何颜色。 然后,将窗口类结构的hbrBackground参数设置为此新创建的画笔。

RegisterRedPanelClass();

CreateWindowW(L"RedPanelClass", NULL, 
                WS_CHILD | WS_VISIBLE,
                20, 20, 80, 80,
                hwnd, (HMENU) 1, NULL, NULL);

首先,我们注册一个新的窗口类。 完成此步骤后,我们将创建此类的窗口。

我们的两个子窗口都共享PanelProc窗口过程。 当我们与 Windows OS 进行交互时,将调用此过程。

case WM_LBUTTONUP:

    MessageBeep(MB_OK);
    break;    

单击子窗口时,我们将与它们交互。 通过在子窗口上单击鼠标左键,Windows 操作系统将调用子窗口过程并发送WM_LBUTTONUP消息。 在我们的示例中,我们调用MessageBeep()函数。 如果我们在两个子窗口的背景上单击鼠标左键,则会听到 Windows 默认的蜂鸣声。

void RegisterBluePanelClass(void) {

    HBRUSH hbrush = CreateSolidBrush(RGB(0, 0, 255));

    WNDCLASSW rwc = {0};

    rwc.lpszClassName = L"BluePanelClass";
    rwc.hbrBackground = hbrush;
    rwc.lpfnWndProc   = PanelProc;
    rwc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&rwc);
} 

该函数注册一个新的窗口类。 此窗口类类型的窗口具有红色背景。 编辑,按钮和静态控件是从预定义的窗口类创建的,这些窗口类已可用于所有进程。 因此,在这些情况下,我们不需要为其注册窗口类。

More windows

图:更多窗口

退出键

通常,通过按 Escape 键可以终止应用。 还显示一个消息框以确认终止。

escapekey.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Escape";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Escape",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 250, 180, 0, 0, hInstance, 0);  

  while (GetMessage(&msg, NULL, 0, 0)) {

      TranslateMessage(&msg);
      DispatchMessage(&msg);
  }

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    switch(msg) {

        case WM_KEYDOWN:

            if (wParam == VK_ESCAPE) {

                int ret = MessageBoxW(hwnd, L"Are you sure to quit?", 
                                  L"Message", MB_OKCANCEL);

                if (ret == IDOK) {

                    SendMessage(hwnd, WM_CLOSE, 0, 0);
                }
             }

             break;

        case WM_DESTROY:

            PostQuitMessage(0);
            break; 
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

询问用户是否确实要关闭应用是一种常见的做法。 如果我们有时钟或计算器,那就没关系了。 但是,如果我们有文本编辑器或绘图应用,那确实很重要。 我们可能不小心按了 Escape 键并失去了所有修改。

case WM_KEYDOWN:

    if (wParam == VK_ESCAPE) {

        int ret = MessageBoxW(hwnd, L"Are you sure to quit?", 
                            L"Message", MB_OKCANCEL);

        if (ret == IDOK) {

            SendMessage(hwnd, WM_CLOSE, 0, 0);
        }
    }

    break;

如果我们按一个键,则窗口过程会收到WM_KEYDOWN消息。 wParam参数具有键码。 我们可以通过发送WM_CLOSE消息来关闭窗口。 该消息通过SendMessage()函数发送。

移动窗口

当我们在屏幕上移动窗口时,窗口过程会收到WM_MOVE消息。 在我们的示例中,我们在屏幕上显示当前窗口的位置-我们显示窗口左上角的坐标。

moving.c

#include <windows.h>
#include <wchar.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateLabels(HWND);

HWND hwndSta1;
HWND hwndSta2;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow) {
    HWND hwnd;
    MSG  msg;

    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Moving";
    wc.hInstance     = hInstance ;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    hwnd = CreateWindowW(wc.lpszClassName, L"Moving",
                         WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                         150, 150, 250, 180, 0, 0, hInstance, 0);

    while(GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    wchar_t buf[10];
    RECT rect;

    switch(msg) {

      case WM_CREATE:

        CreateLabels(hwnd);
        break;

      case WM_MOVE:

        GetWindowRect(hwnd, &rect);

        StringCbPrintfW(buf, BUF_LEN, L"%ld", rect.left);  
        SetWindowTextW(hwndSta1, buf);

        StringCbPrintfW(buf, BUF_LEN, L"%ld", rect.top);  
        SetWindowTextW(hwndSta2, buf);

        break;

      case WM_DESTROY:

        PostQuitMessage(0);
        break; 
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CreateLabels(HWND hwnd) {

    CreateWindowW(L"static", L"x: ",
        WS_CHILD | WS_VISIBLE,
        10, 10, 25, 25, 
        hwnd, (HMENU) 1, NULL, NULL);

    hwndSta1 = CreateWindowW(L"static", L"150",
        WS_CHILD | WS_VISIBLE,
        40, 10, 55, 25, 
       hwnd, (HMENU) 2, NULL, NULL);

    CreateWindowW(L"static", L"y: ",
        WS_CHILD | WS_VISIBLE,
        10, 30, 25, 25, 
        hwnd, (HMENU) 3, NULL, NULL);

    hwndSta2 = CreateWindowW(L"static", L"150",
        WS_CHILD | WS_VISIBLE,
        40, 30, 55, 25, 
        hwnd, (HMENU) 4, NULL, NULL);
}

静态文本控件的创建委托给CreateLabels()函数。

void CreateLabels(HWND hwnd) {

    CreateWindowW(L"static", L"x: ",
        WS_CHILD | WS_VISIBLE,
        10, 10, 25, 25, 
        hwnd, (HMENU) 1, NULL, NULL);

    hwndSta1 = CreateWindowW(L"static", L"150",
        WS_CHILD | WS_VISIBLE,
        40, 10, 55, 25, 
       hwnd, (HMENU) 2, NULL, NULL);

    CreateWindowW(L"static", L"y: ",
        WS_CHILD | WS_VISIBLE,
        10, 30, 25, 25, 
        hwnd, (HMENU) 3, NULL, NULL);

    hwndSta2 = CreateWindowW(L"static", L"150",
        WS_CHILD | WS_VISIBLE,
        40, 30, 55, 25, 
        hwnd, (HMENU) 4, NULL, NULL);
}

有四个静态文本控件。 在应用的生命周期中,其中两个会更改。 因此,我们只需要两个句柄。

case WM_MOVE:

  GetWindowRect(hwnd, &rect);

  StringCbPrintfW(buf, BUF_LEN, L"%ld", rect.left);  
  SetWindowTextW(hwndSta1, buf);

  StringCbPrintfW(buf, BUF_LEN, L"%ld", rect.top);  
  SetWindowTextW(hwndSta2, buf);

  break;

要获取窗口坐标,我们调用GetWindowRect()函数。 由于坐标是数字,因此必须将其转换为字符串。 为此,我们使用StringCbPrintfW()函数。

Moving a window

图:移动窗口

闪烁窗口

有时,当发生重要事件时,标题栏或任务栏按钮开始闪烁。 闪烁是标题栏从非活动状态更改为活动状态,反之亦然。 当我们收到新消息时,这是 Miranda IM 中的常见功能。

flashing.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {
    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Flash";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0,IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Flash",
                 WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                 100, 100, 250, 180, 0, 0, hInstance, 0);

    while(GetMessage(&msg, NULL, 0, 0)) {

      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

   return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam) {

    FLASHWINFO fwi;

    switch(msg)  {

      case WM_CREATE:

          CreateWindowW(L"Button", L"Flash",
                  WS_CHILD | WS_VISIBLE,
                  10, 10, 80, 25, 
                  hwnd, (HMENU) 1, NULL, NULL);
          break;

      case WM_COMMAND:

          fwi.cbSize = sizeof(fwi);
          fwi.dwFlags = FLASHW_ALL;
          fwi.dwTimeout = 0;
          fwi.hwnd = hwnd;
          fwi.uCount = 4;

          FlashWindowEx(&fwi);
          break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break; 
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

为了刷新窗口,我们必须执行两个步骤:创建并填充FLASHWINFO结构并调用FlashWindowEx()函数。

fwi.dwFlags = FLASHW_ALL;

我们已经设置了FLASHW_ALL标志。 这将同时闪烁标题栏和任务栏按钮。 要仅闪烁标题栏,我们可以使用FLASHW_CAPTION标签。 要闪烁任务栏按钮,我们可以使用FLASHW_TRAY标志。

fwi.dwTimeout = 0;

dwTimeout成员是刷新窗口的速率,以毫秒为单位。 如果dwTimeout为零,则该功能使用默认的光标闪烁速率。

fwi.hwnd = hwnd;
fwi.uCount = 4;

在这里,我们设置要闪烁的窗口以及要闪烁多少次。 在本例中,我们将主窗口闪烁四次。

FlashWindowEx(&fwi);

FlashWindowEx()开始闪烁。

在 Windows API 教程的这一部分中,我们创建了一些简单的 UI 示例。

posted @ 2024-10-24 18:15  绝不原创的飞龙  阅读(11)  评论(0编辑  收藏  举报