原文链接:
Drag and Drop API
我们在讨论 Revit 2012 的拖放动作 ( drag and drop in Revit 2012) 时向大家展示了如何从外部进程触发标准的Revit拖放动作。但是在那时我们无法改变 Revit 接收拖放文件的默认行为。好消息是在 Revit 2013 里我们可以这么做了。
Revit 2013 SDK 的例程 UIAPI 向我们展示了最新的拖放API以及插件集成特性:应用程序可用性、预览控件、定制WPF选项对话框。需要注意的是:
1. UIAPI 是一个 External Application,所以无法使用 RvtSamples 来加载
2. UIAPI 没有在 Revit 2013 SDK 文档中出现
UIAPI 拖放命令会显示如下的无模式对话框
UIApplication DoDragDrop 方法
1. DoDragDrop ( ICollection<string> )
触发一次包含多个文件的拖放操作
2. DoDragDrop ( object, IDropHandler )
触发一次可定制Revit接收行为的拖放操作
ApplicationEntryPoint 类也提供同样的方法,但只是针对 Revit 宏。
拖放多个文件
DoDragDrop方法的第一个重载操作的对象是一组文件,并导致确定的Revit接收行为(注意:至少在当前版本这些默认行为是无法改变)。
从无模式对话框调用 Revit API 的缺点
值得注意的是,从无模式对话框调用 UIApplication.DoDragDrop 意味着在非 Revit API 上下文中调用 Revit API。绝大多数情况下这是非法的,可能导致无法预测的结果。所以通常我们应该从 Idling 事件或者 External 事件的回调函数中调用 Revit API。但是针对拖放操作,这种方式却可能导致死锁。
原因是 Revit 触发的 Idling 事件和 External 事件都是假设没有用户界面操作的。如果在它们的事件回调中调用触发UI操作,可能会导致如下情况:
1. UI请求被发送到相同的消息队列。但是由于消息队列还在等待 Idling/External 事件的返回,所以这个新的UI请求将永远无法被处理
2. UI请求被发送到另外的消息队列,这个不同的消息队列最终会触发一个新的 Idling/External 事件,然后导致用户界面死锁
所以结论令人失望,没有安全的方式从无模式对话框中调用拖放操作。
定制Revit处理方式的拖放
DoDragDrap的第二个重载是个令人激动的新特性。它允许我们定义Revit接收拖放文件的行为。我们需要做的就是实现一个 IDropHandler 接口,并且在它的 Execute() 方法中定义我们期望的行为。下面这个例子里,IDropHanlder 接收一个族类型的 ElementId,然后调用 PromptForFamilyInstancePlacement() 方法要求用户拜访它。显然,你可以向 IDropHanlder 传递任何定制的数据,来实现你的需求。
我们在讨论 Revit 2012 的拖放动作 ( drag and drop in Revit 2012) 时向大家展示了如何从外部进程触发标准的Revit拖放动作。但是在那时我们无法改变 Revit 接收拖放文件的默认行为。好消息是在 Revit 2013 里我们可以这么做了。
Revit 2013 SDK 的例程 UIAPI 向我们展示了最新的拖放API以及插件集成特性:应用程序可用性、预览控件、定制WPF选项对话框。需要注意的是:
1. UIAPI 是一个 External Application,所以无法使用 RvtSamples 来加载
2. UIAPI 没有在 Revit 2013 SDK 文档中出现
UIAPI 拖放命令会显示如下的无模式对话框
UIApplication DoDragDrop 方法
1. DoDragDrop ( ICollection<string> )
触发一次包含多个文件的拖放操作
2. DoDragDrop ( object, IDropHandler )
触发一次可定制Revit接收行为的拖放操作
ApplicationEntryPoint 类也提供同样的方法,但只是针对 Revit 宏。
拖放多个文件
DoDragDrop方法的第一个重载操作的对象是一组文件,并导致确定的Revit接收行为(注意:至少在当前版本这些默认行为是无法改变)。
- 当只有一种CAD格式或者图片格式的文件被拖放时,Revit会弹出一个新的导入拜访编辑器来处理文件导入
- 当多种CAD格式或者图片格式的文件被拖放时,Revit仅会针对第一种格式弹出一个新的导入拜访编辑器来处理文件导入
- 当只有一个族文件被拖放时,Revit会加载该族,并弹出一个编辑器来拜访族实例
- 当多个族文件被拖放时,Revit会加载所有的族
- 当多个族文件加上其它格式的文件被拖放时,Revit会尝试打开所有文件(注意:此时不会加载族了)
- 如果一个有效文件或者一组有效文件被拖放,Revit会尝试合理地使用它们。如果有文件无法使用,Revit会弹出一个失败消息,但是不会抛出异常,也不会通知插件
private void listBox1_MouseMove( object sender, MouseEventArgs e )
{
if( System.Windows.Forms.Control.MouseButtons == MouseButtons.Left )
{
FamilyListBoxMember member = (FamilyListBoxMember) listBox1.SelectedItem;
// 触发标准的 Revit 拖放行为
List<String> data = new List<String>();
data.Add( member.FullPath );
UIApplication.DoDragDrop( data );
}
}
从无模式对话框调用 Revit API 的缺点
值得注意的是,从无模式对话框调用 UIApplication.DoDragDrop 意味着在非 Revit API 上下文中调用 Revit API。绝大多数情况下这是非法的,可能导致无法预测的结果。所以通常我们应该从 Idling 事件或者 External 事件的回调函数中调用 Revit API。但是针对拖放操作,这种方式却可能导致死锁。
原因是 Revit 触发的 Idling 事件和 External 事件都是假设没有用户界面操作的。如果在它们的事件回调中调用触发UI操作,可能会导致如下情况:
1. UI请求被发送到相同的消息队列。但是由于消息队列还在等待 Idling/External 事件的返回,所以这个新的UI请求将永远无法被处理
2. UI请求被发送到另外的消息队列,这个不同的消息队列最终会触发一个新的 Idling/External 事件,然后导致用户界面死锁
所以结论令人失望,没有安全的方式从无模式对话框中调用拖放操作。
定制Revit处理方式的拖放
DoDragDrap的第二个重载是个令人激动的新特性。它允许我们定义Revit接收拖放文件的行为。我们需要做的就是实现一个 IDropHandler 接口,并且在它的 Execute() 方法中定义我们期望的行为。下面这个例子里,IDropHanlder 接收一个族类型的 ElementId,然后调用 PromptForFamilyInstancePlacement() 方法要求用户拜访它。显然,你可以向 IDropHanlder 传递任何定制的数据,来实现你的需求。
public class LoadedFamilyDropHandler : IDropHandler
{
public void Execute( UIDocument doc, object data )
{
ElementId familySymbolId = (ElementId) data;
FamilySymbol symbol = doc.Document.GetElement( familySymbolId ) as FamilySymbol;
if( symbol != null )
{
doc.PromptForFamilyInstancePlacement( symbol );
}
}
}
private void listView_MouseMove( object sender, MouseEventArgs e )
{
if( System.Windows.Forms.Control.MouseButtons == MouseButtons.Left )
{
ListViewItem selectedItem = this.listView1.SelectedItems.Cast<ListViewItem>().FirstOrDefault<ListViewItem>();
if( selectedItem != null )
{
LoadedFamilyDropHandler myhandler = new LoadedFamilyDropHandler();
UIApplication.DoDragDrop( selectedItem.Tag, myhandler );
}
}
}