Building Coder(Revit 二次開發) - 拖放API

原文鏈接: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 拖放命令會顯示如下的無模式對話框

UIAPI DragAndDrop form

左側的ListView顯示已經加載的族,右側的List顯示所有可用的族。

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 );
      }
    }
  }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章