Я хочу создать немодальный всплывающий диалог в VBA 7.0.
Пока что самым многообещающим маршрутом является CreateDialog
.
Сначала я попробовал CreateDialogW
и получил Entry point not found for CreateDialogW in DLL
,
После открытия DLL я убедился, что этой функции нет в списке. Ссылка MSDN, указанная выше, показывает User32 как DLL для этой функции и перечисляет имена функций CreateDialogW
а также CreateDialogA
(Unicode / ansi соответственно), но они не перечислены в этой DLL на моем компьютере (Win 7 professional, 64bit).
Итак, глядя на список функций, которые являются в DLL я увидел CreateDialogParam
а также CreateDialogIndirectParam
функции (Анси и Unicode версии каждого).
Я пытался следовать MSDN и преобразовать примеры C в VB, но я что-то упустил, и я застрял, потому что не знаю, что делаю неправильно. Код компилируется и запускается без ошибок, но при вызове API ничего не происходит — он выполняется, но ничего не происходит.
Если бы кто-нибудь мог дать мне несколько указателей в правильном направлении, я был бы очень признателен.
Мой текущий обходной путь — отстой, и мне бы очень хотелось, чтобы этот проект был на высоте.
Option Explicit
'Reference conversion of C to VB type declarations here
'http://msdn.microsoft.com/en-us/library/aa261773(v=vs.60).aspx
'Declare function to Win API CreateDialog function
'http://msdn.microsoft.com/en-us/library/ms645434(v=vs.85).aspx
Private Declare PtrSafe Function CreateDialog Lib "User32.dll" Alias "CreateDialogParamW" _
(ByVal lpTemplateName As LongPtr, _
ByRef lpDialogFunc As DIALOGPROC, _
ByVal dwInitParam As Long, _
Optional ByVal hInstance As Long, _
Optional ByVal hWndParent As Long) _
As Long
'Windows Style Constants
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
Public Const WS_BORDER As Long = &H800000
Public Const WS_CAPTION As Long = &HC00000
Public Const WS_CHILD As Long = &H40000000
Public Const WS_CHILDWINDOW As Long = &H40000000
Public Const WS_CLIPCHILDREN As Long = &H2000000
Public Const WS_CLIPSIBLINGS As Long = &H4000000
Public Const WS_DISABLED As Long = &H8000000
Public Const WS_DLGFRAME As Long = &H400000
Public Const WS_GROUP As Long = &H20000
Public Const WS_HSCROLL As Long = &H100000
Public Const WS_ICONIC As Long = &H20000000
Public Const WS_MAXIMIZE As Long = &H1000000
Public Const WS_MAXIMIZEBOX As Long = &H10000
Public Const WS_MINIMIZE As Long = &H20000000
Public Const WS_MINIMIZEBOX As Long = &H20000
Public Const WS_OVERLAPPED As Long = &H0
Public Const WS_POPUP As Long = &H80000000
Public Const WS_SIZEBOX As Long = &H40000
Public Const WS_SYSMENU As Long = &H80000
Public Const WS_TABSTOP As Long = &H10000
Public Const WS_THICKFRAME As Long = &H40000
Public Const WS_TILED As Long = &H0
Public Const WS_VISIBLE As Long = &H10000000
Public Const WS_VSCROLL As Long = &H200000
Public Const WS_OVERLAPPEDWINDOW As Long = (WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_THICKFRAME + WS_MINIMIZEBOX + WS_MAXIMIZEBOX)
Public Const WS_TILEDWINDOW As Long = (WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_THICKFRAME + WS_MINIMIZEBOX + WS_MAXIMIZEBOX)
Public Const WS_POPUPWINDOW As Long = (WS_POPUP + WS_BORDER + WS_SYSMENU)
'Declare custom type for lpDialogFunc argument
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms645469(v=vs.85).aspx
Public Type DIALOGPROC
hwndDlg As Long
uMsg As LongPtr
wparam As Long
lparam As Long
End Type'MAKEINTRESOURCE Macro emulation
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms648029(v=vs.85).aspx
'Bitwise function example found here: http://support.microsoft.com/kb/112651
'VB conversion found here: https://groups.google.com/forum/#!topic/microsoft.public.vb.winapi/UaK3S-bJaiQ _
modified with strong typing and to use string pointers for VB7
Private Function MAKEINTRESOURCE(ByVal lID As Long) As LongPtr
MAKEINTRESOURCE = StrPtr("#" & CStr(MAKELONG(lID, 0)))
End Function
Private Function MAKELONG(ByRef wLow As Long, ByRef wHi As Long)
'Declare variables
Dim LoLO As Long
Dim HiLO As Long
Dim LoHI As Long
Dim HiHI As Long
'Get the HIGH and LOW order words from the long integer value
GetHiLoWord wLow, LoLO, HiLO
GetHiLoWord wHi, LoHI, HiHI
If (wHi And &H8000&) Then
MAKELONG = (((wHi And &H7FFF&) * 65536) Or (wLow And &HFFFF&)) Or &H80000000
Else
MAKELONG = LoLO Or (&H10000 * LoHI)
'MAKELONG = ((wHi * 65535) + wLow)
End If
End Function
Private Function GetHiLoWord(lparam As Long, LOWORD As Long, HIWORD As Long)
'This is the LOWORD of the lParam:
LOWORD = lparam And &HFFFF&
'LOWORD now equals 65,535 or &HFFFF
'This is the HIWORD of the lParam:
HIWORD = lparam \ &H10000 And &HFFFF&
'HIWORD now equals 30,583 or &H7777
GetHiLoWord = 1
End Function
Public Function TstDialog()
Dim dpDialog As DIALOGPROC
dpDialog.hwndDlg = 0
dpDialog.uMsg = StrPtr("TEST")
dpDialog.lparam = 0
dpDialog.wparam = 0
CreateDialog hInstance:=0, lpTemplateName:=MAKEINTRESOURCE(WS_POPUPWINDOW + WS_VISIBLE), lpDialogFunc:=dpDialog, dwInitParam:=&H110
End Function
Это может быть сделано, чтобы работать, хотя если вы должны попытаться заставить это работать, другой вопрос. У меня есть рабочая версия, которая показывает пустой диалог. Сегодня у меня нет больше времени, чтобы закончить с реальными элементами управления в диалоге, но я пишу в надежде, что это поможет вам начать.
Во-первых, вам нужно забыть о CreateDialog, потому что они требуют, чтобы шаблон диалога находился в разделе ресурсов. Вы можете использовать CreateDialogIndirectParam для создания диалога из шаблона диалога в памяти. Вам понадобится это:
Private Type DLGTEMPLATE
style As Long
dwExtendedStyle As Long
cdit As Integer
x As Integer
y As Integer
cx As Integer
cy As Integer
End Type
Private Type DLGITEMTEMPLATE
style As Long
dwExtendedStyle As Long
x As Integer
y As Integer
cx As Integer
cy As Integer
id As Integer
End Type
Private Type DLG
dlgtemp As dlgtemplate
menu As Long
classname As String
title As String
End Type
Private 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 LongPtr
Const WM_INITDIALOG As Long = &H110
Const DS_CENTER As Long = &H800&
Const DS_SETFONT As Long = &H40
Const DS_MODALFRAME As Long = &H80
Const WS_EX_APPWINDOW As Long = &H40000
Тогда назовите это так:
Dim d As DLG
d.dlgtemp.style = DS_MODALFRAME + WS_POPUP + WS_VISIBLE + WS_CAPTION + WS_SYSMENU
d.dlgtemp.dwExtendedStyle = WS_EX_APPWINDOW
d.dlgtemp.cdit = 0
d.dlgtemp.x = 100
d.dlgtemp.y = 100
d.dlgtemp.cx = 200
d.dlgtemp.cy = 200
d.menu = 0
d.title = "Test"d.classname = "Test"
CreateDialogIndirectParam 0, d.dlgtemp, 0, AddressOf DlgFunc, 0
с DlgFunc выглядит примерно так:
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 If
End Function
Прошло более десяти лет с тех пор, как я последний раз занимался этим. Но если вы полны решимости пойти по этому пути, я думаю, что этот подход является наиболее многообещающим — следующим шагом будет адаптация структуры DLG для добавления некоторых членов DLGITEMTEMPLATE, установив d.dlgtemp.cdit в число элементов управления в вашем диалоге, и начните обрабатывать управляющие сообщения в вашем DlgFunc.
Я не хочу отвлекать внимание от глубоко и хорошо изученных, но есть возможные обходные пути динамически создавать немодальные диалоговые окна в VBA. Это была первоначальная проблема, прежде чем Аскер смело нырнул в кроличью нору с CreateDialog
, Таким образом, этот ответ для исходной проблемы динамического создания немодальных диалоговых окон в VBA, а не как использовать CreateDialog
, Я не могу помочь там.
Как уже говорилось ранее, немодальные диалоговые окна могут быть созданы с использованием пользовательской формы, но мы не хотим, чтобы бесполезные формы засоряли проект. Обходной путь, которого я достиг, использует библиотеку Microsoft VBA Extensibility. Короче говоря, мы создаем класс, который добавляет универсальную пользовательскую форму в проект при создании и удаляет пользовательскую форму при завершении.
Также обратите внимание, что это проверено с использованием Excel VBA. У меня нет SolidWorks, поэтому я не могу проверить его там.
Грубо сделано как модуль класса.
Option Explicit
Private pUserForm As VBIDE.VBComponent
Private Sub Class_Initialize()
' Add the userform when created '
Set pUserForm = ThisWorkbook.VBProject.VBComponents.Add(VBIDE.vbext_ct_MSForm)
End Sub
Private Sub Class_Terminate()
' remove the userform when instance is deleted '
ThisWorkbook.VBProject.VBComponenets.Remove pUserForm
End Sub
Public Property Get UserForm() As VBIDE.VBComponent
' allow crude access to modify the userform '
' ideally this will be replaced with more useful methods '
Set UserForm = pUserForm
End Property
Public Sub Show(ByVal mode As Integer)
VBA.UserForms.Add(pUserForm.Name).Show mode
End Sub
В идеале этот класс был бы лучше разработан и позволил бы упростить доступ к изменению формы, но пока это решение.
Private Sub TestModelessLocal()
Dim localDialog As New Dialog
localDialog.UserForm.Properties("Caption") = "Hello World"localDialog.Show vbModeless
End Sub
Вы должны увидеть, как окно появляется и исчезает как localDialog
выходит за рамки. UserForm1
был создан в вашем VBProject и удален.
Этот тест создаст постоянное диалоговое окно. К несчастью, UserForm1
останется в вашем VBProject как globalDialog
все еще определяется. Сброс проекта не удалит пользовательскую форму.
Dim globalDialog As Dialog
Private 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
Поэтому никогда не используйте это в области видимости модуля.
В заключение, это уродливое решение, но оно гораздо менее уродливо, чем то, что пытался сделать Аскер.
У вас очень плохое начало этого проекта. Вы полностью зашифровали порядок аргументов для CreateDialogParam, обратите внимание, как hInstance
аргумент первый, dwInitParam
аргумент последний.
Вы полностью нащупали объявление DIALOGPROC, это указатель на функцию. Это требует LongPtr
в декларации и AddressOf
оператор, когда вы делаете звонок.
Это был только первый 1%, заставляющий это работать. Следующая проблема заключается в том, что вам придется написать функциональную диалоговую процедуру (цель AddressOf
), который обрабатывает уведомления, которые генерирует диалог. Основные вещи, такие как признание того, что пользователь нажал кнопку ОК. Очень сложно писать, когда вы недостаточно разбираетесь в программировании WinAPI, небольшие ошибки — это большие не диагностируемые проблемы во время выполнения.
Это просто мелочи, есть много большие проблемы. lpTemplateName
Аргумент является очень серьезным препятствием. Это должен быть идентификатор ресурса, тип, сгенерированный «rc.exe» и добавленный компоновщиком в исполняемый файл. Вы не можете повторно связать SolidWorks. Немодальный диалог требует помощи из цикла сообщений, он должен вызвать IsDialogMessage()
, Вы не можете убедить SolidWorks сделать этот звонок за вас. Без этого диалог плохо себя ведет в трудных для диагностики способах, таких как табуляция не будет работать.
Вы должны знать, когда у вас нет абсолютно никаких шансов заставить это работать. Вы не можете заставить это работать.
Этот ответ, как Cheezsteak-х не занимается непосредственно проблемами, с которыми вы сталкиваетесь CreateDialog
, Это касается конечной цели создания немодального диалогового окна.
Мое предложение состоит в том, чтобы использовать UserForm чтобы сделать это. Это Показать метод принимает необязательный параметр, который определяет, будет ли пользовательская форма отображаться в модальной или немодальной форме.
Из документации MSDN:
модальный Необязательно. Логическое значение, определяющее, является ли пользовательская форма модальной
или немодальный.
В коде, который создает экземпляр пользовательской формы, просто передайте ему vbModeless
постоянная.
Option Explicit
Private frm As UserForm1
Sub test2()
Set frm = New UserForm1
frm.Show vbModeless
End Sub
Если вы беспокоитесь о том, чтобы загромождать ваш проект формами, не беспокойтесь. Просто создать форму на лету.