1.Winform窗體與CAD關系、窗體與CAD焦點切換
非模態窗口修改CAD圖元
在非模態窗口中修改實體時,需要將圖層鎖定,否則會報錯;模態窗口則無此情況。
Winfrom中打開DWG圖紙文件
如果要在Winform中打開DWG圖形文件,這個Form必須用Application.ShowModelessDialog方式顯示,不然會報錯(執行環境無效)。
模態窗口焦點切換
使用模態窗口時,如果需要與CAD主窗體進行交互,則用using (EditorUserInteraction edUI = ed.StartUserInteraction(this));使用非模態窗口時,會出現焦點切換問題
(如:當在非模態窗口中點擊按鈕后要去CAD中選擇一個實體,但是應用程序的焦點還在非模態窗口中,此時需要在CAD主窗口中點擊一下讓CAD獲取焦點,才能正常進行實體選取),此時可以用WinAPI中的SetFocus將焦點移到CAD主窗口即可:
[DllImport("user32.dll", EntryPoint = "SetFocus")]
public static extern int SetFocus(IntPtr hWnd);
調用:SetFocus(Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Window.Handle);
要注意的是:需要配合窗體的MouseEnter、MouseLeave事件使用,但效果並不太理想,如果鼠標移動較快的時候,事件來不及觸發。暫時還沒有找到其他更好的方法,C++中可以接收一個KeepFocus消息,來監視和設置程序焦點,使焦點轉換更靈活,
C#中也可以收到CAD發來的這個消息,但是這個消息的Msg值不是固定的,C++中使用的時候不需要管這個值,因為是由CAD已經提供了,所以可以准確地方監聽這個消息;但是在C#中CAD並沒有提供這個Msg值,所以並不能使用監聽Windows消息的方法來實現焦點的切換功能。
Application.ShowModelessDialog在不同版本CAD的調用不同
在CAD2010之前,這個方法有三個重載:
public static void ShowModelessDialog(Form formToShow);
public static void ShowModelessDialog(IWin32Window owner, Form formToShow);
public static void ShowModelessDialog(IWin32Window owner, Form formToShow, bool persistSizeAndPosition);
而在CAD2010中,這個方法有五個重載:
public static void ShowModelessDialog(Form formToShow);
public static void ShowModelessDialog(IntPtr owner, Form formToShow);
public static void ShowModelessDialog(IWin32Window owner, Form formToShow);
public static void ShowModelessDialog(IntPtr owner, Form formToShow, bool persistSizeAndPosition);
public static void ShowModelessDialog(IWin32Window owner, Form formToShow, bool persistSizeAndPosition);
然而,在CAD2010中,第三和第五個重載並沒有什么用,因為CAD2010的窗體對象(Application.MainWindow,即Autodesk.AutoCAD.Windows.Window類)不再實現IWin32Window接口,所以第一個參數沒辦法傳了,只能使用第一、二、四個重載方法。這是我目前的研究結果,不知道第三、第五個重載方法在CAD2010中還有沒有其他使用方式。
另外,上面的ShowModelessDialog是針對Winform窗體的,而CAD2010的窗體卻都是WPF的,於是在CAD2010中又提供了一個新接口方法:ShowModelessWindow,這個方法也是有五個重載:
public static void ShowModelessWindow(System.Windows.Window formToShow);
public static void ShowModelessWindow(IntPtr owner, System.Windows.Window formToShow);
public static void ShowModelessWindow(System.Windows.Window owner, System.Windows.Window formToShow);
public static void ShowModelessWindow(IntPtr owner, System.Windows.Window formToShow, bool persistSizeAndPosition);
public static void ShowModelessWindow(System.Windows.Window owner, System.Windows.Window formToShow, bool persistSizeAndPosition);
這個接口方法是用來顯示WPF窗體對象的,但是,雖然有五個重載方法,但是應該和ShowModelessDialog一樣,只有第一、二、四個方法能正常使用。
2.CAD注冊表
CAD 32與64位系統注冊表不同
AutoCAD 注冊表項在32位與64系統中的不同
AutoCAD注冊表信息讀取,如獲取電腦上已安裝的所有CAD版本、安裝路徑等注冊表信息。
32位Windows系統中,這些信息保存在:HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD 中;
64位Windows系統中,32位的CAD軟件信息保存在HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Autodesk\AutoCAD 中,64位CAD軟件信息保存在HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD 中。
在64位系統中,安裝的CAD版本可能是32位,也可能是64位。
如果在32位的CAD中調用我們的DLL,此DLL讀取 HKEY_LOCAL_MACHINE\SOFTWARE 時,實際讀到的是 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node;如果在64位的CAD中調用我們的DLL,則此時DLL讀取 HKEY_LOCAL_MACHINE\SOFTWARE 時,會得到真正的HKEY_LOCAL_MACHINE\SOFTWARE 對象。
上述情況是因為,為了做到64位系統兼容32位程序,同時又為了防止注冊表項沖突,所以32位應用程序在64位Windows系統中操作 HKEY_LOCAL_MACHINE\SOFTWARE 鍵時會被自動轉到 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node。
具體說明見:http://www.cnblogs.com/mingmingruyuedlut/archive/2011/01/20/1940371.html
這種問題的解決方法參考:較通用的WINAPI方法
http://www.cnblogs.com/mingmingruyuedlut/archive/2011/01/21/1941225.html
適用.NET 4.0的新方法:
http://www.cnblogs.com/langu/archive/2012/02/26/2368877.html
但是還有一點要注意:在CAD二次開發中,由於我們的DLL最終是要被CAD調用的,所以我們程序集的位數是由CAD的位數來決定的。
注冊表權限
不允許所請求的注冊表訪問權。在win7等64位操作系統中,在CAD調用的C# dll插件中,如果操作注冊表(讀取、寫入等),可能會報這個錯誤,原因是CAD不是以管理員權限運行的,權限不足,要防止這種情況發生,需要讓CAD以管理員身份運行,在CAD的快捷方式或安裝目錄下的acad.exe上右鍵,選擇“屬性”,切換到“兼容性”頁面,選中“以管理員身份運行此程序”,這樣就不會出現這種情況了。
另外,對於這種錯誤,網上還有一種解決方案,是在操作注冊表的方法上加上RegistryPermissionAttribute屬性標識,如[RegistryPermissionAttribute(SecurityAction.PermitOnly, Read = @"HKEY_LOCAL_MACHINE\SOFTWARE\YourApp")],但是這種情況在CAD開發上好像並不適用,會提示錯誤。
CAD通過注冊表自動加載
用注冊表實現C#DLL或C++ARX隨CAD自動加載,可以往以下兩個地方添加啟動信息:
1)、HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\RXX.X\ACAD-XXXX:XXX\Applications
2)、64位系統中的64位CAD 或 32位系統中的CAD:HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\RXX.X\ACAD-XXXX:XXX\Applications
64位系統中的32位CAD:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Autodesk\AutoCAD\RXX.X\ACAD-XXXX:XXX\Applications
其中,Current_User項不分32位和64位,而LOCAL_Machine則會分32位和64位,用程序進行注冊表讀寫時需要注意。但是CURRENT_USER注冊表信息只對當前登錄的系統用戶有效,而LOCAL_MACHINE注冊表信息則會對系統所有用戶都有效。
C#DLL或C++ARX隨CAD啟動需要向Applications項中添加一個自己命名的項,然后在該項下添加以下幾個值:
DESCRIPTION |
字符串 |
程序名稱或描述 |
LOADCTRLS |
DWord |
啟動類型(十進制值): 2=隨CAD啟動加載 4=有命令觸發時加載 8=有載入請求時加載 16=從不加載該應用程序 32=顯式加載該應該程序 |
LOADER |
字符串 |
要加載的文件位置 |
MANAGED |
DWord |
如果是C#DLL,十進制為1,如果是ARX則不添加此項 |
LOADCTRLS的值可以單獨使用,也可以多個值相加使用,例如:12=4+8=有命令觸發時加載或有載入請求時加載。
另外,如果要使用類型4(有命令觸發時加載),除了設置以上幾個值之外,還需要在自己創建的應用程序節點下創建新項並注冊命令,這樣在CAD啟動時,不會直接加載DLL或ARX,當注冊的命令被觸發時,會加載DLL或ARX:
在自己的應用程序節點下添加“Commands”項,在此項中添加String類型的值,這些值就是注冊的命令,例如:
圖中右側的Fcmd就是我們注冊的命令,當啟動CAD后,MyAP所指向的程序並不會被直接加載,只有當Fcmd命令被使用的時候,CAD才會加載MyAP程序。
關於命令注冊這個,在官方AutoCAD.NET文檔和常見的二次開發書籍中都沒有提到,還是無意間從這里(https://through-the-interface.typepad.com/through_the_interface/2009/05/creating-demand-loading-entries-automatically-for-your-autocad-application-using-net.html)找到的。
3.事務
嵌套事務問題
使用嵌套事務時,如果外層的事務不提交,即使里面的事務提交了,最終也會失效。
4.CAD文件、塊、擴展數據操作
寫塊克隆
寫塊克隆:WblockCloneObjects第二個參數為塊表記錄模型空間的ObjectID,此方法會將一個對象集(實體集)完全克隆到另一個DWG文件中,包括圖層名、圖層設置、坐標位置、擴展數據等。
后台打開dwg文件,用SaveAs保存
在CAD中用后台打開方式打開DWG圖形文件,並進行修改后,要用SaveAs保存,用Save會出錯。
Autodesk.AutoCAD.DatabaseServices.DataTable 擴展數據表使用時要注意
當對象實體的擴展字典中還沒有這個表時,
DBDictionary.SetAt和Transaction.AddNewlyCreatedDBObject將擴展數據表添加到實體對象,
但是修改時就不能再這樣了,只需要獲取DataTable對象,然后用 DataTable.SetCellAt 方法修改數據表的值,最后提交事務即可。
(注:向實體擴展字典中添加DataTable時,必須將DataTable添加到BlockTableRecord中,不然在保存CAD圖紙的時候,會保存失敗)。
匿名塊
如果要創建匿名塊(在CAD的塊編譯器中看不到,但是在BlockTable中可以查找到並可以添加引用顯示到圖形中,並且不可以手動編輯),只需要在創建BlockTableRecord對象的時候指定其Name為“*U”即可,當把這個BlockTableRecord被成功添加到BlockTable后,會自動會重新分配一個名稱,格式為“*U數字”。
5.CAD選擇集
SelectionFilter 根據XData擴展數據過濾選擇集問題
使用根據對象的擴展數據XData進行過濾選擇時,如果過濾條件僅為DxfCode.ExtendedDataRegAppName(即注冊的應用程序名)時,可以過濾所有類型的對象,
但是如果要用DxfCode.ExtendedDataAsciiString、DxfCode.ExtendedDataInteger16等具體數據內容進行過濾時,則僅能選取到部分類型的對象,如:Line、PolyLine、Ellipse、Region,而像DBText、MText、Circle等類型的實體則會被忽略掉。
SelectionFilter過濾選擇單行文本對象問題
用SelectionFilter過濾選擇文本時,切記單行文本必須寫TypedValue((int)DxfCode.Start,"Text"),要寫成"Text",而不是"DBText"。
6.CAD圖案填充
Hatch填充時的問題
用Hatch填充Region時,如果面域對象是由多個面域合並而來,會填充失敗,此時可以調用Region對象的Explode方法將Region炸散,得到多個Region,然后將這些Region一一填充。
對於Hatch.AppendLoop第二個參數:ObjectIdCollection 類型,傳入的是一個或多個實體對象的ID,這些實體必須滿足以下幾個條件:
-
- 如果只傳入一個對象,那么這個對象必須是一個非“回”字型閉合對象,如閉合的pline、單個的Region、Circle、Ellipse等;如果傳入的對象是回字型,即中間是空心的,那么將會報錯,填充將失敗。
- 如果傳入的是多個對象,那么這些對象必須正好可以首尾相接地組成一個閉合圖形,且沒有富余的對象,這樣才能成功填充;如果組成的圖形不能閉合,或者閉合后還剩余一些無用處的線、弧等,又或者組成了多個閉合圖形,那么將會報錯,填充將失敗。
7.CAD圖層
當前圖層代碼
AutoCAD.NET獲取CAD當前圖層代碼: Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Database.Clayer;
8.命令
修改命令快捷鍵並立即生效
在CAD中點擊:工具-》自定義-》編輯程序參數(acad.pgp),在打開的文件中找到“Command alias format”,在其下方可以看到命令別名定義列表,定義的格式為:“別名,*命令”,每行只能定義一個,定義完成之后,重啟CAD即可生效。如果不想重啟CAD,還有兩種方式可以讓你的定義生效:
1)、在命令文本中輸入reinit,回車,在彈窗中選中“PGP文件”,然后確定,如下圖:
2)、在命令文本中輸入re-init,回車,然后輸入16,再回車即可。
第二種方法應該可以在程序中進行實現,不知道AutoCAD.NET中有沒有提供現成的方法,不過可以用sendcommand實現。這樣可以為用戶提供快捷鍵的自定義功能,並且在用戶定義完之后,可以立即生效。acad.pgp文件位置在:CAD安裝目錄\UserDataCache\Support中,是一個隱藏文件。
SendStringToExecute向CAD發送命令
使用SendStringToExecute向CAD發送命令時,如果命令參數中帶有路徑,這時如果執行有問題,可以嘗試使用“/”代替路徑中的“\”。
P/Invoke方式調用CAD的acedCmd或acedCommand方法發送同步命令
關於在C#中使用P/Invoke方式調用CAD的acedCmd或acedCommand方法發送同步命令,如果這兩個接口函數是在自己的自定義命令中調用的,那么可以成功執行,並且是同步的,
但如果是在自己的某個窗體程序里調用的,那么可能會執行不成功(接口返回-5001)或者執行成功,但成了異步命令。另外,CAD2013以下版本這兩個接口是在acad.exe中的,而從CAD2013開始放到了accore.dll中。
另外,在CAD中還有一個未公開接口也可以發送同步命令:acedPostCommand,這個命令同樣在CAD2013以下版本中是在acad.exe中,從CAD2013開始放到了accore.dll中,
另外還有一點不同的是:在CAD2008中它的入口點是“?acedPostCommand@@YAHPB_W@Z”,而從CAD2009開始,它的入口點名稱變成了“?acedPostCommand@@YAHPEB_W@Z”,這一點使用的時候需要注意。
當加載的多個DLL或ARX中有同名的自定義命令時,最后在使用時觸發的是最后加載的那個程序集中的命令。
9.PaletteSet
PaletteSet可以用程序控制其最小尺寸,停靠位置,可停靠位置,按鈕、菜單顯示等,但是用程序指定其停靠位置時,每個面板只能占據單獨一列或一行,不能做到讓多個面板上下同列多行停靠,或左右同行多列停靠。
PaletteSet有兩個重載函數:
一般都用第一個,即在聲明PaletteSet時只傳一個name參數。
第二個重載有兩個參數,一個是name參數,一個是guid參數,CAD會以這個guid為標識,記錄此面板的位置、大小等信息,這樣在下一次,加載這個面板的時候,如果不指定面板的Dock屬性,則會自動使用上次保存的設置來管理面板的停靠位置和大小等。
即:第一次在CAD里加載此面板時,可以給面板指定一個默認的停靠位置,此時用戶可以隨意拖動面板重新指定停靠位置,當用戶關閉CAD時,這個面板的停靠設置會被CAD保存下來,下一次加載的時候,如果不再給此面板指定Dock屬性,則會自動使用上次保存的設置信息進行停靠,要實現這一點,最好用PaletteSet的Add和Save事件進行配合(具體實現,請看這篇文章https://adndevblog.typepad.com/autocad/2015/04/finding-if-paletteset-is-newly-created.html#tpe-action-posted-6a0167607c2431970b01b8d11e7d09970c)。
但是,這種實現方式,會有一個問題:如果在聲明PaletteSet時指定了傳了name參數,CAD會自動創建一個和這個name一樣的啟動命令,就像每次打開CAD時的“COMMANDLINE”命令一樣,每次CAD啟動時都會執行,但是我們創建PaletteSet的方法並不一定會聲明成為一個命令,而且就算聲明成命令,這個命令也不一定和PaletteSet的name參數一樣,這樣在CAD啟動時,就會提示“未知命令”,令人很不爽,解決方法有三種:
(1) 將創建PaletteSet的方法聲明為一個命令,且命令要和PaletteSet的name參數保持一致,而且你的dll要隨CAD自動啟動(可以用注冊表方法實現),但是這種方法太過狹隘,並不好。
(2) 在聲明PaletteSet的時候,name參數只傳一個空字符串,PaletteSet實例化之后,再為PaletteSet指定Text屬性指定面板的名稱即可,這樣CAD啟動時,就不會自動執行命令了。
(3) CAD保存面板停靠信息時,是保存在特殊文件夾Application Data中的,即C#中的Environment.SpecialFolder.ApplicationData文件夾,比如我在XP系統中用32位CAD 2008測試,保存路徑為:C:\Documents and Settings\Administrator\Application Data\Autodesk\AutoCAD 2008\R17.1\chs\Support\Profiles\FixedProfile.aws,這是一個xml文件。
如果聲明PaletteSet的時候指定了第二個guid參數,CAD會在FixedProfile.aws文件ToolsInfo節點中添加一個Tool節點,用來保存面板信息,同時會在StartupInfo中也添加一個Tool節點,指定啟動命令,這個啟動命令和PaletteSet的name參數一樣,就算在聲明PaletteSet的時候傳的是一個空字符串,CAD也會在StartupInfo節點下添加一個Command=""的啟動命令,CAD啟動時,應該是會把空命令忽略.
所以使用第二個解決方法,CAD在啟動時不會執行面板對應的啟動命令,如果你對此有強迫症,不希望CAD保存啟動命令節點的話,那么你可以自行將其刪除,方法如下:
在聲明PaletteSet的時候,可以隨意指定name屬性,或干脆留空,這個不再有影響;實現CAD提供的IExtensionApplication接口,此的接口中的Terminate會在你的DLL被卸載時(即CAD被關閉時)執行,我們可以在這個方法內,打開當前CAD對應的FixedProfile.aws文件,然后把StartupInfo節點下面板對應的啟動命令刪除,此命令和之前聲明PaletteSet的時候指定的name參數一樣,刪除之后,保存FixedProfile.aws文件即可。
10.CAD事件處理
Document.CommandEnded
關於Document.CommandEnded事件:正常來說自己定義的命令都應該可以觸發CommandEnded事件.
但如果你的命令中使用了Application.ShowModelDialog,那么你的命令將不會觸發CommandEnded事件,如果把Application.ShowModelDialog換成Form本身的ShowDialog,則可以正常觸發CommandEnded事件。
Database_ObjectModified
關於Database_ObjectModified事件:如果啟動了一個事務,且最后沒有提交它,那么在這個事務中的所有更改都不生效,且不會觸發Database_ObjectModified事件。
如果最后提交了這個事務,那么如果在打開對象時是以OpenMode.ForWrite方式打開的,那么不管到底是否對這個對象有更改都會觸發Database_ObjectModified事件;
而如果在打開對象時是使用的OpenMode.ForRead打開的,后面沒有對實體進行修改則不會觸發Database_ObjectModified事件,如果后面使用UpgradeOpen升級了讀寫方式不管到底有沒有對實體做出修改,也都會觸發Database_ObjectModified事件。
另外,在此事件內部使用事務時要注意以下兩點,否則會彈出崩潰提示:Error handler re-entered;Exiting now
u 此事件內不能使用嵌套事務。
u 並且此事件外有其他事務正在被使用時不能再打開新事務。
ESC鍵影響DocumentManager_DocumentActivated
具體情況是:
有一個程序為DocumentManager綁定了DocumentActivated事件,在這個事件中進行圖紙掃描,掃描大部分的圖形實體。在打開測試圖紙時,會有一個彈窗:
此時如何按下ESC來關閉此窗體,那么在窗體關閉之后由於圖紙窗口又獲取到了焦點,所以會觸發DocumentActivated事件,但是之前按的ESC鍵會影響事件中掃描程序的執行,使程序報錯:eUserBreak。
這個情況只在AutoCAD2010中會出現,在其他版本CAD中測試未發現這種情況。
解決方案:
至於在CAD2010中,用了一個簡單方法解決了這個問題:創建一個窗體,在創建中放一個定時器,把掃描程序放到定時器中執行,再定義一個公共方法啟動定時器,這樣在DocumentActivated事件中調用此窗體的公共方法啟動定時器進行圖紙掃描.
這樣不不需要彈出窗體就可以完成工作。除了這個方法使用線程或者直接聲明定時器也許也能解決這個問題,不過沒有進行嘗試,不知道行不行得通,等有時間試過之后再更新結果。
11.CAD炸開Explode
在使用Explode接口將實體炸開到一個DBObjectCollection中之后,發現不能直接按從尾到頭的順序去遍歷DBObjectCollection,否則會報錯(大致意思是說索引超出邊界或索引不能為負值),但事實上索引值肯定是沒有錯的。
后來發現,如果將使用正向對DBObjectCollection進行一次遍歷,或者按照正向順序取出DBObjectCollection的前兩三個元素(例如DBObject dbo1=dbos[0];DBObject dbo2=dbos[1];先用正向索引讀取一下集合的前幾個元素),
然后再使用倒序方式對DBObjectCollection進行遍歷,就不會再提示錯誤了,這個問題感覺有些不可思議。(在AutoCAD2008中發現的問題,其他版本沒有測試)
12.CAD圖元編輯
使用各種實體后調用Dispose釋放
在使用各種實體對象之后(如DBObject、Entity、Region、Polyline、DBObjectCollection等),盡量主動調用其Dispose方法進行釋放,否則當你頻繁對一組實體進行重復操作時,可能會引發一些錯誤,甚至引起CAD的崩潰。
面域顏色
如果為面域指定了實體顏色,在此面域和其他面域進行了布爾運算之后(面域相加、相減、合並),面域的顏色會變成ByLayer 。
過濾選擇polyine,類型名LWPolyline
用過濾選擇進行polyine的時候,類型名必須寫成LWPolyline,而要過濾選擇polyline2d對象時,類型名要寫成polyline。
IntersectWith
實體的IntersectWith方法,當參與運算的實體的頂點越多耗時越長,兩個實體,當調用此方法的主實體是頂點數較少的那個實體,則耗時會較短,當調用此方法的主實體是頂點數較多的那個實體,則耗時會較長(使用Stopwatch的ElapsedTicks檢測)。
13.CAD低版本VisualStudio調試
使用VS2010 + AutoCAD2008進行開發時,如果不能調試,可以嘗試修改AutoCAD2008安裝目錄中的acad.exe.config文件:
<configuration>
<startup>
<!--We always use the latest version of the framework installed on the computer. If youare having problems then explicitly specify .NET 2.0 by uncommenting the following line.
<supportedRuntime version="v2.0.50727"/>-->
<supportedRuntime version="v2.0.50727" />
</startup>
</configuration>
使用VS調試AutoCAD 2012-2014版本時,如果出現命中不了斷點,且提示“無可用源”,那么可以嘗試查看AutoCAD的FIBERWORLD變量的值是否為1,如果是1則可以使用NEXTFIBERWORLD命令將其修改為0,修改完成之后再進行調試即可正常命中斷點。(詳情可見這里https://www.cnblogs.com/junqilian/archive/2011/03/18/1988327.html)
14.CAD報錯
eInvalidOpenState錯誤問題
當在事務A中調用GetObject方法獲取了對象E,在事務A結束之后,如果在另一個事務B中調用E的UpgradeOpen時,會出現錯誤提示:eInvalidOpenState,這時候需要在事務B中用GetObject根據E.ObjectId重新獲取E才行。
eWasNotOpenForWrite錯誤問題
同問題情況類似,在事務A中調用GetObject方法獲取了對象E,在事務A結束之后,如果在另一個事務B中調用E的DowngradeOpen方法,則會出現錯誤提示:eWasNotOpenForWrite,這時候需要在事務B中用GetObject根據E.ObjectId重新獲取E。