Fog Creek Software
Discussion Board




Trouble with using SetWindowPos

One of my calls to SetWindowPos, although does not set GetLastError, also does not keep my form on top of the all other forms as I want it to do. This program I made reads scheduled events from a table in an MS Access database and raises alerts accordingly. In order to achieve the functionality I made the following classes:

RegistryEditor - provides an interface to the registry
PMData - an interface to the database
Schedule - represents a schedule read as a record from the specific table in the MS Access database
Schedule Reader - responsible for reading the schedules from the database, filtering the schedules for which alerts have to be raised and attaching them to a queue
Schedules - a queue of schedules to be raised. This is needed since there could be more than one alert to be raised at the same time, in wich case, if the first alert is being shown in the alert window occupying the screen real-estate, the second alert waits till the first alert is through.
Alert controller - the component responsible for creating the alert window and displaying the alert window.

First the schedule reader reads a schedule, filters the schedules for which the time has come to raise alerts and attaches them to the end of a queue. The Alert Controller keeps querying the queue and picks up the schedules from the queue on a FIFO basis and creates alerts for them.

The form which pops up on an alert is <b>frmAlert</b>. This slides up from near the systray region, like an MSN alert, and then slides back after a few seconds delay of standing in the right bottom corner of the screen. When it is done, it sees whether the alert can be disposed off or needs to come again. If the alert needs to be raised again (because the user chose from a combo on the form an option like Remind me again after XXX minutes), the schedule is added to the end of the queue.

<b>Main Function</b>
<PRE>
Public Sub Main()
   
    If App.PrevInstance Then Exit Sub
    Call PutInStartUp
    Set PMData = New clsPMData
    With PMData
        .ActiveDB = App.Path & "\PrintMate.mdb"
        .DatabaseType = pmdMSAccess
    End With
    IsAlertOnScreen = False
   
Set Reader = New ScheduleReader
Set Queue = New Schedules
Set Controller = New AlertController

ReadPrintMatePathFromRegistry
Load frmHidden

End Sub
</PRE>


<b>frmHidden</b> - This is only a means to put the application icon in the systray and this is not the form that raises alerts. This one remains hidden.

<PRE>

Private Sub Form_Load()
   
    SetHook Me
    AddIconToTray hWnd, Icon, Icon.handle, "AlertManager"
   
    If Reader Is Nothing Then Set Reader = New ScheduleReader
    Call Reader.ReadSchedules
    Call Controller.RaiseAlert
   
    tmrReadSchedules.Enabled = True
End Sub
</PRE>


<b>Schedule Reader Object</b>

<PRE>
Public Function ReadSchedules() As Schedules

Dim rs As Recordset
Dim LngRemindBeforeMinutes As Long
Dim DtOneMinuteBeforeCurrentTime As Date
Dim DtOneMinuteAfterCurrentTime As Date
Dim Arr() As String
Dim dtScheduleDateTime As Date

 
    'Get Schedule from database
    Set rs = PMData.GetData("select * from [Scheduler] where " & _
    "[Sched_Date] >= #" & DateAdd("d", -1, Date) & "# and [Sched_Date] <= #" & DateAdd("d", 1, Date) & "#")
   
    If rs.RecordCount = 0 Then Exit Function
    Do
        If IsNull(rs("Sched_Remind_Before")) Then
            LngRemindBeforeMinutes = 0
        Else
            Arr = Split(rs("Sched_Remind_Before"), ":")
            If UBound(Arr) > 0 Then
                LngRemindBeforeMinutes = (Arr(0) * 60) + Arr(1)
            Else
                LngRemindBeforeMinutes = Arr(0)
            End If
            Erase Arr
        End If
         
        DtOneMinuteBeforeCurrentTime = DateAdd("n", -1, Now)
        DtOneMinuteAfterCurrentTime = DateAdd("n", 1, Now)
       
        If Not IsNull(rs("Sched_Date")) And Not IsNull(rs("Sched_Time")) Then
            dtScheduleDateTime = CDate(rs("Sched_Date") & Space(1) & rs("Sched_Time"))
        End If
       
        If DateAdd("n", -LngRemindBeforeMinutes, dtScheduleDateTime) >= DtOneMinuteBeforeCurrentTime _
        And DateAdd("n", -LngRemindBeforeMinutes, dtScheduleDateTime) <= DtOneMinuteAfterCurrentTime Then
            'Type cast the recordset into a schedule
            Set CurrentSchedule = CSchedule(rs)
       
            'Push into queue
            If Not Queue.ScheduleExists(CurrentSchedule.ScheduleID) Then Call Queue.Push(CurrentSchedule)
        End If
       
        dtScheduleDateTime = CDate(Empty)
       
        rs.MoveNext
    Loop Until rs.EOF

    If rs.State <> adStateClosed Then rs.Close
    If Not rs Is Nothing Then Set rs = Nothing
   
End Function

</PRE>


<b>Alert Controller Object</b>
<PRE>
Public Sub RaiseAlert()

    Do
        DoEvents
    Loop While IsAlertOnScreen
   
    'Checks the queue for pending alerts
    If Not AlertsPendingInQueue Then Exit Sub
   
   
    'Picks up a schedule on FIFO basis
    Set CurrentSchedule = PickScheduleOnFIFO
   
    'Reads the schedule and creates an alert and writes necessary display information on the alert window, displays the alert
    If CurrentSchedule.ScheduleRemindType = None Then
        Set CurrentSchedule = Nothing: Exit Sub
    ElseIf CurrentSchedule.ScheduleRemindType = Beep Then
        Call SoundBeep
    ElseIf CurrentSchedule.ScheduleRemindType = PopUpWindow Then
        Call CreateAlertWindow
   
    ElseIf CurrentSchedule.ScheduleRemindType = PopUpWindowAndBeep Then
        Call SoundBeep
        Call CreateAlertWindow
    End If
   
    'Gets notified when the alert is done with.
    Do
        DoEvents
    Loop While IsLoaded("frmAlert")
   
    'Depending on the RemindAgain or whatever property to be decided,
    'either abandones the schedule by throwing it in the garbage or adds it to the queue again.
    If CanThrowAlert Then Set CurrentSchedule = Nothing: Exit Sub
    Call UpdateSchedule(CurrentSchedule.ScheduleID, CurrentSchedule.ScheduleRemindBefore, gLngRemindMeAgainAfterMinutes)
    If Not CurrentSchedule Is Nothing Then Set CurrentSchedule = Nothing
   
End Sub


Private Sub CreateAlertWindow()

    Set AlertWindow = New frmAlert
    With AlertWindow
        .Show
    End With
    Set AlertWindow = Nothing
   
End Sub
</PRE>



<b>frmAlert</b> - The alert window
<PRE>
Private Sub Form_Load()
    Left = Screen.Width - Width
    Top = Screen.Height
    AlwaysOnTop True
    Call DeckUpGUI
    IsAlertOnScreen = True
    BoolMoveUp = True

    'StepY must be a factor of (form's height+ Task bar's height)
    LngTaskBarHeight = GetTaskBarHeight
    StepY = (Height + LngTaskBarHeight) / 20
    MarginY = (Height + LngTaskBarHeight) Mod 20
   
    tmr.Enabled = True
   
End Sub

Private Sub tmr_Timer()

Dim LngTime As Long
   
    DoEvents
    If (Top >= (Screen.Height - Height - LngTaskBarHeight) - StepY / 2) And _
    (Top <= (Screen.Height - Height - LngTaskBarHeight) + StepY / 2) Then
        LngTime = Timer
        Do
            AlwaysOnTop
            DoEvents
        Loop Until Timer > LngTime + 5
        BoolMoveUp = False
        DoEvents
    End If
   
    If BoolMoveUp Then
        If Top >= Screen.Height - Height - LngTaskBarHeight Then
            Move Left, Top - StepY
            Refresh
            AlwaysOnTop
            BoolMoveUp = True
            DoEvents
            Exit Sub
        End If
    Else
        If Top <= Screen.Height Then
            Move Left, Top + StepY
            Refresh
            AlwaysOnTop
            BoolMoveUp = False
            DoEvents
            Exit Sub
        End If
    End If
   
    If Top > Screen.Height Then
        DoEvents
        tmr.Enabled = False
        Unload Me
    End If
   
End Sub
</PRE>


<b>modGlobal</b>
<PRE>
Private Sub AlwaysOnTop(Optional ByVal flag As Boolean = True)
    Call SetWindowPos(hWnd, IIf(flag, HWND_TOPMOST, HWND_NOTOPMOST), 0, _
    0, 0, 0, SWP_NOSIZE Or SWP_NOMOVE)
End Sub
</PRE>

Any ideas as to why the SetWindowPos does not work? I've used it many number of times but this time it does not work strangely. Is it something to do with the function being called in a timer?

sathyaish Chakravarthy
Wednesday, September 17, 2003

Most of that code is totally irrelevant to your question. However, off the top of my head:
1. Are you sure the timer is firing?
2. Is hWnd set correctly?
3. Is flag set correctly?

Troy King
Wednesday, September 17, 2003

Yes, the timer fires every 150 milliseconds and I step in to see that the hWnd is set correctly and also that the flag is set correctly because I have a default value there. I provided the code snippets for other modules because I thought I might be making a mistake elsewhere, like (just guessing, have tried them but failed) while showing the form, may be I need to show it modally (because SetWindowPos if I remember correctly that I read on Common Controls Replacement Project notes some months ago destroys the window and recreates it everytime) because the call is within a timer, or could be something to do with the new instance of frmAlert that I create in the object variable AlertWindow causes a problem. Just guessing. Any ideas where I might be wrong?

sathyaish Chakravarthy
Wednesday, September 17, 2003

*  Recent Topics

*  Fog Creek Home