Как расширить JButton, но изменить actionPerformed?

Я пытаюсь нарисовать кнопку 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()?

Это, вероятно, неправильный подход, вместо этого вы должны реализовать внешний вид пользовательского интерфейса кнопки и делегат. Кстати, у JButton есть метод fireActionPerformed.

MadProgrammer 02.05.2023 03:00

@MadProgrammer Поставил бы я fireActionPerformed() в свой метод actionPerformed()?

Ben 02.05.2023 03:02
Пример ButtonUI
MadProgrammer 02.05.2023 03:04

Вы бы поставили fireActionPerformed там, где вы хотите вызвать событие действия. Проблема, с которой вы столкнулись, заключается в том, чтобы выяснить, как в противном случае остановить это, что, по моему мнению, ButtonUI лучшее место для начала.

MadProgrammer 02.05.2023 03:07

Я бы посоветовал взглянуть на BasicButtonUI и BasicButtonListener

MadProgrammer 02.05.2023 03:12

Еще ButtonUIпример , пример и пример

MadProgrammer 02.05.2023 03:51
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Доказательство концепции

Это предназначено для проверки концепции, основанной на ограниченном понимании требований, и не предназначено для полного, готового к производству решения - просто говорю

Это своего рода предположение с моей стороны, но вам кажется, что иметь какую-то кнопку «на основе формы», которая может быть запущена мышью только в том случае, если она находится над рассматриваемой «формой».

Это несколько сложнее, так как единственный реальный способ изменить управление мышью — это делегировать кнопки 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 02.05.2023 13:12

@kleopatra Да, вы «могли» нарисовать метод компонента paintComponent, но я хотел попытаться копнуть немного глубже. Я также согласен с критикой, но тогда у меня нет жесткого набора требований, с которыми можно работать 😜, поэтому я был больше сосредоточен на «идее», чем на конечном результате. Мне нравится подход делегата пользовательского интерфейса, поскольку он означает, что его можно применить к любой кнопке, вам не нужно специально использовать «расширенную» версию, но это всего лишь «один» способ, которым это может быть достигнуто.

MadProgrammer 02.05.2023 15:29

да, я знаю о качестве вопроса (и принял это во внимание :). Тем не менее, этот вопрос - по крайней мере частично, B - заново изобретает колесо как восьмиугольник: contains предназначен для переопределения именно для этого варианта использования. И мы оба знаем, что реализация дополнительных функций (при условии, что это действительно требование или необходимость) в пользовательских UI-делегатах — это гораздо больше работы, чем просто расширение BasicXX (SwingX имеет сложный механизм ... те, где есть время и решения :)

kleopatra 02.05.2023 15:45

Расширение @kleopatra против композиции - это всего лишь «один» способ сделать это. Лично я думаю, что это работает хорошо, так как мне не нужно много возиться со всеми «другими» свойствами кнопки (например, границы, рисование фокуса и т. д.), и поскольку Swing поддерживает «делегирование», это кажется хорошим подходит, я просто хотел посмотреть, смогу ли я это сделать

MadProgrammer 02.05.2023 15:48

@MadProgrammer Когда я запускаю ваш первый пример, я вижу это: i.stack.imgur.com/w2aFs.gif. Это должно произойти?

Ben 02.05.2023 17:27

@MadProgrammer Но ваш комментарий выше дал мне несколько полезных примеров! Спасибо!

Ben 02.05.2023 17:50

@Ben :/ У меня отлично работает - попробуйте установить цвет фона на что-нибудь другое (например, Color.RED и посмотрите, будет ли это иметь значение)

MadProgrammer 02.05.2023 23:48

@MadProgrammer Да, теперь это работает! Добавьте код в свой ответ, пожалуйста!

Ben 03.05.2023 18:16

@Ben, потому что это проблема конкретной платформы: P

MadProgrammer 04.05.2023 00:08

Другие вопросы по теме