第
5
章
用户互操作:提示和选择
背景
提示通常包含一个描述性信息,伴随一个停止以让用户理解所给的信息并输入数据。数据可以通过多种方式被输入,如通过命令行、对话框或AutoCAD编辑窗口。给出的提示要遵循一定的格式,格式要与一般的AutoCAD提示相一致,这一点是非常重要的。例如,关键字要用“/”号分隔并放在方括号“[]”中,缺省值要放在“<>”内。对于一个AutoCAD用户来说,坚持统一的格式将会减少信息理解错误的产生。
当用户在AutoCAD命令行中选择一个实体时,实体是使用选择机制被选择的。这种机制包括一个提示,用来让用户知道选择什么并怎样选择(如,窗口或单一实体),然后是一个停顿。
试一下诸如PINE这种命令来看一下提示的显示,PEDIT来看一下使用单一实体或多线来进行选择。
练习
Prompts:
提示:
在本章中,我们将提示输入雇员名字、职位、薪水和部门来创建一个雇员块索引对象。如果输入的部门不存在,我们将提示输入部门经理的名字来创建一个新的部门。在我们继续之前,让我们试着重用以前的代码。
为了进行选择,我们将提示用户在一个窗口中进行选择或选择一个实体,而我们只显示选择集中的雇员对象。
在前面的章节中,我们创建了一个名叫“Earnest Shackleton”的雇员,名字被存储为“EmployeeBlock”块定义(块表记录)中的MText。如果我们多次插入这个块,那么我们看到的都是同一个雇员的名字。我们怎样才能自定义这个块以使每次插入这个块的时候显示不同雇员的名字?这就要使用块属性的功能了。属性是存储在每一个块索引实例中的文本,并被作为实例的一部分来被显示。属性从存储在块表记录中的属性定义中继承相关的属性。
属性:
让我们来把MText实体类型改变为属性定义。在CreateEmployeeDefinition()函数中,把下面的代码替换
‘ 文本:
Dim text As MText = New MText()
text.Contents = "Earnest Shackleton"
text.Location = center
为
'属性定义
Dim text As AttributeDefinition = New AttributeDefinition(center, "NoName", "Name:", "Enter Name", db.Textstyle)
text.ColorIndex = 2
试着使用TEST命令来测试一下CreateEmployeeDefinition()函数:
<CommandMethod("TEST")> _
Public Function Test()
CreateEmployeeDefinition()
End Function
你现在应该可以使用INSERT命令来插入EmployeeBlock块并对每一个实例确定一个雇员名。
当你插入Employee块时,请注意一下块插入的位置。它是正好被放置在所选点还是有些偏移?试试怎样修复它。(提示:检查块定义中的圆心)
修改CreateEmployee ()
以重用
1)让我们来修改CreateEmployee()函数,以让它可以接收名字、薪水、部门和职位并返回创建的雇员块索引的ObjectId。函数的形式如下(你可以改变参数顺序)
Public Function CreateEmployee(ByVal name As String, ByVal division As String, ByVal salary As Double, ByVal pos As Point3d) as ObjectId
2) 移除上面函数中的CommandMethod属性”CREATE”,这样它就不再是用来创建雇员的命令。
3) 修改函数的代码,这样就可以正确地设置块索引的名字、职位、部门和薪水和它的扩展字典。
- 替换
Dim br As New BlockReference(New Point3d(10, 10, 0), CreateEmployeeDefinition())
为
Dim br As New BlockReference(pos, CreateEmployeeDefinition())
- 替换
xRec.Data = New ResultBuffer( _
New TypedValue(DxfCode.Text, "Earnest Shackleton"), _
New TypedValue(DxfCode.Real, 72000), _
New TypedValue(DxfCode.Text, "Sales"))
为
xRec.Data = New ResultBuffer( _
New TypedValue(DxfCode.Text, name), _
New TypedValue(DxfCode.Real, salary), _
New TypedValue(DxfCode.Text, division))
4) 因为我们把雇员的名字从MText替换成块的属性定义,因此我们要创建一个相应的属性索引来显示雇员的名字。属性索引将使用属性定义的属性。
- 替换:
btr.AppendEntity(br) '加入索引到模型空间
trans.AddNewlyCreatedDBObject(br, True) '让事务处理知道
为
Dim attRef As AttributeReference = New AttributeReference()
'遍历雇员块来查找属性定义
Dim empBtr As BlockTableRecord = trans.GetObject(bt("EmployeeBlock"), OpenMode.ForRead)
Dim id As ObjectId
For Each id In empBtr
Dim ent As Entity = trans.GetObject(id, OpenMode.ForRead, False) '打开当前的对象!
If TypeOf ent Is AttributeDefinition Then '
'设置属性为属性索引中的属性定义
Dim attDef As AttributeDefinition = CType(ent, AttributeDefinition)
attRef.SetPropertiesFrom(attDef)
attRef.Position = New Point3d(attDef.Position.X + br.Position.X,
_
attDef.Position.Y + br.Position.Y, _
attDef.Position.Z + br.Position.Z)
attRef.Height = attDef.Height
attRef.Rotation = attDef.Rotation
attRef.Tag = attDef.Tag
attRef.TextString = name
End If
Next
btr.AppendEntity(br) '把索引加入模型空间
'把属性索引加入到块索引
br.AttributeCollection.AppendAttribute(attRef)
'让事务处理知道
trans.AddNewlyCreatedDBObject(attRef, True)
trans.AddNewlyCreatedDBObject(br, True)
研究一下上面的代码,看看是怎样把属性定义中除显示用的文本字符串外的属性复制到属性索引的。属性被加入到块索引的属性集合中。这就是你怎样来为每一个实例自定义雇员名字。
5)不要忘记返回雇员块索引的ObjectId,但要在提交事务处理之后才能返回:
trans.Commit()
Return br.ObjectId
6) 测试CreateEmployee。
加入一个Test命令来测试CreateEmployee:
<CommandMethod("TEST")> _
Public Function Test()
CreateEmployee("Earnest Shackleton", "Sales", 10000, New Point3d(10, 10, 0))
End Function
修改CreateDivision()
以重用
:
让我们来修改CreateDivision ()函数,以让它可以接收部门名字、经理名字并返回创建的部门经理扩展记录的ObjectId。如果部门经理已经存在,则不改变经理的名字。
1) 如果你先前在CreateEmployeeDefinition()中调用了CreateDivision(),请把它注释掉,因为我们在这里不需要创建一个部门
2) 改变CreateDivision()的形式让它接收部门和经理的名字并返回一个ObjectId。
Public Function CreateDivision(ByVal division As String, ByVal manager As String) As ObjectId
3) 修改上面函数的代码创建部门的名字和经理:
- 替换:
divDict = trans.GetObject(acmeDict.GetAt("Sales"), OpenMode.ForWrite)
为:
divDict = trans.GetObject(acmeDict.GetAt(division), OpenMode.ForWrite)
- 替换:
acmeDict.SetAt("Sales", divDict)
为:
acmeDict.SetAt(division, divDict)
- 替换:
mgrXRec.Data = New ResultBuffer(New TypedValue(DxfCode.Text, "Randolph P. Brokwell"))
为
mgrXRec.Data = New ResultBuffer(New TypedValue(DxfCode.Text, manager))
不要忘了返回部门经理这个扩展记录的ObjectId,但要在提交事务处理后才返回。
trans
.
Commit
()
'返回部门经理这个扩展记录的ObjectId
Return mgrXRec.ObjectId
现在把在中CreateEmployeeDefinition调用的CreateDivision函数给注释掉。
4) 现在通过使用TEST命令来测试调用CreateDivision函数。使用ArxDbg工具来检查条目是否已被加入到“ACME_DIVISION”下的命名对象字典。
CreateDivision("Sales", "Randolph P. Brokwell")
使用CREATE命令来创建雇员
:
我们将加入一个名为CREATE的新命令,此命令用来提示输入雇员的详细资料来创建雇员块索引。让我们来看一下这个命令是怎样使用的。
1) 让我们加入一个名为CREATE的新命令,并声明几个常用的变量和一个try-finally块。
<CommandMethod("CREATE")> _
Public Sub CreateEmployee()
Dim db = HostApplicationServices.WorkingDatabase
Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor
Dim trans As Transaction = db.TransactionManager.StartTransaction()
Try
trans.Commit()
Finally
trans.Dispose()
End Try
End Sub
2) 让我们来为雇员定义可以用作为提示缺省值的常数。注意,布尔值gotPosition是用来判断用户是否已输入
职位。
. 雇员名 - 类型 :String -缺省值 “Earnest Shackleton”
. 雇员所在部门名 - 类型:String -缺省值“Sales”
. 薪水 -类型:Double (non-negative and not zero) -缺省值10000
.
职位 -类型:Point3d -缺省值(0,0,0)
把这些常数加入到try语句后面:
Dim empName As New String("Earnest Shackleton")
Dim divName As New String("Sales")
Dim salary As New Double() : salary = 10000
Dim position As New Point3d(0, 0, 0)
'布尔值用来判断用户是否已输入
职位
Dim gotPosition As New Boolean() : gotPosition = False
3) 现在让我们提示用户输入值。我们先使用PromptXXXOptions类来初始化要显示的提示字符串。
'提示输入每个雇员的详细资料
Dim prName As PromptStringOptions = New PromptStringOptions("Enter Employee Name <" & empName & ">")
Dim prDiv As PromptStringOptions = New PromptStringOptions("Enter Employee Division <" & divName & ">")
Dim prSal As PromptDoubleOptions = New PromptDoubleOptions("Enter Employee Salary <" & salary & ">")
Dim prPos As PromptPointOptions = New PromptPointOptions("Enter Employee Position or")
注意,提示字符串用尖括号来显示变量的值。这是AutoCAD用来提示用户这个值为缺省值。
4) 当提示用户输入
职位时,我们也提供了一个关键字列表选项,如名字、部门和薪水。如果用户想要在选择一个点的时候改变为其它值,他可以选择那个关键字。
一个命令提示的例子如下:
Command: CREATE
Enter Employee Position or [Name/Division/Salary]:
要创建一个雇员,用户会选择一个点而其它的值被设置为缺省值。如果用户要改变其它的值,如名字,他可以输入”N”或全名”Name”,然后输入名字:
Command: CREATE
Enter Employee Position or [Name/Division/Salary]:N
Enter Employee Name <Earnest Shackleton>:
如果用户想要再次选择缺省的名字,他可以按回车键。
让我们创建用于
职位提示的关键字列表:
'加入用于
职位提示的关键字
prPos.Keywords.Add("Name")
prPos.Keywords.Add("Division")
prPos.Keywords.Add("Salary")
'设置提示的限制条件
prPos.AllowNone = False '不允许没有值
5) 现在让我们声明PromptXXXResult变量来获取提示的结果:
'prompt results
Dim prNameRes As PromptResult
Dim prDivRes As PromptResult
Dim prSalRes As PromptDoubleResult
Dim prPosRes As PromptPointResult
6) 直到用户成功输入一个点后,循环才结束。如果输入错误的话,我们会提示用户并退出函数:
判断用户是否输入了关键字,我们通过检查promptresult的状态来进行:
'循环用来获取雇员的详细资料。当
职位被输入后,循环终止。
While (Not (gotPosition))
'提示输入
职位
prPosRes = ed.GetPoint(prPos)
'取得一个点
If prPosRes.Status = PromptStatus.OK Then
gotPosition = True
position = prPosRes.Value
ElseIf prPosRes.Status = PromptStatus.Keyword Then '获取一个关键字
'输入了Name关键字
If prPosRes.StringResult = "Name" Then
'获取雇员名字
prName.AllowSpaces = True
prNameRes = ed.GetString(prName)
If prNameRes.Status <> PromptStatus.OK Then
Return
End If
'如果获取雇员名字成功
If prNameRes.StringResult <> "" Then
empName = prNameRes.StringResult
End If
End If
Else
'获取
职位时发生错误
ed.WriteMessage("***Error in getting a point, exiting!!***" + vbCrLf)
Return
End If '如果获取一个点
End While
7) 上面的代码只提示输入名字,请加入提示输入薪水和部门的代码。
8) 完成提示输入后,我们将使用获得的值来创建雇员。
'创建雇员
CreateEmployee(empName, divName, salary, position)
9) 现在来检查部门经理是否已存在。我们通过检查NOD中部门的扩展记录中的经理名字来进行。如果检查到的是一个空字符串,那么我们会提示用户输入经理的名字。注意,通过修改CreateDivision()函数,获取经理的名字变得简单了。
Dim manager As String = New String("")
'创建部门
'给经理传入一个空字符串来检查它是否已存在
Dim depMgrXRec As Xrecord
Dim xRecId As ObjectId
xRecId = CreateDivision(divName, manager)
'打开部门经理扩展记录
depMgrXRec = trans.GetObject(xRecId, OpenMode.ForRead)
Dim val As TypedValue
For Each val In depMgrXRec.Data
Dim str As String
str = val.Value
If str = "" Then
'经理没有被设置,现在设置它
'先提示输入经理的名字
ed.WriteMessage(vbCrLf)
Dim prManagerName As PromptStringOptions = New PromptStringOptions("No manager set for the division! Enter Manager Name")
prManagerName.AllowSpaces = True
Dim prManagerNameRes As PromptResult = ed.GetString(prManagerName)
If prManagerNameRes.Status <> PromptStatus.OK Then
Return
End If
'设置经理的名字
depMgrXRec.Data = New ResultBuffer(New TypedValue(DxfCode.Text, prManagerNameRes.StringResult))
End If
Next
10) 测试CREATE命令
选择集:
现在让我们来创建一个命令,当用户在图形中选择一个雇员对象时,它会显示雇员的详细资料。
我们会使用上一章中创建的ListEmployee()函数在命令行中输出雇员的详细资料。
下面是你必须遵循的步骤:
- 调用“LISTEMPLOYEES”命令
- 调用Editor的GetSelection()函数来选择实体
Dim res As PromptSelectionResult = ed.GetSelection(Opts, filter)
- 上面的filter用来过滤选择集中的块索引。你可以创建如下的过滤列表:
Dim filList() As TypedValue = {New TypedValue(DxfCode.Start, "INSERT")}
Dim filter As SelectionFilter = New SelectionFilter(filList)
- 从选择集中获取ObjectId数组:
'如果选择失败则什么也不做
If Not res.Status = PromptStatus.OK Then Return
Dim SS As Autodesk.AutoCAD.EditorInput.SelectionSet = res.Value
Dim idArray As ObjectId() = SS.GetObjectIds()
5. 最后,把选择集中的每个ObjectId输入到ListEmployee()函数来获取一个雇员详细资料的字符串数组。把雇员的详细资料输出到命令行。例如:
'获取saEmployeeList 数组中的所有雇员
For Each employeeId In idArray
ListEmployee(employeeId, saEmployeeList)
'把雇员的详细资料输出到命令行
Dim employeeDetail As String
For Each employeeDetail In saEmployeeList
ed.WriteMessage(employeeDetail)
Next
ed.WriteMessage("----------------------" + vbCrLf)
Next