Archive: Correct way to redraw a label?


Correct way to redraw a label?
Alright, back to that old topic...

There's the camp of ShowWindow hide/show (flicker), so a more correct way would be invalidaterect or redrawwindow.

I've been using invalidaterect in a ${RedrawControl} macro with apparent success for some time now, but now I ran into a problem with it.

Basically I've got a GroupBox on which I place a Label. Why do I do that? Because I can't change the color of the text in a GroupBox - it's always happily blue.

Of course that does bring other issues with it - such as Windows' UI drawing being.. suboptimal. Suffice to say that there's several situation in which you might end up having the groupbox's label (a long string of spaces so as not to cut through the label control) being on top of the label control.
Despite the fact that the groupbox should have a transparent background, that now-underlying label control does not show through.

So, I have to redraw that label control either periodically, or based on some event, or whatever.

Now to the meat of this post.. I can't seem to redraw that label control properly with InvalidateRect or RedrawWindow. As soon as I redraw the control, it essentially paints on top of itself. That sounds like it's not so bad, until you're using font Smoothing or ClearType; the smoothing effect composites on top of itself, making the text look increasingly bold.. and increasingly jaggy.

So below is a test script to play with...


!addplugindir "."
!addincludedir "."

!include "nsDialogs.nsh"
!include "winmessages.nsh"
!include "logiclib.nsh"

!include "MUI2.nsh"

OutFile "test.exe"

var dialog
var hwnd
var null

var text
var button.showhide
var button.invalidaterect
var button.redrawwindow

Page custom test

Function test
nsDialogs::Create 1018
Pop $dialog

/*
${NSD_CreateGroupBox} 0 0 100% 12% "This is a test"
Pop $hwnd
SetCtlColors $hwnd 0xFF0000 transparent ; This doesn't work.
*/
${NSD_CreateLabel} 0 0 100% 6% "This is a test"
Pop $text

${NSD_CreateButton} 0 15% 30% 10% "&Show/Hide"
Pop $button.showhide
${NSD_OnClick} $button.showhide button.showhide.onclick

${NSD_CreateButton} 0 30% 30% 10% "&InvalidateRect"
Pop $button.invalidaterect
${NSD_OnClick} $button.invalidaterect button.invalidaterect.onclick

${NSD_CreateButton} 0 45% 30% 10% "&RedrawWindow"
Pop $button.redrawwindow
${NSD_OnClick} $button.redrawwindow button.redrawwindow.onclick

nsDialogs::Show
FunctionEnd

Function button.showhide.onclick
Pop $hwnd
ShowWindow $text ${SW_HIDE}
SetCtlColors $text 0x008000 transparent
ShowWindow $text ${SW_SHOW}
FunctionEnd

Function button.invalidaterect.onclick
Pop $hwnd
SetCtlColors $text 0x800000 transparent
System::Call "user32::InvalidateRect(i,i,i)i ($text, 0, 1)"
FunctionEnd


!ifndef RDW_INVALIDATE
!define RDW_INVALIDATE 0x0001
!define RDW_INTERNALPAINT 0x0002
!define RDW_ERASE 0x0004

!define RDW_VALIDATE 0x0008
!define RDW_NOINTERNALPAINT 0x0010
!define RDW_NOERASE 0x0020

!define RDW_NOCHILDREN 0x0040
!define RDW_ALLCHILDREN 0x0080

!define RDW_UPDATENOW 0x0100
!define RDW_ERASENOW 0x0200

!define RDW_FRAME 0x0400
!define RDW_NOFRAME 0x0800
!endif

Function button.redrawwindow.onclick
Pop $hwnd
SetCtlColors $text 0x000080 transparent
System::Call "user32::RedrawWindow(i,i,i,i)i ($text, 0, 0, ${RDW_INVALIDATE}|${RDW_ERASE}|${RDW_UPDATENOW})"
FunctionEnd

Section
SectionEnd
!insertmacro MUI_LANGUAGE "English"


I've hotkeyed the buttons, so just press e.g. Alt+S to use the show/hide method (flickers), and Alt+I for InvalidateRect (font smoothing issue), Alt+R for RedrawWindow (font smoothing issue).

I'm probably going to have to just drop the groupboxes (faking them about with bitmaps isn't exactly appealing either, as you might imagine) which would circumvent the problem in the first place.

But I'm still curious as to what the correct way to redraw a label should be. I've always had problems like SetCtlColors not working immediately, not even after an InvalidateRect (I guess because the next paint message gets 'halted' until the script finishes sometimes), but also always have been able to work around it.. but this one's got me baffled.
If this is just a bug in Windows and the only -real- way to redraw a control effectively is the ShowWindow method, then is there some way to prevent the flicker from it?

add some more flags to RedrawWindow, start with adding RDW_ERASENOW and maybe RDW_INTERNALPAINT


had tried RDW_ERASENOW.. that didn't do much.
( ${RDW_INVALIDATE}|${RDW_ERASE}|${RDW_ERASENOW}|${RDW_UPDATENOW} )

just tried RDW_INTERNALPAINT as well - still the ClearType compositing problem.
( ${RDW_INVALIDATE}|${RDW_ERASE}|${RDW_ERASENOW}|${RDW_UPDATENOW}|${RDW_INTERNALPAINT} )

Removing RDW_UPDATENOW (when using RDW_ERASENOW) didn't work either.


could the leftover pixels be on the groupbox and not the label?


I ones created a 'label' with a textbox, by setting the textbox readonly and borderless.
Not sure what the parameters where, but you could give it a try.


Anders: there's no actual leftover pixels.. they're essentially the same pixels. But because it's redrawing on top of those with partial transparency (font smoothing / cleartype), the text looks increasingly 'bold' and jagged.

You can try the example, it should be complete and not require any external dependencies that don't ship with NSIS.

jpderuiter: I'll give that a shot later


Function button.redrawwindow.onclick
Pop $hwnd
SetCtlColors $text 0x000080 transparent
FindWindow $0 "#32770" "" $HWNDPARENT
System::Call "user32::RedrawWindow(i,i,i,i)i ($0, 0, \
0,${RDW_INVALIDATE}|${RDW_ERASE}|${RDW_ERASENOW}|${RDW_ALLCHILDREN})"
FunctionEnd


maybe just calling InvalidateRect on the label and the dialog might be enough, too lazy to find the minimal amount of refresh combo that works

Anders:
Unfortunately that re-introduces flickering (on all controls)

jpderuiter:
Edit control at least doesn't suffer from the font smoothing/ClearType issue, so that's a good thing. Apparently you can't give it a transparent background, so one would have to figure out the color used by the dialog background (if not custom). In addition, the text is always selectable (doesn't respond to an OnClick to set focus away either), so there are some ifs-and-buts involved.


and

Pop $hwnd
SetCtlColors $text 0x000080 transparent
FindWindow $0 "#32770" "" $HWNDPARENT
System::Call "user32::RedrawWindow(i,i,i,i)i ($0, 0, 0,${RDW_INVALIDATE}|${RDW_ERASE})"
?

the problem is, the leftover pixels will not get erased since transparent gives you a hollow brush (with themes on, I'm not sure if the label will erase anything since there might be a gradient in the whole dialog and only the dialog could paint that, the only workaround I could think of is calling DrawThemeParentBackground() either in the COLOR msg or by subclassing )

Hmmm, and how about this wild idea:
Use a transparent bitmap with your text as groupbox header instead of normal text.
http://msdn.microsoft.com/en-us/library/bb775951(VS.85).aspx

Like:

        ${NSD_CreateGroupBox} 0 0 100% 12% "This is a test"
Pop $hwnd
${NSD_AddStyle} $hwnd "${BS_BITMAP}"
System::Call 'user32::LoadImage(i 0, t "c:\test.bmp", i ${IMAGE_BITMAP}, i 0, i 0, i ${LR_LOADTRANSPARENT}|${LR_CREATEDIBSECTION}|${LR_LOADFROMFILE}) i.s'
Pop $6
SendMessage $hwnd ${BM_SETIMAGE} ${IMAGE_BITMAP} $6

Should set the background of the image to transparent according to msdn.
http://msdn.microsoft.com/en-us/library/ms648045.aspx

Kind of working
I have got it kind of working (with some flickering) like this:


Function hidecontrol
SetCtlColors $TestHelpLabel1 0x000000 ${MUI_BKCOLOR}
ShowWindow $TestHelpLabel1 ${SW_HIDE}
ShowWindow $TestDialog ${SW_HIDE}
System::Call "user32::InvalidateRect(i,i,i)i ($TestHelpLabel1, 0, 1)"
System::Call "user32::RedrawWindow(i,i,i,i)i ($TestHelpLabel1, 0, 0, ${RDW_INVALIDATE}|${RDW_ERASE}|${RDW_UPDATENOW})"
ShowWindow $TestDialog ${SW_SHOW}
FunctionEnd

Function showcontrol
ShowWindow $TestHelpLabel1 ${SW_SHOW}
SetCtlColors $TestHelpLabel1 0x000000 transparent
FunctionEnd