Java → Работа с формами и рисование(график)

В этой статье будет описан процесс создания простого графического приложения, которое рисует график функции используя примитивную графику в Java. Также вы узнаете как создавать простые формы с элементами управления. 

Как пример возьмем функцию sin(x). В приложение добавим возможность указывать цвет графика и название графика, чтобы было немного прикольно. 

И так приступим...

Форма

Как создавать простые окна в Java можно прочитать вот здесь. Для создания окна используется класс JFrame, внутри которого будут распологаться все другие элементы. 

Сначала создадим основной класс, в котором мы будем хранить все наши элементы формы и инициализировать их. Назовем его GraphicApp.

import javax.swing.*;
import java.awt.*;

public class GraphicApp {
    private JFrame frame;

    public GraphicApp(){
        createFrame();
    }

    private void createFrame() {
        frame = new JFrame("Графическое приложение");
        frame.setSize(600, 300);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    public void show(){
        frame.setVisible(true);
    }
}

Как видно, в констркуторе класс вызывается метод createFrame(), в котором мы создаем фрейм с размером 600х300. А ссылка на фрейм(окно) у нас будет храниться как свойство класса. 

Также добавлен метод show(), который делает фрейм видимым. Создаем еще один класс для запуска нашего кода.

public class Starter {
    public static void main(String[] args) {
        GraphicApp app = new GraphicApp();
        app.show();
    }
}

если запустим этот класс, увидим нашу форму. 

Теперь добавим элементы управления в форму. Снизу добавим панель состояния, слева будут два поля ввода названия  и цвета графика и кнопка "Нарисовать". Справа будет панель, на которую мы будем рисовать график. 

Создадим еще один метод, который будет называться createElements() который создаст все нужные элементы формы. Все элементы, к которым мы будем обращаться после создания формы, мы сохраним как свойства класса GraphicApp. А элементы которые будут созданы один раз и больше не будут меняться, мы оставим внутри функции. 

Создание элементов занимает много много строк, поэтому получилось вот так.

import javax.swing.*;
import java.awt.*;

public class GraphicApp {
    private JFrame frame;
    private JLabel statusLabel;
    private JTextField colorTextField;
    private JTextField nameTextField;

    public GraphicApp(){
        createFrame();
        initElements();
    }

    private void createFrame() {
        frame = new JFrame("Графическое приложение");
        frame.setSize(600, 300);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    public void show(){
        frame.setVisible(true);
    }

    private void initElements() {
        Container mainContainer = frame.getContentPane();
        mainContainer.setLayout(new BorderLayout());

        JPanel bottomPanel = new JPanel(); // нижняя панель состояния
        bottomPanel.setBackground(Color.lightGray); // фон светло-серый
        mainContainer.add(bottomPanel, BorderLayout.SOUTH); // распологается внизу

        statusLabel = new JLabel("Инициализация приложения.."); // Элемент, который будет показывать текст состояния программы
        bottomPanel.add(statusLabel);    // добавляем его в нижнюю панель

        Box leftPanel = createLeftPanel(); // создаем левую панель в другом методе
        mainContainer.add(leftPanel, BorderLayout.WEST); // эта панель будет слева
    }

    private Box createLeftPanel() {
        Box panel = Box.createVerticalBox();  // вертикальный Box
        // Box это контейнер, в котором элементы выстраиваются в одном порядке

        JLabel title = new JLabel("<html>Построение графика функции</html>");
        // чтобы добавить перевод строки в тексте, нужно писать в тегах <html>
        title.setFont(new Font(null, Font.BOLD, 12)); // изменяем шрифт
        panel.add(title);

        panel.add(Box.createVerticalStrut(20)); //в Box можно добавлять отступы

        panel.add(new JLabel("Название:"));

        nameTextField = new JTextField();  // поле ввода названия
        nameTextField.setMaximumSize(new Dimension(300, 30)); // чтобы не был слишком большим
        panel.add(nameTextField);

        panel.add(new JLabel("Цвет:"));

        colorTextField = new JTextField("#FF0000");  // поле ввода с начальным текстом
        colorTextField.setMaximumSize(new Dimension(300, 30));
        panel.add(colorTextField);

        panel.add(Box.createVerticalGlue()); // также в Box можно добавлять заполнитель пустого места

        JButton button = new JButton("Нарисовать"); // Кнопка
        panel.add(button);
        return panel;
    }
}

Мы вывели statusLabel, colorTextField,nameTextField  в свойства класс, потому что эти элементы мы будем в дальнейшем использовать в других местах. Если мы запустим класс Starter, то увидим примерно следующее.

Теперь нам нужно создать свою панель, в котором мы будем рисовать график. Для этого создадим класс GraphicPanel, который будет наследником класса JPanel. Поместим эту панель справа и зададим ему белый фон.

import javax.swing.*;
import java.awt.*;

public class GraphicPanel extends JPanel {

}

Пока класс пустой. Потом добавим его внутренности.

Теперь в класс GraphicApp добавим вначале еще одно поле, в котором будем хранить панель  GraphicPanel: 

public class GraphicApp {
    private JFrame frame;
    private JLabel statusLabel;
    private JTextField colorTextField;
    private JTextField nameTextField;
    private GraphicPanel graphicPanel;
...

И в конце метода createElements() добавим эту панель в основной контейнер фрейма:

graphicPanel = new GraphicPanel();
graphicPanel.setBackground(Color.WHITE);
mainContainer.add(graphicPanel);

Теперь у нас в приложение появится справа белая панель. 

Рисование

Чтобы нарисовать на элементе, мы должны создать наследник какого-либо графического элемента и переопределить метод paint(). Рисовать можно во всех потомках класса JComponent. Метод paint() принимает один параметр типа Graphics, который представляет графический контекст элемента. Используя этот объект мы можем рисовать на элементе. Про то как рисовать можно почитать вот здесь.

Мы будем рисовать на нашей панели, который называется GraphicPanel. Переопределим метод paint(), и вызовем метод paint() у родителя, чтобы родитель тоже выполнил нужную отрисовку(например фона): 

import javax.swing.*;
import java.awt.*;

public class GraphicPanel extends JPanel {

    public void paint(Graphics g)
    {
        super.paint(g);
    }
}

Метод paint() будет вызываться каждый раз, когда меняется размер окна или при вызове метода repaint() и будет перерисовывать все заново. 

Теперь мы готовы рисовать. Так как мы унаследовали класс JPanel, мы можем получить ширину и высоту панели используя метода getWidth() и getHeight(). Они нам пригодятся, для определения центра и краев панели. 

Сначала нарисуем сетку по всей панели серым цветом. Сетку будем рисовать циклом с растоянием 30 пикселей. Так как координата центра у нас будет width/2, мы будем рисовать сетку от центра в две стороны, чтобы центральные оси совпадали с сеткой. Потом нарисуем две оси OX и ОY черным цветом по центру:

import javax.swing.*;
import java.awt.*;

public class GraphicPanel extends JPanel {
    private int width;
    private int height;

    public void paint(Graphics g)
    {
        super.paint(g);
        width = getWidth(); // сохраняем текущую ширину панели
        height = getHeight(); // и высоту

        drawGrid(g); // рисуем сетку
        drawAxis(g); // рисуем оси
    }

    private void drawGrid(Graphics g) {
        g.setColor(Color.LIGHT_GRAY);  //задаем серый цвет

        for(int x=width/2; x<width; x+=30){  // цикл от центра до правого края
            g.drawLine(x, 0, x, height);    // вертикальная линия
        }

        for(int x=width/2; x>0; x-=30){  // цикл от центра до леваого края
            g.drawLine(x, 0, x, height);   // вертикальная линия
        }

        for(int y=height/2; y<height; y+=30){  // цикл от центра до верхнего края
            g.drawLine(0, y, width, y);    // горизонтальная линия
        }

        for(int y=height/2; y>0; y-=30){  // цикл от центра до леваого края
            g.drawLine(0, y, width, y);    // горизонтальная линия
        }
    }

    private void drawAxis(Graphics g) {
        g.setColor(Color.BLACK);
        g.drawLine(width/2, 0, width/2, height);
        g.drawLine(0, height/2, width, height/2);
    }
}

Метод drawLine() принимает четыре параметры, первые две это координаты начальной точки, последние две это координаты конечной точки.

Сейчас у нас получится что-то такое:

Теперь пора рисовать сам график функции sin(x). Чтобы вычислять значение синуса мы будем использовать метод Math.sin(), который принимает угол в радианах и возвращает синус этого угла. Так как значение синуса может быть только в диапозоне от -1 до 1, мы будем умножать его на 90, чтобы растянуть график на 90 пикселей по высоте. 

Добавляем свойство graphicColor, в котором будем хранить цвет графика, по умолчанию зеленый:

public class GraphicPanel extends JPanel {
    private Color graphicColor = Color.GREEN;
    private int width;
    private int height;

...

создаем еще один метод:

private void drawGraphic(Graphics g) {
    g.setColor(graphicColor); // устанавливаем цвет графика

    for(int x=0; x<width; x++){           // делаем цикл с левой стороны экрана до правой
        int realX = x - width/2;   // так, как слева от оси OX минус, то отнимаем от текущей точки центральную точку
        double rad = realX/30.0;   // переводим текущую коориднату в радианы, 30 пикселей по ширине == 1 радиану
        double sin = Math.sin(rad);       // вычисляем синус угла
        int y = height/2 + (int) (sin * 90);  // переводим значение синуса в координату нашей системы

        g.drawOval(x, y, 2, 2);   // рисуем кружок в этой точке
    }
}

и вызываем этот метода после drawAxis():

public void paint(Graphics g)
{
    super.paint(g);
    width = getWidth();
    height = getHeight();

    drawGrid(g);
    drawAxis(g);
    drawGraphic(g);
}

 Запустив, вы должны увидеть такое:

Теперь сделаем так чтобы при нажатии на кнопку менялся цвет графика на указанный и добавлялся текст. Для этого сначала добавим обработчик действия для кнопки с помощью метода addActionListener(). В этом обработчике вызовем метод changeGraphicColor() класса GraphicApp. Кнопка у нас находится в конце метода createLeftPanel класса GraphicApp. После создания кнопки добавляем это:

JButton button = new JButton("Нарисовать"); // Кнопка
panel.add(button);

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        changeGraphicColor();
    }
});
return panel;

и еще внизу добавляем метод changeGraphicColor() в котором будем получать введенные название и цвет в полях ввода, и передавать их нашей панели graphicPanel:

private void changeGraphicColor(){
    String name = nameTextField.getText();
    String color = colorTextField.getText();
    graphicPanel.setNameAndColor(name, color);
}

Так, как у панели нет такого метода, мы создадим его. В класс GraphicPanel добавим в конце метод:

public void setNameAndColor(String name, String color) {
    try {
        this.graphicColor = Color.decode(color);
    }catch (Exception e){
        this.graphicColor = Color.RED;
    }
    repaint();        
}

Этот метод принимает два параметра, но пока будет использовать только второй параметр. Чтобы перенести цвет из текста в объект класса Color, используем метод Color.decode(). Если пользователь введет текст цвета неправильно, то произойдет ошибка. Но мы поймаем ошибку, и присвоим красный цвет. После присвоения цвета вызываем метод repaint()  чтобы перерисовать панель. 

Теперь запустим программу и введем цвет #FF00FF, нажмем на кнопку Нарисовать,  получим:

Вот и все на этом.
Надеюсь вам было понятно. 

2109 9
Alisher Alikulov