隨著Silverlight的版本快速更迭,其對於資料庫開發的相關技術也日益成熟,就目前現況而論,若程式設計師要使用Silverlight 4來開發資料庫應用程式,至少有三種技術可以選擇,
分別是WCF Service、WCF Data Service、WCF RIA Serivce,其中的WCF Data Service及WCF RIA Service皆是架構於WCF Service之上,但如果設計師深入其中,會發現到我們
無時無刻都在與XAML奮戰,不能像在Windows Form中般,僅動動滑鼠即可完成資料繫結,一定得到XAML中鍵入要繫結的資料欄位,也因為如此,常常有學員提及,Microsoft似乎
在此走了回頭路。
Expression Blend 4 中的資料繫結
文/黃忠成
Silverlight 4與資料庫
隨著Silverlight的版本快速更迭,其對於資料庫開發的相關技術也日益成熟,就目前現況而論,若程式設計師要使用Silverlight 4來開發資料庫應用程式,至少有三種技術可以選擇,
分別是WCF Service、WCF Data Service、WCF RIA Serivce,其中的WCF Data Service及WCF RIA Service皆是架構於WCF Service之上,但如果設計師深入其中,會發現到我們
無時無刻都在與XAML奮戰,不能像在Windows Form中般,僅動動滑鼠即可完成資料繫結,一定得到XAML中鍵入要繫結的資料欄位,也因為如此,常常有學員提及,Microsoft似乎
在此走了回頭路。
但事實真是如此嗎?以一個程式設計師的角度上來看,我個人認為,既然要使用Silverlight或WPF,就必然得對其根本的XAML有相當程度的了解,方能完全掌握,就這點來看,
其實要求程式設計師要熟悉XAML的方向也是正確的,正如同以往提供了Typed DataSet,但後來走向了Entity Class(Value Class)一樣,程式設計師本來就該對自己的角色有所覺悟,
不該期待著開發工具幫你產生原本就該你寫的程式,而後再來抱怨其產生的程式或是提供的元件有效率不彰或是設計不良的缺失。
但從視覺畫面設計的人員角度來看,事情就不是這樣了,常有很多視覺畫面設計人員反應Blend 4/Visual Studio中缺乏了設計時期的資料繫結,讓它們在調整畫面時,非得要到
XAML中才能做到,實在很沒效率。
從傳統的分工來看,當設計UI介面時,多半是由程式設計師先完成資料面的程式碼,接著交給UI設計人員來排畫面,最後再交回程式設計師來完成內部的商業邏輯及細部控制,
在這種模式下,設計工具便扮演著重要的角色,工具本身必定要提供接近於所見及所得的呈現,並且要讓設計人員可以脫離程式語言,直接編排畫面及其繫結的資料欄位。
當然,我並不是說UI設計人員不需要熟悉XAML,只是這種小調整,實在沒理由要他們進到XAML去改,那Blend 4是否具備這種能力呢?亦或是說,Blend 4是否可以達到
設計人員這點小小的要求?
Blend 4中的資料繫結功能
關於設計時期的資料繫結,Blend 4提供了幾種方案,第一種是直接由設計者定義欄位,然後由Blend 4來產生範例資料,藉此達到於設計時期進行資料繫結的目的。
首先,請透過Visual Studio 2010建立一個Silverlight 4 Project,接著於.Web專案中添加一個LINQ To SQL的實體類別,於其內添加Customers資料表(Northwind)。
圖1
接著建立一個WCF Service來將此資料表的內容輸出,讓Silverlight應用程式可以取得。
程式1
INorthwindSvc.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WCFServiceDemo.Web { [ServiceContract] public interface INorthwindSvc { [OperationContract] List<Customers> GetCustomers(); } } |
NorthwindSvc.svc.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WCFServiceDemo.Web { public class NorthwindSvc : INorthwindSvc { public List<Customers> GetCustomers() { NorthwindDataContext context = new NorthwindDataContext(); return context.Customers.ToList(); } } } |
然後由Silverlight專案中新增服務參考。
圖2
按下探索即可找到剛建立的WCF Service。
圖3
按下確定後並重新編譯專案,再透過Blend 4來編輯預設的MainPage.xaml。
圖4
接著切換到Data頁籤,點選上方的新增範例資料。
圖5
此時Blend 4會詢問Data Source的名稱及存放位置,存放位址若放在本文件則僅能用於此XAML,若放在專案,則可用於專案中的
所有XAML。
圖6
按下確定後,即可看到屬性編輯窗,設計師可於此新增或修改、刪除屬性,以資料庫而論,此時的屬性便代表著欄位。
圖7
點選屬性(Property1)右方的框框,則可以調整此屬性的資料型別,及其產生資料的方式。
圖8
有趣的是格式部分,Blend 4可以依據選定的格式產生不同的資料。
圖9
於此,我依據Customers資料表的定義,隨意的添加Customer ID、CompanyName、ContactName、Address屬性。
圖10
到此,Blend 4會依據定義來產生範例資料,此時可以添加一個DataGrid至畫布上,設定其資料繫結。
圖11
圖12
完成後會看到DataGrid中已經有資料了,這便是Blend 4中第一種完成設計時期資料繫結的功能。
圖13
更棒的是,你可以直接於Blend 4按下F5來執行,此時DataGrid中一樣有資料。
圖14
當網頁交回給程式設計師時,程式設計師只要於Load或建構子中進行真正的資料繫結即可。
程式2
MainPage.xaml.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace WCFServiceDemo { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { LayoutRoot.DataContext = null; dataGrid1.DataContext = null; ServiceReference1.NorthwindSvcClient client = new ServiceReference1.NorthwindSvcClient(); client.GetCustomersCompleted += (s, args) => { dataGrid1.ItemsSource = args.Result; }; client.GetCustomersAsync(); } } } |
特別注意,Blend 4進行資料繫結的手段是透過DataContext,所以會改變DataGrid的容器的DataContext,此處先進行清空動作。
圖15
不過這裡仍然有個問題,這與UI設計師原先設計的排列不同,由於是使用DataGrid的AutoGenerateColumns,所以DataGrid會依據欄位來調整,以往沒有設計時期資料繫結功能時,設計師就只能夠透過XAML來調整,現在不需要了,只需要回到Blend 4,將DataGrid的AutoGenerateColumns設為False,手動添加欄位定義即可。
圖16
圖17
接著就可以透過Binding來挑選繫結欄位。
圖18
圖19
逐一完成四個欄位的繫結,最後即可得到如下的執行畫面。
圖20
不過由於程式設計師已添加了取得真實資料的程式碼,所以此時已無法在Blend 4直接執行就可看到真實資料,但至少
設計時期仍然是可見的,這點對UI設計師來說很重要。
同場加映-DataForm
同樣的手法,在DataForm也可適用,不過繫結ItemsSource的方式不太一樣,是透過屬性窗完成。
圖21
圖22
當把這個技巧用在DataForm時,事情就變得有趣了,因為UI設計師可以直接排列新增、修改、刪除的UI介面。
圖23
圖24
圖25
圖26
依樣畫葫蘆,就可完成其他的NewItemTemplate、EdittemTemplate,最後將DataForm的AutoGenerateFields設為False,並修改.cs中的Load事件,
即可得到以下的執行畫面。
圖27
遺珠之憾 – 關於Blend 4的執行時期
截至目前為止,一切都還不錯,唯一的缺憾是當程式設計師添加了取得實體資料的程式碼後,UI設計師便失去了於Blend 4中直接執行來看實際執行畫面的功能了,
這個問題其實也不大,在Blend 4的設計時期資料繫結能力下,其實UI設計師也不需要在執行時期確認了,如果真的需要,就直接使用Visual Studio 2010即可。
當然,要找回這個功能也不是辦不到,只要請設計師於畫面中放入一個TextBlock,然後將其設定為隱藏,當此TextBlock的值為Design時,就不進行取得實體資料的動作。
圖28
MainPage.xaml.cs |
private void UserControl_Loaded(object sender, RoutedEventArgs e) { if (designLabel.Text != "Design") { LayoutRoot.DataContext = null; dataGrid1.DataContext = null; ServiceReference1.NorthwindSvcClient client = new ServiceReference1.NorthwindSvcClient(); client.GetCustomersCompleted += (s, args) => { dataGrid1.ItemsSource = args.Result; dataForm1.ItemsSource = args.Result; }; client.GetCustomersAsync(); } } |
當交回給程式設計師時,只要改變designLabel的值,就可用實際資料來執行。
不想自己定義資料欄位?沒別的方法了嗎?
以上的方法雖然還滿好用的,但UI設計師可能會認為,既然已經有實體的資料結構了,為何不仍直接匯入就好了,還要自己來定義資料欄位?
雖然不會花太多時間,但能省下不是更好? Blend 4對此提供了透過類別產生範例資料的方式。
圖29
圖30
但當用DataGrid繫結至此DataSource時,你會發現資料是空的,原因是,Customers僅代表單列,如果要讓Blend 4由類別產生資料,
那麼得給他List<Customers>才行,我們可以透過新增一個類別來完成此動作。
DS_Customers.cs |
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Collections.Generic; using WCFServiceDemo.ServiceReference1; namespace WCFServiceDemo { public class DS_Custoemrs:List<Customers> { } } |
圖31
圖32
這個作法的缺點是,這些資料僅可視於設計時期,當進入執行時期時就不見了,如果你既想省掉建欄位的動作,又想得到設計時期與
執行時期的效果,那麼得用另一種作法。
Data Generator – Simple Data Generator
要解決前列的問題,那麼我們就得自己產生範例資料,這點其實不難,只要新建一個DataGenerator類別即可。
SimpleDataGenerator.cs |
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Collections.Generic; using System.Reflection; namespace WCFServiceDemo { public class SimpleDataGenerator { private static string[] _nameDictionary = {"Blauer See Delikatessen","Blondesddsl père et fils","Bólido Comidas preparadas", "Bottom-Dollar Markets","Cactus Comidas para llevar","Chop-suey Chinese", "Comércio Mineiro","Consolidated Holdings","Drachenblut Delikatessen","Du monde entier"}; private static string[] _addressDictionary = {"Forsterstr. 77 45","24, place Kléber","C/ Araquil, 672", "12, rue des Bouchers","23 Tsawassen Blvd.","Hauptstr. 29", "Av. dos Lusíadas, 23","Berkeley Gardens 12 Brewery","67, rue des Cinquante Otages"}; private static int[] _numberDictionary = {13844,39393,39302, 112343,32234,43656, 79392,13945,193844}; private static DateTime[] _dateDictionary = {DateTime.Parse("2000/1/3"),DateTime.Parse("2010/4/13"),DateTime.Parse("2019/6/15"), DateTime.Parse("2002/3/1"),DateTime.Parse("2003/7/8"),DateTime.Parse("2003/2/3"), DateTime.Parse("2004/4/5"),DateTime.Parse("2008/9/9"),DateTime.Parse("2006/10/3")}; public static object GenerateValue(object dataObject, int rowIndex) { foreach (PropertyInfo pi in dataObject.GetType().GetProperties()) { try { pi.SetValue(dataObject, SimpleDataGenerator.GenerateValue(pi, rowIndex), null); } catch (Exception) { } } return dataObject; } public static object GenerateValue(PropertyInfo pi, int rowIndex) { if (pi.PropertyType == typeof(string)) { return _nameDictionary[rowIndex]; } else if (pi.PropertyType == typeof(int) || pi.PropertyType == typeof(double) || pi.PropertyType == typeof(float) || pi.PropertyType == typeof(short) || pi.PropertyType == typeof(Int32) || pi.PropertyType == typeof(Int64) || pi.PropertyType == typeof(long)) return _numberDictionary[rowIndex]; else if (pi.PropertyType == typeof(DateTime)) return _dateDictionary[rowIndex]; return null; } } } |
接著修改DS_Customers.cs,令其使用DataGenerator來產生資料。
DS_Customers.cs |
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Collections.Generic; using WCFServiceDemo.ServiceReference1; namespace WCFServiceDemo { public class DS_Custoemrs:List<Customers> { public DS_Custoemrs() { for (int i = 0; i < 10; i++) { Add((ServiceReference1.Customers)SimpleDataGenerator.GenerateValue( new ServiceReference1.Customers(), i)); } } } } |
編譯後回到Blend 4,直接對DataGrid的ItemsSource進行資料繫結。
圖33
點選+CLR物件按鈕。
圖34
選擇DS_Customers後,繫結動作就算完成了。
圖35
圖36
這個方法可以解決UI設計師需重新定義欄位,還能兼顧設計時期與執行時期的效果,接下來的動作就與前面大致相同了,程式設計師可以於程式碼中
調整DataContext與ItemsSource來取得實體資料,UI設計師也能透過Blend 4來微調畫面。
WCF Data Service與 WCF RIA Service
本文以WCF Service做為取得資料的手段,這意味著倘若要寫出可新增、修改、刪除的資料庫應用程式,我們還得寫上一些程式碼,幸運的是,
WCF Data Service、WCF RIA Service可以節省這些時間,而此文中的手法,搬到WCF Data Service及WCF RIA Service時也同樣適用。