- UID
- 3825
- 积分
- 2760
- 精华
- 贡献
-
- 威望
-
- 活跃度
-
- D豆
-
- 在线时间
- 小时
- 注册时间
- 2002-4-14
- 最后登录
- 1970-1-1
|
楼主 |
发表于 2003-8-14 23:29:10
|
显示全部楼层
问:能够给AutoCAD的VBA宏传递参数吗?
答:从AutoCAD直接传递参数到一个VBA宏中是不可能的。但是你可以用VBA的GetString方法和LISP的(command)函数来传递信息。
举例:
首先定义一个VBA宏:
Sub testparams()
Dim str, str2 As String
str=ThisDrawing.Utility.GetString(False)
str2=ThisDrawing.Utility.GetString(False)
MsgBox str
MsgBox str2
End Sub
然后用以下LISP语句调用此宏:
(command "-VBARUN" "testparams" "param1" "param2")
--------------------------------------------------------------------------------
问:.MNU和.LSP文件的调用顺序是怎样的?
答:他们在AutoCAD 2000的调用顺序是这样的:
当AutoCAD启动时:
- acadr2000.lsp
- acad.lsp
对于每个新打开的文档:
- acad.lsp, 如果在AutoCAD的配置对话框中的系统标签下做了相应的选项设定。
- acaddoc2000.lsp
- acaddoc.lsp
- .MNU菜单文件和它们的关联.MNL文件。
--------------------------------------------------------------------------------
问:我已经写了一个VisualLisp函数和一个VBA宏。这个VBA宏显示一个对话框,并且有一些按钮允许有用户输入(例如:输入一个字符串)。现在我从VisualLisp用(vl-vbarun)调用这个VBA宏。当AutoCAD执行到这一行时,LISP停在这一行。但是一旦我在此宏中使用了一个用户输入函数,LISP就不等VBA对话框出现,而继续执行下面的LISP函数。有些时候甚至首先执行LISP的输入函数,然后再执行VBA宏中的输入语句。怎样才能使我的LISP函数和VBA宏同步?
答:现在只有一个解决办法。在你的LISP函数中(vl-vbarun) 函数必须最后调用。当关闭VBA对话框或者从VBA宏中返回时,你得调用另一个你想继续执行的LISP函数。你可以象下面这样做。
首先:你需要一个LISP函数调用这个VBA宏:
(defun c:test ()
;; Call a VBA macro which displays
;; a dialog and writes a file.
(vl-vbarun "test")
;;If you uncomment the following line, then AutoCAD will prompt you
;;to select an entity first, input a string second.
;;(entsel)
(princ)
)
然后你需要第二个LISP函数以供你的VBA宏返回时调用:
(defun c:test_cont ()
;; Here you can continue.
(print " I am continuing.")
(princ)
)
以下是这个VBA宏的示例:
Sub test()
' Show a dialog which uses some
' user input functions.
UserForm1.Show
' Call the lisp function which should
' be now executed.
' You can use the SendCommand method:
' ThisDrawing.SendCommand ("test_cont ")
' or use the ActiveX interface of VisualLisp:
' In order to use it, please remember to reference vl.tlb file which is in Acad2000 folder
' into your VBA project and execute (vl-load-com) on the AutoCAD command line first.
Dim vl As Object
Set vl = CreateObject("VL.Application.1")
vl.ActiveDocument.functions.Item("c:test_cont").funcall
End Sub
--------------------------------------------------------------------------------
问:怎样用Visual Basic生成一个用户接口模块以供Visual LISP方便地调用?
答:最简单和最有效的方法是为AutoCAD写一个内过程(in-process)的自动客户(Automation Client)。例如:一个VB ActiveX DLL。这个DLL 以后可以从 Visual LISP, VBA, Java (通过Automation)和ObjectARX加载。它可以是一个AutoCAD的自动客户,也可以是一个任何的自动服务器( Automation server),或者多重服务器。
1. 启动Visual Basic 5 or 6;
2. 在New Project Wizard中选择ActiveX DLL;
3. 把工程名改为"MyProject";
4. 在工程中有一个缺省的类模块,把它的名称改为"MyClass";
5. 添加一个函数或者子例程到类模块中。例如:
' This function takes two arguments, and will return a list of data to the calling function
Public Function MyFn(ByRef arg1 as Integer, ByRef arg2 as Double) As Variant
myForm.Show vbModal
' Create a list of items to return to the caller (the items are in this case purely arbitrary)
MyFn = Array(1.0,"Arbitrary string",2)
End Function
(这里,myForm是一个你必须添加到工程中的表格。同时切记MyFn是一个函数,它将返回一个值或者一组值给调用例程。)
6. 点取File -
Make MyProject.dll。这就会生成一个DLL并且把它注册为COM。(如果你想在其他机器上运行此DLL,你需要首先确认在所有的机器上安装并注册了这个DLL。这通常需要你用Visual Basic生成一个安装包。)
7. 如果你想从Visual LISP中使用此DLL,你需要定义一个简单的函数,并且把他加载到AutoCAD中:
(defun showDialog (/ acadApp vbApp retVal retList)
;; required in AutoCAD 2000, not R14
(if (car (atoms-family 1 '("vl-load-com"))) (vl-load-com))
;; get the main AutoCAD application object
(setq acadApp (vlax-get-acad-object))
;; load VB ActiveX DLL into AutoCAD's address space (either line will work)
;;(setq vbApp (vlax-invoke acadApp "GetInterfaceObject" "MyProject.MyClass")
(setq vbApp (vla-GetInterfaceObject acadApp "MyProject.MyClass"))
(if (not vbApp)
(princ "\nError loading ActiveX DLL.")
(vlax-invoke vbApp "MyFn"
7 ; arg1, an integer
1.5 ; arg2, a 'double'
)
)
)
为了调用已经暴露出的ActiveX方法,在命令行上输入:
(showDialog)
将把下列内容返回给AutoCAD:
(1.0 "Arbitrary string" 2)
你会发现你可以给VB对话框传递参数并且在AutoCAD中处理返回值。这对于生成选项对话框非常有用,因为有些参数需要初始化并且修改后的值需要返回给AutoCAD。
7. 如果你想从VBA使用这个DLL,你需要把此DLL添加到引用中。(用COM注册它,就会把它添加到ActiveX 服务器的列表中。然后它就可以被VBA引用,不然就请浏览并且选择MyProject.dll。)
8. 然后你就可以用下面的机制加载这个内过程 ActiveX DLL,并且调用其中的函数:
Sub MyVBAProject()
Dim oMyApp as Object
dim vReturn as Variant
set oMyApp = ThisDrawing.Application.GetInterfaceObject( "MyProject.MyClass"
)
vReturn = oMyApp.MyFn(7,1.5)
End Sub
--------------------------------------------------------------------------------
问:可以在VBA中获得带有预览图象的AutoCAD "Open File Dialog"对话框吗?
答:在VBA没有直接的方法这样做。但是,你可以通过LISP和AutoCAD之间的通讯来完成。在LISP中有一个名叫getfiled的函数,它可以预览DWG并且与AutoCAD的"Open File Dialog"表现一样。
首先,通过SendCommand方法发送getfiled表达式给AutoCAD命令行并且定义一个系统变量USERS1以保存文件名。然后,你可以用GetVariable方法获得这个系统变量。最后,象使用其它任何变量一样使用它。
Public Sub OpenDialog()
Dim fileName As String
ThisDrawing.SendCommand "(setvar " & """users1""" & "(getfiled " & """Select a DWG File""" & """c:/program files/acad2000/""" & """dwg""" & "8)) "
fileName = ThisDrawing.GetVariable("users1")
MsgBox "You have selected " & fileName & "!!!", , "File Message"
End Sub
--------------------------------------------------------------------------------
问: 如何在AutoLISP调用完一个VBA宏后,卸载包含它的VBA工程?我想首先加载这个工程,调用一个宏,然后卸载它。下面是我使用的代码,但是VBAUNLOAD在Test宏结束之前被发送到命令行。
(command "-VBARUN" "Test")
(command "vbaunload")
答:主要的问题是你的宏当中有要求用户输入的语句,这时LISP试图执行下一条命令,即VBAUNLOAD。我们需要等待LISP语句执行完毕,或者让LISP在调用VBARUN命令后什么也不做,而让VBA卸载它自己。下面是这两种方法:
1) 在LISP中等待。
(defun c:RunMacro( / oldFileDia oldCmdEcho)
(setq oldFileDia (getvar "FILEDIA")
oldCmdEcho (getvar "CMDECHO")
)
(setvar "FILEDIA" 0)
(setvar "CMDECHO" 0)
(command "_VBALOAD" "c:\\test.dvb")
(command "_-VBARUN" "Test")
(while (= "-VBARUN" (getvar "CMDNAMES"))
(command pause)
)
(command "_VBAUNLOAD")
(setvar "FILEDIA" oldFileDia)
(setvar "CMDECHO" oldCmdEcho)
(princ)
)
2) VBA中自动卸载。
你可以使VBA代码强制卸载它自己。在你的宏的最后(例如:UserForm_Terminate()),使用下列代码可以达到此目的:
AppActivate ThisDrawing.Application.Caption
SendKeys "_VBAUNLOAD" + Chr$(13)
以上的代码在AutoCAD R14.01中工作良好,但是AutoCAD 2000允许加载多个工程。相应地,VBAUNLOAD增加了一个额外参数。有两个新的函数可以加载和运行VBA 代码:(vl-vbaload)和(vl-vbarun)。你可以通过在宏名中指定工程名字把这两种操作联合在一起。
(defun c:RunMacro2000( / oldFileDia oldCmdEcho)
(setq oldFileDia (getvar "FILEDIA")
oldCmdEcho (getvar "CMDECHO")
)
(setvar "FILEDIA" 0)
(setvar "CMDECHO" 0)
(vl-vbarun "c:\\test.dvb!Test")
(while (= "-VBARUN" (getvar "CMDNAMES"))
(command pause)
)
(command "VBAUNLOAD" "c:\\test.dvb")
(setvar "FILEDIA" oldFileDia)
(setvar "CMDECHO" oldCmdEcho)
(princ)
)
第二种方法也需要做一些小的改动。我们可以通过自动接口向AutoCAD发送命令,并且这比SendKeys更安全:
ThisDrawing.SendCommand "_VBAUNLOAD C:\TEST.DVB" + Chr$(13)
如果我们卸载另一个工程,我们可以使用自动接口 (ThisDrawing.Application.UnloadDVB "test.dvb" )。但是在这种情况下我们需要使用SendKeys或SendCommand来卸载其本身。
--------------------------------------------------------------------------------
问:在R13中,我可以通过检查DWK文件知道DWG文件是否被锁定了。在R14中,没有一个ARX函数来检查文件锁定状态。有什么其它办法吗?
答:因为我们现在使用了操作系统支持的文件锁定方式,你可以通过用_fsopen()打开图形文件来检查文件是否锁定。使用模式"r+"和共享标志SH_DENYWR,如果失败,这个图形就已经被打开并且是写的状态。如果成功,千万不要忘记关闭此文件,然后再让AutoCAD打开它。
同时你也需要检查.DWK文件,因为这个文件也可能被另外的R13例程所打开。
2) 用LISP运行一些VBA代码打开这个文件,通过一个系统变量返回一个成功或者失败标志给LISP。例如:下面的VBA代码可以做这件事情。
Sub Test_File_Data()
Dim FileNum As Integer
Dim MyFileName As String
Dim sysVarName As String
Dim sysVarData As Variant
On Error GoTo ErrHandler
FileNum = 1
MyFileName = "D:\PROGRAM FILES\AUTOCAD R14\ARCTEXT.DWG"
sysVarName = "USERI1"
sysVarData = 0
ThisDrawing.SetVariable sysVarName, sysVarData
Open MyFileName For Binary Access Read Lock Read Write As FileNum
Close 1
sysVarData = 1
ThisDrawing.SetVariable sysVarName, sysVarData
Exit Sub
ErrHandler:
MsgBox "Error Number = " & Err.Number
MsgBox "Error Description = " & Err.Description
Err.Clear
sysVarData = 0
ThisDrawing.SetVariable sysVarName, sysVarData
End Sub
--------------------------------------------------------------------------------
问:有什么方法可以检测一个VBA宏是否加载?
答:在AutoCAD 2000中,你可以获取一个属性名叫VBE的ActiveX应用程序对象。它使你可以访问VBAIDE扩展对象。这个对象拥有方法和属性允许你判断一个工程是否已经加载。
如果你想深入学习,就请启动VBAIDE,添加一个对"Microsoft Visual Basic for Applications Extensibility"的引用,然后你就可以在你的ObjectBrowser (F2)浏览此对象了。
以下是四个例子。第一个"loadMyProcedure"检测所有已经加载的工程名。如果没有发现"TestMenuEcho",就加载它。
第二个例子"testMacros"在每一个加载的模块中搜索每一行并且在立即窗口中 (cntrl + G)打印true或者false。目的在于寻找文本"test4"(宏的名字)。
第三个例子"displayLoadedProjects"在信息窗口中显示所有已加载的工程。
第四个例子是一个LISP例程。它使用相同的ActiveX对象判断一个工程是否已经加载,如果没有,就加载它。
Public Sub loadMyProcedure()
Dim int1 As Integer
Dim bProjectLoaded As Boolean
' Iterate through all the projects
For int1 = 1 To Application.VBE.VBProjects.Count
' Make the test boolean variable to true if the Project I want to load
' is already loaded, Change TestMenuEcho to the name of your project
If Application.VBE.VBProjects(int1).Name = "TestMenuEcho" Then
' Debug.Print Application.VBE.VBProjects(int1).Name
bProjectLoaded = True
End If
Next int1
' Display a message if my the project is already loaded
' if it isn't then load it, change the directory and
' name to that of your project
If bProjectLoaded = True Then
MsgBox "TestMenuEcho is already loaded"
Else
MsgBox "Going to load the project TestMenuEcho"
Application.LoadDVB "D:\Vba-apps\a-2000\menuecho in menu.dvb"
End If
End Sub
Public Sub testMacros()
Dim int1 As Integer
Dim int2 As Integer
' Iterate through all the loaded projects
For int1 = 1 To Application.VBE.VBProjects.Count
' Name of loaded project
Debug.Print Application.VBE.VBProjects(int1).Name
' Iterate through the text of each module looking for "test4"
For int2 = 1 To Application.VBE.VBProjects(int1).VBComponents.Count
' Get the number of lines in each module - use in the
' find method below
Dim L As Long
L = Application.VBE.VBProjects(int1).VBComponents(int2) _
.CodeModule.CountOfLines
Debug.Print Application.VBE.VBProjects(int1).VBComponents(int2) _
.CodeModule.Find("test4", 1, 1, L, 1, False, False)
Next int2
Next int1
End Sub
Public Sub displayLoadedProjects()
Dim strProjectNames
Dim int1 As Integer
strProjectNames = "Names of loaded projects " & vbCrLf
For int1 = 1 To Application.VBE.VBProjects.Count
strProjectNames = strProjectNames + Application.VBE.VBProjects(int1).Name & vbCrLf
Next int1
MsgBox strProjectNames
End Sub
(defun c:loadMyProject ()
;; This routine will load a project if it is not already loaded
;; the VBE (VB extensibility) ActiveX object is used to reference
;; the loaded projects
;; Load ActiveX
(vl-load-com)
;; Get the VBE extisibility object
(setq acadObject (vlax-get-acad-object))
(setq acadVbe (vla-get-vbe acadObject))
(setq acadVbeProjects (vlax-get-property acadVbe 'VBProjects))
;; Get the number of loaded VBA projects
(setq int1 (vlax-get-property acadVbeProjects 'count))
;; Counter and test variable named loaded
(setq int2 1)
(setq loaded "False")
;; Repeat for each project
(repeat int1
;; Itereate through the projects, getting the name of
;; next project, each time through
(setq Item (vlax-invoke-method acadVbeProjects 'Item int2))
(setq pName (vlax-get-property Item 'Name))
;; Test the name for the name of the project I want to load
;; If it is already loaded the set the test variable to True
(if (= pName "my_test_project")
(progn
(prompt "\nmy_test_project is already loaded\n")
(Setq Loaded "True")
)
)
;; Increment the number used to get the next Project
(setq int2 (+ int2 1))
)
;; Load project if it is not already loaded by testing
;; the Loaded variable
(if (= Loaded "False")
(progn
(princ "\nLoading my_test_project")
(command "-VBALOAD"
"D:/vba-apps/a-2000/already loaded test.dvb"
)
) |
|