使用ComponentResizer时,具有透明背景的JFrame闪烁

时间:2017-12-04 22:21:57

标签: java swing jframe transparency flicker

问题:使用ComponentResizer调整大小时,带有透明背景的未修饰JFrame会闪烁。如下面的视频和MCVE所示,不透明的背景不会出现问题。

ComponentResizer(A MouseAdapter)的工作原理是拖动鼠标时计算拖动距离和方向,并相应地更改其组件的大小。

What causes the Jframe to flicker while resizing?How to stop the auto-repaint() when I resize the Jframe链接的答案,表示用Toolkit.getDefaultToolkit().setDynamicLayout(false)转换动态布局,然而,这并没有解决问题,因为它没有效果,可能是因为macOS不是允许它被禁用的平台。

JFrame with transparent background flickers when resized

问题:如何让用户调整具有透明背景的未修饰JFrame的大小,而不会闪烁?问题是ComponentResizer吗?

MCVE:(由ComponentResizer类引起的长度)

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;

public class JFrameFlickerMCVE {

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            Toolkit.getDefaultToolkit().setDynamicLayout(false);
            final JFrame frame = new JFrame();
            frame.setUndecorated(true);
            final JToggleButton backgroundButton = new JToggleButton("Break me!");
            backgroundButton.setSelected(true);
            backgroundButton.addActionListener(e -> {
                if(!backgroundButton.isSelected()) {
                    frame.setBackground(new Color(0, 0, 0, 0));
                    backgroundButton.setText("Fix me!");
                } else {
                    frame.setBackground(UIManager.getColor("control"));
                    backgroundButton.setText("Break me!");
                }
            });
            final JLabel label = new JLabel("Resize Here");
            label.setBorder(BorderFactory.createLineBorder(Color.RED));
            frame.getContentPane().add(backgroundButton);
            frame.getContentPane().add(label, BorderLayout.SOUTH);
            new ComponentResizer(frame);

            frame.pack();
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }

    /**
     *  The ComponentResizer allows you to resize a component by dragging a border
     *  of the component.
     */
    public static class ComponentResizer extends MouseAdapter
    {
        private final static Dimension MINIMUM_SIZE = new Dimension(10, 10);
        private final static Dimension MAXIMUM_SIZE =
                new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);

        private static Map<Integer, Integer> cursors = new HashMap<Integer, Integer>();
        {
            cursors.put(1, Cursor.N_RESIZE_CURSOR);
            cursors.put(2, Cursor.W_RESIZE_CURSOR);
            cursors.put(4, Cursor.S_RESIZE_CURSOR);
            cursors.put(8, Cursor.E_RESIZE_CURSOR);
            cursors.put(3, Cursor.NW_RESIZE_CURSOR);
            cursors.put(9, Cursor.NE_RESIZE_CURSOR);
            cursors.put(6, Cursor.SW_RESIZE_CURSOR);
            cursors.put(12, Cursor.SE_RESIZE_CURSOR);
        }

        private Insets dragInsets;
        private Dimension snapSize;

        private int direction;
        protected static final int NORTH = 1;
        protected static final int WEST = 2;
        protected static final int SOUTH = 4;
        protected static final int EAST = 8;

        private Cursor sourceCursor;
        private boolean resizing;
        private Rectangle bounds;
        private Point pressed;
        private boolean autoscrolls;

        private Dimension minimumSize = MINIMUM_SIZE;
        private Dimension maximumSize = MAXIMUM_SIZE;

        /**
         *  Convenience contructor. All borders are resizable in increments of
         *  a single pixel. Components must be registered separately.
         */
        public ComponentResizer()
        {
            this(new Insets(5, 5, 5, 5), new Dimension(1, 1));
        }

        /**
         *  Convenience contructor. All borders are resizable in increments of
         *  a single pixel. Components can be registered when the class is created
         *  or they can be registered separately afterwards.
         *
         *  @param components components to be automatically registered
         */
        public ComponentResizer(Component... components)
        {
            this(new Insets(5, 5, 5, 5), new Dimension(1, 1), components);
        }

        /**
         *  Convenience contructor. Eligible borders are resisable in increments of
         *  a single pixel. Components can be registered when the class is created
         *  or they can be registered separately afterwards.
         *
         *  @param dragInsets Insets specifying which borders are eligible to be
         *                    resized.
         *  @param components components to be automatically registered
         */
        public ComponentResizer(Insets dragInsets, Component... components)
        {
            this(dragInsets, new Dimension(1, 1), components);
        }

        /**
         *  Create a ComponentResizer.
         *
         *  @param dragInsets Insets specifying which borders are eligible to be
         *                    resized.
         *  @param snapSize Specify the dimension to which the border will snap to
         *                  when being dragged. Snapping occurs at the halfway mark.
         *  @param components components to be automatically registered
         */
        public ComponentResizer(Insets dragInsets, Dimension snapSize, Component... components)
        {
            setDragInsets( dragInsets );
            setSnapSize( snapSize );
            registerComponent( components );
        }

        /**
         *  Get the drag insets
         *
         *  @return  the drag insets
         */
        public Insets getDragInsets()
        {
            return dragInsets;
        }

        /**
         *  Set the drag dragInsets. The insets specify an area where mouseDragged
         *  events are recognized from the edge of the border inwards. A value of
         *  0 for any size will imply that the border is not resizable. Otherwise
         *  the appropriate drag cursor will appear when the mouse is inside the
         *  resizable border area.
         *
         *  @param  dragInsets Insets to control which borders are resizeable.
         */
        public void setDragInsets(Insets dragInsets)
        {
            validateMinimumAndInsets(minimumSize, dragInsets);

            this.dragInsets = dragInsets;
        }

        /**
         *  Get the components maximum size.
         *
         *  @return the maximum size
         */
        public Dimension getMaximumSize()
        {
            return maximumSize;
        }

        /**
         *  Specify the maximum size for the component. The component will still
         *  be constrained by the size of its parent.
         *
         *  @param maximumSize the maximum size for a component.
         */
        public void setMaximumSize(Dimension maximumSize)
        {
            this.maximumSize = maximumSize;
        }

        /**
         *  Get the components minimum size.
         *
         *  @return the minimum size
         */
        public Dimension getMinimumSize()
        {
            return minimumSize;
        }

        /**
         *  Specify the minimum size for the component. The minimum size is
         *  constrained by the drag insets.
         *
         *  @param minimumSize the minimum size for a component.
         */
        public void setMinimumSize(Dimension minimumSize)
        {
            validateMinimumAndInsets(minimumSize, dragInsets);

            this.minimumSize = minimumSize;
        }

        /**
         *  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 );
                component.removeMouseMotionListener( 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 );
                component.addMouseMotionListener( this );
            }
        }

        /**
         *  Get the snap size.
         *
         *  @return the snap size.
         */
        public Dimension getSnapSize()
        {
            return snapSize;
        }

        /**
         *  Control how many pixels a border must be dragged before the size of
         *  the component is changed. The border will snap to the size once
         *  dragging has passed the halfway mark.
         *
         *  @param snapSize Dimension object allows you to separately spcify a
         *                  horizontal and vertical snap size.
         */
        public void setSnapSize(Dimension snapSize)
        {
            this.snapSize = snapSize;
        }

        /**
         *  When the components minimum size is less than the drag insets then
         *  we can't determine which border should be resized so we need to
         *  prevent this from happening.
         */
        private void validateMinimumAndInsets(Dimension minimum, Insets drag)
        {
            int minimumWidth = drag.left + drag.right;
            int minimumHeight = drag.top + drag.bottom;

            if (minimum.width  < minimumWidth
                    ||  minimum.height < minimumHeight)
            {
                String message = "Minimum size cannot be less than drag insets";
                throw new IllegalArgumentException( message );
            }
        }

        /**
         */
        @Override
        public void mouseMoved(MouseEvent e)
        {
            Component source = e.getComponent();
            Point location = e.getPoint();
            direction = 0;

            if (location.x < dragInsets.left)
                direction += WEST;

            if (location.x > source.getWidth() - dragInsets.right - 1)
                direction += EAST;

            if (location.y < dragInsets.top)
                direction += NORTH;

            if (location.y > source.getHeight() - dragInsets.bottom - 1)
                direction += SOUTH;

            //  Mouse is no longer over a resizable border

            if (direction == 0)
            {
                source.setCursor( sourceCursor );
            }
            else  // use the appropriate resizable cursor
            {
                int cursorType = cursors.get( direction );
                Cursor cursor = Cursor.getPredefinedCursor( cursorType );
                source.setCursor( cursor );
            }
        }

        @Override
        public void mouseEntered(MouseEvent e)
        {
            if (! resizing)
            {
                Component source = e.getComponent();
                sourceCursor = source.getCursor();
            }
        }

        @Override
        public void mouseExited(MouseEvent e)
        {
            if (! resizing)
            {
                Component source = e.getComponent();
                source.setCursor( sourceCursor );
            }
        }

        @Override
        public void mousePressed(MouseEvent e)
        {
            //  The mouseMoved event continually updates this variable

            if (direction == 0) return;

            //  Setup for resizing. All future dragging calculations are done based
            //  on the original bounds of the component and mouse pressed location.

            resizing = true;

            Component source = e.getComponent();
            pressed = e.getPoint();
            SwingUtilities.convertPointToScreen(pressed, source);
            bounds = source.getBounds();

            //  Making sure autoscrolls is false will allow for smoother resizing
            //  of components

            if (source instanceof JComponent)
            {
                JComponent jc = (JComponent)source;
                autoscrolls = jc.getAutoscrolls();
                jc.setAutoscrolls( false );
            }
        }

        /**
         *  Restore the original state of the Component
         */
        @Override
        public void mouseReleased(MouseEvent e)
        {
            resizing = false;

            Component source = e.getComponent();
            source.setCursor( sourceCursor );

            if (source instanceof JComponent)
            {
                ((JComponent)source).setAutoscrolls( autoscrolls );
            }
        }

        /**
         *  Resize the component ensuring location and size is within the bounds
         *  of the parent container and that the size is within the minimum and
         *  maximum constraints.
         *
         *  All calculations are done using the bounds of the component when the
         *  resizing started.
         */
        @Override
        public void mouseDragged(MouseEvent e)
        {
            if (resizing == false) return;

            Component source = e.getComponent();
            Point dragged = e.getPoint();
            SwingUtilities.convertPointToScreen(dragged, source);

            changeBounds(source, direction, bounds, pressed, dragged);
        }

        protected void changeBounds(Component source, int direction, Rectangle bounds, Point pressed, Point current)
        {
            //  Start with original locaton and size

            int x = bounds.x;
            int y = bounds.y;
            int width = bounds.width;
            int height = bounds.height;

            //  Resizing the West or North border affects the size and location

            if (WEST == (direction & WEST))
            {
                int drag = getDragDistance(pressed.x, current.x, snapSize.width);
                int maximum = Math.min(width + x, maximumSize.width);
                drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);

                x -= drag;
                width += drag;
            }

            if (NORTH == (direction & NORTH))
            {
                int drag = getDragDistance(pressed.y, current.y, snapSize.height);
                int maximum = Math.min(height + y, maximumSize.height);
                drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);

                y -= drag;
                height += drag;
            }

            //  Resizing the East or South border only affects the size

            if (EAST == (direction & EAST))
            {
                int drag = getDragDistance(current.x, pressed.x, snapSize.width);
                Dimension boundingSize = getBoundingSize( source );
                int maximum = Math.min(boundingSize.width - x, maximumSize.width);
                drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
                width += drag;
            }

            if (SOUTH == (direction & SOUTH))
            {
                int drag = getDragDistance(current.y, pressed.y, snapSize.height);
                Dimension boundingSize = getBoundingSize( source );
                int maximum = Math.min(boundingSize.height - y, maximumSize.height);
                drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
                height += drag;
            }

            source.setBounds(x, y, width, height);
            source.validate();
        }

        /*
         *  Determine how far the mouse has moved from where dragging started
         */
        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;
        }

        /*
         *  Adjust the drag value to be within the minimum and maximum range.
         */
        private int getDragBounded(int drag, int snapSize, int dimension, int minimum, int maximum)
        {
            while (dimension + drag < minimum)
                drag += snapSize;

            while (dimension + drag > maximum)
                drag -= snapSize;


            return drag;
        }

        /*
         *  Keep the size of the component within the bounds of its parent.
         */
        private Dimension getBoundingSize(Component source)
        {
            if (source instanceof Window)
            {
                GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
                Rectangle bounds = env.getMaximumWindowBounds();
                return new Dimension(bounds.width, bounds.height);
            }
            else
            {
                return source.getParent().getSize();
            }
        }
    }

}

1 个答案:

答案 0 :(得分:0)

我搜索了一个解决方案,但没有人在工作... 原因似乎是在调整大小用户操作期间重绘了所有组件树。 由于调整大小的组件及其子组件的重绘不仅在鼠标释放后(调整大小结束)而且在调整大小期间(在鼠标拖动期间)完成,我试图评论

source.validate();

线入 changeBounds 方法(在拖动过程中不断调用)。 我把 mouseReleased 方法开头的那一行放到一个 if 块中:

if(resizing == true) {
        e.getComponent().validate();
}

它有效(几乎没有闪烁)。

让我知道此解决方案是否也适合您...