简体   繁体   中英

Program not repainting upon JFrame height maximizing

You can resize JFrame with mouse dragging in several ways:

  1. You can resize it's height (top/bottom edge)
  2. You can resize it's width (left/right edge)
  3. You can resize both (in corners)
  4. You can maximize it by dragging the whole window to the monitor top edge
  5. You can maximize it's height by dragging it's top/bottom edge to the top/bottom edge of the monitor

My program is being repainted on every one of this actions except the number 5.

Why is that?

Could that be bug in my program? It doesn't seem so. I don't know where would I put repaint request for that particular case... It seems that JFrame itself should call repaint on it's contentPane after each resize, right?

It smells like a bug in Java itself, in JFrame class.

SSCCE:

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

class Gui
{
    Gui()
    {
        JFrame masterWindow = new JFrame("My very own SSCCE");

        masterWindow.setSize(1100, 100);
        masterWindow.setLocationRelativeTo(null);
        masterWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        masterWindow.setVisible(true);
    }
}

class Main
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                new Gui();
            }
        });
    }
}

Grab the top edge of the JFrame and drag it to the top edge of the screen and release. Bottom extended part should be pitch black. If you then maximize it, it will all be grey, as it should.

I still say it's Java or Windows bug.

It appears to be a bug in the Windows implementation of Java. (I am also using Windows 7 and JDK7.)

When Windows decides to change the height of the window, a COMPONENT_MOVED event is received by Window, but no COMPONENT_RESIZED event is received.

Inside the Windows class, the non-public method dispatchEventImpl() will respond to COMPONENT_RESIZED events by calling invalidate() and validate() , but it will ignore COMPONENT_MOVED events.

Here is a brute-force method of making the window re-validate itself after such an event. This may occasionally make the window re-validate in other situations, but not very often since the Window class itself is doing re-validation after every COMPONENT_RESIZED event, and the reported bug only happens when the window is being actively resized by the user.

    masterWindow.addComponentListener(new ComponentAdapter() {
      private int oldWidth = 0;
      private int oldHeight = 0;

      @Override
      public void componentResized(ComponentEvent e) {
        oldWidth = masterWindow.getWidth();
        oldHeight = masterWindow.getHeight();
      }

      @Override
      public void componentMoved(ComponentEvent e) {
          if (masterWindow.getWidth() != oldWidth || masterWindow.getHeight() != oldHeight) {
            masterWindow.invalidate();
            masterWindow.validate();
          }
          oldWidth = masterWindow.getWidth();
          oldHeight = masterWindow.getHeight();
      }
    });

BTW: Action 5 can also be invoked by double-clicking on the top/bottom corner of a window (in Windows 8 at least).

@Enwired: Your solution works only when the window is actually moved during the maximize. But it does not work, when the window is already at y=0, when invoking the vertical maximize. (I use a little tool called AltDrag which snaps windows to the borders of the screen, which makes this case a lot more likely. But a new window which wasn't moved or repositioned initially might have the same problem.)

I noticed, that when a window is vertically maximized paint() is called once on the window.

So a solution for the maximize action would be to override paint() and do this:

@Override
public void paint(Graphics g)
{
    checkResizeWindow(this);
    super.paint(g);
}

public static void checkResizeWindow(Window window)
{
    if (!window.isShowing())
    {
        return;
    }
    Component[] components = window.getComponents();
    if (components.length == 0)
    {
        return;
    }
    Component comp = components[0];
    Insets insets = window.getInsets();
    Dimension innerSize = window.getSize();
    innerSize.width -= insets.left + insets.right;
    innerSize.height -= insets.top + insets.bottom;
    if (!innerSize.equals(comp.getSize()))
    {
        window.invalidate();
        window.validate();
    }
}

The calculation i use is to determine if the window actually needs revalidation, by checking the the innerSize against the first child component's size (which usually is a single Panel filling out the whole innerSize ).

But, the same original problem still remains for the restore action. We would have to find an event which is called in that case and also call checkResizeWindow() at that point.

I was also thinking about...

  • using other events like MouseMotion or Focus, but they occur only later and some might even occur very often which could cause problems with our checkResizeWindow() method when being invoked too fast repeatedly.
  • using a recurring polling check calling checkResizeWindow() on every window, but polling is a bad concept in general and has it's own downsides.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM