Font Dialog (64 Bit compatible) or Get List of Fonts

MajP

You've got your good things, and you've got mine.
Local time
Today, 04:41
Joined
May 21, 2018
Messages
8,948
I had code that brought up the font dialog, but have been unsuccessful for getting it to work with 64 bit. Anyone have a working version? If not anyone have code to get the list of installed fonts? I have seen some excel versions, but again unable to get it to work with Access. Thanks.
 
Does it mean you're using an API? If so, can you post it? Thanks.
 
Here is the original 32 bit code from Lebans.

I already converted all the Declares to Declare PTRSAFE and then changed the longs returned from the API to LongPTR. It compiles and runs, but no windows open. This returns a FormFontInfo type.

Code:
Option Compare Database
Option Explicit

' Original Code by Terry Kreft
' Modified by Stephen Lebans
' Contact Stephen@lebans.com


'************  Code Start  ***********
Private Const GMEM_MOVEABLE = &H2
Private Const GMEM_ZEROINIT = &H40
Private Const GHND = (GMEM_MOVEABLE Or GMEM_ZEROINIT)

Private Const LF_FACESIZE = 32

Private Const FW_BOLD = 700

Private Const CF_APPLY = &H200&
Private Const CF_ANSIONLY = &H400&
Private Const CF_TTONLY = &H40000
Private Const CF_EFFECTS = &H100&
Private Const CF_ENABLETEMPLATE = &H10&
Private Const CF_ENABLETEMPLATEHANDLE = &H20&
Private Const CF_FIXEDPITCHONLY = &H4000&
Private Const CF_FORCEFONTEXIST = &H10000
Private Const CF_INITTOLOGFONTSTRUCT = &H40&
Private Const CF_LIMITSIZE = &H2000&
Private Const CF_NOFACESEL = &H80000
Private Const CF_NOSCRIPTSEL = &H800000
Private Const CF_NOSTYLESEL = &H100000
Private Const CF_NOSIZESEL = &H200000
Private Const CF_NOSIMULATIONS = &H1000&
Private Const CF_NOVECTORFONTS = &H800&
Private Const CF_NOVERTFONTS = &H1000000
Private Const CF_OEMTEXT = 7
Private Const CF_PRINTERFONTS = &H2
Private Const CF_SCALABLEONLY = &H20000
Private Const CF_SCREENFONTS = &H1
Private Const CF_SCRIPTSONLY = CF_ANSIONLY
Private Const CF_SELECTSCRIPT = &H400000
Private Const CF_SHOWHELP = &H4&
Private Const CF_USESTYLE = &H80&
Private Const CF_WYSIWYG = &H8000
Private Const CF_BOTH = (CF_SCREENFONTS Or CF_PRINTERFONTS)
Private Const CF_NOOEMFONTS = CF_NOVECTORFONTS

Public Const LOGPIXELSY = 90

Public Type FormFontInfo
  Name As String
  Weight As Integer
  Height As Integer
  UnderLine As Boolean
  Italic As Boolean
  Color As Long
End Type

Private Type LOGFONT
  lfHeight As Long
  lfWidth As Long
  lfEscapement As Long
  lfOrientation As Long
  lfWeight As Long
  lfItalic As Byte
  lfUnderline As Byte
  lfStrikeOut As Byte
  lfCharSet As Byte
  lfOutPrecision As Byte
  lfClipPrecision As Byte
  lfQuality As Byte
  lfPitchAndFamily As Byte
  lfFaceName(LF_FACESIZE) As Byte
End Type

Private Type FONTSTRUC
  lStructSize As Long
  hwnd As Long
  hdc As Long
  lpLogFont As Long
  iPointSize As Long
  Flags As Long
  rgbColors As Long
  lCustData As Long
  lpfnHook As Long
  lpTemplateName As String
  hInstance As Long
  lpszStyle As String
  nFontType As Integer
  MISSING_ALIGNMENT As Integer
  nSizeMin As Long
  nSizeMax As Long
End Type

Private Declare Function ChooseFont Lib "comdlg32.dll" Alias "ChooseFontA" _
(pChoosefont As FONTSTRUC) As Long
Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalAlloc Lib "kernel32" _
  (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
Private Declare Function GetDeviceCaps Lib "gdi32" _
  (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long


Private Function MulDiv(In1 As Long, In2 As Long, In3 As Long) As Long
Dim lngTemp As Long
  On Error GoTo MulDiv_err
  If In3 <> 0 Then
    lngTemp = In1 * In2
    lngTemp = lngTemp / In3
  Else
    lngTemp = -1
  End If
MulDiv_end:
  MulDiv = lngTemp
  Exit Function
MulDiv_err:
  lngTemp = -1
  Resume MulDiv_err
End Function
Private Function ByteToString(aBytes() As Byte) As String
  Dim dwBytePoint As Long, dwByteVal As Long, szOut As String
  dwBytePoint = LBound(aBytes)
  While dwBytePoint <= UBound(aBytes)
    dwByteVal = aBytes(dwBytePoint)
    If dwByteVal = 0 Then
      ByteToString = szOut
      Exit Function
    Else
      szOut = szOut & Chr$(dwByteVal)
    End If
    dwBytePoint = dwBytePoint + 1
  Wend
  ByteToString = szOut
End Function

Private Sub StringToByte(InString As String, ByteArray() As Byte)
Dim intLbound As Integer
  Dim intUbound As Integer
  Dim intLen As Integer
  Dim intX As Integer
  intLbound = LBound(ByteArray)
  intUbound = UBound(ByteArray)
  intLen = Len(InString)
  If intLen > intUbound - intLbound Then intLen = intUbound - intLbound
For intX = 1 To intLen
ByteArray(intX - 1 + intLbound) = Asc(Mid(InString, intX, 1))
Next
End Sub


Public Function DialogFont(ByRef f As FormFontInfo) As Boolean
  Dim LF As LOGFONT, FS As FONTSTRUC
  Dim lLogFontAddress As Long, lMemHandle As Long

  LF.lfWeight = f.Weight
  LF.lfItalic = f.Italic * -1
  LF.lfUnderline = f.UnderLine * -1
  LF.lfHeight = -MulDiv(CLng(f.Height), GetDeviceCaps(GetDC(hWndAccessApp), LOGPIXELSY), 72)
  Call StringToByte(f.Name, LF.lfFaceName())
  FS.rgbColors = f.Color
  FS.lStructSize = Len(FS)

    ' To be modal must be valid Hwnd
    FS.hwnd = Application.hWndAccessApp
    
  lMemHandle = GlobalAlloc(GHND, Len(LF))
  If lMemHandle = 0 Then
    DialogFont = False
    Exit Function
  End If

  lLogFontAddress = GlobalLock(lMemHandle)
  If lLogFontAddress = 0 Then
    DialogFont = False
    Exit Function
  End If

  CopyMemory ByVal lLogFontAddress, LF, Len(LF)
  FS.lpLogFont = lLogFontAddress
  FS.Flags = CF_SCREENFONTS Or CF_EFFECTS Or CF_INITTOLOGFONTSTRUCT
  If ChooseFont(FS) = 1 Then
    CopyMemory LF, ByVal lLogFontAddress, Len(LF)
    f.Weight = LF.lfWeight
    f.Italic = CBool(LF.lfItalic)
    f.UnderLine = CBool(LF.lfUnderline)
    f.Name = ByteToString(LF.lfFaceName())
    f.Height = CLng(FS.iPointSize / 10)
    f.Color = FS.rgbColors
    
    DialogFont = True
  Else
    DialogFont = False
  End If
End Function

Function test_DialogFont(ctl As Control) As Boolean
    Dim f As FormFontInfo
    With f
      .Color = 0
      .Height = 12
      .Weight = 700
      .Italic = False
      .UnderLine = False
      .Name = "Arial"
    End With
    Call DialogFont(f)
    With f
        Debug.Print "Font Name: "; .Name
        Debug.Print "Font Size: "; .Height
        Debug.Print "Font Weight: "; .Weight
        Debug.Print "Font Italics: "; .Italic
        Debug.Print "Font Underline: "; .UnderLine
        Debug.Print "Font COlor: "; .Color
        
        ctl.FontName = .Name
        ctl.FontSize = .Height
        ctl.FontWeight = .Weight
        ctl.FontItalic = .Italic
        ctl.FontUnderline = .UnderLine
        ctl = .Name & " - Size:" & .Height
    End With
    test_DialogFont = True
End Function
'************  Code End  ***********
I have googled this and have not found a thread with any success. Some suggestions here that it does not exist for 64bit.
https://social.msdn.microsoft.com/F...ont-dialog-in-vba-access-2016?forum=accessdev

Only solution I have found is to purchase Total Visual sourcebook
http://www.fmsinc.com/microsoftaccess/modules/code/Windows/CommonDialogs/FontDialog_class.htm
But I am not that interested in this.

Here is the Excel code to pull the list of fonts (which would suffice) from the commandbar
Code:
Sub ShowInstalledFonts()
    Dim fontlist As Object
    Dim tempBar As CommandBar
    Dim i As Integer
    
    Set fontlist = Application.CommandBars("Formatting").FindControl(Id:=1728)
    
'   If Font control is missing, create a temp CommandBar
    If fontlist Is Nothing Then
        Set tempBar = Application.CommandBars.Add
        Set fontlist = tempBar.Controls.Add(Id:=1728)
    End If
    
'   Put the fonts into column A
    For i = 0 To fontlist.ListCount - 1
        Me.txtFontName.AddItem fontlist.List(i + 1)
    Next i
    
'   Delete temp CommandBar if it exists
    On Error Resume Next
    tempBar.Delete
End Sub
 
You are not supposed to change all Longs to LongPtr!
I'm slighly surprised that you did not find my musings on How to convert Windows API declarations in VBA for 64-bit

Actually I have read it many time in the past. I did say or think I implied implied I changed all longs. Sorry if I was not clear, I changed those longs that return a memory location or a handle. There are several in the declarations.

LongPtr is the perfect type for any pointer or handle in your Declare Statement. You can use this data type in both environments and it will always be appropriately sized to handle the pointer size of your environment.

Only if a function parameter or return value is representing a pointer to a memory location or a handle (e.g. Window Handle (HWND) or Picture Handle), it will be a 64-bit Integer. Only these types of function parameters should be declared as LongPtr.



The LongPtr data type
On 32-bit Windows, all pointers to memory addresses are 32-bit Integers. In VBA, we used to declare those pointer variables as Long. On 64-bit Windows, these pointers were changed to 64-bit Integers to address the larger memory space. So, obviously, we cannot use the unchanged Long data type anymore.

In theory, you could use the new LongLong type to declare integer pointer variables in 64-bit VBA code. In practice, you absolutely should not. There is a much better alternative.

Particularly for pointers, Microsoft introduced an all new and very clever data type. The LongPtr data type. The really clever thing about the LongPtr type is, it is a 32-bit Integer if the code runs in 32-bit VBA and it becomes a 64-bit Integer if the code runs in 64-bit VBA.

LongPtr is the perfect type for any pointer or handle in your Declare Statement. You can use this data type in both environments and it will always be appropriately sized to handle the pointer size of your environment.

Misconception: “You should change all Long variables in your Declare Statements and Type declarations to be LongPtr variables when adapting your code for 64-bit.”

Wrong!

As mentioned above, the size of the existing, generic 32-bit data types has not changed. If an API-Function expected a Long Integer on 32-bit it will still expect a Long Integer on 64-bit.

Only if a function parameter or return value is representing a pointer to a memory location or a handle (e.g. Window Handle (HWND) or Picture Handle), it will be a 64-bit Integer. Only these types of function parameters should be declared as LongPtr.

If you use LongPtr incorrectly for parameters that should be plain Long Integer your API calls may not work or may have unexpected side effects. Particularly if you use LongPtr incorrectly in Type declarations. This will disrupt the sequential structure of the type and the API call will raise a type mismatch exception.


Public Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal nCmdShow As Long) As Boolean

The hWnd argument is a handle of a window, so it needs to be a LongPtr. nCmdShow is an int32, it should be declared as Long in 32-bit and in 64-bit as well.

Do not forget a very important detail. Not only your Declare Statement should be written with the LongPtr data type, your procedures calling the external API function must, in fact, use the LongPtr type as well for all variables, which are passed to such a function argument
 
Actually I have read it many time in the past.
:-)

I can confirm that the above code for the Choose-Font-Dialog actually can be converted to run successfully on 64-bit. - If you want to try it again as an exercise, pay particular attention to what I wrote about user-defined types.

Here's what I made of it: ChooseFont-Dialog-API for 64-Bit-VBA
 
Thanks a lot. Now that I see it I understand it much better, but I was not close at getting the correct longPtr conversions and did not fully understand the issue with user defined types. I guess if you work with these a lot you get familiar with the function argurment and returns to know if it is a handle or mem location. This is a great example because it forces you to understand each conversion and to understand the functions. Thanks again. Now I will see if I can add conditional compilation.
 
I was working on the conversion myself but Phillip beat me to it.
Interestingly the declarations also compile in 64-bit with 3 changes back from LongPtr to Long as follows:

Code:
Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As Long [COLOR="seagreen"]'Ptr[/COLOR]

Private Declare PtrSafe Function GlobalAlloc Lib "kernel32" _
  (ByVal wFlags As Long, ByVal dwBytes As LongPtr) As Long[COLOR="seagreen"] 'Ptr[/COLOR]

Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As Long[COLOR="SeaGreen"] 'Ptr[/COLOR]

However, the Windows API Viewer utility that I use for reference when converting APIs also says LongPtr for each of these.

Now all I need to know is how to use the code & I can test it both ways!

UPDATE: Now tested - the 3 LongPtr are all needed - see post 11
 
Last edited:
Code:
'Dim f As FormFontInfo
    'Dim ctl As Access.TextBox
    'Set ctl = Me.txtFontName
    'With f
    '  .Height = CInt(Nz(Me.txtFontSize, 0))
    '  .Italic = CBool(Nz(Me.txtFontItalic, 0))
    '  .UnderLine = CBool(Nz(Me.txtFontUnderline, 0))
    '  .Name = Nz(Me.txtFontName, "")
    '  .Color = Nz(Me.txtForeColor, 255)
    'End With
    'Call modFontPicker.DialogFont(f)
    'With f
    '    ctl.FontSize = .Height
    '    ctl.FontWeight = .Weight
    '    ctl.FontItalic = .Italic
    '    ctl.FontUnderline = .UnderLine
    '    ctl.Value = .Name
    '    ctl.ForeColor = .Color
    'End With
    'Me.txtFontItalic = txtFontName.FontItalic
    'Me.txtFontSize = Me.txtFontName.FontSize
    'Me.txtFontUnderline = Me.txtFontName.FontUnderline
    'Me.txtFontWeight = Me.txtFontName.FontWeight
    'Me.txtForeColor = Me.txtFontName.ForeColor

This is the code I was using to call it.
 
Thanks. I've just found the original MDB demo at http://access.mvps.org/access/api/api0061.htm

I converted it to ACCDB & added Phillip's code (attached)
Testing showed that whilst it compiles without those 3 LongPtr, they are needed for the font dialog to work.
In fact the font dialog works perfectly in both 32-bit & 64-bit with no need for conditional compilation (Access 2010 onwards).

However, the other module modColorPicker doesn't work in ACCDB files - error 13 type mismatch in both 32-bit or 64-bit Access
I'll have a look at why that is another day.

I have my own colour selector module code in my Colour Converter app which works in both bitnesses - see
 

Attachments

Last edited:
Interestingly the declarations also compile in 64-bit with 3 changes back from LongPtr to Long as follows:
[....]
UPDATE: Now tested - the 3 LongPtr are all needed - see post 11
Always keep in mind: API-Declarations only require to be syntactically correct to compile. There is no type checking, not even checking of the function actually exists, or if the signature matches at compile time. So, if they compile, it does not mean anything.


Even just running them to confirm they work is of limited value. Any memory address that would need a LongPtr on 64-Bit might by chance just reference an address value that fits into a Long during a single execution and perfectly work by chance once or twice and crash the third time. - You need to look at the documentation to be sure of the correct implementation!
 
Hi Phillip
Thanks for your answer and I'm only too aware of that.
The reason I raised those items was that I had converted each of them in another very old app of mine a few days ago.

Using the Win API viewer as guide I had included the LongPtr for each.
The app errored but worked when I swopped the GetDC back to Long!

As I couldn't recall what that API was being used for (it was written over 10 years ago, I decided to leave it be. Guess I better go back to it again ….

If you have time, have a look at the colour part of Stephen's old utility after converting it to ACCDB. You will probably see the reason for the type mismatch error 13 faster than me.

I'm sending you a PM about another matter shortly.
 

Users who are viewing this thread

Back
Top Bottom