Using CreateDialog in VBA in an Attempt to Create Modeless Dialog Boxes Using CreateDialog in VBA in an Attempt to Create Modeless Dialog Boxes vba vba

Using CreateDialog in VBA in an Attempt to Create Modeless Dialog Boxes


This can be made to work, although if you should try to make it work is another question. I have a working version that shows an empty dialog. I don't have any more time tonight to finish with actual controls on the dialog, but I'm posting in the hope it'll get you started.

First you need to forget about CreateDialog because they require the dialog template to be in the resource section. You can use CreateDialogIndirectParam to create a dialog from an in-memory dialog template. You will need this:

Private Type DLGTEMPLATE    style As Long    dwExtendedStyle As Long    cdit As Integer    x As Integer    y As Integer    cx As Integer    cy As IntegerEnd TypePrivate Type DLGITEMTEMPLATE    style As Long    dwExtendedStyle As Long    x As Integer    y As Integer    cx As Integer    cy As Integer    id As IntegerEnd TypePrivate Type DLG    dlgtemp As dlgtemplate    menu As Long    classname As String    title As StringEnd TypePrivate Declare PtrSafe Function CreateDialogIndirectParam Lib "User32.dll" Alias "CreateDialogIndirectParamW" _  (ByVal hInstance As Long, _  ByRef lpTemplate As DLGTEMPLATE, _  ByVal hWndParent As Long, _  ByVal lpDialogFunc As LongPtr, _  ByVal lParamInit As Long) _  As LongPtrConst WM_INITDIALOG As Long = &H110Const DS_CENTER As Long = &H800&Const DS_SETFONT As Long = &H40Const DS_MODALFRAME As Long = &H80Const WS_EX_APPWINDOW As Long = &H40000

Then call it like this:

Dim d As DLGd.dlgtemp.style = DS_MODALFRAME + WS_POPUP + WS_VISIBLE + WS_CAPTION + WS_SYSMENUd.dlgtemp.dwExtendedStyle = WS_EX_APPWINDOWd.dlgtemp.cdit = 0d.dlgtemp.x = 100d.dlgtemp.y = 100d.dlgtemp.cx = 200d.dlgtemp.cy = 200d.menu = 0d.title = "Test"d.classname = "Test"CreateDialogIndirectParam 0, d.dlgtemp, 0, AddressOf DlgFunc, 0

with DlgFunc looking something like this:

Public Function DlgFunc(ByVal hwndDlg As LongPtr, ByVal uMsg As LongPtr, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr    If uMsg = h110 Then  ' = WM_INITDIALOG - you should make a const for the various window messages you'll need...        DlgFunc = True    Else        DlgFunc = False    End IfEnd Function

It's been over a decade since I last did any of this stuff. But if you're determined to go this route, I think this approach is the most promising - the next step is to adapt the DLG struct to add some DLGITEMTEMPLATE members, set d.dlgtemp.cdit to the number of controls on your dialog, and start handling control messages in your DlgFunc.


I don't want to detract from the in depth and well researched but there are possible work-arounds to dynamically creating modeless dialog boxes in VBA. That was the original problem before the asker bravely dived down the rabbit hole with CreateDialog. So this answer is for the original problem of dynamically creating Modeless dialog boxes in VBA not how to use CreateDialog. I can't help there.

As previously stated, modeless dialog boxes can be created using a UserForm, but we don't want useless forms littering the project. The work-around I have achieved uses the Microsoft VBA Extensibility Library. In short, we create a class that adds a generic userform to the project on construction and removes the userform on termination.

Also note, this is tested using Excel VBA. I don't have SolidWorks so I can't test it there.

Crudely done as a class module.

Option ExplicitPrivate pUserForm As VBIDE.VBComponentPrivate Sub Class_Initialize()    ' Add the userform when created '    Set pUserForm = ThisWorkbook.VBProject.VBComponents.Add(VBIDE.vbext_ct_MSForm)End SubPrivate Sub Class_Terminate()    ' remove the userform when instance is deleted '    ThisWorkbook.VBProject.VBComponenets.Remove pUserFormEnd SubPublic Property Get UserForm() As VBIDE.VBComponent    ' allow crude access to modify the userform '    ' ideally this will be replaced with more useful methods '    Set UserForm = pUserFormEnd PropertyPublic Sub Show(ByVal mode As Integer)    VBA.UserForms.Add(pUserForm.Name).Show modeEnd Sub

Ideally, this class would be better developed and would allow easier access to modifying the form, but for now it's a solution.

Tests

Private Sub TestModelessLocal()    Dim localDialog As New Dialog    localDialog.UserForm.Properties("Caption") = "Hello World"    localDialog.Show vbModelessEnd Sub

You should see a window appear and disappear as localDialog leaves scope. A UserForm1 was created in your VBProject and deleted.

This test will create a persistent dialog box. Unfortunately, UserForm1 will remain in your VBProject as globalDialog is still defined. Resetting the project will not remove the userform.

Dim globalDialog As DialogPrivate Sub TestModeless()    Set globalDialog = New Dialog    globalDialog.UserForm.Properties("Caption") = "Hello World"    globalDialog.Show vbModeless    'Set globalDialog = Nothing  closes window and removes the userform '    'Set gloablDialog = new Dialog should delete userform1 after added userform2'End Sub

So never use this at module scope.

In conclusion, its an ugly solution but it's far less ugly than what the Asker was trying to do.


You got a very poor start on this project. You completely scrambled the order of the arguments for CreateDialogParam, note how the hInstance argument is first, the dwInitParam argument is last.

You completely fumbled the DIALOGPROC declaration, it is a function pointer. That requires LongPtr in the declaration and the AddressOf operator when you make the call.

This was just the first 1% of making it work. Next problem is that you'll have to write a functional dialog procedure (the target of AddressOf) that handles the notifications that the dialog generates. Basic stuff, like recognizing that the user clicked the OK button. Very hard to write when you don't know enough about WinAPI programming, little mistakes are big undiagnosable problems at runtime.

That's just the small stuff, there are much bigger problems. The lpTemplateName argument is a very serious obstacle. That needs to be a resource identifier, the kind that's generated by "rc.exe" and added to the executable file by the linker. You cannot relink SolidWorks. A modeless dialog requires help from the message loop, it must call IsDialogMessage(). You cannot convince SolidWorks to make this call for you. Without it, the dialog misbehaves in hard to diagnose ways, like tabbing will not work.

You have to know when you stand absolutely no chance to make it work. You can't make it work.