Source Code
3 Code Libraries Source Code  
 

Centre a Common Dialog - The Easy and the Hard Way

Download the Easy Common Dialog Centre project files (10kb)
Download the "Hard" Common Dialog Centre project files (54kb)

  This sample requires the CommonDialog/Direct DLL component. Make sure you have loaded and registered this before trying the project.

Updated 27 September 1998
The previous release of CommonDialog/Direct did not fully support hooks. The new version has much improved hook support and the sample has been updated. Also, there was a foolish bug in the code which caused the Dialog box to disappear when you tried to centre to the screen. This has now been fixed, and the code also uses a neater method using SystemParametersInfo to take account of the taskbar(s)

Introduction
By default, a Common Dialog appears aligned to the top left position of the form which owns the common dialog. Often, you want to centre the common dialog to a particular form, or control on the form, or to the screen. VB offers you no way to do it directly, but there are ways of doing it. In fact there are two ways - the easy, VB type way, and the hard, API subclassing way. This article will show you how to do both.

The Easy Way
Since a common dialog is aligned to the top/left of a form, the easy way is this:

  • Add a new form to your project, and put a Common Dialog on it.
  • Rather than show the Common Dialog from a particular form, instead use the one on the new form
  • Before showing your Common Dialog, load the new form and move it so the top/left of the new form appear where the top,left of the Common Dialog should appear to centre it to the object you want to centre to.
It turns out it doesn't matter whether the form containing the Common Dialog is visible or not, so you can keep the new form hidden the whole time.

Simple! Here is sample code to put into the form containing the Common Dialog to help automate this process:

Option Explicit
Private m_oThis As Object
Public Enum ECentreCDlgTypes
    eCDLFile = 0
    eCDLPrint = 1
    eCDLFont = 2
    eCDLColor = 3
End Enum
Private m_lCLO As Long
Private m_lCTO As Long
Private m_lCW As Long
Private m_lCH As Long

Public Property Let Owner(ByRef oThis As Object)
    Set m_oThis = oThis
End Property
Public Property Let DialogType(ByRef eType As ECentreCDlgTypes)
    Select Case eType
    Case eCDLFile
        m_lCLO = 0
        m_lCTO = 0
        m_lCW = 427 * Screen.TwipsPerPixelX
        m_lCH = 287 * Screen.TwipsPerPixelY
    Case eCDLPrint
        m_lCLO = 48 * Screen.TwipsPerPixelX
        m_lCTO = 48 * Screen.TwipsPerPixelY
        m_lCW = 439 * Screen.TwipsPerPixelX
        m_lCH = 328 * Screen.TwipsPerPixelY
    Case eCDLFont
        m_lCLO = 20 * Screen.TwipsPerPixelX
        m_lCTO = 88 * Screen.TwipsPerPixelY
        m_lCW = 402 * Screen.TwipsPerPixelX
        m_lCH = 345 * Screen.TwipsPerPixelY
    Case eCDLColor
        m_lCLO = 0
        m_lCTO = 0
        m_lCW = 455 * Screen.TwipsPerPixelX
        m_lCH = 326 * Screen.TwipsPerPixelY
   
    End Select
End Property
Public Property Get cdlg() As CommonDialog
Dim lL As Long
Dim lT As Long
Dim lW As Long
Dim lH As Long
    On Error Resume Next
    lL = m_oThis.Left
    lT = m_oThis.Top
    lW = m_oThis.Width
    lH = m_oThis.Height
    Me.Move lL - m_lCLO + (lW - m_lCW) \ 2, lT - m_lCTO + (lH - m_lCH) \ 2, 1, 1
    On Error GoTo 0
    Set cdlg = cdlMain
End Property

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Set m_oThis = Nothing
End Sub

Here is the code you can then use to produce a centred File Open dialog (assuming the form with the Common Dialog control on is called fCdlg):

    fCdlg.Owner = Me
    fCdlg.DialogType = eCDLFile
    With fCdlg.cdlg
        .DialogTitle = "Choose a File to Open"
        .Filter = "Text Files (*.TXT)|*.TXT|All Files (*.*)|*.*"
        .FilterIndex = 1
        .Flags = cdlOFNFileMustExist Or cdlOFNPathMustExist
        .ShowOpen
    End With

And by the way, remember to include Unload fCDlg when your application ends, otherwise the invisible form will stop your project from terminating.

Whilst this works just fine, the problem with it is we must hardcode the size and offsets from the left and top for each of the Common Dialogs. Can you be sure the dialog is the same size on all systems? What if the user has larger fonts set up in the title bar? Even the above code is not complete. For example, on a color dialog, you can set a flag saying whether the whole dialog appears including colour customisation, or whether just the standard colours appear. Clearly, the dialog has a different size depending on the setting of this flag, but I haven't coded it in.

What we really want to have is an event which occurs when the dialog opens, saying what size its going to be and requesting a Left and Top position. This is possible, but to do it you need to delve into Common Dialog hooks and subclassing. And to do this, you need to create a Common Dialog box using the API rather than using the VB one.

The Hard Way
The real method to achieve a centred Common Dialog is to utilise a Hook function. When Common Dialogs are created via the COMDLG32.DLL API, each dialog provides a method to provide the address of a function in your code. The Common Dialog can then call this function within your code to notify your application of any actions you may wish to respond to.

Taking a sample of the File Open Common Dialog, here is how it works. To create a File Open box through the API, you fill in an OPENFILENAME structure and then call the GetOpenFileName API function exposed by COMDLG32.DLL with the structure as a parameter. One of the fields of the OPENFILENAME structure is lpfnHook. This takes a long pointer to a function, which must be constructed in accordance with the function COMDLG32 expects to call. In VB, you can pass a pointer to a function by using AddressOf, provided the function itself lives within a module (- most irritatingly!).

So, to take advantage of a hook, you do the following:

Set Up a Hook Function
The code for the hook function in VB is as follows:

    Public Function DialogHookFunction( _
        ByVal hDlg As Long, _
        ByVal msg As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long) As Long

        ' Do any processing here

    End Function

Tell the Common Dialog the address of the Hook Function
Assuming you have an OPENFILENAME structure called opfile, you want to set the opfile.lpfnHook property. Essentially what you want to say is:

    opfile.lpfnHook = AddressOf DialogHookFunction

However, VB seems to think this is a syntax error. Instead you have to write a function like this:

    Private Function lHookAddress(lPtr As Long) As Long
        lHookAddress = lPtr
    End Function

    opfile.lpfnHook = lHookAddress(AddressOf DialogHookFunction)

You also need to tell the dialog to use the lpfnHook value. This is done by setting a flag to include the OFN_ENABLEHOOK function:

    opfile.flags = opfile.flags Or OFN_ENABLEHOOK Or OFN_EXPLORER

Respond to Dialog Initialisation Messages
With this done, you will find that when the file open dialog is opened, your DialogHookFunction is called with the msg parameter set to WM_INITDIALOG. To centre the file open dialog then, all you need to do is find out the size of the dialog box and then move it to the appropriate point. This is done with a few API functions (GetParent, GetWindowRect, SystemParametersInfo and MoveWindow) :

Public Sub CentreDialog(ByVal hDlg As Long, ByRef oCentreTo As Object)
Dim lHwnd As Long
Dim tWR As RECT, tDR As RECT
Dim tp As POINTAPI
Dim lHwndCentreTo As Long
Dim lL As Long
Dim lT As Long
Dim lR As Long

   
' If we're showing a file dialog, then the rectangle is the
    ' parent of the dialog itself:
    If (m_bFileDialog) Then
        lHwnd = GetParent(hDlg)
    Else
        lHwnd = hDlg
    End If
    GetWindowRect lHwnd, tDR
    Debug.Print tDR.Right - tDR.Left, tDR.Bottom - tDR.Top
   
    On Error Resume Next
    lHwndCentreTo = oCentreTo.hwnd
    If (Err.Number = 0) Then
        GetWindowRect lHwndCentreTo, tWR
    Else
   
' Assume the screen object:
    lR = SystemParametersInfo(SPI_GETWORKAREA, 0, tWR, 0)
    If (lR = 0) Then
       
' Call failed - just use standard screen:
        tWR.Left = 0
        tWR.Top = 0
        tWR.Right = Screen.Width \ Screen.TwipsPerPixelX
        tWR.Bottom = Screen.Height \ Screen.TwipsPerPixelY
    End If
    End If
    On Error GoTo 0
    If (tWR.Right > 0) And (tWR.Bottom > 0) Then
        lL = tWR.Left + (((tWR.Right - tWR.Left) - (tDR.Right - tDR.Left)) \ 2)
        lT = tWR.Top + (((tWR.Bottom - tWR.Top) - (tDR.Bottom - tDR.Top)) \ 2)
        Debug.Print tDR.Right - tDR.Left, tDR.Bottom - tDR.Top
        MoveWindow lHwnd, lL, lT, (tDR.Right - tDR.Left), (tDR.Bottom - tDR.Top), 1
    End If
End Sub



See It Working
The sample code in the download is small because it utilises my Common Dialog/Direct class library. This provides full support for hooks via a simple interface: simply set the Hooked property to True, and then the class will raise an InitDialog event. You can centre the common dialog by calling the CentreDialog method, which uses the code shown above.

If you want to see the whole implementation, download the Common Dialog/Direct source code and demo application project.



TopBack to top
Source Code - What We're About!Back to Code Libraries
Source Code - What We're About!Back to Source Code Overview

 
 

About  Contribute  Send Feedback  Privacy

Copyright © 1998-1999, Steve McMahon ( steve@vbaccelerator.com). All Rights Reserved.
Last updated: 27 September 1998