Использование TextBubbleBorder во всплывающем окне

Прежде всего большое спасибо Эндрю Томпсону за его решение о границе со скругленными углами.

Теперь моя проблема. Я пытаюсь добавить функцию «Что нового» в наше приложение (небольшой пузырь, указывающий на новый компонент). Все работает нормально, кроме функции прозрачности границы. Вот мой MCVE (извините, что он немного длинный, потому что он содержит код, написанный Эндрю).

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.util.Collections;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.AbstractBorder;

/**
 * <code>PopupTryout</code>.
 */
public class PopupTryout {

    public static void main(String[] args) {
        JFrame frm = new JFrame("Popup test");
        JButton button = new JButton("Test");
        JPanel p = new JPanel();
        p.add(button);
        frm.add(p, BorderLayout.SOUTH);
        frm.add(new JScrollPane(new JTextArea()));
        JPanel hintPanel = new JPanel();
        hintPanel.add(new JLabel("This button has no function ;)"));
        hintPanel.setOpaque(false);
        JLabel closeBtn = new JLabel("Close");
        closeBtn.setForeground(Color.BLUE);
        closeBtn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        hintPanel.add(closeBtn);
        hintPanel.setBorder(new TextBubbleBorder(Color.BLACK, 1, 6, 8, false));

        frm.setSize(600, 500);
        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.setLocationRelativeTo(null);
        frm.setVisible(true);
        Point pt = button.getLocationOnScreen();
        Dimension panelSize = hintPanel.getPreferredSize();
        Dimension buttonSize = button.getSize();
        int x = pt.x + buttonSize.width - panelSize.width;
        int y = pt.y - panelSize.height - 10;
        // popup should only be closed when user clicks the "Close" button.
        Popup popup = PopupFactory.getSharedInstance().getPopup(button, hintPanel, x, y);
        popup.show();
        // "link style" button
        closeBtn.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                Font f = e.getComponent().getFont();
                e.getComponent().setFont(underlineOn(f));
            }

            @Override
            public void mouseExited(MouseEvent e) {
                e.getComponent().setFont(UIManager.getFont("Label.font"));
            }

            @Override
            public void mousePressed(MouseEvent e) {
                popup.hide();
            }
        });
    }

    /**
     * Gets the underline font for the given base font.
     * 
     * @param aBaseFont base font to paint it as underline.
     * @return underline font.
     */
    public static Font underlineOn(Font aBaseFont) {
        return aBaseFont.deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON));
    }

    static class TextBubbleBorder extends AbstractBorder {

        private Color color;

        private int thickness = 4;

        private int radii = 8;

        private int pointerSize = 7;

        private Insets insets = null;

        private BasicStroke stroke = null;

        private int strokePad;

        private int pointerPad = 4;

        private boolean left = true;

        RenderingHints hints;

        TextBubbleBorder(Color color) {
            this(color, 4, 8, 7);
        }

        TextBubbleBorder(Color color, int thickness, int radii, int pointerSize) {
            this.thickness = thickness;
            this.radii = radii;
            this.pointerSize = pointerSize;
            this.color = color;

            stroke = new BasicStroke(thickness);
            strokePad = thickness / 2;

            hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            int pad = radii + strokePad;
            int bottomPad = pad + pointerSize + strokePad;
            insets = new Insets(pad, pad, bottomPad, pad);
        }

        TextBubbleBorder(Color color, int thickness, int radii, int pointerSize, boolean left) {
            this(color, thickness, radii, pointerSize);
            this.left = left;
        }

        @Override
        public Insets getBorderInsets(Component c) {
            return insets;
        }

        @Override
        public Insets getBorderInsets(Component c, Insets insets) {
            return getBorderInsets(c);
        }

        @Override
        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

            Graphics2D g2 = (Graphics2D) g;

            int bottomLineY = height - thickness - pointerSize;

            RoundRectangle2D.Double bubble =
                    new RoundRectangle2D.Double(0 + strokePad, 0 + strokePad, width - thickness, bottomLineY, radii, radii);

            Polygon pointer = new Polygon();

            if (left) {
                // left point
                pointer.addPoint(strokePad + radii + pointerPad, bottomLineY);
                // right point
                pointer.addPoint(strokePad + radii + pointerPad + pointerSize, bottomLineY);
                // bottom point
                pointer.addPoint(strokePad + radii + pointerPad + (pointerSize / 2), height - strokePad);
            } else {
                // left point
                pointer.addPoint(width - (strokePad + radii + pointerPad), bottomLineY);
                // right point
                pointer.addPoint(width - (strokePad + radii + pointerPad + pointerSize), bottomLineY);
                // bottom point
                pointer.addPoint(width - (strokePad + radii + pointerPad + (pointerSize / 2)), height - strokePad);
            }

            Area area = new Area(bubble);
            area.add(new Area(pointer));

            g2.setRenderingHints(hints);

            // Paint the BG color of the parent, everywhere outside the clip
            // of the text bubble.
            Component parent = c.getParent();
            if (parent != null) {
                Color bg = parent.getBackground();
                Rectangle rect = new Rectangle(0, 0, width, height);
                Area borderRegion = new Area(rect);
                borderRegion.subtract(area);
                g2.setClip(borderRegion);
                g2.setColor(bg);
                g2.fillRect(0, 0, width, height);
                g2.setClip(null);
            }

            g2.setColor(color);
            g2.setStroke(stroke);
            g2.draw(area);
        }
    }
}

Вот результат, который я получил Использование TextBubbleBorder во всплывающем окне

Как видите, нижняя часть всплывающего окна непрозрачна. Я понимаю, почему так (первоначальное решение не подходит для этого случая), но не могу найти решение, чтобы это исправить. Есть идеи?

"извините, что немного длинно" 206 LOC не длиннее это! У меня нет времени заглянуть в этот банкомат, так что, надеюсь, кто-то другой скоро возьмется за него.
Andrew Thompson 12.03.2019 11:33

Простой способ сжать код Java — использовать массовый импорт, например java.awt.*

Alex Byrth 01.11.2020 23:18
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
2
270
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Взгляните на Полупрозрачные и фигурные распашные окна | Java.net. Может быть, это помогает.

// Add Popup to a transparent JWindow to create and display.
PopupFactory.setSharedInstance(new TranslucentPopupFactory());

// popup should only be closed when user clicks the "Close" button.
Popup popup = PopupFactory.getSharedInstance().getPopup(button, hintPanel, x, y);

PopupTryout2.java

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.util.Collections;
import javax.swing.*;
import javax.swing.border.AbstractBorder;

public class PopupTryout2 {
  public static void main(String[] args) {
    JFrame frm = new JFrame("Popup test");
    JButton button = new JButton("Test");
    JPanel p = new JPanel();
    p.add(button);
    frm.add(p, BorderLayout.SOUTH);
    frm.add(new JScrollPane(new JTextArea()));

    JPanel hintPanel = new JPanel();
    hintPanel.add(new JLabel("This button has no function ;)"));
    hintPanel.setOpaque(false);
    JLabel closeBtn = new JLabel("Close");
    closeBtn.setForeground(Color.BLUE);
    closeBtn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    hintPanel.add(closeBtn);
    hintPanel.setBorder(new TextBubbleBorder(Color.BLACK, 1, 6, 8, false));
    hintPanel.setOpaque(false);

    frm.setSize(600, 500);
    frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frm.setLocationRelativeTo(null);
    frm.setVisible(true);
    Point pt = button.getLocationOnScreen();
    Dimension panelSize = hintPanel.getPreferredSize();
    Dimension buttonSize = button.getSize();
    int x = pt.x + buttonSize.width - panelSize.width;
    int y = pt.y - panelSize.height - 10;

    PopupFactory.setSharedInstance(new TranslucentPopupFactory());

    // popup should only be closed when user clicks the "Close" button.
    Popup popup = PopupFactory.getSharedInstance().getPopup(button, hintPanel, x, y);

    popup.show();
    // "link style" button
    closeBtn.addMouseListener(new MouseAdapter() {
      @Override public void mouseEntered(MouseEvent e) {
        Font f = e.getComponent().getFont();
        e.getComponent().setFont(underlineOn(f));
      }

      @Override public void mouseExited(MouseEvent e) {
        e.getComponent().setFont(UIManager.getFont("Label.font"));
      }

      @Override public void mousePressed(MouseEvent e) {
        popup.hide();
      }
    });
  }

  public static Font underlineOn(Font aBaseFont) {
    return aBaseFont.deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON));
  }

  static class TextBubbleBorder extends AbstractBorder {
    private Color color;
    private int thickness = 4;
    private int radii = 8;
    private int pointerSize = 7;
    private Insets insets = null;
    private BasicStroke stroke = null;
    private int strokePad;
    private int pointerPad = 4;
    private boolean left = true;
    RenderingHints hints;

    TextBubbleBorder(Color color) {
      this(color, 4, 8, 7);
    }

    TextBubbleBorder(Color color, int thickness, int radii, int pointerSize) {
      this.thickness = thickness;
      this.radii = radii;
      this.pointerSize = pointerSize;
      this.color = color;

      stroke = new BasicStroke(thickness);
      strokePad = thickness / 2;

      hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      int pad = radii + strokePad;
      int bottomPad = pad + pointerSize + strokePad;
      insets = new Insets(pad, pad, bottomPad, pad);
    }

    TextBubbleBorder(Color color, int thickness, int radii, int pointerSize, boolean left) {
      this(color, thickness, radii, pointerSize);
      this.left = left;
    }

    @Override public Insets getBorderInsets(Component c) {
      return insets;
    }

    @Override public Insets getBorderInsets(Component c, Insets insets) {
      return getBorderInsets(c);
    }

    @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
      Graphics2D g2 = (Graphics2D) g;
      int bottomLineY = height - thickness - pointerSize;
      RoundRectangle2D.Double bubble =
        new RoundRectangle2D.Double(0 + strokePad, 0 + strokePad, width - thickness, bottomLineY, radii, radii);

      Polygon pointer = new Polygon();

      if (left) {
        pointer.addPoint(strokePad + radii + pointerPad, bottomLineY);
        pointer.addPoint(strokePad + radii + pointerPad + pointerSize, bottomLineY);
        pointer.addPoint(strokePad + radii + pointerPad + (pointerSize / 2), height - strokePad);
      } else {
        pointer.addPoint(width - (strokePad + radii + pointerPad), bottomLineY);
        pointer.addPoint(width - (strokePad + radii + pointerPad + pointerSize), bottomLineY);
        pointer.addPoint(width - (strokePad + radii + pointerPad + (pointerSize / 2)), height - strokePad);
      }

      Area area = new Area(bubble);
      area.add(new Area(pointer));

      g2.setRenderingHints(hints);

      // Paint the BG color of the parent, everywhere outside the clip
      // of the text bubble.
      Component parent = c.getParent();
      if (parent != null) {
        Color bg = parent.getBackground();
        // Rectangle rect = new Rectangle(0, 0, width, height);
        // Area borderRegion = new Area(rect);
        // borderRegion.subtract(area);
        // g2.setClip(borderRegion);
        // g2.setColor(bg);
        // g2.fillRect(0, 0, width, height);
        // g2.setClip(null);
        g2.setPaint(bg);
        g2.fill(area);
      }
      g2.setColor(color);
      g2.setStroke(stroke);
      g2.draw(area);
    }
  }
}

/*
@see http://today.java.net/pub/a/today/2008/03/18/translucent-and-shaped-swing-windows.html
https://web.archive.org/web/20150515015137/http://today.java.net/pub/a/today/2008/03/18/translucent-and-shaped-swing-windows.html
Translucent and Shaped Swing Windows | Java.net
*/
class TranslucentPopupFactory extends PopupFactory {
  @Override public Popup getPopup(Component owner, Component contents, int x, int y) {
    return new TranslucentPopup(owner, contents, x, y);
  }
}

class TranslucentPopup extends Popup {
  private final JWindow popupWindow;

  protected TranslucentPopup(Component owner, Component contents, int ownerX, int ownerY) {
    super(owner, contents, ownerX, ownerY);
    // create a new heavyweight window
    this.popupWindow = new JWindow();
    // mark the popup with partial opacity
    // AWTUtilities.setWindowOpacity(popupWindow, (contents instanceof JToolTip) ? .8f : .95f);
    // popupWindow.setOpacity(.5f);
    // AWTUtilities.setWindowOpaque(popupWindow, false); // Java 1.6.0_10
    popupWindow.setBackground(new Color(0x0, true)); // Java 1.7.0
    // determine the popup location
    popupWindow.setLocation(ownerX, ownerY);
    // add the contents to the popup
    popupWindow.getContentPane().add(contents);
    contents.invalidate();
    // JComponent parent = (JComponent) contents.getParent();
    // set the shadow border
    // parent.setBorder(new ShadowPopupBorder());
  }

  @Override public void show() {
    System.out.println("Always Heavy weight!");
    this.popupWindow.setVisible(true);
    this.popupWindow.pack();
    // mark the window as non-opaque, so that the
    // shadow border pixels take on the per-pixel
    // translucency
    // AWTUtilities.setWindowOpaque(this.popupWindow, false);
  }

  @Override public void hide() {
    this.popupWindow.setVisible(false);
    this.popupWindow.removeAll();
    this.popupWindow.dispose();
  }
}

Спасибо и голосуйте. Это хорошая идея. Я проверю, работает ли это для меня.

Sergiy Medvynskyy 12.03.2019 12:47

Работает отлично. Только один вопрос: как изменить фон подсказки? Когда я делаю hintPanel непрозрачным и устанавливаю цвет, я снова получаю ту же проблему :(.

Sergiy Medvynskyy 12.03.2019 13:39

Задача решена. Мне просто нужно изменить фон для панели содержимого popupWindow.

Sergiy Medvynskyy 12.03.2019 13:43

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