Problem with extra drawing with native window titles


#1

Note this problem only exists with windows using a native title bar. I have tried it without a native title bar and the problem goes away.

If a ResizableWindow containing an opaque content component is resized, there is a brief moment when the background color is visible, even though the content component should immediately stretch to fill the space. I believe the correct order of events when a user resizes the window is the following:

  • ResizableWindow changed to new size
  • ComponentPeer changed to new size
  • content Component size changed to new size
  • ComponentPeer handles internalRepaint()
  • ResizableWindow::paint() is skipped because the content component occludes it (the setOpaque() optimization)
  • content Component::paint() is called and fills in the entire client area of the ResizableWindow.

Unfortunately, it appears this is not what is happening. Instead, there is a brief moment where the ComponentPeer is allowed to redraw, before the content Component’s size has been changed. I wrote a simple test program that shows this behaviour. To see the problem, try making the test window larger. You will see red, which I think is incorrect.

#include "juce.h"

struct MainPanel : Component
{
  MainPanel()
  {
    setOpaque (true);
  }

  void paint (Graphics& g)
  {
    g.setColour (Colours::grey);
    g.fillAll();

    // simulate a complex UI that is slow to draw
    Thread::sleep(250);
  }
};

struct MainWindow : DocumentWindow
{
  MainWindow()
  : DocumentWindow (JUCE_T("Test")
  , Colours::red
  , DocumentWindow::allButtons
  , true )
  {
    MainPanel* p = new MainPanel;
    p->setSize (512, 384);
    setUsingNativeTitleBar (true);
    setResizable (true, false);
    setContentComponent (p, true, true);
    centreWithSize (getWidth(), getHeight());
    setVisible( true );
  }
  ~MainWindow() {}

  void closeButtonPressed() { JUCEApplication::quit(); }
};

struct MainApp : JUCEApplication
{
  MainApp() : mainWindow(0) { s_app=this; }
  ~MainApp() { s_app=0; }
  static MainApp& GetInstance() { return *s_app; }
  const String getApplicationName() { return JUCE_T("JuceTest"); }
  const String getApplicationVersion() { return JUCE_T("0.1.0"); }
  bool moreThanOneInstanceAllowed() { return true; }
  void anotherInstanceStarted (const String& commandLine) {}

  void initialise (const String& commandLine)
  {
    mainWindow = new MainWindow;
  }

  void shutdown()
  {
    delete mainWindow;
  }

  static MainApp* s_app;
  MainWindow* mainWindow;
};

MainApp* MainApp::s_app = 0;

START_JUCE_APPLICATION (MainApp)

#2

There’s nothing wrong with the way it works. When the window frame moves, win32 repaints the area where it just moved from, so it temporarily asks the app to repaint an area that’s slightly bigger than the client area it previously covered, which in this case will be beyond the edges of your component. Then it gets round to repainting the frame, over the top of what your app just painted. There’s nothing to fix, it’s just how win32 works.


#3

In my example program, if you change to not using native window titles (setUsingNativeTitleBar(false)), then the problem goes away. Yet both are using win32. So I think this is related to the framework and not the OS. Or else why would it work differently?


#4

Since a regular Juce window doesn’t have the problem, and a native title bar window does, I am thinking this is related to how the window gets resized (ResizableBorderComponent versus WM_WINDOWPOSCHANGED) so I will try to step through it with a debugger and figure out what’s going on.


#5

Here’s what’s happening:

juce_win32_Windowing.cpp

            case WM_PAINT:
                handlePaintMessage();
                return 0;

            case WM_NCPAINT:
                if (wParam != 1)
                    handlePaintMessage();

                if (hasTitleBar())
                    break;

                return 0;

Here is the series of messages when the user resizes a window using the frame of a Win32ComponentPeer that is using a native title bar:

WM_NCPAINT
WM_WINDOWPOSCHANGED
WM_PAINT

When WM_NCPAINT comes in, we go through handlePaintMessage() which causes all the Component::paint(), including ResizableWindow which in my test app, draws red. This is why you see the red in my test app window with native title bars. Note that at this point the content component has not had its size changed.

Then we get WM_WINDOWPOSCHANGED which ultimately causes the content component to pick up the new bounds, causing a repaint.

Finally we receive WM_PAINT, and everything draws again but this time the content component is the correct size. The ResizableWindow::paint never gets called because the opaque content component clips it out, and we don’t see any red.

Note that with native window titles, the content component gets drawn two or more times more than necessary while the user is resizing the window.

Why do we respond to WM_NCPAINT by redrawing everything? Isn’t that handled sufficiently by WM_PAINT? I thought WM_NCPAINT was just for people who want to get fancy and change the way the standard window borders are drawn.


#6

It could probably be changed to this:

[code] case WM_NCPAINT:
if (hasTitleBar())
break;
else if (wParam != 1)
handlePaintMessage();

            return 0;

[/code]

There are obscure and messy reasons why it has to process the NCPAINT message when it’s not using a title bar, but I think it’s ok to ignore it if there is one.


#7

I’m embarassed to admit that I tried that already, while it did prevent the ResizableWindow from getting the extra paint(), I can’t seem to figure out why Windows insists on filling in the area with black before the component is resized and draws. Of course you are already setting the hbrBackground in the WNDCLASSEX to 0 so that’s not it. Interesting is that the non-native titlebar Juce windows don’t experience this.

If you play with other apps on windows like Firefox, Explorer, etc… when they resize there is no moment where a black, white, or any other colored background appears, it all works as expected (as with a Juce window without a native title bar). So something is still a bit off but I can’t put my finger on it. It gave me headache but I will revisit this another day and try to figure it out.

In the meanwhile, I wrote a test program that illustrates the problem. If you run it under windows, make sure you have the changes to WM_NCPAINT:

                case WM_PAINT:
                    handlePaintMessage();
                    return 0;

                case WM_NCPAINT:
                    if (wParam != 1)
                        handlePaintMessage();

                    if (hasTitleBar())
                        break;

                    return 0;

And here’s the sample program. When you run it under Windows you will see a window with a gray content component getting wider and wider. Each time it gets wider, you will see a black rectangle in the new area. This should not be there. If you comment out the setUsingNativeTitleBar() line, you will see it work correctly.

#include "juce.h"

struct MainPanel : Component
{
  MainPanel()
  {
    setOpaque (true);
  }

  void paint (Graphics& g)
  {
    g.setColour (Colours::grey);
    g.fillAll();

    // simulate a complex UI that is slow to draw
    Thread::sleep(500);
  }
  void resized()
  {
  }
};

struct MainWindow : DocumentWindow
{
  MainWindow()
  : DocumentWindow (JUCE_T("Test")
  , Colours::red
  , DocumentWindow::allButtons
  , true )
  {
    MainPanel* p = new MainPanel;
    p->setSize (64, 384);
    setUsingNativeTitleBar (true);
    setResizable (true, false);
    setContentComponent (p, true, true);
    centreWithSize (getWidth(), getHeight());

    //setTopLeftPosition( -1400, 400);
    setVisible( true );

    for (int i=0; i<10; i++)
    {
      getPeer()->performAnyPendingRepaintsNow();
      setSize (getWidth()+64, getHeight());
    }
  }
  ~MainWindow() {}

  void closeButtonPressed() { JUCEApplication::quit(); }
};

struct MainApp : JUCEApplication
{
  MainApp() : mainWindow(0) { s_app=this; }
  ~MainApp() { s_app=0; }
  static MainApp& GetInstance() { return *s_app; }
  const String getApplicationName() { return JUCE_T("JuceTest"); }
  const String getApplicationVersion() { return JUCE_T("0.1.0"); }
  bool moreThanOneInstanceAllowed() { return true; }
  void anotherInstanceStarted (const String& commandLine) {}

  void initialise (const String& commandLine)
  {
    mainWindow = new MainWindow;
  }

  void shutdown()
  {
    delete mainWindow;
  }

  static MainApp* s_app;
  MainWindow* mainWindow;
};

MainApp* MainApp::s_app = 0;

START_JUCE_APPLICATION (MainApp)

#8

Sorry, I’ve got many more important things to work on - if you really think this is worth spending part of your finite lifetime investigating, and if you do come up with anything useful, let me know, but I really don’t think it matters.

The difference in other apps is probably just that they use the NCPAINT to fill the frame area with a background colour that matches the rest of their app, so you don’t notice when it gets drawn. You’d see the same effect if you make your ResizableWindow’s background colour match your content component - if in your example it was grey rather than red, you wouldn’t be able to see the effect.


#9

I totally understand and you’re right it is costly in terms of time / benefit to pursue it further, and I don’t expect you to.

Your suggestion for skipping the handlePaintMessage() in WM_NCPAINT when hasTitleBar() is true, is a good change. I tried it, and stepped through it extensively with a debugger and I can confirm that the content component goes through its painting routine exactly once every time the window is resized, instead of twice like before, so I would recommend keeping that change.

Right now I can’t invest any more time in figuring out why the background is now turning black with your WM_NCPAINT change, but I know for a fact it is not Juce doing that drawing, because I set breakpoints everywhere and especially where Juce draws the DibSection to the screen, and none of them cause the black to appear.

However, one day when I will look into it again and see if I can figure it out. Thanks for hearing me out!

[quote="Jules"The difference in other apps is probably just that they use the NCPAINT to fill the frame area with a background colour that matches the rest of their app, so you don’t notice when it gets drawn.[/quote]

This is not the case, because when I do my test by resizing a Juce window without a native title bar, the window frame expands (you can see the drop shadow) but the new space is not filled in with anything, it is completely transparent and you can see my Windows desktop underneath. So there is no additional drawing or filling with a background colour, and shortly after the content Component correctly draws at the new size. There is definitely something a little bit off with using native window titlebars, and when you are not using native window titlebars everything works perfect as expected.


#10

Just pointing out that this is still a problem in the latest tip.