TGridDrawState名字空間衝突問題及解決方法(E2015 Ambiguity between 'TGridDrawState' and 'Gridseh::TGridDrawState')

gxsky發表於2009-03-05

作者:ccrun(老妖) 

相信大家遇到過很多類似這樣的問題,用了某些第三方元件後,編譯時提示:
E2015 Ambiguity between 'TGridDrawState' and 'Gridseh::TGridDrawState'

E2015 Ambiguity between 'TGridDrawState' and 'Grids::TGridDrawState'
之類的,這是典型的名字空間衝突,ccrun(老妖)在CSDN也幫助解決過多次類似的問題,所以寫這篇文件旨在於總結一下,希望能對後來者有幫助。文章是以TGridDrawState來說事,但是道理是相同的,適合VCL中其他的型別是。為了增加文章的字數並且為了顯的本文章有力度,多加了一些廢話在裡面(ccrun是不是很有公務員的潛質呢? - -#)。

TGridDrawState是VCL中Grids單元定義的一個集合(Set)型別,常用在表格(Grid)的單元格(Cell)繪製中,原型如下:
enum Grids__3 { gdSelected, gdFocused, gdFixed };
typedef Set<Grids_3, gdSelected, gdFixed> TGridDrawState;

其中
gdSelected 表示單元格處於被選擇狀態
gdFocused 表示單元格正獲得焦點
gdFixed 表示單元格是固定的(行或列頭)

VCL中常用的Grid控制元件有: TStringGrid, TDrawGrid, TDBGrid,三者都繼承自TCustomGrid,類關係如下:
      TCustomGrid
      /        /
 TDrawGrid   TCustomDBGrid
    |            |
TStringGrid  TDBGrid

TCustomGrid提供了OnDrawCell的方法,使得我們可以自畫單元格,原型如下:
virtual void __fastcall DrawCell(int ACol, int ARow, const TRect &ARect, Grids::TGridDrawState AState) = 0;

TDrawGrid和TStringGrid一直沿用了OnDrawCell這個方法,而TCustomDBGrid卻走上另一條路,增加了OnDrawColumnCell和OnDrawDataCell方法,原型如下:
DYNAMIC void __fastcall DrawColumnCell(const Types::TRect &Rect, int DataCol, TColumn* Column, Grids::TGridDrawState State);

DYNAMIC void __fastcall DrawDataCell(const Types::TRect &Rect, Db::TField* Field,
Grids::TGridDrawState State);
分別對應繪製某列的單元格和單個的單元格。到了TDBGrid這一代的時候,乾脆把OnDrawCell方法也隱藏了,嘿嘿,所以TCustomGrid的這兩個孫子(TStringGrid和TDBGrid)的自畫單元格事件是不太一樣,就象表兄弟似的,看著有點相似,卻又有很多不同。這三個自畫函式的最後一個引數都是TGridDrawState,用來指示當前繪製單元格的狀態。前面加Grids::是因為VCL中的TGridDrawState的名字空間(namespace)是Grids。
這本來這是一個和諧的局面,可是大家都知道C++Builder和Delphi都支援在VCL基礎上派生出來的第三方元件,這實在是個好功能,相當的好啊。你可以擴充套件已有的元件,寫一個新的功能更強的或者利用第三方現成的元件,加快工程的開發進度,美化介面,增強程式功能等等。不過,在方便的同時,也帶來一些負作用:相容性問題。先不說針對不同的IDE版本分別需要不同的元件包(bpl),就元件內部來講,由於新元件的不斷擴充,類,物件,方法,資料型別,名字空間也在不斷的增多,所以有衝突是難免的。有了問題我們就著手解決,下面做個試驗:
  首先安裝Ehlib v4.14 Full Source版本,本站有下載(廣告來的真及時哦):
  http://www.ccrun.com/view.asp?id=149
  新建一個工程,放置一個Ehlib元件中的DBGridEh和一個標準的元件DBGrid在窗體上
  分別雙擊DBGridEh和DBGrid的OnDrawDataCell事件,在IDE產生的兩個函式體中分別加一行註釋://,這是為了在測試編譯時不至於因為函式體空著而被編譯器自動消除了函式宣告及定義部分。
  在工程屬性中新增一下Ehlib的標頭檔案路徑:Project-->Options-->Directories/Conditionals-->Include path中,找到Ehlib的BCB6目錄並新增進來。
  然後試著編譯,彈出一個對話方塊,說找不到元件的標頭檔案:GridsEh,你會說,日,不是新增了Include path了嗎?表著急的說,IDE在安裝Ehlib元件時(原始碼版),生成的標頭檔案是.hpp的,而在單元檔案.h中宣告的#include <xxx>卻是.h的,所以會說找不到標頭檔案了。不過沒有關係,點選查詢標頭檔案視窗的Broswer按鈕,找到Ehlib/BCB6目錄下的GridsEh.hpp,點選OK,恩,這一關算是過了,不關馬上就又產生Error資訊了,大名鼎鼎的E2015錯誤,也就是文章開頭說的那個提示資訊:兩個型別不明確,因為編譯器找到分別屬於兩個名字空間的相同型別的宣告,但是在這裡卻不知道該用具體的哪一個。暈了吧。
  喝口茶,廣告時間:歡迎光臨 C++Builder研究 - http://www.ccrun.com/
  這個時候很多朋友就該上網發帖子問或者搜尋解決方案了。有的朋友把三方元件的標頭檔案中的有衝突的資料型別前全手工加上了名字空間,有的是把VCL自帶的元件標頭檔案中相關的資料型別前加了名字空間。不過ccrun不太建議用這樣的方法。我的原則是儘量不修改IDE生成的標頭檔案內容,注意是儘量而不是從來不,有的人喜歡摳字眼。除非是迫不得已。比如你用Pascal寫了一個元件,元件響應MouseUp事件,於是新增一行 property OnMouseUp; 但是在書寫時卻寫成了onMouseUp(小寫的o),因為在Pascal中不區分大小寫,在BCB中可以成功的編譯和安裝,但是在使用這個元件時就問題來了:編譯器編譯Pas檔案,同時生成相應的.hpp檔案,剛才的property OnMouseUp在.hpp中的宣告就變成了:__property onMouseUp; 汗!Pascal檔案的不區分大小寫變成.cpp或.hpp以後就成了災難了。這是一件很鬱悶的事,因為C++Builder不認識.hpp中的onMouseUp,只認識OnMouseUp。這時才不得不手工修改.hpp檔案。扯遠了扯遠了。繼續名字空間衝突的解決方案。
// 本文轉自 C++Builder研究 - http://www.ccrun.com/article.asp?i=1003&d=15f173
  剛才說的情況,Grid元件的的OnDrawColumnCell處理事件(也就是處理的函式)宣告是由IDE自動產生的,如下:
__published: // IDE-managed Components
    TDBGridEh *DBGridEh1;
    TDBGrid *DBGrid1;
    void __fastcall DBGrid1DrawDataCell(TObject *Sender, const TRect &Rect,
        TField *Field, TGridDrawState State);
    void __fastcall DBGridEh1DrawDataCell(TObject *Sender,
        const TRect &Rect, TField *Field, TGridDrawState State);
  可以看到兩個函式宣告中,都使用了TGridDrawState型別,而事實上,這兩個TGridDrawState是不同的,因為屬於不同的名字空間,但是IDE沒有加上名字空間。既然IDE自動生成的函式宣告不好用,那麼幹脆不用了。我們自己宣告和定義Grid元件的的OnDrawDataCell事件處理函式。
先做一下清除工作:選中兩個Grid,把OnDrawDataCell事件中的函式名刪掉。然後.cpp檔案中兩個DrawDataCell函式中的註釋// 去掉,按一下儲存按鈕,IDE會自動將這兩個空函式的宣告和定義刪除,接下來:

在單元檔案的.h中宣告:
__published: // IDE-managed Components
    TDBGridEh *DBGridEh1;
    TDBGrid *DBGrid1;
public:
    // 注意這裡,把事件宣告在了public段內,而不是__published,
    // 就這意味著在設計時選中Grid元件檢視其OnDrawDataCell事件時,
    // 這個函式名將不會出現在列表中。不過即使這兩個宣告在__published段內,
    // 在設計時的事件列表中也看不到,因為函式的引數有變化了
    void __fastcall DBGrid1DrawDataCell(TObject *Sender, const TRect &Rect,
        TField *Field, Grids::TGridDrawState State);
    void __fastcall DBGridEh1DrawDataCell(TObject *Sender,
        const TRect &Rect, TField *Field, Gridseh::TGridDrawState State);
    // 注意上面兩個TGridDrawState都新增了名字空間

在單元檔案的.cpp中定義:
//---------------------------------------------------------------------------
// 63 63 72 75 6E 2E 63 6F 6D
void __fastcall TForm1::DBGrid1DrawDataCell(TObject *Sender, const TRect &Rect,
    TField *Field, Grids::TGridDrawState State)
{
    // DBGridEh的DrawDataCell事件
    //
    // 此處填寫具體的繪製DBGrid單元格程式碼
}
//---------------------------------------------------------------------------
void __fastcall TForm1::DBGridEh1DrawDataCell(TObject *Sender,
    const TRect &Rect, TField *Field, Gridseh::TGridDrawState State)
{
    // DBGridEh的OnDrawColumnCell事件
    //
    // 此處填寫具體的繪製DBGridEh單元格程式碼
}

  因為不能在設計時為元件的處理事件選擇函式了,所以需要為兩個Grid指定OnDrawColumnCell處理事件,不然的話,這兩個函式就白寫了,不會觸發的:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
    DBGridEh1->OnDrawDataCell = DBGridEh1DrawDataCell;
    DBGrid1->OnDrawDataCell = DBGrid1DrawDataCell;
}

  再次編譯,終於OK了。
  對本文章所述有其他看法可聯絡本人:cbfans#163.com或者QQ:165332
再次嚴重BS不負責任的轉載者(點名批評天X網的某某小P孩),轉載不留名不說,還篡改作者資訊,把玉樹臨風的妖哥竟改成某某鳥人。- -#,素質,注意素質。還有就是本文章是VCL相關的內容,不要冒失的標題寫成在C++中如何如何就貼出去招搖了,太不敬業了。
  其他類似的名字空間衝突都可以按這樣的方法解決。比如BusinessSkinForm元件包中的bsSkinStringGrid,bsSkinDrawGrid等,和其他的Grid混用時,如果都有各自的自畫單元格事件,都會有這情況發生。舉一反三就行了。
為避免成為妖哥眼中之不負責任的轉載者,所以原文未作刪改(含部分扯遠之內容)^_^

相關文章