Classic VB Corner

Honoring Startup Requests

Windows provides numerous ways to tell an application how to size and position itself on startup. Here's how you can honor those requests.

You've seen that dropdown in Shortcut property dialogs that offers to tell the target application whether to start as a Normal Window, Minimized or Maximized, right? Have you also noticed that your Classic VB apps don't pay any attention whatsoever to how that property is set? If you'd like to find out how to do just that, and more, read on.

At first, it may seem like a bug of sorts, that those properties aren't automatically honored. But if they were, this column would probably be on how to override the settings, rather than follow them. There are always good reasons to want to perform in a given way, but in the absence of such reasons it's certainly incumbent on you to run exactly as the user desires. This means you'll want to ask the OS how to optimally start up.

Windows offers the GetStartupInfo API function for just that purpose, as it returns a STARTUPINFO structure filled with instructions on how to present your application:

Private Type StartupInfo
   cb As Long
   lpReserved As Long
   lpDesktop As Long
   lpTitle As Long
   dwX As Long		
   dwY As Long
   dwXSize As Long
   dwYSize As Long
   dwXCountChars As Long
   dwYCountChars As Long
   dwFillAttribute As Long
   dwFlags As Long
   wShowWindow As Integer
   cbReserved2 As Integer
   lpReserved2 As Long
   hStdInput As Long
   hStdOutput As Long
   hStdError As Long
End Type

Call GetStartupInfo by first declaring a STARTUPINFO variable and setting its cb element equal to its length:

Private m_si As StartupInfo

Private Sub Class_Initialize()
   ' This never changes, so just grab it once.
   m_si.cb = Len(m_si)
   Call GetStartupInfo(m_si)
End Sub

The fields returned in STARTUPINFO fall into three rough categories -- most often applying either just to GUI apps or just to console apps. A couple fields (lpDesktop, lpTitle) are more general in nature. And one field, dwFlags, tells you which of the rest you need to pay attention to.

For example, if dwFlags includes STARTF_USEPOSITION, you will want to position the Left/Top of your main window at dwX/dwY. And if dwFlags includes STARTF_USESIZE, then your main window should be dwXSize in Width and dwYSize in Height. Also, you should use the wShowWindow element to set your initial WindowState, if dwFlags includes STARTF_USESHOWWINDOW.

I've wrapped all these fields up in a drop-in ready class module that you can add to your projects. While this is a very straight-forward API function to call and it's equally easy to interpret, there are various checks and tweaks you would want to routinely apply to use its results in Classic VB. Wrapped up in my CStartupInfo class module, it's as easy to use as this:

Public Sub Main()
   Dim si As CStartupInfo
   Dim frm As FStartupDemo
   
   Set si = New CStartupInfo
   Set frm = New FStartupDemo
   
   ' Check for requested size/position.
   If (si.Left <> 0) And (si.Top <> 0) Then
      frm.Move si.Left, si.Top
   End If
   If (si.Width <> 0) Then
      frm.Width = si.Width
   End If
   If (si.Height <> 0) Then
      frm.Height = si.Height
   End If
   
   ' Set to requested WindowState and show.
   frm.WindowState = si.WindowState
   frm.Show
End Sub

As I said, there are reasons to put these structure elements behind class properties. For example, the position elements are returned in pixels, and you'll most likely want them in twips for direct assignment to your Form properties. And they shouldn't be used at all if the proper flags aren't set, in which case CStartupInfo returns 0 for the requested property. Here's how I handled the Left and Width properties, allowing the option to avoid the conversion to twips on request:

Public Property Get Left(Optional Pixels As Boolean) As Long
   ' If dwFlags specifies STARTF_USEPOSITION:
   ' The x-offset of the upper left corner, in pixels.
   If m_si.dwFlags And STARTF_USEPOSITION Then
      If Pixels Then
         Left = m_si.dwX
      Else
         Left = m_si.dwX * Screen.TwipsPerPixelX
      End If
   End If
End Property

Public Property Get Width(Optional Pixels As Boolean) As Long
   ' If dwFlags specifies STARTF_USESIZE:
   ' The width of a new window, in pixels.
   If m_si.dwFlags And STARTF_USESIZE Then
      If Pixels Then
         Width = m_si.dwXSize
      Else
         Width = m_si.dwXSize * Screen.TwipsPerPixelX
      End If
   End If
End Property

The WindowState property offered by CStartupInfo requires a bit more interpretation, reducing the numerous potential wShowWindow values down to just three. This same return element is also used to interpret the Visible property:

Public Property Get WindowState() As FormWindowStateConstants
   ' If dwFlags specifies STARTF_USESHOWWINDOW:
   ' Can be any of the values that can be specified in the nCmdShow
   ' parameter for the ShowWindow function, except for SW_SHOWDEFAULT.
   If m_si.dwFlags And STARTF_USESHOWWINDOW Then
      Select Case m_si.wShowWindow
         Case SW_SHOWMINIMIZED, SW_MINIMIZE, _
              SW_SHOWMINNOACTIVE, SW_FORCEMINIMIZE
            WindowState = vbMinimized
         Case SW_SHOWMAXIMIZED, SW_MAXIMIZE
            WindowState = vbMaximized
         Case Else
            WindowState = vbNormal
      End Select
   End If
End Property

Public Property Get Visible() As Boolean
   ' If dwFlags specifies STARTF_USESHOWWINDOW:
   ' Synthesized based on SW_* flags
   If m_si.dwFlags And STARTF_USESHOWWINDOW Then
      Visible = Not (m_si.wShowWindow = SW_HIDE)
   End If
End Property

You may be wondering where all these properties could possibly be set. After all, the Shortcut property dialog only offers a WindowState analog. Well, the place I've run into them most often is with programmatic application spawning, either using CreateProcess or VB's own Shell function.

Shell offers more than the three simple WindowState values we're most accustomed to, also allowing us to do other interesting things like spawn a new app invisibly. This can be very useful when firing up background processes that shouldn't distract the user at all. CreateProcess takes this a step further in allowing us to not only specify a ShowWindow value, but to also exactly position the new app. I've used that capability in the past to tile multiple spawned instances across a display.

As always, you can download the complete StartupInfo sample on my website. In this column, I've focused mostly on the most common properties -- those that relate to GUI apps. But GetStartupInfo also supplies a number of hints to console apps that some of you will find useful too

.

About the Author

Karl E. Peterson wrote Q&A, Programming Techniques, and various other columns for VBPJ and VSM from 1995 onward, until Classic VB columns were dropped entirely in favor of other languages. Similarly, Karl was a Microsoft BASIC MVP from 1994 through 2005, until such community contributions were no longer deemed valuable. He is the author of VisualStudioMagazine.com's new Classic VB Corner column. You can contact him through his Web site if you'd like to suggest future topics for this column.

comments powered by Disqus

Featured

Subscribe on YouTube