程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 《Programming WPF》翻譯 第4章 4.數據源

《Programming WPF》翻譯 第4章 4.數據源

編輯:關於.NET

目前為止,我們已經簡單的處理了對象。然而,這並不是數據的唯一來源; XML和突然想到的相關數據庫,都是流行的選擇。更進一步地,由於XML或

相關數據庫並不能存儲數據為.NET對象,某些轉換可能需要支持數據綁定, 正如你會想到的,需要數據源對象上的.NET屬性。而且即使我們可以直接在xaml 中聲明對象,仍然希望有一個層間接地從其他源中拉數據,甚至於將這個工作交 給一個工作線程,如果說取回是一個呆板的操作。

簡而言之,為了對象的轉換和加載,我們希望間接的而不是直接的聲明方式 。對於這個間接方式,我們必須致力於IDataSource接口的實現,其中一種就是 數據對象源。

4.4.1數據對象源

一種對IDataSource接口的實現是,為所有的操作提供一個間接的層,這些操 作用於生成要綁定到的對象。例如,如果我們想要在Web上加載一組Person對象 ,我們需增強一些代碼中的邏輯,如示例4-34。

示例4-34

namespace PersonBinding {
  public class Person : INotifyPropertyChanged {}
  public class People : ObservableCollection<Person> {}

  public class RemotePeopleLoader : People {
    public RemotePeopleLoader(  ) {
      // Load people from afar

    }
  }
}

在示例4-34中,RemotePeopleLoader類從People集合類中派生,在構造器中 檢索數據,因為對象數據源希望它創建的對象是一個集合,正如示例4-35。

示例4-35

<Window.Resources>

  <ObjectDataSource
    x:Key="Family"
    TypeName="PersonBinding.RemotePeopleLoader"
    Asynchronous="True" />
</Window.Resources>
<Grid DataContext="{StaticResource Family}">

  <ListBox ItemsSource="{Binding}" >
</Grid>

ObjectDataSource元素通常位於資源塊中,按名稱在xaml的其他位置中使用 。TypeName屬性引用了集合類的完整的限定名稱。

WPF中的大部分具有type參數的類,如DataTemplate元素的DataType屬性,在 設置中帶上type擴展標記,這包括類,命名空間和使用mapping語法的編譯集信 息。

<!-- set up DataTemplate for Bar.Quux in assembly foo -- >
<?Mapping
  XmlNamespace="local"
  ClrNamespace="Bar"  Assembly="foo" /><Window  xmlns="local">
  <Window.Resources>
    <DataTemplate
      DataType="{x:Type local:Quux}"></DataTemplate>
  </Window.Resources>

</Window>

然而,ObjectDataSource以自己的方式設置type的信息。

<!-- set up ObjectDataSource for Bar.Quux in assembly foo -->
<ObjectDataSource x:Key="foo" TypeName="Bar.Quux, foo" />

願望是美好的,現實是殘酷的。在RTM版本之前,兩種技術都是合理的。

伴隨著對象數據源擔當數據和綁定之間的中介者,我們需要更新代碼,當我 們遍歷People集合時(現在是一個基本類RemotePeopleLoader,但是仍然是 Person對象的容器),正如示例4-36所示。

示例4-36

public partial class Window1 : Window {

  ICollectionView GetFamilyView(  ) {
    IDataSource
 ds = (IDataSource)this.FindResource("Family");
    People people = (People)ds.Data;
    return BindingOperations.GetDefaultView(people);
  }

  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    ICollectionView view = GetFamilyView(  );
    Person person = (Person)view.CurrentItem;

    ++person.Age;
    MessageBox.Show();
  }

  void addButton_Click(object sender, RoutedEventArgs e) {
    IDataSource ds = (IDataSource)this.FindResource("Family");
    People people = (People)ds.Data;
    people.Add(new Person("Chris", 35));
  }
}

由於Family資源現在是一個ObjectDataSource,本身就是IDataSource接口的 實現,在示例4-26中,當我們需要People集合的時候,我們將Family中的資源轉 換為IdataSource,並從Data屬性中拉出這個集合。

即使數據源對象通過Data屬性暴露他的數據,這並不意味著你必須綁定它。 如果你注意到示例4-35,我們仍然像從前一樣綁定了列表框。

<!--do not bind to Path=Data -->
<ListBox ItemSource=”{Binding}” …>

這樣做的原因是WPF對IDataSource提供內建的支持,因此沒有必要間接地這 樣做。

4.4.1.1異步數據遍歷

在示例4-35中,我們應用了Asynchronous屬性,這是最有趣的一塊功能:數 據源對象提供給我們所欠缺的——當我們直接在xaml中聲明對象圖的時候。

當Asynchronous屬性設置為true時(默認為false),通過TypeName創建詳細 對象的任務就交給工作線程處理,當遍歷過數據,僅僅在UI線程表現綁定。這與 綁定到數據並不一樣——數據是在網絡流中遍歷到的,但是這總比當一個長時間 的遍歷發生時阻塞了UI線程要好。

4.4.1.2傳遞參數

除Asynchronous屬性外,數據源對象還提供了Parameters屬性,這是一個逗 號分隔的字符串列表,作為一個字符串傳遞到由數據源對象創建的類型中。例如 ,如果我們要傳遞一組URL參數,用來嘗試遍歷其中的數據,我們可以使用 Parameters參數如示例4-37。

示例4-37

<ObjectDataSource
  x:Key="Family"
  TypeName="PersonBinding.RemotePeopleLoader"
  Asynchronous="True"
Parameters="http://sellsbrothers.com/sons.dat, http://sellssisters.com/daughters.dat" />

在示例4-37中,我們已經添加了一個包含兩個URL的列表,這將被轉換為調用 RemotePeopleLoader有兩個參數的構造函數,如示例4-38

示例4-38

namespace PersonBinding {
  public class RemotePeopleLoader : People {
    public RemotePeopleLoader(string url1, string url2) {
      // Load People from afar using two URLs

    }
}

不幸的是,如果我們把其他的數據類型放入由數據源對象的Property屬性支 持的參數列表,如整型,這將不會被轉換,即使構造函數擁有適當的類型是有效 的;數據源對象只支持創建帶有無參或有參構造函數的對象。如果每一個數據都 必須轉換,你就不得不這麼做了。

4.4.2 XMLDataSource

正如我提及的,對象是僅由數據綁定支持的,但數據究竟不僅僅存為對象。 實際上,大部分數據並不存儲為對象。一種日益流行的方法是把數據存儲到XML 。例如,示例4-39顯示了我們的家庭數據,表示以XML的形式。

示例4-39

<!-- family.xml -->
<Family xmlns="">
  <Person Name="Tom" Age="9" />
  <Person Name="John" Age="11" />
  <Person Name="Melissa" Age="36" />
</Family>

這個文件作為可執行應用程序,在同樣的文件夾中是有效的,我們能夠使用 XmlDataSource綁定到它,正如示例4-40所示。

示例4-40

<!-- Window1.xaml -->
<Window >
  <Window.Resources>

    <XmlDataSource
      x:Key="Family"
      Source="family.xml"
      XPath="/Family/Person" />
  </Window.Resources>
  <Grid DataContext="{StaticResource Family}">

    <ListBox  ItemsSource="{Binding}">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal">
            <TextBlock TextContent="{Binding XPath=@Name}" />
            <TextBlock TextContent=" (age: " />
            <TextBlock TextContent="{Binding XPath=@Age}"  />
            <TextBlock TextContent=")" />
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

    <TextBlock >Name:</TextBlock>
    <TextBox Text="{Binding XPath=@Name}" />
    <TextBlock >Age:</TextBlock>
    <TextBox Text="{Binding XPath=@Age}"  />

  </Grid>
</Window>

注意到,XmlDataSource的使用,帶著一個相對的URL指向family.xml文件, 這個Xpath表達式在Family根元素下推出Person元素。在XAML文件中唯一改變的 是,使用ObjectDataSource綁定Name和Age到TextBox控件,而我們使用Xpath表 達式代替了Path表達式

*對Xpath語法的說明草果了本書的范圍,一個好的參考書目是,Essential XML Quick Reference by Aaron Skonnard and Martin Gudgin (Addison Wesley)。

4.4.2.1 XML數據島

如果你恰好在編譯期知道你的數據,XML數據源也以同樣的方式支持“數據島 ”:由XAML直接創建對象,如示例4-41所示。

示例4-41

<XmlDataSource x:Key="Family" XPath="/Family/Person">
  <Family xmlns="">
    <Person Name="Tom" Age="9" />
    <Person Name="John" Age="11" />
    <Person Name="Melissa" Age="36" />
  </Family>
</XmlDataSource>

在示例4-41中,我們將XmlDataSource元素下的內容復制到了family.xml中, 去掉Source屬性而保留了Xpath表達式。

盡管如此,既然我們使用XML替代了對象數據,我們示例中的操作需要改動, 如訪問和改變當前項(正如我們對Birthday按鈕的實現),添加新項,排序和過 濾。簡而言之,任何我們使用Person對象集合的地方,都需要改動。另一方面, 在數據項之間移動的一系列方法ICollectionView.MovingCurrentToXxx()繼續工 作的很好,我們的AgeToForegroundValueConverter也是這樣。

IValueConverter.Convert的實現可以繼續工作,因為我們對對象的字符串值 進行語法解析,而不是直接將其轉換為Int32。在Person對象的情形中使用轉換 是首選的,因為Age是Int32類型的,對其進行語法分析是不必要的。盡管如此, 在XML以及我們的應用程序缺少XSD的情形,Age是一個String類型,因此解析它 就是必要的了。

4.4.2.2 XML數據源和訪問數據項

為了訪問和操作XML數據源,取代你的自定義類型實例,你可以使用位於 System.Xml命名空間的XMLElement實例,正如示例4-42所示。

示例4-42

// Window1.xaml.cs

namespace PersonBinding {
  public partial class Window1 : Window {


    ICollectionView GetFamilyView(  ) {
      IDataSource ds = (IDataSource)this.FindResource ("Family");
      IEnumerable people = (IEnumerable)ds.Data;
      return BindingOperations.GetDefaultView(people);
    }

    void birthdayButton_Click(object sender, RoutedEventArgs e) {
      ICollectionView view = GetFamilyView(  );

       XmlElement person = (XmlElement)view.CurrentItem;
      person.SetAttribute("Age",
        (int.Parse(person.Attributes["Age"].Value) + 1).ToString(  ));
      MessageBox.Show(
        string.Format(
          "Happy Birthday, {0}, age {1}!",
          person.Attributes["Name"].Value,
          person.Attributes["Age"].Value),
        "Birthday");
    }

  }
}

在示例4-42中,首先要注意的是GetFamilyView的實現,我們不再直接尋找 People集合,而是實現由Xml數據源提供的IEnumerable接口。IEnumerable 是.NET中你能擁有的最簡單接口,仍然有一個集合——是GetdefaultView方法所 需要的。

還要注意示例4-42中集合視圖的CurrentItem屬性,是一個XmlElement實例。 為了增加age,我們訪問元素的Age屬性,取出它的值,將其解析為一個整型,增 加它的值,再將這個整型轉換為String類型,設置為當前元素的新的Age屬性值 。顯示每一個屬性不過是對成對屬性的訪問。

4.4.2.3 XML數據源以及添加數據項

當添加(或移除)一個數據項時,最好訪問XmlDataSource自身,從而可以訪 問Document屬性來創建和添加新元素,正如示例4-43。

示例4-43

void addButton_Click(object sender, RoutedEventArgs e) {
  XmlDataSource xds = (XmlDataSource)this.FindResource("Family");
  XmlElement person = xds.Document.CreateElement("Person");
  person.SetAttribute("Name", "Chris");
  person.SetAttribute("Age", "35");
  xds.Document.ChildNodes[0].AppendChild(person);
}

這裡,我們使用了XmlDataSource來獲取XmlDocument,以及使用XmlDodument 來創建一個叫做Person的新元素(使之符合其余Person元素),設置Name和Age 屬性,以及在Family根元素下添加這個元素(在頂級Document對象上 ChildNodes[0]是有效的)。

4.4.2.4 XML數據源以及排序

對Xml數據源的條目進行排序,大概會想起我們要使用XmlElements進行處理 ,正如示例4-44。

示例4-44

class PersonSorter : IComparer {
public int Compare(object x, object y) {
XmlElement lhs = (XmlElement)x;
XmlElement rhs = (XmlElement)y;
// Sort Name ascending and Age descending
int nameCompare =
lhs.Attributes["Name"].Value.CompareTo(
rhs.Attributes["Name"].Value);
if( nameCompare != 0 ) {
return nameCompare;
}
return int.Parse(rhs.Attributes["Age"].Value) -
int.Parse(lhs.Attributes["Age"].Value);
}
}
void sortButton_Click(object sender, RoutedEventArgs e) {
ListCollectionView view = (ListCollectionView)GetFamilyView( );
// Managing the view.Sort collection would work, too
if( view.CustomSort == null ) {
view.CustomSort = new PersonSorter( );
}
else {
view.CustomSort = null;
}
}

在示例4-44中,我們進行了排序,正如先前一樣,但是我們從Name和Age屬性 中拉出數據並適當的進行轉換。

4.4.2.5 XML數據源以及過濾

XML的過濾機制非常像對象的過濾,只是我們使用XmlElements進行處理,正 如示例4-45。

示例4-45

void filterButton_Click(object sender, RoutedEventArgs e) {
ICollectionView view = GetFamilyView( );
if( view.Filter == null ) {
view.Filter = delegate(object item) {
return
int.Parse(((XmlElement)item).Attributes["Age"].Value) >= 18;
};
}
else {
view.Filter = null;
}
}

這裡我們的過濾器使用了匿名委托,將每一個數據項轉換為一個XmlElement 元素來進行過濾。

4.4.3相關數據源

目前的版本,WPF沒有直接支持綁定到相關的數據庫,而且間接的支持范圍並 不是很廣。作為WPF一個關於當前狀態的綁定到相關數據的示例,我建議WinFX SDK示例提名為“Binding with Data in an ADO DataSet Sample”

4.4.4自定義數據源

如果你願意利用為遍歷對象提供的間接數據源,但是沒有一個內嵌數據源會 使你滿意,一個自定義的IDataSource實現應該會獲得成功。例如,代替創建 RemotePersonLoader集合來加載或移除家庭數據(在集合的構造函數中添加集合 項,無論如何都有點做作),我們將要創建一個自定義的IDataSource實現,來 達到這一點,如示例4-46。

示例4-46

namespace PersonBinding {
public class Person : INotifyPropertyChanged {}
public class People : ObservableCollection<Person> {}
public class RemotePeopleSource : IDataSource {
People people = null;
public RemotePeopleSource( ) {
// Load People from afar
// Let data binding know we've got data
if( DataChanged != null ) {
DataChanged(this, EventArgs.Empty);
}
}
// IDataSource Members
// Gets the underlying data object
public object Data {
get { return people; }
}
// Occurs when a new data object becomes available
// Especially handy for async object retrieval
public event EventHandler DataChanged;
// Refreshes the data source object using the most current
// values for the object's configuration properties
public void Refresh( ) {
// Not needed in our case
}
}
}

在示例4-46中,通過創建一個People集合的實例,我們已經實現了 IDataSource接口,而且,在構造函數中,在一個神秘的數據遍歷過程之後,我 們激發了一個事件,讓數據綁定知道我們已經得到了數據,還有再次檢查Data屬 性。這個協議特別有用——一旦你進行異步的數據遍歷(像對象數據源那樣)。

如果你的數據源通過自定義屬性,像Asynchronous,一個或更多屬性可以在 運行期被改變。如果你已經得到了多個影響數據遍歷的屬性,你可能不想開始搜 索新數據直到Refresh方法被調用,你可能開始於一個屬性的改變,但是在客戶 端有機會改變其他的屬性之前。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved