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