緣由
這個標題下的有些古怪。
我的專案,原本都以 ASP.NET為主。排程的程式大都是 Console 的程式。然而最近接了一個 Client Application 的專案。
經過比較後,我選擇了較新的 WPF,而放棄了 WinForm。問題來了,專案中的成員會問到WPF中如何在DataGrid 取得使用者所選擇ComboBox 的SelectedIndex。
為了演示 WebForm 與 WPF 兩者的差異,我寫了這個 demo.
這個 Demo其實很簡單:設定10個 Emp 的性別。然而分別以 WebForm 與 WPF 做一次,再討論兩者的設計方式。
資料來源
雖然是不同的應用程式,但有相同的資料,故,將兩者相同的程式放在 CommonLib 中.
using System.Collections.Generic; using System.Linq; namespace CommonLib { public class Emp { public int Id { get; set; } public string Name { get; set; } public int Gender { get; set; } } public class GenderStruct { public int Id { get; set; } public string Name { get; set; } } public class MyDataSource { public List<Emp> GetAllEmps() { var q = from i in Enumerable.Range(1, 10) select new Emp() { Id = i, Name = string.Format("N{0}", i), Gender = 1 }; return q.ToList(); } public List<GenderStruct> GetAllGenders() { return new List<GenderStruct> { new GenderStruct { Id = 1, Name = "Boy"}, new GenderStruct { Id = 2, Name = "Girl"}, new GenderStruct { Id = 3, Name = "Gay"}, }; } } }
WebForm
ASP.NET WebForm的設計其實真的很麻煩,但用習慣了,也就習以為常。下是在 aspx 中放入一個 GridView 以顯示員工的資料。使用者輸入性別再按下 button,網頁顯示所有員工的最後值。
Default.aspx
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="Id" onrowdatabound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="Id" HeaderText="Id" /> <asp:BoundField DataField="Name" HeaderText="Name" /> <asp:TemplateField HeaderText="Gender"> <ItemTemplate> <asp:DropDownList runat="server" ID="ddl"> </asp:DropDownList> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
Default.aspx.cs
using System; using System.Web.UI.WebControls; using CommonLib; namespace WebApplication1 { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { var dataSource = new MyDataSource(); var emps = dataSource.GetAllEmps(); GridView1.DataSource = emps; GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { var dataSource = new MyDataSource(); var allGender = dataSource.GetAllGenders(); if (e.Row.RowType == DataControlRowType.DataRow) { var ddl = e.Row.FindControl("ddl") as DropDownList; ddl.DataSource = allGender; ddl.DataValueField = "Id"; ddl.DataTextField = "Name"; ddl.DataBind(); } } protected void Button1_Click(object sender, EventArgs e) { //由 GridView1 讀取資料 foreach (GridViewRow row in GridView1.Rows) { var ddl = row.FindControl("ddl") as DropDownList; var gender = ddl.SelectedIndex; var id = int.Parse(row.Cells[0].Text); string message = string.Format("Id:{0}, Gender:{1}", id, gender); Response.Write(message + "<br />"); } } } }
這一段程式寫的還真不怎麼樣,竟然還用 Response.Write 這種古老 ASP 的寫作方式?重點不是在演示如何取得資料,而是 ASP.NET 先天上的限制: Stateless。
由於 Web 的 Stateless 的特性,使得Server 端不會記得 Client 曾經輸入或給過的值。因此,當網頁第一次進入時取得所有 Emp 的資料,並在 GridView 的 RowDataBound 事件發生時,為每個性別欄位設定 DropDownList。
之後,使用者輸入完畢後,Submit 回到 Server後,emp 及 gender 的資料都已經不見了,只剩下 GridView 這個 Control 中的值能幫我們留下使用者最終的選擇。
在不得已的情況下,我們必須研究 GridView 的組成,如 GridView 的 Row,Row 下有 Cells,再使用 FindControl 取得 DropDownList…等等。
WPF
在 WPF 中,由於是 Stateful,相同的功能,寫作方式相對 ASP.NET 大為進步。
MainForm.xaml
<DataGrid AutoGenerateColumns="False" Height="206" HorizontalAlignment="Left" Margin="12,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="288" > <DataGrid.Columns> <DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" /> <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" /> <DataGridTextColumn Header="Gender" Binding="{Binding Path=Gender}" /> <DataGridComboBoxColumn Header="Gender2" x:Name="cbGender" SelectedValuePath="Id" DisplayMemberPath="Name" SelectedValueBinding="{Binding Gender}" /> </DataGrid.Columns> </DataGrid> <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="359,115,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
MainForm.xaml.cs
public partial class MainWindow : Window { private List<Emp> _emps; private List<GenderStruct> _allGenders; public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { //取得資料並放到 Window 的變數中。因為Stateful,所以可以這樣做 var dataSource = new MyDataSource(); _emps = dataSource.GetAllEmps(); _allGenders = dataSource.GetAllGenders(); dataGrid1.ItemsSource = _emps; cbGender.ItemsSource = _allGenders; } private void button1_Click(object sender, RoutedEventArgs e) { var emp = dataGrid1.SelectedItem as Emp; var message = string.Format("Name: {0}, Gender: {1}", emp.Name, emp.Gender); MessageBox.Show(message); } }
值得注意的是,在 Window Loaded 後,取得所有 Emp 及 Gender 資料後,直接放到 Window 的 private member 中,讓後續的資料繫結(databind) 作業直接存取window private member即可。
使用者輸入完畢後,更新後的資料「直接」反應到資料繫結的對象(即 _emps, _allGenders),不必像 WebForm一樣透過 Control 間接取得資料。
結論
這個範例中,主要演示了 ASP.NET WebForm 與 WPF 的先天上的差異(Stateless vs Stateful),並且由於 WPF 雙向繫結的功能,使用者的輸入可以直接更新到資料來源,遠較WebForm 來的直覺與方便。
沒有留言:
張貼留言