|
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.
Back to top
Back to Code Libraries
Back to Source Code Overview
|
  |