Динамическое добавление и удаление компонентов с JPanel и JScrollPane

Java Swing - Динамическое добавление и удаление компонентов Временами при написании приложений на Java Swing, требуется реализовать динамическое добавление и удаление компонентов пользовательского интерфейса — то есть добавление и удаление компонентов должно происходить во время работы приложения. В недавних комментариях в одному посту проявлялся интерес к этому вопросу. Пример такого приложения будет рассмотрен далее — это достаточно простое приложение с двумя кнопками: добавить + и удалить -. При нажатии на кнопку добавления должно происходить создание нового компонента JLabel и добавление его на панель, при удалении — последний добавленный JLabel удаляется, панель перерисовывается и больше мы его не видим. Давайте посмотрим как это сделать.

Если немного порассуждать по поводу того, как такое должно происходить, сразу приходит на ум что-то вроде: для добавления у JPanel (или куда там еще мы собираемся добавить компонент) есть метод add для добавления, значит для удаления должен быть remove. Всё верно, всё правильно — такие методы есть и они делают именно то, что надо. Однако, есть пара хитростей, которые нужно знать, если хочется сделать (или есть не хочется, но надо) динамическое добавление и удаление.

Посмотрим код всего примера.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class TestFrame extends JFrame {
	
	private static List<JLabel> labels = new ArrayList<JLabel>();

	public static void createGUI() {
		JFrame frame = new JFrame("Test frame");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		final Font font = new Font("Verdana", Font.PLAIN, 25);

		JPanel butPanel = new JPanel();		

		JButton addButton = new JButton("+");
		addButton.setFont(font);
		addButton.setFocusable(false);
		butPanel.add(addButton);
		
		JButton remButton = new JButton("-");
		remButton.setFont(font);
		remButton.setFocusable(false);
		butPanel.add(remButton);
				
		final JPanel labPanel = new JPanel();
		final JScrollPane scrollPane = new JScrollPane(labPanel);
		labPanel.setLayout(new BoxLayout(labPanel, BoxLayout.Y_AXIS));

		addButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int number = labels.size() + 1;
				JLabel label = new JLabel("Label " + number);
				labels.add(label);
				label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
				label.setFont(font);
				labPanel.add(label);
				scrollPane.revalidate();
			}			
		});
		
		remButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(labels.size() > 0) {
					int index = labels.size() - 1;
					JLabel label = labels.remove(index);
					labPanel.remove(label);
					labPanel.repaint();
					scrollPane.revalidate();					
				}				
			}			
		});
		
		frame.getContentPane().setLayout(new BorderLayout());
		frame.getContentPane().add(butPanel, BorderLayout.NORTH);
		frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
		
		frame.setPreferredSize(new Dimension(250, 200));
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);
	}

	public static void main(String[] args) {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				JFrame.setDefaultLookAndFeelDecorated(true);
				createGUI();
			}
		});
	}
}

Обратимся сразу к тестовому примеру и посмотрим на заветный код кнопок «Добавить» и «Удалить». Что тут собственно делается. При добавлении нового JLabel делается вот, что:

int number = labels.size() + 1;
JLabel label = new JLabel("Label " + number);
labels.add(label);
label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
label.setFont(font);
labPanel.add(label);
scrollPane.revalidate();

Для начала высчитываем индекс, который будет у нового JLabel, чтобы нумерация шла по порядку. Далее создаем новый JLabel, устанавливаем ему шрифт, выравнивание и добавляем на панель. А теперь самое интерсное — вызываем метод revalidate у JScrollPane. Если не сделать этот вызов, то ничего мы не увидим.

Посмотрим, что происходит при удалении ранее добавленного JLabel.

if(labels.size() > 0) {
	int index = labels.size() - 1;
	JLabel label = labels.remove(index);
	labPanel.remove(label);
	labPanel.repaint();
	scrollPane.revalidate();					
}

Для начала мы проверяем, есть ли JLabel’ы, которые могут быть удалены или нет. Если такой JLabel имеется, то находим его индекс (последний в списке), удаляем его с панели с помощью вызова метода remove. Затем мы должны вызвать repaint у панели JPanel, на которой располагается JLabel. Вызов repaint говорит JPanel о том, что ему необходимо перерисоваться. И наконец, самый последний вызов — это revalidate у JScrollPane, на которой располагается JPanel.

15 Responses to Динамическое добавление и удаление компонентов с JPanel и JScrollPane

  1. cyper_:

    Такое же можно сделать и без списка:

    public class TestFrame extends JFrame {

    public static void createGUI() {
    final JFrame frame = new JFrame(«Test frame»);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    final Font font = new Font(«Verdana», Font.PLAIN, 25);

    JPanel butPanel = new JPanel();

    JButton addButton = new JButton(«+»);
    addButton.setFont(font);
    addButton.setFocusable(false);
    butPanel.add(addButton);

    JButton remButton = new JButton(«-«);
    remButton.setFont(font);
    remButton.setFocusable(false);
    butPanel.add(remButton);

    final JPanel labPanel = new JPanel();
    final JScrollPane scrollPane = new JScrollPane(labPanel);
    labPanel.setLayout(new BoxLayout(labPanel, BoxLayout.Y_AXIS));

    addButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    int number = labPanel.getComponentCount()+1;
    JLabel label = new JLabel(«Label » + number);
    label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
    label.setFont(font);
    labPanel.add(label);
    scrollPane.revalidate();
    }
    });

    remButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    if(labPanel.getComponentCount() > 0) {
    int index = labPanel.getComponentCount()-1;
    JLabel label = (JLabel) labPanel.getComponent(index);
    labPanel.remove(label);
    labPanel.repaint();
    scrollPane.revalidate();
    }
    }
    });

    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(butPanel, BorderLayout.NORTH);
    frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

    frame.setPreferredSize(new Dimension(250, 200));
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    }

    public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
    JFrame.setDefaultLookAndFeelDecorated(true);
    createGUI();
    }
    });
    }
    }

    • bondforever:

      Согласен, лучше использовать getComponentCount(). Не знал, что есть такой метод, надо лучше доку читать 🙂

  2. eto_ya:

    Эм… непонятное решение.

    Куда гибче будет создать JList с собственной моделью (extends AbstractListModel), в которой везде плясать от собственного private списка. В этом списке, естественно, можно хранить не только порядковый номер и удалять не только последний элемент.

    А дальше через ListCellRenderer выводить на экран что угодно, причем нечто сложнее надписи соорудить будет тоже сравнительно просто, потому что команду или фабрику компонентов удобно привязать к модели списка.

    • bondforever:

      Решение гибкое, согласен. Но гибкость следует использовать, если в этом есть необходимость.

  3. cyper_:

    eto_ya :
    Эм… непонятное решение.
    Куда гибче будет создать JList с собственной моделью (extends AbstractListModel), в которой везде плясать от собственного private списка. В этом списке, естественно, можно хранить не только порядковый номер и удалять не только последний элемент.
    А дальше через ListCellRenderer выводить на экран что угодно, причем нечто сложнее надписи соорудить будет тоже сравнительно просто, потому что команду или фабрику компонентов удобно привязать к модели списка.

    Это, в принципе, не решение. Автор просто хотел показать что/как можно добавлять/удалять компоненты с контейнера динамически…

  4. Makc:

    Как в данном случае добавить еще один jLabel, что бы он шел параллельно первому?

    • bondforever:

      Если я правильно понял, то нужно ориентацию BoxLayout сменить с Y_AXIS на X_AXIS.
      То есть вместо вот этой строки
      labPanel.setLayout(new BoxLayout(labPanel, BoxLayout.Y_AXIS));
      будет вот такая
      labPanel.setLayout(new BoxLayout(labPanel, BoxLayout.X_AXIS));

  5. badmod:

    bondforev а можешь еще рассказать как работать с xml

  6. maxim:

    а можно это как-нибудь без JScrollPane провернуть? мне не нужно чтобы компоненты листались

    • bondforever:

      По идее можно. Панель прокрутки я брал для того, чтобы было видно все добавленные кнопки — только для демонстрации так сказать.

  7. liosha:

    Большое спасибо за статью.
    Целая куча учебников, а ни в одном не написано что после метода
    setPreferredSize(…);
    нужно вызывать
    revalidate();
    для применения установленных изменений.

  8. Den:

    На самом деле спасибо что сделал со списком, потому что я так и не понимал как работать с ним)

    А еще мне кажется лишней строчка JFrame.SetDefaultLookAndFeelDecorated(true);
    без нее превычнее) хотя спасибо что показал)

  9. Денис:

    Спасибо, revalidate — это то, что нужно)

  10. Alik:

    Это работает если к кнопки прилеплен именованный слушатель. и выдает кучу ошибок при абстрактном слушателе. Есть ещё вопрос, как обращаться не по индексу(здесь удаляется последняя кнопка) а скажем обращаясь через текст на кнопки, её имя?
    ps
    спрашиваю поскольку столкнуться с обработкой в ToggleButton с проблемами. тесть пользователь выбрал некие Togglebutton? ткнул на JButton и выбранные ToggleButton удаляются.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: