WPF 命名範圍
更新:2007 年 11 月
命名範圍 (Namescope) 是一種概念,也是一種程式設計物件,會儲存在物件的 XAML 定義名稱和其執行個體對等間的關聯性。WPF Managed 程式碼中的命名範圍是在載入 XAML 應用程式的頁面時建立。命名範圍程式設計物件是由 INameScope 介面所定義,而且是由實用類別 NameScope 所實作。
這個主題包含下列章節。
- 載入之 XAML 應用程式中的命名範圍
- 樣式和樣板中的命名範圍
- 命名範圍以及與名稱相關的 API
- 相關主題
載入之 XAML 應用程式中的命名範圍
在處理 XAML 頁面時,會在頁面的根項目上建立命名範圍。頁面中所指定的每個名稱都會加入到相關的命名範圍中。屬於通用根項目的項目 (例如 Page 和 Window) 一定會控制命名範圍。如果像 FrameworkElement 或 FrameworkContentElement 的項目成為標記頁面的根項目,會有 XAML 處理器隱含加入 Page 根,讓 Page 能提供命名範圍。就算一開始在 XAML 中沒有定義 Name 或 x:Name 屬性 (Attribute),依然會建立命名範圍。
如果您嘗試在任何命名範圍中使用相同的名稱兩次,會引發例外狀況。針對有程式碼後置 (Code-Behind) 且為完成編譯之應用程式一部分的 XAML,在為該頁面建立產生之類別時會引發例外狀況。
加入項目至剖析出的項目樹狀結構
若要將項目加入完成初始載入和處理的項目樹狀結構,必須針對定義命名範圍的類別呼叫適當的 RegisterName 實作。否則,名稱就無法透過 FindName 等方法來參考加入的物件。僅設定 Name 屬性 (或 x:Name 屬性) 並不會將該名稱註冊到任何命名範圍中。將具名項目加入到有命名範圍的項目樹狀結構,也不會將該名稱註冊到命名範圍中。雖然命名範圍可以是巢狀結構,但一般會將名稱註冊到位於根項目上的命名範圍,如此一來,命名範圍位置就與在已載入的對等 XAML 頁面中所建立的命名範圍平行。對應用程式開發人員來說,最常見的案例就是使用 RegisterName 註冊名稱到目前根項目上的命名範圍。RegisterName 是重要案例的一部分,可用來尋找當做動畫執行的 Storyboard。如需詳細資訊,請參閱腳本概觀。如果您在同一邏輯樹狀結構中之根項目以外的項目上呼叫 RegisterName,名稱仍會註冊到最接近根部的項目,就如同在根項目上呼叫 RegisterName 一樣。
程式碼中的命名範圍
對於以程式設計方式建立而不是來自於載入之 XAML 的應用程式,根項目必須實作 INameScope,或者必須是 FrameworkElement 或 FrameworkContentElement 衍生類別,才能支援命名範圍。
此外,對於不是由 XAML 處理器載入和處理的任何項目,預設不會建立或初始化物件的命名範圍。要將項目名稱註冊到命名範圍中,您必須先明確建立新的命名範圍。若要為項目建立命名範圍,請呼叫靜態 SetNameScope 方法。將項目指定做為 dependencyObject 參數,並將新的 NameScope 建構函式呼叫指定做為 value 參數。
如果在 SetNameScope 提供的 dependencyObject 物件不是 INameScope 實作、FrameworkElement 或 FrameworkContentElement,那麼在任何子項目上呼叫 RegisterName 將不會有任何作用。如果您無法明確建立新的命名範圍,則呼叫 RegisterName 會引發例外狀況。
如需在程式碼中使用命名範圍 API 的範例,請參閱 HOW TO:定義名稱範圍。
樣式和樣板中的命名範圍
WPF 中的樣式和樣板可讓您直接重複使用和重新套用內容,但樣式和樣板也可能包含具有在樣板層級中所定義名稱的項目。同一頁面可能會多次使用該樣板。因此,樣式和樣板都會定義其本身的命名範圍,不受套用樣式或樣板的頁面影響。
參考下列範例:
<Page
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<Page.Resources>
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Border BorderBrush="Red" Name="TheBorder" BorderThickness="2">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Page.Resources>
<StackPanel>
<Button Template="{StaticResource MyButtonTemplate}">My first button</Button>
<Button Template="{StaticResource MyButtonTemplate}">My second button</Button>
</StackPanel>
</Page>
在此例中,相同的樣板套用到兩個不同的按鈕。如果樣板沒有獨立的命名範圍,樣板中所使用的 TheBorder 名稱就會造成名稱衝突。樣板的每個執行個體都有自己的名稱範圍,因此,在這個範例中,每個已具現化之樣板的名稱範圍就只會包含一個名稱。
樣式也有自己的命名範圍,這主要是讓 Storyboard 的各部分能夠有特定的名稱。這些名稱可產生控制項特定行為,然後對應到該名稱的項目,即使在自訂控制項時重新定義了樣板。
由於命名範圍各自獨立,因此在樣板中尋找具名項目要比在頁面中尋找非樣板項目更困難。您首先需要取得套用樣板之控制項的 Template 屬性值,以判斷套用的樣板。接著,您會呼叫所取得的 FindName 樣板,將套用樣板的控制項當做第二個參數傳遞。
如果您是控制項作者,並設定慣例 (Convention) 讓控制項本身定義的行為,是以所套用之樣板中的特定具名項目為目標,那麼就可以在控制項實作程式碼中使用 GetTemplateChild 方法。GetTemplateChild 方法是受到保護的,因此只有控制項作者可以存取它。
如果您在樣板中工作,而需要取得套用樣板的命名範圍,請取得 TemplatedParent,然後再從此處呼叫 FindName。在使用樣板的範例中,如果您撰寫事件處理常式實作,其中事件會從所套用之樣板中的項目來引發。
命名範圍以及與名稱相關的 API
FrameworkElement 有 FindName, RegisterName 和 UnregisterName 方法。如果呼叫這些方法的項目擁有命名範圍,那麼項目方法就會直接呼叫命名範圍方法。否則,會檢查父項目是否擁有命名範圍,並反覆不斷進行此檢查,直到找到命名範圍 (因 XAML 處理器行為之故,根部一定會有命名範圍)。FrameworkContentElement 也有類似的行為,唯一的不同是 FrameworkContentElement 不會擁有命名範圍。方法是存在於 FrameworkContentElement 上,因此呼叫最後可轉送到 FrameworkElement 父項目。
SetNameScope 可用來將新的命名範圍對應到現有的物件。您可以呼叫 SetNameScope 多次來重設或清除命名範圍,但這並不是常見的用法。此外,GetNameScope 也通常不在程式碼中使用。
命名範圍實作
下列類別會直接實作 INameScope:
ResourceDictionary 不使用命名範圍;它使用索引鍵,因為其為字典雜湊資料表實作。ResourceDictionary 實作 INameScope 的唯一理由是為程式碼引發例外狀況,這些例外狀況有助於說明真正命名範圍與 ResourceDictionary 處理索引鍵之方式的不同之處,而且也可確保父項目不會特別將命名範圍套用到 ResourceDictionary。
FrameworkTemplate 和 Style 透過明確介面定義實作 INameScope。明確實作可讓這些命名範圍在透過 INameScope 介面存取 (這是 WPF 內部處理序傳遞命名範圍的方式) 時,產生慣例的行為。不過明確介面定義不屬於 FrameworkTemplate 和 Style 的傳統 API 介面的一部分,因為您很少需要直接在 FrameworkTemplate 和 Style 上呼叫 INameScope 方法。
下列類別會定義自己的命名範圍,其使用 System.Windows.NameScope Helper 類別並透過 NameScope 附加屬性連接至其命名範圍實作: