Я пытаюсь нарисовать кнопку Windows 11 на моем JFrame. Это сработало хорошо, но я не могу правильно добавить к нему прослушиватели действий. Вот мой код:
public class MyButton extends JButton implements MouseListener, ActionListener {
public MyButton(String text) {
//super(label);
enableInputMethods(true);
addMouseListener(this);
setFocusPainted(false);
setBorderPainted(false);
buttonText = text;
}
@Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
FontMetrics metrics = g2.getFontMetrics(Constants.arialFont);
g2.setStroke(new BasicStroke(2.0f));
g2.setFont(Constants.arialFont);
g2.setPaint(Constants.buttonNormal);
rect = new RoundRectangle2D.Double(0, 0, metrics.stringWidth(buttonText) + 20, 27, 10, 10);
g2.fill(rect);
g2.setPaint(Constants.fontColor);
g2.drawString(buttonText, Functions.getMiddleFromX(g2, rect, buttonText), Functions.getMiddleFromY(g2, rect, buttonText));
}
Я попытался добавить к нему слушателей мыши следующим образом:
@Override
public void mouseClicked(MouseEvent e) {
// Check if the click is on the button.
Point p = e.getPoint();
if (rect.contains(p)) System.out.println("Triangle contains point");
else { return; }
}
Но когда я запускаю этот класс с этим кодом:
MyButton myButton = new MyButton("Epic button!");
myButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action Performed!");
}
});
frame.add(myButton);
Я могу щелкнуть в любом месте холста (или JFrame) и все равно увидеть сообщение «Выполнено действие!» печатается на консоли.
Как мне фильтровать входящие события и делать что-то только в том случае, если щелчок на моем RoundRectangle2D.Double()?
@MadProgrammer Поставил бы я fireActionPerformed() в свой метод actionPerformed()?
Вы бы поставили fireActionPerformed там, где вы хотите вызвать событие действия. Проблема, с которой вы столкнулись, заключается в том, чтобы выяснить, как в противном случае остановить это, что, по моему мнению, ButtonUI лучшее место для начала.
Я бы посоветовал взглянуть на BasicButtonUI и BasicButtonListener
Еще ButtonUIпример , пример и пример




Это предназначено для проверки концепции, основанной на ограниченном понимании требований, и не предназначено для полного, готового к производству решения - просто говорю
Это своего рода предположение с моей стороны, но вам кажется, что иметь какую-то кнопку «на основе формы», которая может быть запущена мышью только в том случае, если она находится над рассматриваемой «формой».
Это несколько сложнее, так как единственный реальный способ изменить управление мышью — это делегировать кнопки ButtonUI.
В следующем примере в основном создается пользовательская ButtonUI, которая представляет собой круглую кнопку, но будет срабатывать только при нажатии пользователем внутри формы самого круга. Конечно, пользователь по-прежнему может активировать кнопку с помощью клавиатуры, но это совсем другая проблема (но на самом деле решается с помощью тех же механизмов).
Он имеет поддержку значков по умолчанию!
Обратите внимание, что красный прямоугольник показывает физические границы самой кнопки и предназначен только для демонстрационных целей.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.LinearGradientPaint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicButtonUI;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
setBackground(Color.RED);
JButton circleButton = new JButton("Hello");
circleButton.setUI(new CircleButtonUI());
circleButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("I've been triggered");
}
});
add(circleButton);
}
}
public class CircleButtonUI extends BasicButtonUI {
private Shape circleShape;
@Override
public void installUI(JComponent c) {
super.installUI(c);
c.setOpaque(false);
}
@Override
public boolean contains(JComponent c, int x, int y) {
if (circleShape == null) {
return c.contains(x, y);
}
return circleShape.contains(x, y);
}
@Override
public void paint(Graphics g, JComponent c) {
int width = c.getWidth() - 2;
int height = c.getHeight() - 2;
int size = Math.min(width, height);
int x = ((width - size) / 2) + 1;
int y = ((height - size) / 2) + 1;
circleShape = new Ellipse2D.Double(x, y, size, size);
AbstractButton b = (AbstractButton) c;
paintContent(g, b);
super.paint(g, c);
}
protected void paintContent(Graphics g, AbstractButton b) {
if (circleShape == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
// paint the interior of the button
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
ButtonModel model = b.getModel();
Color highlight = b.getBackground();
if (model.isArmed() && model.isPressed()) {
highlight = highlight.darker();
}
Color darklight = highlight.darker();
LinearGradientPaint lgp = new LinearGradientPaint(
circleShape.getBounds().getLocation(),
new Point((int) circleShape.getBounds().getMaxX(), (int) circleShape.getBounds().getMaxY()),
new float[]{0, 1f},
new Color[]{highlight, darklight});
g2d.setPaint(lgp);
g2d.fill(circleShape);
// draw the perimeter of the button
g2d.setColor(b.getBackground().darker().darker().darker());
g2d.draw(circleShape);
g2d.dispose();
}
@Override
protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {
// Paint focus highlight, if you want to
}
public Dimension getMinimumSize(JComponent c) {
Dimension size = super.getMinimumSize(c);
int maxSize = Math.max(size.width, size.height);
return new Dimension(maxSize, maxSize);
}
public Dimension getPreferredSize(JComponent c) {
Dimension size = super.getPreferredSize(c);
int maxSize = Math.max(size.width, size.height);
return new Dimension(maxSize, maxSize);
}
public Dimension getMaximumSize(JComponent c) {
Dimension size = super.getPreferredSize(c);
int maxSize = Math.max(size.width, size.height);
return new Dimension(maxSize, maxSize);
}
}
}
Как и в большинстве случаев, всегда есть несколько способов решения проблемы.
Этот пример просто расширяет JButton. Критическая область заключается в переопределении метода contains.
Лично мне нравится подход ButtonUI, так как его относительно легко внедрить в существующую кодовую базу (возможно), и вы не получите какого-то странного результата, потому что установленный внешний вид делает что-то «другое», просто говоря.
public class CircleButton extends JButton {
private Shape circleShape;
public CircleButton() {
configureDefaults();
}
public CircleButton(Icon icon) {
super(icon);
configureDefaults();
}
public CircleButton(String text) {
super(text);
configureDefaults();
}
public CircleButton(Action a) {
super(a);
configureDefaults();
}
public CircleButton(String text, Icon icon) {
super(text, icon);
configureDefaults();
}
protected void configureDefaults() {
setBorderPainted(false);
setFocusPainted(false);
setOpaque(false);
}
@Override
public void invalidate() {
super.invalidate();
circleShape = null;
}
@Override
public boolean contains(int x, int y) {
if (circleShape == null) {
return super.contains(x, y);
}
return circleShape.contains(x, y);
}
public Dimension getMinimumSize() {
Dimension size = super.getMinimumSize();
int maxSize = Math.max(size.width, size.height);
return new Dimension(maxSize, maxSize);
}
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
int maxSize = Math.max(size.width, size.height);
return new Dimension(maxSize, maxSize);
}
public Dimension getMaximumSize() {
Dimension size = super.getPreferredSize();
int maxSize = Math.max(size.width, size.height);
return new Dimension(maxSize, maxSize);
}
@Override
protected void paintComponent(Graphics g) {
if (circleShape == null) {
int width = getWidth() - 2;
int height = getHeight() - 2;
int size = Math.min(width, height);
int x = ((width - size) / 2) + 1;
int y = ((height - size) / 2) + 1;
circleShape = new Ellipse2D.Double(x, y, size, size);
}
Graphics2D g2d = (Graphics2D) g.create();
// paint the interior of the button
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
ButtonModel model = getModel();
Color highlight = getBackground();
if (model.isArmed() && model.isPressed()) {
highlight = highlight.darker();
}
Color darklight = highlight.darker();
LinearGradientPaint lgp = new LinearGradientPaint(
circleShape.getBounds().getLocation(),
new Point((int) circleShape.getBounds().getMaxX(), (int) circleShape.getBounds().getMaxY()),
new float[]{0, 1f},
new Color[]{highlight, darklight});
g2d.setPaint(lgp);
g2d.fill(circleShape);
// draw the perimeter of the button
g2d.setColor(getBackground().darker().darker().darker());
g2d.draw(circleShape);
g2d.dispose();
super.paintComponent(g);
}
}
вопрос отсутствует, но .. *кашель .. это выглядит слишком много и в то же время недостаточно хорошо: есть два аспекта (предполагаемой) проблемы - а) графика и б) взаимодействие с мышью. К а) графика здесь хороша, я предполагаю (хотя и не пробовал), что ее можно обработать в компоненте paintComponent. б) обработчик не завершен (например, эффекты наведения запускаются внутри всей области кнопки, что приводит к неправильным изменениям состояния в модели кнопки). Честно говоря, нет необходимости в специальном обработчике — переопределение содержит (x, y) должно помочь.
@kleopatra Да, вы «могли» нарисовать метод компонента paintComponent, но я хотел попытаться копнуть немного глубже. Я также согласен с критикой, но тогда у меня нет жесткого набора требований, с которыми можно работать 😜, поэтому я был больше сосредоточен на «идее», чем на конечном результате. Мне нравится подход делегата пользовательского интерфейса, поскольку он означает, что его можно применить к любой кнопке, вам не нужно специально использовать «расширенную» версию, но это всего лишь «один» способ, которым это может быть достигнуто.
да, я знаю о качестве вопроса (и принял это во внимание :). Тем не менее, этот вопрос - по крайней мере частично, B - заново изобретает колесо как восьмиугольник: contains предназначен для переопределения именно для этого варианта использования. И мы оба знаем, что реализация дополнительных функций (при условии, что это действительно требование или необходимость) в пользовательских UI-делегатах — это гораздо больше работы, чем просто расширение BasicXX (SwingX имеет сложный механизм ... те, где есть время и решения :)
Расширение @kleopatra против композиции - это всего лишь «один» способ сделать это. Лично я думаю, что это работает хорошо, так как мне не нужно много возиться со всеми «другими» свойствами кнопки (например, границы, рисование фокуса и т. д.), и поскольку Swing поддерживает «делегирование», это кажется хорошим подходит, я просто хотел посмотреть, смогу ли я это сделать
@MadProgrammer Когда я запускаю ваш первый пример, я вижу это: i.stack.imgur.com/w2aFs.gif. Это должно произойти?
@MadProgrammer Но ваш комментарий выше дал мне несколько полезных примеров! Спасибо!
@Ben :/ У меня отлично работает - попробуйте установить цвет фона на что-нибудь другое (например, Color.RED и посмотрите, будет ли это иметь значение)
@MadProgrammer Да, теперь это работает! Добавьте код в свой ответ, пожалуйста!
@Ben, потому что это проблема конкретной платформы: P
Это, вероятно, неправильный подход, вместо этого вы должны реализовать внешний вид пользовательского интерфейса кнопки и делегат. Кстати, у
JButtonесть метод fireActionPerformed.