Page 1 of 2

Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 6:16 am
by Mistrel
Windows already has a function FlashWindow and FlashWindowEx. However, these functions don't work on windows outside of the current process. So I wrote a replacement to mimic this behavior.

Thanks to netmaestro for this high performance timer code I snagged from elsewhere on the forum.

By capturing the #WM_MOUSEACTIVATE message, you can imitate the effect of a modal window by calling MessageBeep_(#MB_OK) along with FlashWindow(hWnd), which was my intent. It works great when you want to halt your main process until another one you just launched exits.

Code: Select all

;/ The default blink rate for Windows is four blinks with a 60 ms delay
Procedure FlashWindow(hWnd, Count=4, Timeout=60)
  Protected Timer.TIMECAPS
  Protected i
  Protected OldStyle
  
  ;/ Set the window to a non-popup child so that it imitates a modal
  ;/ window and does not blink any borders around the currently
  ;/ highlighted control or other selection
  OldStyle=GetWindowLongPtr_(hWnd,#GWL_STYLE)
  SetWindowLongPtr_(hWnd,#GWL_STYLE,OldStyle&(~#WS_POPUP)|#WS_CHILD)
  
  ;/ Get timer capabilities
  timeGetDevCaps_(@Timer,SizeOf(TIMECAPS))
  
  ;/ Reset timer resolution to lowest possible (usually 1)
  timeBeginPeriod_(Timer\wPeriodMin)
  
  ;/ Blink the window by changing the foreground window between the
  ;/ target hWnd and the desktop
  SetForegroundWindow_(GetDesktopWindow_())
  For i=1 To Count
    SetForegroundWindow_(hWnd)
    Sleep_(Timeout)
    SetForegroundWindow_(GetDesktopWindow_())
    Sleep_(Timeout)
  Next i
  
  ;/ Restore the old window style
  SetWindowLongPtr_(hWnd,#GWL_STYLE,OldStyle)
  
  ;/ Reset timer resolution to normal
  timeEndPeriod_(Timer\wPeriodMin)
EndProcedure

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 7:05 am
by Mistrel
This new version uses threads so you can click a bunch of times and have the blinking blink faster. Also, if you want to emit a message beep as well, you can beep-beep as quick as you can click your mouse instead of having to wait for the blinking to stop.

The atomic counter is so that a non-modal window won't have its non-modal status reinstated until the last thread has ended.

Code: Select all

Global Glob_FlashWindow_ThreadCount

Structure FlashInfo
  hWnd.i
  Count.l
  Timeout.l
  IsModal.l
EndStructure

Procedure FlashWindowThread(*FlashInfo.FlashInfo)
  Protected Timer.TIMECAPS
  Protected i
  Protected OldStyle
  
  
  Debug *FlashInfo\hWnd
  Debug *FlashInfo\Count
  Debug *FlashInfo\Timeout
  
  ;/ Set the window to a non-popup child so that it imitates a modal
  ;/ window and does not blink any borders around the currently
  ;/ highlighted control or other selection
  If Not *FlashInfo\IsModal
    Style=GetWindowLongPtr_(*FlashInfo\hWnd,#GWL_STYLE)
    SetWindowLongPtr_(*FlashInfo\hWnd,#GWL_STYLE,Style&(~#WS_POPUP)|#WS_CHILD)
  EndIf
  
  ;/ Blink the window by changing the foreground window between the
  ;/ target hWnd and the desktop
  SetForegroundWindow_(GetDesktopWindow_())
  For i=1 To *FlashInfo\Count
    SetForegroundWindow_(*FlashInfo\hWnd)
    Sleep_(*FlashInfo\Timeout)
    SetForegroundWindow_(GetDesktopWindow_())
    Sleep_(*FlashInfo\Timeout)
  Next i
  
  ;/ Decrement the total thread count
  ThreadCount=InterlockedDecrement_(@Glob_FlashWindow_ThreadCount)
  
  ;/ Restore the old window style only if the thread count is zero
  If Not *FlashInfo\IsModal And Not ThreadCount
    SetWindowLongPtr_(*FlashInfo\hWnd,#GWL_STYLE,Style&(~#WS_CHILD)|#WS_POPUP)
  EndIf
  
  FreeMemory(*FlashInfo)
EndProcedure

;/ The default blink rate for Windows is four blinks with a 60 ms delay
Procedure FlashWindow(hWnd, Count=4, Timeout=60, IsModal=#False)
  Protected *FlashInfo.FlashInfo
  
  ;/ Increment the total thread count
  InterlockedIncrement_(@Glob_FlashWindow_ThreadCount)
  
  *FlashInfo=AllocateMemory(SizeOf(FlashInfo))
  *FlashInfo\hWnd=hWnd
  *FlashInfo\Count=Count
  *FlashInfo\Timeout=Timeout
  *FlashInfo\IsModal=IsModal
  
  CreateThread(@FlashWindowThread(),*FlashInfo)
EndProcedure

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:01 am
by PB
> these functions don't work on windows outside of the current process

What do you mean? I can use the following code to flash the Calculator window:

Code: Select all

c=FindWindow_(0,"Calculator")

For n=1 To 4
  FlashWindow_(c,1)
  Sleep_(250)
  FlashWindow_(c,0)
  Sleep_(250)
Next
> So I wrote a replacement to mimic this behavior

Hmm, it's not an exact replacement though, because it doesn't flash the button
on the Taskbar of the app in another color, which FlashWindow does. So, can
you give an example of where FlashWindow is failing to flash a window of another
process? It works for me on XP.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:21 am
by Mistrel
Using your code to try and flash a window four times with a 60 ms delay doesn't work. I get on average between 1 and 2 flashes:

Code: Select all

hWnd=FindWindow_(0,"Calculator")

For i=1 To 4
  FlashWindow_(hWnd,1)
  Sleep_(60)
  FlashWindow_(hWnd,0)
Next
End
FlashWindowEx doesn't work at all:

Code: Select all

Structure FLASHWINFO
  cbSize.l
  hwnd.i
  dwFlags.l
  uCount.l
  dwTimeout.l
EndStructure

#FLASHW_ALL=$00000003
#FLASHW_CAPTION=$00000001
#FLASHW_STOP=0
#FLASHW_TIMER=$00000004
#FLASHW_TIMERNOFG=$0000000C
#FLASHW_TRAY=$00000002

FlashWindow.FLASHWINFO

FlashWindow\cbSize=SizeOf(FLASHWINFO)
FlashWindow\dwFlags=#FLASHW_CAPTION
FlashWindow\dwTimeout=60
FlashWindow\uCount=4

hWnd=FindWindow_(0,"Calculator")

FlashWindow\hwnd=hWnd

If hWnd
  FlashWindowEx_(@FlashWindow)
EndIf

Repeat
  Delay(1)
ForEver
FlashWindowEx works fine if the window is from the same process:

Code: Select all

Structure FLASHWINFO
  cbSize.l
  hwnd.i
  dwFlags.l
  uCount.l
  dwTimeout.l
EndStructure

#FLASHW_ALL=$00000003
#FLASHW_CAPTION=$00000001
#FLASHW_STOP=0
#FLASHW_TIMER=$00000004
#FLASHW_TIMERNOFG=$0000000C
#FLASHW_TRAY=$00000002

FlashWindow.FLASHWINFO

FlashWindow\cbSize=SizeOf(FLASHWINFO)
FlashWindow\dwFlags=#FLASHW_CAPTION
FlashWindow\dwTimeout=60
FlashWindow\uCount=4

OpenWindow(0,0,0,240,240,"Grue")

Procedure FlashThread(*FlashWindow.FLASHWINFO)
  Delay(1000)
  hWnd=FindWindow_(0,"Grue")
  
  *FlashWindow\hwnd=hWnd
  
  If hWnd
    Debug "Attempting to flash Grue"
    FlashWindowEx_(*FlashWindow)
  Else
    Debug "Can't find Grue"
  EndIf
EndProcedure

CreateThread(@FlashThread(),@FlashWindow)

Repeat
Until WaitWindowEvent()=#WM_CLOSE
But if you try to open window "Grue" from another process you will see that it cannot be flashed.
PB wrote:it's not an exact replacement though, because it doesn't flash the button on the Taskbar of the app in another color, which FlashWindow does.
The code for flashing the title bar works great. If you want it to flash something else then it will be a cinch to modify. I only needed the titlebar to flash.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:41 am
by cas
Mistrel wrote:FlashWindowEx doesn't work at all:

Code: Select all

Structure FLASHWINFO
  cbSize.l
  hwnd.i
  dwFlags.l
  uCount.l
  dwTimeout.l
EndStructure

#FLASHW_ALL=$00000003
#FLASHW_CAPTION=$00000001
#FLASHW_STOP=0
#FLASHW_TIMER=$00000004
#FLASHW_TIMERNOFG=$0000000C
#FLASHW_TRAY=$00000002

FlashWindow.FLASHWINFO

FlashWindow\cbSize=SizeOf(FLASHWINFO)
FlashWindow\dwFlags=#FLASHW_TIMERNOFG
FlashWindow\dwTimeout=60
FlashWindow\uCount=4

hWnd=FindWindow_(0,"Calculator")

If hWnd
  FlashWindowEx_(@FlashWindow)
EndIf

Repeat
  Delay(1)
ForEver
You forgot to store hWnd to structure. It works fine when you do it right.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:45 am
by Mistrel
Sorry about that. However, if you test it, it still does not work:

Code: Select all

Structure FLASHWINFO
  cbSize.l
  hwnd.i
  dwFlags.l
  uCount.l
  dwTimeout.l
EndStructure

#FLASHW_ALL=$00000003
#FLASHW_CAPTION=$00000001
#FLASHW_STOP=0
#FLASHW_TIMER=$00000004
#FLASHW_TIMERNOFG=$0000000C
#FLASHW_TRAY=$00000002

FlashWindow.FLASHWINFO

FlashWindow\cbSize=SizeOf(FLASHWINFO)
FlashWindow\dwFlags=#FLASHW_CAPTION
FlashWindow\dwTimeout=60
FlashWindow\uCount=4

hWnd=FindWindow_(0,"Calculator")

FlashWindow\hwnd=hWnd

If hWnd
  FlashWindowEx_(@FlashWindow)
EndIf

Repeat
  Delay(1)
ForEver

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:50 am
by cas
It looks like 60ms is not good a good value because it fades out after every blink for more than 100-200ms. This looks fine here:

Code: Select all

FlashWindow\dwFlags=#FLASHW_ALL
FlashWindow\dwTimeout=700
FlashWindow\uCount=4

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:58 am
by Mistrel
cas wrote:It looks like 60ms is not good a good value because it fades out after every blink for more than 100-200ms.
And on my system is does nothing. Also, I want 4 blinks every 60 ms, like a modal window. And as I said before, this works fine for windows of the same process but not for those of another. Hence why I wrote the substitute.

If you can illustrate 4 blinks with a delay of 60 ms using either FlashWindow or FlashWindowEx on another process like "Calculator", then you will have a counter argument. Getting the window to kinda-sorta-flash or just obtain focus doesn't count.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 11:10 am
by cas
Your code form 1st and 2nd post ignores Count parameter on Windows 7 and it flashes 7 times every time. Maybe we need Select OsVersion() : EndSelect. #FLASHW_ALL and then set #FLASHW_STOP when window is active works fine and it doesn't ignore Count parameter on Windows 7.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 11:21 am
by Mistrel
Would you post example code? The project I implemented both instances (post 1 and 2) both blink the expected number of times.

When I count blinks I count on/off as a single blink. Are you sure you're not counting each separately?

I'm running Windows XP 64-bit and compiling with PureBasic 4.51 x86. I did not test my code for x64 compatibility.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 12:02 pm
by cas
Yes, i'm sure. No matter what Count parameter is, it flashes 7 times with this code:

Code: Select all

FlashWindow(FindWindow_(0,"Calculator"),4,60)
OS is Windows 7 x64 and PB 4.51 x86.
And there is a registry key in HKEY_CURRENT_USER\ControlPanel\Desktop called ForegroundFlashCount which is set to 7 by default.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 1:04 pm
by PB
> try and flash a window four times with a 60 ms delay doesn't work

That's why I used 250. Where did you get 60 from? The MSDN docs
for FlashWindowEx says the flash rate is the default cursor blink rate,
which you can get with GetCaretBlinkTime_(). This isn't 60 by default.

> Sorry about that. However, if you test it, it still does not work

Well, it works fine here on XP with a value of 60 or 250 for the rate.

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 9:13 pm
by Mistrel
PB wrote:> Well, it works fine here on XP with a value of 60 or 250 for the rate.
Would you post your code? Are you using it on the same process or on Calculator?

Here is the behavior I'm imitating. Click on the main window and the message box will flash 4 times with a 60 ms delay (at least it does on XP):

Code: Select all

OpenWindow(0,0,0,320,240,"")

MessageRequester("","Hello!")

Repeat
Until WaitWindowEvent()=#WM_CLOSE

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 9:52 pm
by PB
> Would you post your code? Are you using it on the same process or on Calculator?

I was using your own code example on Calculator. But, read on! :)

> the message box will flash 4 times with a 60 ms delay

We are talking about 2 different window flashes here, which is the issue. :lol:
You're talking about a modal window flash, and I'm talking about the window
flash when a non-focussed window wants to get your attention by flashing
itself in the taskbar. That's what FlashWindow is intended for (from MSDN:
"a window is flashed to inform the user that the window requires attention
but that it does not currently have the keyboard focus"). That is what I
assumed you were trying to do because you said you were mimicking the
FlashWindow API. But now, from your comment above, I can see you're
trying to flash in a modal way instead. No wonder we're getting different
results! ;)

Re: Blink the titlebar of the target window

Posted: Wed Oct 06, 2010 10:03 pm
by Mistrel
Yes, that would make sense. I did mention the FlashWindowEx but I didn't mention that I used non-default parameters. :|

Basically, I wanted to launch an application with RunProgram and have the main application alert you to the other one as if it was part of the application. I did this by duplicating the behavior of a blinking message box.

The blinking may be different on Windows 7. I haven't tried it there yet. But on XP it seems to flash 4 times with a 60 ms interval. I "can" duplicate this with FlashWindowEx but "only" if the window is in the same process. Hence why I wrote a replacement.

And, yes. You are correct. It doesn't duplicate the default behavior of FlashWindow or FlashWindowEx.

The root of the problem was that I didn't provide any example code for a frame of reference. But I had no idea this would turn into such an event!