FAQ созданное в процессе разработки java-апплета
Как вызывать функции JavaScript из апплета?
Это делается с помощью следующего кода:
applet.getAppletContext().
showDocument(new URL("javascript:" + js));
Где applet объект класса JApplet, js — имя функции JavaScript, например show().
При этом апплет должен быть запущен из JavaScript.
Как вызывать методы апплета из JavaScript?
Для этого апплет должен быть запущен из JavaScript. Например
<script src="https://www.java.com/js/deployJava.js"></script>
<script>
var attributes = { id: 'mainApplet', code:
'org.jazzteam.Example', archive: 'example.jar', width: 812, height:
635};
var parameters = {};
deployJava.runApplet(attributes, parameters, '1.7');
function actionInApplet(url) {
mainApplet.appletMethod(url);
}
</script>
В данном случае апплет запущен из JavaScript с id= ‘mainApplet’. В апплете находится метод appletMethod(), который мы хотим вызвать. Для этого нужно у объекта JavaScript с именем id вызвать этот метод. Также можно передавать параметры в вызываемые методы, что продемонстрировано в данном примере.
Также стоит знать, что метод апплета, вызываемый из JavaScript должен быть привилегированным. Для этого нужно обернуть код метода в такую “обёртку”:
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
//method code
return new Object();
}
});
Как использовать параметры запуска апплета?
В приведённом выше примере в конструкцию «var parameters = {};» необходимо добавить нужные параметры. Например,
var parameters = {firstParameter:true, secondParameter:false};
В апплете параметры считываются следующим образом:
String parameter=applet.getParameter(“firstParameter”);
Где applet — объект класса JApplet (или Applet).
Такой способ так же работает при запуске апплета с помощью тегов <object> и <applet>.
Как создать скриншот Swing/AWT компонента?
Это можно сделать 2-мя способами. В обоих случаях компонент должен быть видимым.
Без использования робота:
BufferedImage image = new BufferedImage(component.getWidth(),
component.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
component.paint(g);
С использованием робота:
В данном случае будет сделан скриншот области, в которой находится компонент. То есть если компонент перекрывает окно другого приложения, то в скриншот попадёт область стороннего окна.
Point point = new Point(0, 0);
SwingUtilities.convertPointToScreen(point, component);
Rectangle region = component.getBounds();
region.x = point.x;
region.y = point.y;
BufferedImage image= new Robot().createScreenCapture(region);
Как использовать одновременно 2 апплета в одном браузере, если в них присутствуют static поля?
Для этого нужно запускать апплеты в разных JVM. Чтобы запустить апплеты в разных JVM нужно запустить их с параметром separate_jvm=true.
Как в Swing можно сделать поле для ввода, которое динамически расширяется при переполнении, а так же имеет минимальный размер?
Пример такого JTextField:
public class TextField extends JTextField {
public static final int TEXT_FIELD_PADDING = 4;
public TextField() {
addAutoResize();
init();
}
public void init() {
setStyleByParentStyle();
this.setPreferredSize(new Dimension(Constant.
TEXT_FIELD_MINIMAL_WIDTH, this.getPreferredSize().height));
}
private void addAutoResize() {
this.getDocument().addDocumentListener
(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
checkResize();
}
@Override
public void removeUpdate(DocumentEvent e) {
checkResize();
}
@Override
public void changedUpdate(DocumentEvent e) {
checkResize();
}
});
}
private void checkResize() {
FontMetrics fontMetrics = this.getFontMetrics(this.
getFont());
if (fontMetrics.stringWidth(this.getText()) +
TEXT_FIELD_PADDING * 2 < Constant.TEXT_FIELD_MINIMAL_WIDTH) {
this.setPreferredSize(new
Dimension(Constant.TEXT_FIELD_MINIMAL_WIDTH, this.getHeight()));
this.getParent().revalidate();
} else {
//NOTE 2 - border width*2
this.setPreferredSize(new
Dimension(fontMetrics.stringWidth(this.getText()) + 2 +
Constant.CARRET_WIDTH + TEXT_FIELD_PADDING * 2, this.getHeight()));
this.getParent().revalidate();
}
}
}
Как реализовать Drag&Drop в Swing?
В Java 6 появился довольно удобный механизм для реализации Drag&Drop, однако у него есть свои минусы. Например, необходимо явно указывать Drop Target, что не очень удобно, когда объект необходимо положить рядом с Drop Target’ом. Также в стандартной реализации нет гарантии порядка выполнения методов слушателей. Я расскажу концепцию реализации более расширяемого Drag&Drop.
Изначально на все перетягиваемые компоненты (Drag Source) необходимо назначить слушателей мыши (Mouse Listener и MouseMotionListener). Необходимо реализовать 3 метода: метод нажатия мыши на объект, метод перемещения мыши при зажатой кнопке мыши на объекте (mouseDragged в MouseMotionListener) и метод отпускания кнопки мыши.
Выглядит назначение слушателей так:
component.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
//block click right mouse button
if (MouseEvent.BUTTON1 == e.getButton()) {
startDrag(e);
}
}
@Override
public void mouseReleased(MouseEvent e) {
//block click right mouse button
if (MouseEvent.BUTTON1 == e.getButton()) {
endDrag(e);
}
}
});
component.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
drag(e);
}
});
Соответственно при нажатии мыши на объект стартует Drag&Drop, при перемещении мыши объект должен перемещаться, при отпускании кнопки мыши объект должен изменить свои координаты и переместиться на новый контейнер. Если перемещение объекта будет происходить только в пределах одного контейнера, то можно реализовать лишь метод mouseDragged(), в котором будут изменяться координаты перетаскиваемого объекта:
@Override
public void mouseDragged(MouseEvent e) {
Point mouseLocation = e.getLocationOnScreen();
Component draggedComponent = (Component) e.getSource();
SwingUtilities.convertPointFromScreen(mouseLocation,
draggedComponent.getParent());
draggedComponent.setLocation(mouseLocation);
}
Но у перетаскиваемого объекта можно устанавливать координаты относительно контейнера, на котором он находится. Соответственно при перемещении мыши на другой контейнер необходимо добавлять компонент на новый контейнер, высчитывать новые координаты и т.д. Данный способ не очень красивый и расширяемый, потому я предлагаю использовать GlassPane, для отображения перетаскиваемого объекта.
Алгоритм получается приблизительно такой:
- Нажимаем на объект.
- Получаем скриншот объекта (как сделать скриншот см. выше).
- Прячем исходный объект.
- Рисуем на glassPane скриншот объекта, основываясь на координатах мыши.
- При перемещении мыши перерисовываем скриншот по новым координатам.
- При отпускании клавиши мыши кладём объект на контейнер, под которым находится курсор.
- Отображаем исходный объект.
При таком подходе у нас нет завязки на контейнер, в который должен попасть курсор, чтобы произошел Drop и соответственно объект можно “ронять” куда угодно.
GlassPane с эффектом прозрачности:
public class GhostGlassPane extends JPanel {
private final AlphaComposite composite;
private BufferedImage ghostImage = null;
private Point location = new Point(0, 0);
public GhostGlassPane() {
setOpaque(false);
composite = AlphaComposite.getInstance(AlphaComposite.
SRC_OVER, 0.7f);
}
public void paintComponent(Graphics g) {
if (ghostImage == null)
return;
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(composite);
g2.drawImage(ghostImage, (int) (location.getX()),
(int) (location.getY()), null);
}
}
В данном ответе расписан лишь концепт реализации.
Как подписать апплет сертификатом?
Прочитайте нашу статью «Подписывание Java-апплетов«.
Как проигрывать MP3- и WAV-файлы в Java?
Прочитайте нашу статью «Поддержка воспроизведения MP3 и WAV форматов в Java«.
Как использовать HTML-форматирование в Swing компонентах?
Html форматирование поддерживают следующие Swing-компоненты: buttons, menu items, labels, tool tips, и tabbed panes. Для использования форматирования необходимо в начале текста поставить тег <html> (закрывать его в конце не обязательно). Например:
<html><b><u>T</u>wo</b>
На компоненте будет отображен следующий текст: Two
Не отображаются компоненты при использовании цикла, что делать?
Скорее всего цикл используется в потоке отрисовки Swing. Для того, чтобы выполнить какие-то действие над UI и не заблокировать поток отрисовки можно использовать Swing Timer. Пример использования:
Timer timer= new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//actions
}
});
timer.setRepeats(true);
timer.start();
Данный код будет раз в секунду выполнять какие-то действия, не блокируя поток отрисовки.
Так же можно установить свойство repeats=false, для выполнения действия только 1 раз.
Для остановки таймера необходимо выполнить timer.stop()
Как сделать плавный переход между фреймами в Swing?
Предложу следующий эффект:
Плавно появляется белый фон на одном фрейме (можно вставить картинку или скриншот фрейма), затем белый фон плавно исчезает, а под ним появляется следующий фрейм.
Алгоритм таков:
- Делаем GlassPane фрейма белым и изменяющим свою прозрачность (пример такой реализации можно найти выше).
- Плавно уменьшаем прозрачность glassPane со 100% до 0%, чтобы он стал полностью белым и непрозрачным.
- Под glassPane отображаем новый фрейм.
- Увеличиваем прозрачность glassPane до 100% (потом его можно вообще удалить).
Как запустить апплет?
Апплет на HTML-странице можно запустить 3 способами: с помощью тега <applet>, с помощью тега <object>, с помощью JavaScript.
Тег <applet> считается устаревшим, однако до сих пор большинство апплетов запускаются с помощью этого тега:
<applet code="HelloWorldApplet.class" width="320" height="120">
</applet>
Пример запуска апплета с помощью тега <object>:
<object type="application/x-java-applet" name="previewersGraph"
width="360" height="320">
<param name="codebase" value="/applets" />
<param name="code" value="my.full.class.Name" />
<param name="archive" value="applets.jar" />
</object>
Как можно разместить Swing компоненты на контейнере со случайными координатами?
Могу предложить следующий алгоритм:
- Из списка компонентов, выбираем самый большой.
- Далее разбиваем контейнер на ячейки, равные самому большому компоненту. Например, если размер контейнера 100х60, размер самого большого компонента 25х20, то компонент будет разбит на 12 ячеек, 4 колонки и 3 строки.
- Далее убираем лишние ячейки, дабы найти минимальное количество ячеек, в которые могут поместится компоненты. Можно изначально убирать лишние столбцы, либо лишние строки, я реализовывал изначальное убирание лишних столбцов. Рассмотрим приведённый выше пример: у нас получилось 12 ячеек, предположим, что было 7 компонентов. Уберём 1 столбец и проверим, что компоненты вмещаются в ячейки. Получилось 9 ячеек (3х3), соответственно в них могут поместиться 7 компонентов. Далее мы видим, что не сможем убрать лишнюю строкустолбец, так как получится 6 ячеек, что меньше, чем 7 компонентов.
- Далее растягиваем получившиеся ячейки по всей ширине контейнера. У нас получатся 9 ячеек размером 33х20.
- Потом размещаем компоненты внутри получившихся ячеек с использованием случайных координат.
Реализация алгоритма:
/**
* Class for adding components to container with random
* coordinates
*/
public class ComponentRandomizer {
/**
* Add all components to container with random
* coordinates
*/
public static void addComponentsToContainer(Container
container, List<JComponent> components) {
List<Rectangle> cells = getContainerCells(container,
components);
for (JComponent component : components) {
Rectangle randomCell = cells.get(new Random().
nextInt(cells.size()));
cells.remove(randomCell);
addComponentToContainer(container, component,
randomCell);
}
}
/**
* Add one component to container by it cell
*/
private static void addComponentToContainer(Container
container, JComponent component, Rectangle cell) {
container.add(component);
int xCoordinate = getRandomInt(cell.getX(),
cell.getWidth() - component.getWidth());
int yCoordinate = getRandomInt(cell.getY(),
cell.getHeight() - component.getHeight());
component.setLocation(xCoordinate, yCoordinate);
}
/**
* Get random integer
*/
private static int getRandomInt(double minimal,
double interval) {
if (interval > 0) {
return (int) (minimal +
new Random().nextInt((int) interval));
} else {
return (int) minimal;
}
}
/**
* Create a cells that serves to adding component into it
*/
private static List<Rectangle> getContainerCells(Container
container, List<JComponent> components) {
Pair<Integer, Integer> optimalCellsNumber =
getOptimalNumberOfContainerCells(container, components);
Dimension cellSize = getCellSize(container,
optimalCellsNumber);
List<Rectangle> cells = new LinkedList<Rectangle>();
for (int stringIndex = 0; stringIndex <
optimalCellsNumber.getKey(); stringIndex++) {
for (int columnIndex = 0; columnIndex <
optimalCellsNumber.getValue(); columnIndex++) {
cells.add(createCell(cellSize, stringIndex,
columnIndex));
}
}
return cells;
}
/**
* Create a cell that serves to adding component into it
*/
private static Rectangle createCell(Dimension cellSize,
int stringIndex, int columnIndex) {
int xCellCoordinate = (int) (columnIndex *
cellSize.getWidth());
int yCellCoordinate = (int) (stringIndex *
cellSize.getHeight());
return new Rectangle(xCellCoordinate, yCellCoordinate,
(int) cellSize.getWidth(), (int) cellSize.getHeight());
}
/**
* Create cell size
*/
private static Dimension getCellSize(Container container,
Pair<Integer, Integer> optimalCellsNumber) {
double cellWidth = container.getWidth() /
optimalCellsNumber.getValue();
double cellHeight = container.getHeight() /
optimalCellsNumber.getKey();
return new Dimension((int) Math.floor(cellWidth),
(int) Math.floor(cellHeight));
}
/**
* Get the optimal amount of cells that can fit all of the
* components
*
* @return count strings and columns
*/
private static Pair<Integer, Integer>
getOptimalNumberOfContainerCells(Container container,
List<JComponent> components) {
//<Strings count, Columns count>
Pair<Integer, Integer> numberOfContainerCells =
getNumberOfContainerCells(container, components);
//remove extra columns
while (Math.ceil((double) components.size() /
numberOfContainerCells.getKey()) <
numberOfContainerCells.getValue()) {
numberOfContainerCells = new Pair<Integer,
Integer>(numberOfContainerCells.getKey(),
numberOfContainerCells.getValue() - 1);
}
//remove extra strings
while (Math.ceil((double) components.size() /
numberOfContainerCells.getValue()) <
numberOfContainerCells.getKey()) {
numberOfContainerCells = new Pair<Integer,
Integer>(numberOfContainerCells.getKey() - 1,
numberOfContainerCells.getValue());
}
return numberOfContainerCells;
}
/**
* Split container on the cells by largest component and get
* the number of cells of the container
*
* @return count strings and columns
*/
private static Pair<Integer, Integer>
getNumberOfContainerCells(Container container,
List<JComponent> components) {
JComponent largestComponent = getLargestComponent(components);
int stringsCount = (int) Math.floor(container.getHeight() /
largestComponent.getHeight());
int columnsCount = (int) Math.floor(container.getWidth() /
largestComponent.getWidth());
if (stringsCount * columnsCount < components.size()) {
throw new IllegalArgumentException(Constant.
MESSAGE_THE_COMPONENTS_CANNOT_BE_ACCOMMODATED_IN_THE_CONTAINER);
}
return new Pair<Integer, Integer>(stringsCount, columnsCount);
}
/**
* Get component with largest square
*/
private static JComponent getLargestComponent(List<JComponent>
components) {
JComponent largestComponent = components.get(0);
for (JComponent checkedComponent : components) {
if (getComponentSquare(
checkedComponent.getPreferredSize()) >
getComponentSquare(largestComponent.getPreferredSize())) {
largestComponent = checkedComponent;
}
}
return largestComponent;
}
/**
* Create component square
*/
private static double getComponentSquare(Dimension
checkedComponentSize) {
return checkedComponentSize.getHeight() *
checkedComponentSize.getWidth();
}
/**
* Class for saving pair values
*/
public static class Pair<K, V> {
private K key;
private V value;
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public Pair(K k, V v) {
key = k;
value = v;
}
}
}
Как автоматизировать сборку апплета с помощью Maven?
Для этого необходимо иметь 2 проекта: проект апплета, который будет собираться как Jar-файл, и проект веб-приложения, которые нужно будет деплоить (на выходе получим War-файл).
Соответственно создадим многомодульный проект, в котором модулями будут 2 вышеуказанных проекта:
<project
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jazzteam</groupId>
<artifactId>applet-project</artifactId>
<packaging>pom</packaging>
<modules>
<module>applet</module>
<module>applet-web</module>
</modules>
<version>1.0-SNAPSHOT</version>
</project>
После сборки модуля апплета, необходимо скопировать получившийся JAR-файл в веб-приложение.
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>copy</id>
<phase>site</phase>
<configuration>
<tasks>
<copy file="targetapplet-eugliajar"
tofile="..euglia-appletsrcmainwebappapplet-euglia.jar"/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
В данном случае общий проект нужно собирать командой “clean package site”, потому как на фазе site начинается копирование файлов.
При таком подходе в веб-приложении должна быть HTML-страница, запускающая апплет.