Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations bkrike on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Can VB6 detect when windows turns off the monitor in power saving mode 2

Status
Not open for further replies.

SkennyR

Programmer
Mar 7, 2004
157
US
I would like to be able to detect when windows goes into power saving mode, for example, turns the monitor off.
I would like to turn on (or off) a printer port output to dim a marquee lamp above the monitor when the monitor shuts down.
I already know how to read and write to the printer port, but I cant find any threads in here to tell me how to get VB6 to know when the computer has powered down automatically.
Thanks in advance.
 
It seems you can, rather than type the reams and reams I'd need to write to explain this I'll post this which should explain everything you'd need to know about power management and the associated messages.

Hope this helps

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
OK, after testing the above link properly it seems neither WM_POWERBROADCAST or WM_POWER are broadcast when the system tells the monitor to change a power setting (I thought they would. I also won't rule out I'm wrong [wink]).

However, all is not lost. After a little bit of analysis of the WM_SYSCOMMAND parameters it seems that when the power state of the monitor changes the WM_SYSCOMMAND (&H112) is broadcast with a wParam of SC_MONITORPOWER (&HF170&). The lParam when the monitor goes into power saving mode is 1 and is then 2 (or seems to be on my machine, though this would appear to be contrary to all documentation I've seen which would indicate 2 is the monitor powering off) when the power is resored.

So if you subclass your window and check those assciated value you can tell when the monitor changes state.

If you need an example of subclassing this I'd be happy to provide that.

Hope this helps

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
Thanks HarleyQuinn, but I think I may be over my head.
I searched this forum for WM_SYSCOMMAND and returned no results.
 
Ok, no worries [smile]

I didn't imagine I'd be having a detailed subclassing introduction at 10am but I'll give it a go (and I'm sure I'll get something wrong as it's so early but Hypetia and strongm are excellent at this side of VB so I'm sure they'll keep me right [wink])

Very basically, subclassing is allowing your object (in this case a form) to get at the stream of messages Windows sends it. This lets you customize your form by reacting to or ignoring events that VB doesn't normally let you control. While this sounds good (for this example it is also fairly simple thankfully) it is also a very quick way to crash VB and your IDE (if you're not careful) as it takes away alot of the stability VB provides.

So, using API calls we're going to tell windows that when our form gets a windows message that we're going to use a custom handler for that message, handle it (if it's what we're looking for do something different, if not pass it back to the original procedure for normal handling) and finally when we close our form tell windows to use the standard message handling procedure.

Here we go, first in a module (.bas) we need the API, Constant (these are available in the API Text Viewer in Visual Basic 6.0 Tools woth the VS install) and Public variable declarations:
Code:
' API Declaration

Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

' Constants declarations

Public Const GWL_WNDPROC = -4
Public Const SC_MONITORPOWER = &HF170&
Public Const WM_SYSCOMMAND = &H112

' Variable to hold ID of old Wnd Proc

Public oldWndProc As Long
OK, so now we've got those we're going to code our replacement message handling procedure (also in the .bas file or the code used later to change handling procedure won't work, specifically AddressOf). I've commented it so hopefully it should be fairly easy to understand.
Code:
' this function mimics a WindowsProc accepting the same parameters so we can custom handle windows messages

Public Function MyWndProc(ByVal Hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

' check to see if the message is a system command and it's a change to the monitor power
' as indicated by the wParam passed to the procedure

If wMsg = WM_SYSCOMMAND And wParam = SC_MONITORPOWER Then
    Select Case lParam ' check to see what value the procedure has been passed and execute the appropriate code
        Case 1
            'your code for when the monitor goes into low power mode
        Case 2
            'your code for when the monitor comes back to life
    End Select
End If

' as we don't want to ignore the message (we want it to execute properly and any other messages we're sent also),
' we use CallWindowProc passing the old procedure's handle and the params
' so that windows will process this message as normal

MyWndProc = CallWindowProc(oldWndProc, Hwnd, wMsg, wParam, lParam)

End Function
So, all we have left to do is to tell windows we want to use a custom handler for messages (when we load the form) and then tell it we're restoring the original WindowProc (when we unload the form) as so:
Code:
Private Sub Form_Load()
' tell that we're using a custom handler as specified after AddressOf operator
oldWndProc = SetWindowLong(Me.Hwnd, GWL_WNDPROC, AddressOf MyWndProc)
End Sub

Private Sub Form_Unload(Cancel As Integer)
' we're resatoring the normal message handler
Ret& = SetWindowLong(Me.Hwnd, GWL_WNDPROC, oldWndProc)
End Sub
Phew! after an extra long post (for me anyway) that should be about it. The only other thing to say it that you should always save your app before debugging/testing and always close it down properly (not using halt in the IDE) so that the proper unloading events fire and everything is put back to normal, that should help limit the crashes you more than likely will experience.

If I've not explained something fully enough please post back and if I've not exaplined something correctly please someone else post back and correct me [smile].

Hope this helps

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
Pretty impressive!
[pipe]

[navy]"We had to turn off that service to comply with the CDA Bill."[/navy]
- The Bastard Operator From Hell
 
Some time ago I tried finding the routine that I could use to make VB6 send a monitor to sleep at midnight and wake it up at sunrise.
(An advertising display in a shop window) but couldn't work it out.
Is this the right area for that?
 
MakeItSo - cheers [smile]

ted - Yeah, you can do that using some of the stuff above in conjunction with SendMessage, try (in a module):
Code:
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Any) As Long ' note SendMessage has the last paramter declared as ByVal which isn't how it is in the API text viewer and it fails if you don't use ByVal in the function declaration.

Public Const SC_MONITORPOWER = &HF170&
Public Const MONITOR_ON = -1&
Public Const MONITOR_STANDBY = 1&
Public Const MONITOR_OFF = 2&
Public Const WM_SYSCOMMAND = &H112
And in say a button use:
Code:
SendMessage hwnd, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOR_OFF ' to turn off the monitor
You can of course use the other constants to turn it on etc.

Hope this helps

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
I've noticed a slight error in my post on 6 May 09 6:02

When the monitor goes to sleep two messages are received, the first that the monitor is going into standby (lParam = 1) and the second that it's turning off (lParam = 2, so the documentation is correct [wink]).

There doesn't seem to be a message broadcast when the monitor restarts itself by using the keyboard or mouse. The message will be broadcast however if the monitor is turned on using SendMessage (and will have the lParam = -1). Thanks to ted, as if he'd not asked about turning the monitor on and off I'd not have noticed this.

Cheers

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
Wow, thats some good coding there HarleyQuinn.
But, I had some trouble getting it to do what I want.

First I modified your original code to look like this:


If wMsg = WM_SYSCOMMAND And wParam = SC_MONITORPOWER Then
Select Case lParam ' check to see what value the procedure has been passed and execute the appropriate code
Case 1
Form1.Label1.Caption = "Monitor Off"
Open "c:\message.txt" For Output As #1
Print #1, "Monitor went to sleep at " & Now
Close 1
'your code for when the monitor goes into low power mode
Case 2
Open "c:\message.txt" For Append As #1
Print #1, "Monitor awoke at " & Now
Close 1
'your code for when the monitor comes back to life
End Select
End If

As you can see, I added routines to write a text file to C:\, recording the times the monitor goes to sleep and wakes up.

I couldnt get the code to do anything.
Monitor turns off after set time, but when I check for the message.txt file, it was never written.
I then added a button to the form and placed this code:

Label1.Caption = MyWndProc(Me.Hwnd, WM_SYSCOMMAND, SC_MONITORPOWER, 1)

Now when I click the button, the monitor goes into sleep mode.
Im sure Im missing something here, but this is not what I want (but it is neat and your code will be stored away for future projects.)
I need to know when the monitor turns off, not be able to turn it off.
Thank you for your time and help on this.
Also, I see what you mean by saving the project first.
Ive had to copy your code and paste it in a new project more than once.
:)

 
Hi SkennyR,

With the caveat of my post (6 May 09 11:04) about the message not being broadcast when the monitor turns back on (and the OFF message is broadcast after the STANDBY) the code posted in your reply works exactly as intended (i.e. when the monitor powers off after a set time the two messages are sent and I receive two entries in the text file with the same time).

It's strange that yours doesn't do as expected. When you click the button to execute the proc does the text file get written? Also could you test without writing to the text file and rather writing to the immediate window (Debug.Print, somethnig like):
Code:
If wMsg = WM_SYSCOMMAND And wParam = SC_MONITORPOWER Then
    Select Case lParam ' check to see what value the procedure has been passed and execute the appropriate code
        Case 1
        Debug.Print "STANDBY"
        Case 2
        Debug.Print "OFF"
    End Select
End If
Also, if your problem still persists could you post the entire code from your module containing your custom procedure just so we can have a look at it?

One last question, how are you testing this? Just so we have the same conditions, I'm using Power Options, Turn Off Monitor After: 1 min (also if I get a bit impatient I'm testing with two SendMessage calls, one with STANDBY and the other with OFF to simulate the behaviour I've experienced with a power down).

Cheers

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
>Label1.Caption = MyWndProc(Me.Hwnd, WM_SYSCOMMAND, SC_MONITORPOWER, 1)

I'd advise against calling the WindowProc directly like this. I'd use SendMessage as per HarleyQuinn's examples,
 
Ok, Im using screensaver on at 1 min, monitor off at 2 min, and standby at 3 mins.
I put the debug code in and I still cant get it to work.
Thanks for your suggestion strongm, I have removed that command button and its code.

Here is my complete code.
After I bring the computer back from standby, I see the start and stop messages in the debug window that I added, but I dont see the messages you added.

Here is form1 code:


Private Sub Form_Load()
Debug.Print "Started"
' tell that we're using a custom handler as specified after AddressOf operator
oldWndProc = SetWindowLong(Me.Hwnd, GWL_WNDPROC, AddressOf MyWndProc)
End Sub

Private Sub Form_Unload(Cancel As Integer)
Debug.Print "Ended"
' we're resatoring the normal message handler
Ret& = SetWindowLong(Me.Hwnd, GWL_WNDPROC, oldWndProc)
End Sub

---------------------------------------
And here is module.bas code:

' API Declaration

Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal Hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal Hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

' Constants declarations

Public Const GWL_WNDPROC = -4
Public Const SC_MONITORPOWER = &HF170&
Public Const WM_SYSCOMMAND = &H112

' Variable to hold ID of old Wnd Proc

Public oldWndProc As Long

' this function mimics a WindowsProc accepting the same parameters so we can custom handle windows messages

Public Function MyWndProc(ByVal Hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

' check to see if the message is a system command and it's a change to the monitor power
' as indicated by the wParam passed to the procedure

If wMsg = WM_SYSCOMMAND And wParam = SC_MONITORPOWER Then
Select Case lParam ' check to see what value the procedure has been passed and execute the appropriate code

Case 1
Debug.Print "STANDBY"
Case 2
Debug.Print "OFF"
'your code for when the monitor comes back to life
End Select
End If

' as we don't want to ignore the message (we want it to execute properly and any other messages we're sent also),
' we use CallWindowProc passing the old procedure's handle and the params
' so that windows will process this message as normal

MyWndProc = CallWindowProc(oldWndProc, Hwnd, wMsg, wParam, lParam)

End Function

------------------------------------------------

Thanks for your help..








 
The only thing I can think of is that the screen saver is somehow interferring with it. Test it without the screen saver just to verify if it does indeed work at all on your machine.

As I have no facility to test on my dev machine here with a screen saver that's about as much as I can offer at the moment I'm afraid. You can also check to see if the screen saver has been activated... [wink]

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
Might be worth adding a

Debug.Print oldWndProc

at the end of the Form_Load event
 
Ok, I added the debug.print oldWndProc to the end of form_load

Here are the debug results with screen saver enabled:

Started
4217646
Ended

I disabled screen saver and it seems to work.

Here are the results:

Started
4217646
STANDBY
OFF
Ended

So looks like you were right about the screen saver.
In my project, I need the screen saver on, is there any way to make this work with screen saver?
It would be ok if it indicated the screen saver activated.
 
If you check for a wParam of SC_SCREENSAVE (Public Const SC_SCREENSAVE = &HF140&) rather than SC_MONITORPOWER (and ignore the lParam) that should be when the screensaver starts. Unfortunately though, as I say, I don't have the facility to run a screensaver on my dev machine so I can't test it (I also still can't test why the screensaver causes the other messages not to be broadcast in the same way).

Hope this helps

HarleyQuinn
---------------------------------
Carter, hand me my thinking grenades!

You can hang outside in the sun all day tossing a ball around, or you can sit at your computer and do something that matters. - Eric Cartman

Get the most out of Tek-Tips, read FAQ222-2244: How to get the best answers before posting.

 
>is there any way to make this work with screen saver?


I suspect not. It is beginning to look as if the DefScreenSaverProc (screensaver's equivalent of DefWindowProc) eats a number of WM_SYSCOMMAND messages, i.e. prevents them broadcasting (given a screensaver's job you can kind of see why that might be). However, without writing a screensaver and subclassing it it is difficult to be certain ...
 
HarleyQuinn, your method works with the screen saver.
And I could use this procedure for my project, except for one thing.
It does not indicate when the screen saver is deactivated (by key or mouse movement).
I tried

---------------------------
If wMsg = WM_SYSCOMMAND And wParam = SC_SCREENSAVE Then
Debug.Print "screen saver on"
End If

If wMsg = WM_SYSCOMMAND And Not wParam = SC_SCREENSAVE Then
Debug.Print "screen saver off"
End If
-------------------------------

As I said, it works when screen saver comes on, but the NOT statement I used does not work.

 
Ok, I found a work around for the screen saver being deactivated.

In module.bas

I added Dim x as integer

I then modified HarleyQuinn's code:

------------------------------------------------------------
If wMsg = WM_SYSCOMMAND And wParam = SC_SCREENSAVE Then
Debug.Print "screen saver on"
x = 1
End If

If wMsg = WM_SYSCOMMAND And x = 1 Then
Debug.Print "screen saver off"
x = 0
End If
---------------------------------------------

And debug output:

Started
4217646
screen saver on
screen saver off
Ended

This seems to be working, but Im not sure I like it. It seems like a dirty way to do.
Im thinking other events might trigger the screen saver off debug item.

Thoughts?
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top