Solved

Getting Form1.Left while minimized?

Posted on 2001-09-07
18
274 Views
Last Modified: 2006-11-17
This problem is part of a bigger one within my software that I need to get resolved promptly.

To see the problem at hand put together a quick sample Form1 in a new project and then add the following code to the Unload event.

MsgBox Me.Left & " - " & Me.Top

Then run the project and close the form while it's in the default restored position, you should see the Left and Top properties reported correctly.

Now run the project again and minimize the form before closing it through right-click taskbar menu. What I get when doing this is not the correct .Left and .Top properties but instead a fixed number for both, in my case 45,000 (twips) although I tested this on Win2k and it was a different fixed number. In both cases this point is way outside the screen boundaries, and causes havoc with my window positioning routines.

I tried using GetWindowRect API to get the RECT structure for the form while it was minimised, however, this again reported a fixed value of 45,000.

The .Left and .Top properties of the form cannot be changed while it is minimized or maximized, yet these values are changing when the form enters these two states?

What can I do to get the actual .Left and .Top properties reported when the form is about to close when minimised?

One solution I thought of would be to assign my own variables for Top and Left properties and to update these just before the form gets minimised, then use these for the window position save routine. This all seems a bit much though, and it seems like quite a major bug in VB, or possibly even Windows itself.

Any ideas?
0
Comment
Question by:peterchamberlin
  • 8
  • 4
  • 3
  • +3
18 Comments
 
LVL 43

Expert Comment

by:TimCottee
Comment Utility
Peter, why not hide the window before restoring it to get the values. The user will not notice this normal "closing" behaviour and you don't need anything else special.
0
 
LVL 3

Expert Comment

by:SirNick
Comment Utility
Does it make any difference to the numbers you are getting when your screen is running at a different resolution?
0
 
LVL 39

Expert Comment

by:appari
Comment Utility
is it ok to make window state to normal before unloading?

something like

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
Me.WindowState = 0
End Sub
0
 
LVL 4

Accepted Solution

by:
Jeremy_D earned 50 total points
Comment Utility
Check out the GetWindowPlacement API. Here are the declarations you need:

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type WINDOWPLACEMENT
    Length As Long
    flags As Long
    showCmd As Long
    ptMinPosition As POINTAPI
    ptMaxPosition As POINTAPI
    rcNormalPosition As RECT
End Type

Private Declare Function GetWindowPlacement Lib "user32" _
    (ByVal hWnd As Long, _
    lpwndpl As WINDOWPLACEMENT) As Long


Use it something like this:

        Dim WinP As WINDOWPLACEMENT
        WinP.Length = Len(WinP)
        GetWindowPlacement Me.HWnd, WinP
        With WinP.rcNormalPosition
            Debug.Print .Left, .Top, .Right, .Bottom
        End With


Cheers,
Jeremy

0
 

Author Comment

by:peterchamberlin
Comment Utility
Yep, the hide form, restore position, then unload option sounds the best one to me, as I don't have need for forms to be recalled in their minimized positions. At least not yet, and in any case I can make the save windows routine do and detect the change as I'm passing the Form object reference to it in any case.

I did think about GetWindowPlacement, although I suspect it may return similar results to GetWindowRect. I'll try it to see if it does or not.

It is odd how Windows can restore the Window to a known position yet when querying the Windows structures either Windows does not know the position, or is unwilling to give the position away. Very unusual behaviour, I'm running Win98se OS myself but similar things have happened on Win2k etc.

I still get 45,000 for both .Left and .Top properties no matter what screen resolution I'm in. The other value for this was observed in a different Win OS.

Ah, the reason I didn't use GetWindowPlacement was because it gets the Top and Left properties of the window relative to the available dekstop space, i.e. that not taken up by appbars etc. If I did use such then my window positions may start to jump around the screen a bit if taskbar is reposition or appbars added/remove inbetween window showings.

Interestingly enougth the GetWindowPlacement API does in fact return decent screen co-ords for the Window when it's minimized, although I'd have to start using other API to get the available screen space and add on appbar widths and heights to correct the values to actual screen co-ordinates. Perhaps Windows does this because of the other problem?
0
 

Author Comment

by:peterchamberlin
Comment Utility
The Hide and Restore approach doesn't work for me as the WindowState property can be set back to 0 (restored) after detection of 1 (minimized) and hiding the form, but the actual WindowState property does not change until the form is reshown (according to MSDN and observation) and so the .Left and .Top values still remain inaccurate when I try to get them. The IDE of course shows the WindowState property as 1 still after setting it to 0.

So, next up, how to get the reserved area at top and left of screen as taken up by any system/user appbars...
0
 

Author Comment

by:peterchamberlin
Comment Utility
Well, SystemParamInformation with SPI_GETWORKAREA gives the RECT of the available screen real-estate and I can use system metrics such as Height = GetSystemMetrics(SM_CYSCREEN) but this unfortunately still doesn't allow me to determine the height of appbars at the top or side of the screen.

I'm pondering using SetWindowPlacement to set the window position on form load through my LoadWindowPos routine, however, for multi-resolution window placement I've written code that isolates the quadrant that the centre of the window lies in then records the distance from the nearest edges. This could be adapted I guess by using the SPI_GETWORKAREA function and Get/Set WindowPlacement APIs and indeed I suppose should be the preferred method as if setting a window with an appbar on screen then later loading without the appbar then the window should rightly be positioned differently.

Now here's the thing, all this because M$ couldn't write their .Left and .Top properties to work properly for forms when they're minimised. It's all rather pathetic.
0
 
LVL 4

Expert Comment

by:Jeremy_D
Comment Utility
> I did think about GetWindowPlacement, although I suspect it may return similar results to GetWindowRect.

No it doesn't, that's the whole point of GetWindowPlacement. It's used by Windows itself when it restores your window to it's original position. Really, trust me :)

0
 

Author Comment

by:peterchamberlin
Comment Utility
Yep, I trust you on that one as GetWindowPlacement is the only way I can see to get my thing to work :) And I'm coding it up right now. Has the nice side effect of allowing my windows to be relative to available screen area so if shuffling app bars around the place then the placement relative to the nearest edge is still maintained, supporting even windows that overlap the appbar area assuming that the Rect in GetWindowPlacement copes with negative positions fine.

If I get this working then the points are yours. Will let you know shortly...
0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 

Author Comment

by:peterchamberlin
Comment Utility
Yep, I trust you on that one as GetWindowPlacement is the only way I can see to get my thing to work :) And I'm coding it up right now. Has the nice side effect of allowing my windows to be relative to available screen area so if shuffling app bars around the place then the placement relative to the nearest edge is still maintained, supporting even windows that overlap the appbar area assuming that the Rect in GetWindowPlacement copes with negative positions fine.

If I get this working then the points are yours. Will let you know shortly...
0
 

Author Comment

by:peterchamberlin
Comment Utility
I now have a working solution using the GetWindowPlacement API method, which handles minimised windows just fine, and still works with my quadrant positioning system. However, it doesn't seem to be handling the desktop area as I would expect although I think that could be because I tried with my own task bar and I have set that as not Always On Top. Using a mock appbar project I have found that it works just fine with this, so all is well :) The points go to Jeremy D for providing the first working solution.

Annoying that none of the other methods work, but that's so often the case with Micro$oft.
0
 
LVL 15

Expert Comment

by:ameba
Comment Utility
Nice answer, Jeremy_D
0
 

Author Comment

by:peterchamberlin
Comment Utility
For those curious as to my final code :)

' Position Specified Form From Registry Data
Public Sub LoadWindowPos(TargetForm As Form)

   ' Co-Ords
   Dim XPosition As Single
   Dim YPosition As Single
   Dim Corner As Integer
   Dim WorkArea As RECT
   Dim WindowPos As WINDOWPLACEMENT
   
   ' Get Screen Work Area
   Call SystemParametersInfo(SPI_GETWORKAREA, 0, WorkArea, 0)
   ScreenX = WorkArea.Right - WorkArea.Left
   ScreenY = WorkArea.Bottom - WorkArea.Top
   
   ' Get X Displacement
   Call ReadValue("HKLM", RegPath & "\Windows", TargetForm.Name & "X", "S", ScreenX / 2, XPosition)

   ' Get Y Displacement
   Call ReadValue("HKLM", RegPath & "\Windows", TargetForm.Name & "Y", "S", ScreenY / 2, YPosition)

   ' Get Corner
   Call ReadValue("HKLM", RegPath & "\Windows", TargetForm.Name & "C", "D", 0, Corner)
   
   ' Adjust X Displacement -> X Position
   If Corner = 1 Or Corner = 2 Then
   
      ' From Right Hand Edge
      XPosition = ScreenX - XPosition
     
   End If
   
   ' Adjust Y Displacement -> Y Position
   If Corner = 2 Or Corner = 3 Then
   
      ' From Right Hand Edge
      YPosition = ScreenY - YPosition
     
   End If
   
   ' Set Window Position
   WindowPos.Length = Len(WindowPos)
   With WindowPos.rcNormalPosition
      .Left = XPosition - (TargetForm.Width / Screen.TwipsPerPixelX) / 2
      .Top = YPosition - (TargetForm.Height / Screen.TwipsPerPixelY) / 2
      .Right = .Left + TargetForm.Width / Screen.TwipsPerPixelX
      .Bottom = .Top + TargetForm.Height / Screen.TwipsPerPixelY
   End With
   Call SetWindowPlacement(TargetForm.hWnd, WindowPos)
   
End Sub

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

' Save Specified Form's X/Y Co-Ordinates
Public Sub SaveWindowPos(TargetForm As Form)

   Dim XPosition As Single
   Dim YPosition As Single
   Dim Corner As Integer
   Dim WorkArea As RECT
   Dim WindowPos As WINDOWPLACEMENT
   
   ' Get Screen Work Area
   Call SystemParametersInfo(SPI_GETWORKAREA, 0, WorkArea, 0)
   ScreenX = WorkArea.Right - WorkArea.Left
   ScreenY = WorkArea.Bottom - WorkArea.Top
   
   ' Get Window Placement
   WindowPos.Length = Len(WindowPos)
   Call GetWindowPlacement(TargetForm.hWnd, WindowPos)
   
   ' With Normal RECT
   With WindowPos.rcNormalPosition
   
      ' Get X Co-Ord of CENTRE
      XPosition = (.Left + .Right) / 2
     
      ' Get Y Co-ord of CENTRE
      YPosition = (.Top + .Bottom) / 2
     
   End With
   
   ' Determine Corner
   If XPosition < ScreenX / 2 Then
      ' Left Hand Side
      If YPosition < ScreenY / 2 Then
         ' Top Left
         Corner = 0
      Else
         ' Bottom Left
         Corner = 3
      End If
   Else
      ' Right Hand Side
      If YPosition < ScreenY / 2 Then
         ' Top Right
         Corner = 1
      Else
         ' Bottom Right
         Corner = 2
      End If
   End If
   
   ' Check If Horizontally Centered
   If Abs(XPosition - ScreenX / 2) < ScreenX / 25 Then

      ' Reset X Variable to Auto-Centre
      Call DeleteValue("HKLM", RegPath & "\Windows", TargetForm.Name & "X")
     
      ' For Correction of Corner
      XPosition = 0
     
   Else

      ' If Nearest to RH Edge
      If Corner = 1 Or Corner = 2 Then
         ' Correct X-Position to X-Displacement
         XPosition = ScreenX - XPosition
      End If
     
      ' Write X Displacement of CENTRE
      Call WriteValue("HKLM", RegPath & "\Windows", TargetForm.Name & "X", "S", XPosition)
     
   End If

   ' Check If Vertically Centered
   If Abs(YPosition - ScreenY / 2) < ScreenY / 20 Then

      ' Reset Y Variable to Auto-Centre
      Call DeleteValue("HKLM", RegPath & "\Windows", TargetForm.Name & "Y")

      ' For Correction of Corner
      YPosition = 0
     
   Else
   
      ' If Nearest to Bottom Edge
      If Corner = 2 Or Corner = 3 Then
         ' Correct Y-Position to Y-Displacement
         YPosition = ScreenY - YPosition
      End If
     
      ' Write Y Co-Ord of CENTRE
      Call WriteValue("HKLM", RegPath & "\Windows", TargetForm.Name & "Y", "S", Int(YPosition))

   End If
   
   ' Correct Corner to Default
   If XPosition = 0 And YPosition = 0 Then Corner = 0
   
   ' Write Corner
   Call WriteValue("HKLM", RegPath & "\Windows", TargetForm.Name & "C", "D", Corner)
   
End Sub


There you go :)
0
 
LVL 4

Expert Comment

by:Jeremy_D
Comment Utility
Doing the best I can, ameba ;-)

Peter, below I quote from the remarks section of the MSDN entry for the WINDOWPLACEMENT struct. It might help you towards understanding the way windows is using screen coordinates with this structure.


===========================================================

Remarks

If the window is a top-level window that does not have the WS_EX_TOOLWINDOW window style, then the coordinates represented by the following members are in workspace coordinates: ptMinPosition, ptMaxPosition, and rcNormalPosition. Otherwise, these members are in screen coordinates.

Workspace coordinates differ from screen coordinates in that they take the locations and sizes of application toolbars (including the taskbar) into account. Workspace coordinate (0,0) is the upper-left corner of the workspace area, the area of the screen not being used by application toolbars.

The coordinates used in a WINDOWPLACEMENT structure should be used only by the GetWindowPlacement and SetWindowPlacement functions. Passing workspace coordinates to functions which expect screen coordinates (such as SetWindowPos) will result in the window appearing in the wrong location. For example, if the taskbar is at the top of the screen, saving window coordinates using GetWindowPlacement and restoring them using SetWindowPos causes the window to appear to "creep" up the screen.

===========================================================
0
 
LVL 15

Expert Comment

by:ameba
Comment Utility
It's a pity you got only 75% points on this  :-(
0
 
LVL 4

Expert Comment

by:Jeremy_D
Comment Utility
Ah well.... <sigh>   (YKWIM)
0
 

Author Comment

by:peterchamberlin
Comment Utility
Rated B because GetWindowPlacement was something I had already been considering and with API Viewer 2k I would have got around to doing it in the end. Still, 100% points.
0
 
LVL 15

Expert Comment

by:ameba
Comment Utility
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Introduction In a recent article (http://www.experts-exchange.com/A_7811-A-Better-Concatenate-Function.html) for the Excel community, I showed an improved version of the Excel Concatenate() function.  While writing that article I realized that no o…
This article describes some techniques which will make your VBA or Visual Basic Classic code easier to understand and maintain, whether by you, your replacement, or another Experts-Exchange expert.
As developers, we are not limited to the functions provided by the VBA language. In addition, we can call the functions that are part of the Windows operating system. These functions are part of the Windows API (Application Programming Interface). U…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now