第 5 章:資料系結
簡介
第 1 章:「Longhorn」 應用程式模型
第 2 章:建置 「Longhorn」 應用程式
第 3 章:控制項和 XAML
第 4 章:儲存體
第 5 章:資料系結
Brent Rector
明智的 Owl 諮詢
2004 年 2 月
目錄
建立資料系結
資料系結類型
轉換器
提供屬性變更通知
總結
其傳統意義的資料系結表示將某些基礎資料與一或多個使用者介面元素產生關聯。 資料會提供要顯示的資訊。 使用者介面元素會以適當的格式呈現資訊。
「Longhorn」 以數種方式擴充資料系結的傳統概念。 您可以將使用者介面專案的 屬性系結至任何 Common Language Runtime (CLR) 物件的屬性,或系結至 XML 節點的屬性。
資料系結可以是單向 (方向) 或雙向。 例如,傳統資料系結在資料來源中的資訊會流向其系結的使用者介面元素。 或者,使用者介面元素中的資訊可以流回資料來源。 當然,雙向資料系結支援每個方向的資訊流程,並透過使用者介面元素讓使用者輸入來更新資料來源中的資料。
資料系結也可以是靜態 (一次只能) 或動態。 使用靜態資料系結時,一開始建立資料系結時,就會發生資訊傳輸。 資料中值的後續變更不會影響使用者介面專案中的值。 動態資料系結允許對資料來源中的資料進行變更,以傳播至使用者介面元素,反之亦然。
「Longhorn」 資料系結也支援在資料流程向資料來源和使用者介面元素時轉換資料。 此轉換可讓應用程式作者將 UI 語意新增至資料。
某些典型的轉換可能如下:
- 以紅色顯示負數,並以黑色顯示正數
- 根據連線或離線連絡人顯示影像
- 藉由將矩形的高度系結至股票價格來建立動態橫條圖
- 將影像的坐標系結至 CLR 物件的屬性,以動畫顯示影像的位置
「Longhorn」 資料系結基本上也是非同步。 當新的資料來源系結至 專案時,使用者介面專案會收到事件。 此外,當資料來源收集資料時,它會引發事件,指出其內容已變更。
一個可以資料系結至任何 CLR 物件或 XMLnode,因此可以輕鬆地將資料系結至 「Longhorn」 中的各種資料模型—CLR 物件、XML、ADO.NET 資料集、Web 服務訊息或 WinFS 物件。 「Longhorn」 也提供許多內建資料來源類別,可讓您以非同步方式將資料以非同步方式帶入應用程式。 XML、.NET 物件、ADO.NET 資料集和 WinFS 物件都有特定的資料來源。 資料來源模型可延伸,因此您可以視需要建立自己的自訂資料來源類別。
建立資料系結
您可以將使用者介面專案的動態屬性系結至任何 CLR 物件的 屬性。 若要這樣做,您必須描述資料來源的某些專案與目標使用者介面元素之間所需的對應。 每個這類對應或系結都必須指定下列各項:
- 資料來源專案
- 資料來源專案中適當值的路徑
- 目標使用者介面專案
- 目標使用者介面元素的適當屬性
資料系結
Framework 代表 MSAvalon.Data.Bind 類別實例的資料系結。 這個類別有許多屬性可控制資料系結: Path、BindType、UpdateType、Transformer、Culture、BindFlags 和 Source。
您可以將 Path 屬性設定為字串,指定系結物件所系結之資料來源中的屬性或值。 BindType屬性會控制資料系結的方向和頻率。 它必須是三個值的其中一個: OneWay、TwoWay 和 OneTime。 OneWay系結類型會導致資料系結將新的值從資料來源傳送至目標屬性,但不會將目標屬性中的變更傳播回資料來源。 TwoWay系結類型會雙向傳播變更。 當您指定 OneTime 系結類型時,資料系結只會在您第一次啟動系結時,將資料來源中的值傳送至目標屬性。
您可以將 Transformer 屬性設定為實作 IDataTransformer 介面的任何物件。 當資料系結傳播值時,它會透過轉換器傳遞值。 轉換器會檢查傳入值,並產生新的值作為輸出。 請注意,輸入和輸出值不需要是相同的類型。 您可以有整數旗標作為輸入,並產生不同的影像檔作為輸出。
UpdateType屬性會決定當系結類型為TwoWay時,目標屬性的變更何時傳播回資料來源。 您可以指定三個值的其中一個: 即時 表示在目標屬性變更之後,立即將新值傳播至資料來源; OnLostFocus 表示當目標控制項失去輸入焦點時傳播值;和 Explicit 表示等到程式碼呼叫系結物件,並告知它傳播新的值。
Source屬性會參考系結的來源資料項。 您可以使用 DataSource、ElementSource、DataCoNtextSource 或 ObjectSource 屬性來設定系結物件的 Source 屬性。 您可以使用 ElementSource 屬性,藉由提供元素識別碼做為ElementSource屬性的值,將 Source 設定為Element。 DataCoNtextSource屬性可讓您將元素識別碼設定為DataCoNtextSource,將來源設定為另一個專案的資料內容。 您可以使用 ObjectSource 屬性,將物件指定為系結的來源。 最後,DataSource屬性可讓您將Source設定為DataSource的Data屬性。 這會在 [資料來源專案] 一節中詳細討論。
Culture屬性可讓您為需要感知文化特性的系結指定CultureInfo。
BindFlags屬性支援單一非零值:NotifyOnTransfer。 請務必瞭解資料系結原本就是非同步。 當您變更資料來源中的值時,對應的目標屬性不會立即收到更新的值。 新值傳播至目標屬性之前,可能需要任意的時間。 當您需要知道系結何時完成更新目標屬性時,您可以將 BindFlags 屬性設定為 NotifyOnTransfer 值。 然後,資料系結會在更新目標屬性之後引發 MSAvalon.Data.DataTransfer 事件。
使用程式碼定義系結運算式
您可以以程式設計方式建立資料系結,雖然我預期您很少需要或想要這麼做。 只要建立 Bind 類別的實例,並呼叫 SetBinding方法即可。 以下是其中一個可能範例:
using MSAvalon.Data;
Bind binding = new Bind ();
binding.Path = path;
binding.BindType = bindType;
binding.Source = source;
binding.UpdateType = updateType;
element.SetBinding (property, binding);
或者,以下是使用其中一個 Bind 類別便利建構函式撰寫先前程式碼的另一種方式:
using MSAvalon.Data;
Bind binding = new Bind (path, bindType, source, updateType);
element.SetBinding (property, binding);
您可以使用其他便利方法,例如元素上的 SetBinding 方法,並簡化上述程式碼:
element.SetBinding (property, path, bindType, source, updateType);
使用標記定義系結運算式
我預期您會偏好使用標記定義大部分的資料系結。 所有先前的概念仍適用—您建立 Bind 物件、將其屬性設定為適當的值,並將它與目標元素的屬性產生關聯。 例如,下列標記會建立 Bind 物件做為 Button 物件的 Text 屬性的值。
<DockPanel xmlns="https://schemas.microsoft.com/2003/xaml" />
<DockPanel.Resources>
<myNameSpace:Person def:Name="MyPerson" Name="Bob"/>
</DockPanel.Resources> . . .
<Button>
<Button.Content>
<Bind Path="Name" BindType="OneWay" ObjectSource="{MyPerson}" />
</Button.Content>
</Button>
若要將資料系結與特定使用者介面元素的 屬性產生關聯,您可以使用資料系結做為 屬性的值。 在剛才顯示的範例中,資料系結會將名為MyPerson的資源系結至 Button 元素的Text屬性,因為我定義了Button.Text開始和結束標記之間的資料系結。
DockPanel Resources屬性會宣告下列子項目是資源。 不同于執行時間剖析 XAML 檔案時所具現化的一般 XAML 元素,執行時間不會具現化資源,直到您實際使用它為止。
在先前的 範例中, 系結的來源是 Person 物件,因此 Bind 實例會參考這個 Person 物件實例。
Path屬性會將資料來源專案內的路徑指定為感興趣的值。 在先前的範例中,路徑只是Name,因此系結會擷取Person實例的Name屬性。 不過,路徑可能更為複雜。 例如,如果 Name 屬性傳回具有其他結構的物件,則路徑可能類似 Name.FirstName。
先前的範例示範如何使用 Button.Text 屬性值的複雜屬性定義來定義資料系結。 不過,您可以使用資料系結運算式的替代方式和更精簡的定義。 在此情況下,您會將字串定義為數據系結運算式。 字串以星號字元開頭,XAML 編譯器會將它解譯為逸出字元,然後類別名稱以具現化,然後以括弧括住,以分號分隔的名稱值組。
<DockPanel >
§
<Button Text="*Bind(Path=Name;BindType=OneWay)" />
§
</DockPanel>
當您定義資料系結,但未使用DataSource、ElementSource、DataCoNtextSource 或 Object) Source (指定Source屬性的值時,資料系結會從目前專案的DataCoNtext屬性擷取資料來源。 當目前元素沒有 DataCoNtext時,系結物件會以遞迴方式擷取父元素的 DataCoNtext 。 這可讓您在標記中的適當專案上定義資料來源一次,然後在子專案的各種系結中使用該資料來源。
在下列範例中,我將DockPanel元素的DataCoNtext屬性設定為參考資料源的資料系結。 實際上,當所有子項目未將它設定為不同的值時,所有子項目都會繼承這個 DataCoNtext 屬性。 因為 Button 元素上的資料系結未指定 Source 屬性的值,所以系結會使用繼承 之 DataCoNtext的來源。 當然,您一律可以指定來源,讓特定資料系結使用不同的資料來源。
<DockPanel xmlns="http:////schemas.microsoft.com//2003//xaml//"
DataContext="{MyPerson}>
§ <Button Text='*Bind(Path="Name";BindType="OneWay")' />
<Button Text='*Bind(Path="Age";BindType="OneWay")' />
§</DockPanel>
資料系結類型
特定資料系結可以是三種類型: OneTime、 OneWay和 TwoWay。 當您宣告系結時,您可以將 BindType 屬性設定為其中一個列舉值。
One-Time資料系結
當您要求單次資料系結時,執行時間會使用資料來源和指定的路徑,擷取來源值,並將指定的目標屬性初始化為該值。 一般而言,來源或目標屬性變更值之後不會發生任何事。
不過,有兩個特殊案例。 當元素的 DataCoNtext 變更時,資料來源已變更,因此系結會執行另一次傳輸。 此外,在許多情況下,資料內容是指 物件的集合。 當集合的目前物件變更時,資料系結會執行一次性傳輸。
One-Way資料系結
當您要求單向資料系結時,執行時間會擷取來源值,並將指定的目標屬性初始化為該值。 每次來源值變更資料系結時,都會擷取新的值,並重新初始化目標屬性。
Two-Way資料系結
當您要求雙向資料系結時,執行時間會擷取來源值,並將指定的目標屬性初始化為該值。 每次來源值變更時,資料系結都會擷取新的值,並重新初始化目標屬性。 此外,當目標屬性變更值時,例如,當使用者輸入編輯控制項時,資料系結會擷取新的目標屬性值,並將其傳播回來源。 雙向資料系結是資料系結的預設類型。
轉換器
轉換程式可讓您將值從一個表單轉換成另一個表單,因為它會傳播至資料來源,以及從資料來源轉換成目標。 您可以使用轉換器,將值從其內部表示轉換成唯一的顯示值。 例如,您可以使用轉換器,使用紅色文字來顯示負浮點數,以及使用黑色文字的正數。 您也可以針對客戶的各種信用評等顯示不同的圖示。
您也可以使用轉換器作為資料類型轉換器。 例如,您的來源值可能是 Point 物件,而您要系結值的屬性需要 Length 實例。
轉換器也會接收使用者介面的文化特性資訊做為其其中一個參數。 您可以使用這項資訊,將呈現的使用者介面量身打造為使用者目前的文化特性,例如,您可以在不同文化特性下執行時提供不同的圖示。
IDataTransformer 介面
轉換器是實作 IDataTransformer 介面的任何物件。 以下是介面的定義:
interface IDataTransformer {
object Transform (object o, DependencyID id, CultureInfo culture);
object InverseTransform (object o, PropertyInfo pInfo, CultureInfo culture);
}
資料系結會在將來源值傳播至目標屬性時呼叫 Transform 方法。 參數 o 是來源值、參數 識別碼 會識別目標屬性,而參數 文化特性 會識別轉換的文化特性。
資料系結會在將已變更的目標屬性值傳播回來源時,呼叫 InverseTransform 方法。 在此情況下,參數 o 是已變更的目標屬性值, pInfo 會識別要轉換值的型別。 如同先前一樣, 文化 特性是轉換的文化特性。
這兩種方法都可讓您傳回 null ,以指出系結不應該以個別方向傳播值。 以下是根據整數值傳回色彩的簡單轉換器:
<SimpleText Text="*Bind(Path=Name)" Foreground="*Bind(Path=Age; Transformer=AgeToColorTransformer)"/>
public class AgeToColorTransformer: IDataTransformer {
public object Transform (object o, DependencyID di, CultureInfo culture) {
int age = (int) o;
if (age < 0 || age > 120) return Grey;
if (age <= 30) return Green;
if (age <= 70) return Gold;
if (age <= 120) return Red;
}
public object InverseTransform (object o, PropertyInfo i, CultureInfo c) {
return null;
}
}
提供屬性變更通知
CLR 不會為物件提供泛型方法,以通知其用戶端其中一個屬性已變更。 不過,動態系結需要這類通知,讓系結可以將變更的屬性值傳播至目標動態屬性。 「Longhorn」 引進 IPropertyChange 介面,以允許物件在其其中一個屬性變更值時發出訊號。 請注意,介面會定義 PropertyChangedEventHandler 類型的單一事件,而且事件處理常式可以使用第二個參數的 PropertyName 屬性來擷取已變更屬性的名稱給處理常式。
interface IPropertyChange {
event PropertyChangedEventHandler PropertyChanged;
}
delegate void PropertyChangedEventHandler (object sender,
PropertyChangedEventArgs e);
class PropertyChangedEventArgs : EventArgs {
public virtual string PropertyName { get ;}
}
在下列程式碼中,我已重寫章節稍早的 Person 類別,以支援變更 Name 和 Age 屬性,並在發生這類變更時引發適當的事件。
namespace MyNamespace {
public class Person : IPropertyChange {
private string m_name;
private int m_age;
public event PropertyChangedEventHandler PropertyChanged;
public string Name {
get { return m_name; }
set {
if (m_name != value) {
m_name = value;
RaisePropertyChangeEvent ("Name");
}
}
}
public int Age {
get { return m_age; }
set {
if (m_age != value) {
m_age = value;
RaisePropertyChangeEvent ("Age");
}
}
}
private void RaisePropertyChangedEvent (string propertyName) {
if (PropertyChanged != null)
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
public Person (string name, int age) {
m_name = name; m_age = age;
}
}
}
每當其中一個屬性的「有趣」屬性變更值時,您的物件就會呼叫 PropertyChanged 委派來實作這個介面。 請注意,只有在動態系結中使用的屬性變更值時,才需要叫用委派。 您的物件可以有您未引發變更通知的屬性。
基於效能考慮,只有在屬性真的變更值時,才應該引發變更通知。 當物件不知道哪些屬性變更值時,它可以要求本身更新任何屬性的所有系結,或者可以傳遞 String.Empty 做為已變更的屬性名稱。
資料來源專案
「Longhorn」 提供一組內建資料來源,可讓您以非同步方式和不封鎖 UI,輕鬆地以宣告方式將資料放入應用程式。
資料來源專案是實作 IDataSource 介面的任何物件。
interface IDataSource {
public virtual Object Data { get; }
public virtual void Refresh()
}
這個介面具有 Data 屬性,可讓系結從資料來源專案取得資料。 Refresh方法可讓系結要求資料來源專案在無法使用時擷取其資料。 「Longhorn」 提供許多資料來源類別,您很快就會看到其中一些類別。
一般而言,資料來源實作也會提供強型別屬性,以傳回資料提供者的原生 API。 例如, SqlDataSource 和 XmlDataSource 類別分別提供 DataSet 和 Document 屬性:
class SqlDataSource : IDataSource, … {
§ DataSet DataSet { get; }
§}
class XmlDataSource : IDataSource, … {
§ XmlDocument Document { get; }
§}
因此,如果您真的必須,您可以直接存取基礎資料提供者。
使用任何 CLR 物件作為資料來源
ObjectDataSource 類別可讓您建立指定型別的實例做為資料來源專案。 通常,您將使用 XAML,並將資料來源宣告為標記中的資源。 例如,假設我在名為 MyAssembly 的元件中具有 Person 類別的下列定義,而且我想要使用這個類別的實例做為資料來源專案:
namespace MyNamespace {
public class Person {
private string m_name;
private int m_age;
public string Name { get { return m_name; } }
public int Age { get { return m_age; } }
public Person (string name, int age) {
m_name = name; m_age = age;
}
}
}
Person類別不需要任何其他支援才能成為資料來源專案。 我可以使用 ObjectDataSource 類別的實例作為資料來源專案,並通知基礎資料提供者應該是 Person 類別的實例。 若要這樣做,可以使用標記將 ObjectDataSource 的實例宣告為 XAML 資源。
<DockPanel>
<DockPanel.Resources>
<ObjectDataSource def:Name ="source1"
TypeName="MyNamespace.Person, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcde f"
Parameters="Brent, 0x30" />
</DockPanel.Resources>
</DockPanel>
一如往常,XAML 元素名稱代表 Framework 類別名稱。 因此, ObjectDataSource 元素指出要建立 ObjectDataSource 類別的實例。 def:Name屬性的值是上述範例中這個資源 (「source1」 的名稱) 。
ObjectDataSource實例會呼叫其預設建構函式,或當您指定Parameters屬性時,呼叫最符合Parameter屬性值簽章的建構函式,建立TypeName所參考類別的新實例。
回想一下,標記相當於程式碼,因此上述標記與下列程式碼相同:
ObjectDataSource source1 = new ObjectDataSource();
source1.TypeName = "MyNamespace.Person, MyAssembly, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=0123456789abcdef";
source1.Parameters = "Brent, 0x30";
ObjectDataSource類別也提供在物件上呼叫方法的機制,以及參考現有物件,以及只具現化新的物件。
搭配資料系結使用資料來源
其中一個可以在元素的 Resources 屬性或應用層級資源中,將資料來源宣告為資源。 在應用程式資源中宣告的任何資料來源,都可以在任何頁面中跨應用程式使用。 元素資源中定義的資料來源只能用在元素的範圍內。
其中一個可以在元素的 Resources 屬性或應用層級資源中,將資料來源宣告為資源。 在應用程式資源中宣告的任何資料來源,都可以在任何頁面中跨應用程式使用。 元素資源中定義的資料來源只能用在元素的範圍內。
在剛才顯示的範例中,資料系結會系結名為 source1 的資料來源。您可以將 DataSource 屬性設定為 XAML 資源的資源識別碼。 當資源實作IDataSource介面時,執行時間會將Bind實例的Source屬性設定為指定DataSource資源之Data屬性所傳回的物件。 當資源未實作 IDataSource 介面時,執行時間會將系結的 Source 屬性設定為資源物件本身。
在先前的範例中, DataSource 屬性會參考 ObjectDataSource 資源。 因此,系結會向ObjectDataSource要求Data屬性。 資料來源接著會依標記中指定的具現化 Person 類別。
在第一個 Button 中,Bind實例的Source屬性會參考這個Person物件實例。 路徑只是Name,因此系結會擷取Person實例的Name屬性。
在第二個 Button 中, DataCoNtext 會系結至 ObjectDataSource。 此動作會將 Button 的 DataCoNtext 設定為 Person 物件,讓 Button 上的任何系結都會使用 Person 物件作為其系結的預設來源。
同樣地,資料系結至任何可用的資料來源。 下列段落會提及一些隨附于 「Longhorn」 的其他資料來源。 其他資料來源,例如從 Web 服務取得資料的資料來源,將會上線。
使用 XML 作為資料來源
XmlDataSource類別是使用 XML 檔物件模型作為基礎資料提供者的資料來源, (DOM) 。 您可以使用標記從統一資源定位器建立 DOM (URL) 參考 XML 資料流程。 您也可以藉由提供 XML 內嵌標記來建立 DOM,如下列範例所示:
使用 URI
<DockPanel>
<DockPanel.Resources>
<XmlDataSource def:Name="source2"
Source="http://www.wiseowl.com/People.xml"
XPath="/People/Person[@Age>21]" />
使用內嵌標記
<XmlDataSource def:Nsme="source3"
XPath="/People/Person[@Age>50]" >
<People>
<Person Name='Bambi' Age='61'>
<Person Name='Bozo' Age='54'>
<Person Name='Brent' Age='48'>
§ </People>
</XmlDataSource>
</DockPanel.Resources>
</DockPanel>
使用 資料集 作為資料來源
SqlDataSource類別是使用DataSet作為基礎資料提供者的資料來源。 它會建立 DataSet ,並在資料庫上執行 SQL 命令來填入資料集。
<DockPanel>
<DockPanel.Resources>
<SqlDataSource def:Name="source4">
ConnectionString="server=localhost;Database=UserGroup"
SelectCommand="SELECT * FROM Members" />
</SqlDataSource>
<DockPanel.Resources>
</DockPanel>
或者,您可以使用 ObjectDataSource 類別,並系結至您在程式碼後置檔案中定義的強型別 DataSet衍生類別。
<DockPanel>
<DockPanel.Resources >
<ObjectDataSource def:Name="sds1"
Type="MyDataset"/>
</ObjectDataSource>
</DockPanel.Resources>
</DockPanel>
使用 Windows 儲存體作為資料來源
WinFS DataSource 類別使用 WinFS 作為基礎資料提供者。 您可以使用它來系結至 Microsoft® Windows® 儲存體所維護的每日資訊。
<DockPanel>
<DockPanel.Resources>
<WinFSDataSource ContextString="c:\">
<WinFSDataSource.Query
Type="Person" Filter="DisplayName='Ted'" Sort="DisplayName ASC">
<Query.ProjectionOptions Field="DisplayName" />
<Query.ProjectionOptions Field="Birthdate">
<Projection.ProjectionOptions … />
</Query.ProjectionOptions>
</ WinFSDataSource.Query>
</WinFSDataSource>
</DockPanel.Resources>
</DockPanel>
使用自訂資料來源
您也可以將自訂類別指定為數據源。 類別必須實作 IDataSource 介面。 您通常會想要將 XML 命名空間前置詞與自訂類別的命名空間產生關聯,然後在標記中使用前置詞限定類別名稱,
<Canvas … >
§
<Canvas.Resources>
<WO:InfraredDataSource def:Name="source8"
PropA='value1'
PropB='value2'
</WO:InfraredDataSource>
</Canvas.Resources>
</Canvas>
總結
資料系結提供簡單且有效率的方法,可將資訊連接到顯示資料的使用者介面元素。 您可以透過方向、一次或重複地自動傳播值,並在需要時即時轉換資料標記法。 而且,您可以使用標記進行少量或沒有程式設計程式碼。 資料系結可讓您取得您想要的資料,並繼續進行撰寫應用程式的其餘部分。
繼續進行第 6 章:通訊