cad.net 选择集问题
保留关键字
无论是cad.arx还是cad.net的开发人员都需要注意交互的保留关键字.
N S W E
是东南西北四个方位,s是南方270度,在设置关键字的时候必须避开这四个关键字.
这个设置在早期的R14也有:
在使用选择集的时候,设置关键字不可以用a,它会是all的首字母,更多的参考一下飞诗的lisp选择集屏蔽关键字.
选择集预选
动图演示
本次测试
测试环境: Acad2008
这个bug是这样的,在预选状态时候执行命令,会导致该命令去重设一次选择集,
而没有预选则十分正常
错误代码
[CommandMethod("tta", CommandFlags.Modal | CommandFlags.UsePickSet | CommandFlags.Redraw)]
public static void tta()
{
Editor ed = Acap.DocumentManager.MdiActiveDocument.Editor;
var psr = ed.GetSelection();
if (psr.Status != PromptStatus.OK)
return;
//重构了一个选择集,为什么预选的时候,要等到再次点鼠标才能成功显示呢?
ed.SetImpliedSelection(psr.Value.GetObjectIds());
//ed.Regen();
}
如果最后加一句:ed.Regen();
是可以成功的,但是,这不应该啊.
因为其中的奥妙在于,预选的获取不应该利用 ed.GetSelection(pso, filter);
而是应该利用 ed.SelectImplied();
正确代码
[CommandMethod("tta", CommandFlags.Modal | CommandFlags.UsePickSet | CommandFlags.Redraw)]
public static void tta()
{
Editor ed = Acap.DocumentManager.MdiActiveDocument.Editor;
var psr = ed.SelectImplied();//预选
if (psr.Status != PromptStatus.OK) //数量0也是不ok
psr = ed.GetSelection(pso, filter);//手选
if (psr.Status != PromptStatus.OK)
return;
ed.SetImpliedSelection(psr.Value.GetObjectIds()); //重构了一个选择集
}
原因为什么会这样呢?
因为手选的 ed.GetSelection()
直接读取了选择集缓冲区,没有很好的处理预选状态..
导致了引起了选择的内部错误,是函数单一职责的封装问题.
相关阅读
com选择集的例子
/// <summary>
/// 输出WMF<br/>
/// </summary>
/// <param name="editor">命令行对象</param>
/// <param name="saveFile">保存文件</param>
/// <param name="ids">选择集的对象,为null时候手选</param>
/// <exception cref="ArgumentNullException"></exception>
public static void ExportWMF(this Editor editor, string saveFile, ObjectId[]? ids = null)
{
if (string.IsNullOrEmpty(saveFile))
throw new ArgumentNullException(nameof(saveFile));
if (File.Exists(saveFile))
throw new FileFormatException("文件重复:" + saveFile);
var dm = Acap.DocumentManager;
if (dm.Count == 0)
return;
// 剔除后缀
int dot = saveFile.LastIndexOf('.');
if (dot != -1)
{
// 因为文件名可以有.所以后缀点必须是最后\的后面
int s = saveFile.LastIndexOf('\\');
if (s < dot)
saveFile = saveFile.Substring(0, dot);
}
// ActiveSelectionSet:
// 第一次执行会触发选择,
// 再次重复命令执行的时候,它会无法再选择
// 因此此处选择一次,此时必然有当前选择集,它就直接获取当前选择集
if (ids == null || ids.Length == 0)
{
var psr = editor.SelectImplied();// 预选
if (psr.Status != PromptStatus.OK)
psr = editor.GetSelection();// 手选
if (psr.Status != PromptStatus.OK)
return;
}
#if zcad
var com = Acap.ZcadApplication;
#else
var com = Acap.AcadApplication;
#endif
var doc = com.GetProperty("ActiveDocument");
var wmfSet = doc.GetProperty("ActiveSelectionSet");
doc.Invoke("Export", saveFile, "wmf", wmfSet); // JPGOUT,PNGOUT
wmfSet.Invoke("Delete");
}
关键字回调
关键字回调时候会重新进入选择集,此时需要再空格才能退出.
如何立即退出?
arx在关键字上面发送cad命令\003,
net可以引发异常退出,自己构造一个异常.
net发送命令它会经过命令模式,进行重入,不好,还是触发异常好
循环选择
1,选择状态有夹点显示,此时缓冲区有id集合且不为0.
2,亮显没有夹点显示.
在Acad.net提供的手选函数,无法在缓冲区不为0时再选择,如果此时循环再手选,不会发生阻塞,而是直接就是ok的...
问题代码展示:
[CommandMethod(nameof(LoopBug))]
public void LoopBug() {
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
for (int i = 0; i < 5; i++) {
// 第1次会阻塞后执行手选,2+就不会了.
var opts = new PromptSelectionOptions() { MessageForAdding = "\n请选择图元:" };
var ss = ed.GetSelection(opts);
if (ss.Status != PromptStatus.OK) {
ed.WriteMessage("\n未选择任何图元.");
continue;
}
var ids = ss.Value.GetObjectIds();
ed.WriteMessage("\n选择了图元数:" + ids.Length);
ed.SetImpliedSelection(ids);
}
}
我们很容易想到一个解决方案,
如果不设置缓冲区,那么是不是只能亮显了?
但是亮显图元无法用shift剔除.
重点:
此时需要通过键盘钩子进行拦截shift键入,然后发送消息结束ssget.把全部图元亮显,求出鼠标矩形在哪些图元上面,暗显并ids也减去.
shift抬起时候,恢复亮显.
[CommandMethod(nameof(Pick), CommandFlags.UsePickSet)]//记得加这个flag
public void Pick() {
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
var opts = new PromptSelectionOptions("\n请选择对象:");
opts.AllowNone = true;
opts.AllowArbitraryInput = true; // 允许任意输入
// opts.SingleOnly = true;//只一个对象不回车
opts.SelectEverythingInAperture = true;// 点击模式
// 过滤器
SelectionFilter fr = new (new TypedValue[] {
new TypedValue((int)DxfCode.Start, "LINE"),
});
var ss = ed.SelectImplied();//预选
if (ss.Status == PromptStatus.OK) {
foreach(var id in ss.Value.GetObjectIds()) {
// 这里是伪代码,没有过滤条件就注释掉
if(!fr.Contains(id)) { _ids.Add(id); }
}
// 清空选择集
ed.SetImpliedSelection(new ObjectId[] { });
}
// 循环选择,重新手选
while(true) {
_ids.ForEach=>{ent.Highlight();}//亮显
ss = ed.GetSelection(opts,fr);
if (ss.Status == PromptStatus.OK) {
_ids.ForEach=>{ent.Unhighlight();}//暗显
_ids.AddRange(ss.Value.GetObjectIds());
ed.SetImpliedSelection(new ObjectId[] { });
continue;
}
if(ss.Status == PromptStatus.Cancel) {
/* 键盘钩子发送Esc消息才到这个状态
方案一:
先选中ids,再开启新选择集,然后减选.
我怀疑不行,因为选中状态(缓冲区有对象)会直接跳过手选.
shift按着也可能不切换到新选择集的反选模式.
释放shift也要结束这个选择集
_ids.ForEach=>{ent.Unhighlight();}//暗显
ed.SetImpliedSelection(_ids.ToArray());//重设选择集
var s2 = ed.GetSelection();
if(s2.Status == PromptStatus.OK) {
_ids.RemoveAll(s2.Value.GetObjectIds());
}
方案二:
不重设选择集,获取选择集矩形两个点ss.PickedPoint,然后直接剔除集合
没有代码
*/
}
// 结束循环
_ids.ForEach=>{ent.Unhighlight();}//暗显
break;
}
}
HashSet<ObjectId> _ids = new();
void 键盘钩子(s,e) {
if(e.Key == "shift".KeyCode()){
Acad.Handle.Foucs().发送异步Esc消息();
//这里_ids.事务跨线程!无法操作图元.
}
}
选择集的本质:
啊?其实可以自己实现一个两点矩形,然后通过选框范围,再触发四叉树选择.
选框还可以利用透明winform画一个颜色,并且才用滤镜算法...很麻烦吗?
(完)