【问题标题】:JDialog with transparent background which blurs things underneath具有透明背景的 JDialog 模糊了下面的东西
【发布时间】:2016-09-15 13:00:43
【问题描述】:

我正在尝试制作一个透明的 JDialog,它模糊了它下面的内容(为了更好的可读性)。已经找到这个link,但那里的对话框内容是模糊的。我还找到了this,但那里的一切(下面的东西和对话框的内容)都变得模糊并且闪烁。

这里有一个截图以便更好地理解:

顶部是我已经拥有的,底部是我想要实现的。

【问题讨论】:

  • 即使您不允许嵌入图片,您也应该在 i.stack.imgur.com 上发布您的图片。指向其他外部网站的链接往往会过时。

标签: java swing background blur jdialog


【解决方案1】:

经过一些测试后,似乎很难获得您想要实现的目标。我最终使用了一个 JLayeredPane,其中一层用于模糊背景,一层用于实际内容。

我对结果并不完全满意,但我现在没有找到更好的解决方案。 问题是 Java 没有提供适当的方法来捕获实际应用程序背后的内容,这导致需要在截屏之前隐藏内容。这也是导致您提到的闪烁的问题。 为了避免这种情况,我只在应用程序调整大小或移动时才重新创建背景。一旦真正使用,应该付出更多的努力,例如如果应用程序被最小化。但是由于这个例子不能调整大小或最小化它现在并不重要。 然而,这种方法的缺点是它完全忽略了背景是否不是静态的。例如,如果您在视频前面使用应用程序,则当前版本不会根据视频当前帧更改背景。如果您在我的示例中取消注释 Timer,这可能是可能的setVisible(...),但随后我们得到了闪烁。

我建议您考虑使用 JavaFx。由于 CSS 已经提供了模糊功能,因此它也可能适用于 Fx。但由于我还没有尝试过,所以我不能向你保证它是有效的。

长话短说,这是一个例子。上面的链接中使用了模糊功能。

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

@SuppressWarnings("serial")
public class BlurryFrame extends JFrame implements ActionListener, ComponentListener {

    public static void main(String... sss) {

        JLabel label1 = new JLabel("TestLabel 1");
        label1.setSize(500, 100);
        label1.setForeground(Color.RED);

        JLabel label2 = new JLabel("TestLabel 2");
        label2.setHorizontalAlignment(SwingConstants.CENTER);
        label2.setForeground(Color.BLUE);

        JPanel testPanel = new JPanel();
        testPanel.setOpaque(false);
        testPanel.setLayout(new BorderLayout());
        testPanel.add(label1, BorderLayout.CENTER);
        testPanel.add(label2, BorderLayout.SOUTH);

        BlurryFrame frame = new BlurryFrame(800, 600, testPanel);
        frame.setBackground(new Color(0, 0, 0, 100));
        frame.setVisible(true);
    }

    private final BackgroundPanel backgroundPane;
    private final JLayeredPane container;
    private final JPanel containerPane;
    private final Robot robot;

    // This rectangle is going to be your screenshots bounds to keep the image only as big as necessary
    private final Rectangle captureRect;

    // This is going to be the blurred screenshot
    private BufferedImage background;

    public BlurryFrame() {
        this(1280, 800);
    }

    public BlurryFrame(int width, int height) {
        this(width, height, null);
    }

    public BlurryFrame(int width, int height, JComponent component) {

        this.captureRect = new Rectangle();
        try {
            this.robot = new Robot();
        } catch (AWTException e) {
            throw new RuntimeException(e);
        }

        this.backgroundPane = new BackgroundPanel();
        this.backgroundPane.setOpaque(false);
        this.backgroundPane.setLayout(new BorderLayout());
        this.backgroundPane.setBounds(0, 0, width, height);
        this.backgroundPane.addComponentListener(this);

        this.containerPane = new JPanel();
        this.containerPane.setOpaque(false);
        this.containerPane.setLayout(new BorderLayout());
        this.containerPane.add(component, BorderLayout.CENTER);
        this.containerPane.setBounds(0, 0, width, height);

        this.setUndecorated(true);
        this.setSize(width, height);
        this.setLocationRelativeTo(null);

        this.container = new JLayeredPane();
        this.container.setOpaque(false);
        this.container.setLayout(new BorderLayout());
        this.container.add(this.backgroundPane, 1);
        this.container.add(this.containerPane, 0);

        this.getContentPane().add(this.container, BorderLayout.CENTER);
    }

    public void add(JComponent component) {
        this.containerPane.add(component, BorderLayout.CENTER);
        this.containerPane.repaint();
    }

    // This method does not really do much but ultimately triggers the screenshot by ending up in #componentHidden(...)
    private void capture() {
        this.containerPane.setVisible(false);
        this.backgroundPane.setVisible(false);
        this.repaint();
    }

    @Override
    public void setVisible(boolean visible) {

        super.setVisible(visible);
        this.capture();

        // XXX uncomment this if you want to see the flickering
        // Timer timer = new Timer(60, this);
        // timer.start();
    }

    @Override
    public void setSize(int width, int height) {

        super.setSize(width, height);

        this.captureRect.setSize(width, height);
        this.backgroundPane.setBounds(0, 0, width, height);
        this.containerPane.setBounds(0, 0, width, height);

        if (this.isVisible())
            this.capture();
    }

    @Override
    public void setSize(Dimension dimension) {

        super.setSize(dimension);

        this.captureRect.setSize(dimension);
        this.backgroundPane.setBounds(0, 0, dimension.width, dimension.height);
        this.containerPane.setBounds(0, 0, dimension.width, dimension.height);

        if (this.isVisible())
            this.capture();
    }

    @Override
    public void setPreferredSize(Dimension dimension) {

        super.setPreferredSize(dimension);

        this.captureRect.setSize(dimension);
        this.backgroundPane.setBounds(0, 0, dimension.width, dimension.height);
        this.containerPane.setBounds(0, 0, dimension.width, dimension.height);

        if (this.isVisible())
            this.capture();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        this.capture();
    }

    int i = 0;

    // This is where the magic happens. The capturing needs to be done here in order to assure the applications content has really been hidden
    @Override
    public void componentHidden(ComponentEvent e) {

        int x = this.getLocationOnScreen().x;
        int y = this.getLocationOnScreen().y;

        this.captureRect.setLocation(x, y);

        this.background = this.robot.createScreenCapture(this.captureRect);

        //XXX uncomment this if you want to see what gets captured
        //      if (this.i < 1) {
        //          try {
        //              ImageIO.write(this.background, "png", new File(System.getProperty("user.home") + "\\test.png"));
        //          } catch (IOException ex) {
        //              ex.printStackTrace();
        //          }
        //          this.i++;
        //      }

        this.containerPane.setVisible(true);
        this.backgroundPane.setVisible(true);
        this.repaint();
    }

    @Override
    public void componentMoved(ComponentEvent e) {
        this.capture();
    }

    @Override
    public void componentResized(ComponentEvent e) {
        this.capture();
    }

    private class BackgroundPanel extends JPanel {

        private final float[] matrix = {
                0.111f, 0.111f, 0.111f,
                0.111f, 0.111f, 0.111f,
                0.111f, 0.111f, 0.111f,
        };

        @Override
        public void paintComponent(Graphics g) {

            super.paintComponent(g);

            if (BlurryFrame.this.background != null) {

                BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, this.matrix));
                BufferedImage blurryBack = op.filter(BlurryFrame.this.background, null);

                g.drawImage(blurryBack, 0, 0, null);
            }
        }
    }

    @Override
    public void componentShown(ComponentEvent e) {
    }
}

【讨论】:

    【解决方案2】:

    我将 BenGe89 的回答标记为已接受,因为它解决了我的问题。

    但是我想分享我的扩展以使框架可移动。我使用了这个ComponentMover 并对其进行了一些修改,以便在拖动开始和停止时通知框架。当框架移动时,背景将只是透明以避免闪烁。

    package de.win;
    
    import java.awt.Component;
    import java.awt.Cursor;
    import java.awt.Dimension;
    import java.awt.Insets;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    
    import javax.swing.JComponent;
    import javax.swing.SwingUtilities;
    
    public class ComponentMover extends MouseAdapter {
    // private boolean moveWindow;
    
    @SuppressWarnings("rawtypes")
    private Class destinationClass;
    private DragNotifier dragNotifier;
    private Component destinationComponent;
    private Component destination;
    private Component source;
    
    private boolean changeCursor = true;
    
    private Point pressed;
    private Point location;
    
    private Cursor originalCursor;
    private boolean autoscrolls;
    
    private Insets dragInsets = new Insets(0, 0, 0, 0);
    private Dimension snapSize = new Dimension(1, 1);
    
    /**
     * * Constructor for moving individual components. The components must be regisetered using the registerComponent() method.
     */
    public ComponentMover() {
    }
    
    /**
     * * Constructor to specify a Class of Component that will be moved when drag events are generated on a registered child component. The events will be passed to the first ancestor of this specified class.
     * 
     * @param destinationClass
     *            the Class of the ancestor component
     * @param component
     *            the Components to be registered for forwarding drag events to the ancestor Component.
     */
    public ComponentMover(@SuppressWarnings("rawtypes") Class destinationClass, DragNotifier dragNotifier, Component... components) {
        this.destinationClass = destinationClass;
        this.dragNotifier = dragNotifier;
        registerComponent(components);
    }
    
    /**
     * * Constructor to specify a parent component that will be moved when drag events are generated on a registered child component.
     * 
     * @param destinationComponent
     *            the component drage events should be forwareded to
     * @param components
     *            the Components to be registered for forwarding drag events to the parent component to be moved
     */
    public ComponentMover(Component destinationComponent, DragNotifier dragNotifier, Component... components) {
        this.destinationComponent = destinationComponent;
        this.dragNotifier = dragNotifier;
        registerComponent(components);
    }
    
    /**
     * * Get the change cursor property
     * 
     * @return the change cursor property
     */
    public boolean isChangeCursor() {
        return changeCursor;
    }
    
    /**
     * * Set the change cursor property
     * 
     * @param changeCursor
     *            when true the cursor will be changed to the Cursor.MOVE_CURSOR while the mouse is pressed
     */
    public void setChangeCursor(boolean changeCursor) {
        this.changeCursor = changeCursor;
    }
    
    /**
     * * Get the drag insets
     * 
     * @return the drag insets
     */
    public Insets getDragInsets() {
        return dragInsets;
    }
    
    /**
     * * Set the drag insets. The insets specify an area where mouseDragged events should be ignored and therefore the component will not be moved. This will prevent these events from being confused with a MouseMotionListener that supports component resizing.
     * 
     * @param dragInsets
     */
    public void setDragInsets(Insets dragInsets) {
        this.dragInsets = dragInsets;
    }
    
    /**
     * * Remove listeners from the specified component
     * 
     * @param component
     *            the component the listeners are removed from
     */
    public void deregisterComponent(Component... components) {
        for (Component component : components)
            component.removeMouseListener(this);
    }
    
    /**
     * * Add the required listeners to the specified component
     * 
     * @param component
     *            the component the listeners are added to
     */
    public void registerComponent(Component... components) {
        for (Component component : components)
            component.addMouseListener(this);
    }
    
    /**
     * * Get the snap size
     * 
     * @return the snap size
     */
    public Dimension getSnapSize() {
        return snapSize;
    }
    
    /**
     * * Set the snap size. Forces the component to be snapped to the closest grid position. Snapping will occur when the mouse is dragged half way.
     */
    public void setSnapSize(Dimension snapSize) {
        this.snapSize = snapSize;
    }
    
    /**
     * * Setup the variables used to control the moving of the component:
     * 
     * source - the source component of the mouse event destination - the component that will ultimately be moved pressed - the Point where the mouse was pressed in the destination component coordinates.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        dragNotifier.dragStart();
    
        source = e.getComponent();
        int width = source.getSize().width - dragInsets.left - dragInsets.right;
        int height = source.getSize().height - dragInsets.top - dragInsets.bottom;
        Rectangle r = new Rectangle(dragInsets.left, dragInsets.top, width, height);
    
        if (r.contains(e.getPoint()))
            setupForDragging(e);
    }
    
    private void setupForDragging(MouseEvent e) {
        source.addMouseMotionListener(this);
    
        // Determine the component that will ultimately be moved
    
        if (destinationComponent != null) {
            destination = destinationComponent;
        } else if (destinationClass == null) {
            destination = source;
        } else // forward events to destination component
        {
            destination = SwingUtilities.getAncestorOfClass(destinationClass, source);
        }
    
        pressed = e.getLocationOnScreen();
        location = destination.getLocation();
    
        if (changeCursor) {
            originalCursor = source.getCursor();
            source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
        }
    
        // Making sure autoscrolls is false will allow for smoother dragging of individual components
    
        if (destination instanceof JComponent) {
            JComponent jc = (JComponent) destination;
            autoscrolls = jc.getAutoscrolls();
            jc.setAutoscrolls(false);
        }
    }
    
    /**
     * * Move the component to its new location. The dragged Point must be in the destination coordinates.
     */
    @Override
    public void mouseDragged(MouseEvent e) {
        Point dragged = e.getLocationOnScreen();
        int dragX = getDragDistance(dragged.x, pressed.x, snapSize.width);
        int dragY = getDragDistance(dragged.y, pressed.y, snapSize.height);
        destination.setLocation(location.x + dragX, location.y + dragY);
    }
    
    /*
     * * Determine how far the mouse has moved from where dragging started (Assume drag direction is down and right for positive drag distance)
     */
    private int getDragDistance(int larger, int smaller, int snapSize) {
        int halfway = snapSize / 2;
        int drag = larger - smaller;
        drag += (drag < 0) ? -halfway : halfway;
        drag = (drag / snapSize) * snapSize;
    
        return drag;
    }
    
    /**
     * * Restore the original state of the Component
     */
    @Override
    public void mouseReleased(MouseEvent e) {
        source.removeMouseMotionListener(this);
    
        if (changeCursor)
            source.setCursor(originalCursor);
    
        if (destination instanceof JComponent) {
            ((JComponent) destination).setAutoscrolls(autoscrolls);
        }
    
        if (dragNotifier != null) {
            dragNotifier.dragComplete();
        }
    
    }
    
    interface DragNotifier {
        public void dragComplete();
    
        public void dragStart();
    }
    
    }
    

    修改后的模糊帧:

    package de.win;
    
    import java.awt.AWTException;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Rectangle;
    import java.awt.Robot;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.ComponentEvent;
    import java.awt.event.ComponentListener;
    import java.awt.image.BufferedImage;
    import java.awt.image.BufferedImageOp;
    import java.awt.image.ConvolveOp;
    import java.awt.image.Kernel;
    
    import javax.swing.JComponent;
    import javax.swing.JDialog;
    import javax.swing.JLabel;
    import javax.swing.JLayeredPane;
    import javax.swing.JPanel;
    import javax.swing.SwingConstants;
    
    import de.win.ComponentMover.DragNotifier;
    
    @SuppressWarnings("serial")
    public class BlurryFrame extends JDialog implements ActionListener,     ComponentListener, DragNotifier {
    
    public static void main(String... sss) {
    
        JLabel label1 = new JLabel("TestLabel 1");
        label1.setSize(500, 100);
        label1.setForeground(Color.RED);
    
        JLabel label2 = new JLabel("TestLabel 2");
        label2.setHorizontalAlignment(SwingConstants.CENTER);
        label2.setForeground(Color.BLUE);
    
        JPanel testPanel = new JPanel();
        testPanel.setOpaque(false);
        testPanel.setLayout(new BorderLayout());
        testPanel.add(label1, BorderLayout.CENTER);
        testPanel.add(label2, BorderLayout.SOUTH);
    
        BlurryFrame frame = new BlurryFrame(800, 600, testPanel);
        frame.setBackground(new Color(0, 0, 0, 100));
        frame.setVisible(true);
        new ComponentMover(frame, frame, testPanel);
    }
    
    private final BackgroundPanel backgroundPane;
    private final JLayeredPane container;
    private final JPanel containerPane;
    private final Robot robot;
    
    // This rectangle is going to be your screenshots bounds to keep the image only as big as necessary
    private final Rectangle captureRect;
    
    // This is going to be the blurred screenshot
    private BufferedImage background;
    
    public BlurryFrame() {
        this(1280, 800);
    }
    
    public BlurryFrame(int width, int height) {
        this(width, height, null);
    }
    
    public BlurryFrame(int width, int height, JComponent component) {
    
        this.captureRect = new Rectangle();
        try {
            this.robot = new Robot();
        } catch (AWTException e) {
            throw new RuntimeException(e);
        }
    
        this.backgroundPane = new BackgroundPanel();
        this.backgroundPane.setOpaque(false);
        this.backgroundPane.setLayout(new BorderLayout());
        this.backgroundPane.setBounds(0, 0, width, height);
        this.backgroundPane.addComponentListener(this);
    
        this.containerPane = new JPanel();
        this.containerPane.setOpaque(false);
        this.containerPane.setLayout(new BorderLayout());
        this.containerPane.add(component, BorderLayout.CENTER);
        this.containerPane.setBounds(0, 0, width, height);
    
        this.setUndecorated(true);
        this.setSize(width, height);
        this.setLocationRelativeTo(null);
    
        this.container = new JLayeredPane();
        this.container.setOpaque(false);
        this.container.setLayout(new BorderLayout());
        this.container.add(this.backgroundPane, 1);
        this.container.add(this.containerPane, 0);
    
        this.getContentPane().add(this.container, BorderLayout.CENTER);
    }
    
    public void add(JComponent component) {
        this.containerPane.add(component, BorderLayout.CENTER);
        this.containerPane.repaint();
    }
    
    // This method does not really do much but ultimately triggers the screenshot by ending up in #componentHidden(...)
    private void capture() {
        this.containerPane.setVisible(false);
        this.backgroundPane.setVisible(false);
        this.repaint();
    }
    
    @Override
    public void setVisible(boolean visible) {
    
        super.setVisible(visible);
        this.capture();
    
        // XXX uncomment this if you want to see the flickering
        // Timer timer = new Timer(60, this);
        // timer.start();
    }
    
    @Override
    public void setSize(int width, int height) {
    
        super.setSize(width, height);
    
        this.captureRect.setSize(width, height);
        this.backgroundPane.setBounds(0, 0, width, height);
        this.containerPane.setBounds(0, 0, width, height);
    
        if (this.isVisible())
            this.capture();
    }
    
    @Override
    public void setSize(Dimension dimension) {
    
        super.setSize(dimension);
    
        this.captureRect.setSize(dimension);
        this.backgroundPane.setBounds(0, 0, dimension.width, dimension.height);
        this.containerPane.setBounds(0, 0, dimension.width, dimension.height);
    
        if (this.isVisible())
            this.capture();
    }
    
    @Override
    public void setPreferredSize(Dimension dimension) {
    
        super.setPreferredSize(dimension);
    
        this.captureRect.setSize(dimension);
        this.backgroundPane.setBounds(0, 0, dimension.width, dimension.height);
        this.containerPane.setBounds(0, 0, dimension.width, dimension.height);
    
        if (this.isVisible())
            this.capture();
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        this.capture();
    }
    
    int i = 0;
    
    // This is where the magic happens. The capturing needs to be done here in order to assure the applications content has really been hidden
    @Override
    public void componentHidden(ComponentEvent e) {
    
        int x = this.getLocationOnScreen().x;
        int y = this.getLocationOnScreen().y;
    
        this.captureRect.setLocation(x, y);
    
        this.background = this.robot.createScreenCapture(this.captureRect);
    
        // XXX uncomment this if you want to see what gets captured
        // if (this.i < 1) {
        // try {
        // ImageIO.write(this.background, "png", new File(System.getProperty("user.home") + "\\test.png"));
        // } catch (IOException ex) {
        // ex.printStackTrace();
        // }
        // this.i++;
        // }
    
        this.containerPane.setVisible(true);
        this.backgroundPane.setVisible(true);
        this.repaint();
    }
    
    @Override
    public void componentMoved(ComponentEvent e) {
        this.capture();
    }
    
    @Override
    public void componentResized(ComponentEvent e) {
        this.capture();
    }
    
    private class BackgroundPanel extends JPanel {
    
        private final float[] matrix = { 0.111f, 0.111f, 0.111f, 0.111f, 0.111f, 0.111f, 0.111f, 0.111f, 0.111f, };
    
        @Override
        public void paintComponent(Graphics g) {
            if (BlurryFrame.this.background != null) {
    
                BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, this.matrix));
                BufferedImage blurryBack = op.filter(BlurryFrame.this.background, null);
    
                g.drawImage(blurryBack, 0, 0, null);
            }
        }
    }
    
    @Override
    public void componentShown(ComponentEvent e) {
    }
    
    @Override
    public void dragComplete() {
        this.capture();
    }
    
    @Override
    public void dragStart() {
        this.background = null;
        this.backgroundPane.repaint();
    }
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-12
      • 1970-01-01
      • 2015-03-18
      • 2018-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-24
      相关资源
      最近更新 更多