Field's Space

.NET技术学习

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
在資料繫結專題的前兩部分,我們討論了自訂 DataGrid 控制項之圖形使用者介面的一些方法。 DataGird 絕對是 ASP .NET 資料繫結的伺服器端資料繫結控制項中效用最大的一個。這個月,我將深入探討替 DataGrid 使用者介面增添互動的各種可能性。這項功能的代價就是必須連結至伺服器,並進行自動且隱藏的來回資料傳輸, ASP .NET 自動回傳事件引擎會管理這些傳輸的進行。
對於 DataGrid 元件而言,互動性通常代表選取一列或數列的能力、編輯與將更新套用至儲存裝置上。除非你使用自訂的 ActiveX® 控制項或 Java Applet,不然絕對需要 DHTML。當然,使用 DHTML 會失去部分潛在讀者。如果單純使用舊式的 HTML 碼與指令碼來達到選取與就地編輯的效果,你需要重新整理頁面才能反映編輯結果與變更。
常見的解決方法是傳送兩種版本的應用程式。一個版本以 Microsoft® Internet Explorer 4.0 或更新版本為目標,發揮 DHTML 與指令碼程式的威力。另一個版本以相容於 HTML 3.2 版的瀏覽器為目標,利用一種能偵測使用者在某個表格列 (<TR> 標記) 上按一下的機制,將網頁導向另一頁。跟著這一頁就會將被選取的那一列以不同的顏色標示出來,或是提供一個表單,而這個表單中的輸入欄位已經初始化成某些儲存格 (<TD> 標記) 的值。別忘了這是一個簡單又有效的結構描述,因為這與 ASP .NET 藉由自動回傳事件機制的協助所達成的效果非常類似。
在這期文章中,我會先利用指令碼程式與 Internet Explorer 的行為提供一個解決方案。接著我會將重心轉到 ASP .NET 並檢視 DataGrid 在支援選取與就地編輯上的能力。

使用 DHTML 的互動性方格
DHTML 讓你能輕易更新網頁版面以反映諸如選取與編輯等互動事件的效果。一旦你顯示了一個記錄表格,選取這個動作不過就是記錄目前選取列的索引值與在按下事件發生後更新其樣式這兩件事,程式碼如下:
<script language="JavaScript"
for="tableRow" event="onclick">
if (m_selectedRow == null)
return;
SelectRow(window.event.srcElement);
</script>
<script language="JavaScript">
function SelectRow(oElem) {
m_selectedRow.className = "defItem";
m_selectedRow = oElem.parentElement;
m_selectedRow.className = "itemSelected";
}
</script>
上面這一小段程式碼,來自本月指令碼程式的原始碼中 (請參照文章最上方的連結)。
基本上,這段程式會攔截所有發生在表格列上的按下事件 (表格內的所有列都有同一識別名稱,也就是 tableRow)。當一個按下事件發生時,這段指令碼會被執行,然後它會呼叫 SelectRow 函式,這個函式只是簡單地修改一些階層式樣式表裡的樣式以反映新狀態。全域變數 m_selectedRow 會以 DHTML 物件的方式記錄目前被選取的列。在 SelectRow 的本體內,第一個動作就是取消目前選取列。只須修改連結至對應 <TR> 標記的階層式樣式表名稱即可完成這個動作。接著,更新這個變數使之包含新選取列,並把其樣式設成選取列應有的樣式。
這段程式碼十分簡單,但是它仍需要 DHTML 碼或瀏覽器對動態更新階層式樣式表的支援。你只需簡單的客戶端指令碼,這我已經嵌在一個指令碼程式中,使有更佳的再使用性。要讀取被選取列的內容,可以利用這個物件的 innerHTML 屬性:
alert(m_selectedRow.innerHTML);
關於選取的討論到此告一段落。編輯,可就是另外一件事了。首先,你必須有一個方法來提示使用者某一列目前的內容。接著,你必須讓使用者能進行編輯並儲存。不論你用甚麼方法 (XML 資料島、隱藏欄位或隱藏元件) 將表格內容傳至用戶端,最後你還是要回到伺服器儲存使用者所做的改變。唯一的例外就是你透過 COM 物件或 Internet Explorer 5.0 的保存行為來達成用戶端保存性。不過,這在網際網路上大概永遠不會發生。
在建構互動性的 DHTML 方格時,你可以設計一個炫麗的對話方塊,使之從邊界展開或顯示。不過儘管這些對話方塊看起來炫麗,仍然需要與伺服器進行來回的資料傳輸。圖一展現執行中的指令碼程式。

圖一 執行中的指令碼程式
圖一 執行中的指令碼程式

ASP .NET 的設計,就是為了讓使用者與網頁的互動性容易設計同時又具有瀏覽器可攜性。如果你不用 DHTML,重新整理頁面將會需要與伺服器更密集的來回資料傳輸。上個月我們探討的伺服器端 DataGrid 控制項與欄位範本,其實能提供的功能不止文章中所介紹的,在表格列的編輯範本與可選擇樣式上,也著墨不少。

在 DataGrid 中選取項目
如同前面所解釋的,一個被選取項目不過就是一個有著不同的版面或樣式的一般項目。要啟用 DataGrid 中的項目選取功能,你必須提供兩個參數:引發選取程序的動作與被選取項目的新樣式。接著 DataGrid 就會在被選取項目改變時觸發 SelectedIndexChanged 這個特殊事件。
如你所知,使用者選取項目的方法就是在上面按一下。一般而言,使用者可以在欲選取列的任意處按一下。不過,DataGrid 控制項也提供了讓你能縮小可按區域的選項。例如,你可以加一新列到表格中,其唯一的用途就是選取其父資料列。
<asp:ButtonColumn
Text="Select"
CommandName="Select" />
DataGrid 知道如何處理 Select 這個特殊命令名稱。當使用者按下方格項目中的按鈕時,會引發 ItemCommand 事件。如果命令名稱是 Select,則會進行以下動作。 DataGrid 會找出哪一個項目被點選,依此更新 DataGird 的 SelectedIndex 屬性值,再根據 SelectedItemStyle 屬性來設定該列的樣式。如果之前有另外一列已被選取,則該列會重設為預設樣式。注意這些動作都是自動進行的。你唯一要做的事就是如之前所示範的加上一按鈕列與指定選取列樣式即可:
<property name="SelectedItemStyle">
<asp:TableItemStyle
BackColor="blue"
ForeColor="white"
Font-Bold="true" />
</property>
Figure 2 圖二顯示設定樣式後的結果。

圖二 選取列樣式
圖二 選取列樣式

要改善使用者介面,你可以用影像來取代按鈕文字:
<asp:ButtonColumn
Text="<img border=0 src=select.gif>"
CommandName="Select" />
Figure 3 圖三顯示這個新表格。

圖三 顯示一個選取按鈕
圖三 顯示一個選取按鈕

有幾個理由需要設計 SelectedIndexChanged 事件的自訂處理常式。例如,你也許想要在某列被選取時切換影像。另一個更有可能的情況是,你想重新整理部分使用者介面。讓我們來看看如何達成這樣的效果。
一開始,先替 SelectedIndexChanged 事件加上處理常式:
<asp:DataGrid id=DataGrid1 runat="server"
•••
OnSelectedIndexChanged="Grid_SelectionChanged">
處理常式的宣告如下:
void Grid_SelectionChanged(Object Sender, EventArgs e)
SelectedIndex 屬性為可讀寫屬性。要改變某一列的選取狀態,你可以將該列的索引值 (從 0 開始) 指派給這個屬性。若是你定義一個 SELECT 指令,你就無須再手動設定屬性。事實上,DataGrid 會在標準命令的內部處理常式中更動其值。在 SelectionIndexChanged 事件中,你可以將此視為理所當然並利用新值。例如:
statusbar.Text = "Item: " + DataGrid1.SelectedIndex.ToString();
圖四顯示最後結果。

圖四 顯示新選取列
圖四 顯示新選取列

記得選取項目的索引值並不含頁次資訊。例如,假設你選取了第 1 頁第 2 列,然後移到第 2 頁。你期待畫面上並不會出現被選取列。但是,結果 DataGrid 仍然顯示第 2 列為選取列。如果你是透過 SelectedItem 屬性來存取被選取項目,並回傳一個 DataGridItem 物件,其內容仍然正確地來自第 1 頁第 2 列。
這樣的行為是當初就設計好的。方格的選取索引值並不會因 PageIndexChanged 事件處理函式切換頁次而更動。例如,假設一個使用者正在編輯被選取列,你可能會希望在他換到下一頁前提示他儲存變更。

圖五 選取項目不符
圖五 選取項目不符

如果處理不當,這個行為會產生如圖五所示的副作用。在右邊的畫面上,看起來被選取的項目與狀態列上顯示的資訊不符,因為狀態列的資訊來自 SelectedItem 屬性。要解決這個問題,每當程式要巡覽至下一頁時,就把 SelectedIndex 設為 -1。

被選取項目
SelectedItem 是一個唯讀屬性,它會傳回 DataGrid 中目前被選取的列,其型別為 DataGridItem 的物件。透過這個方法你可以取得這一列的內容,再來就看你怎麼去使用它。通常,你會在 SelectedIndexChanged 事件中,重新整理狀態列的文字以反映選取列的內容。
圖五的方格在發出 SELECT 命令時會利用圖六的程式碼重新整理使用者介面。每一方格列都以一個 DataGridItem 代表,而每一個 DataGridItem 又是一群 TableCell 物件的集合,每一個 TableCell 都是所產生的 HTML 表格中的某個儲存格。這段程式碼會檢查這一列的 Controls 集合,一次一個地讀進項目內容。一旦你讀到內含影像的儲存格,就可以改變其影像以反映選取狀態的改變。
如之前所提的,SelectedIndex 是一個可讀寫的屬性,因此你可以設定成手動改變想要選擇的某一列。如果你使用 OnClick 事件按鈕並加入下列程式碼,你就能使用以零為基準的索引 (zero-based index) 選擇特定的某一列。
void SelectRow(Object sender, EventArgs e)
{
DataGrid1.SelectedIndex = RowNumber.Text.ToInt32()-1;
}
此時,這一列已被妥當地選取,同時也正確地套用了特定的階層式樣式表的樣式。但是令人吃驚的是,SelectedIndexChanged 事件從未被引發。因此,你所設計用來依被選取列重新整理使用者介面的程式碼也從未執行。這是當初刻意設計的。 SelectedIndexChanged 事件只在使用者與頁面有互動時才會被引發。
一個簡單又有效的解決方案是將程式碼直接加在事件處理常式中。只要將以下這一行加入上面的 SelectRow 程序即可。
Grid_SelectionChanged(sender, e);
要取消目前的選取列,將 SelectedIndex 屬性設成 -1 即可。但是,要謹慎設計 SelectionIndexChanged 事件處理函式。當你取消選取時,你不該預期 SelectedItem 屬性必然是非 Null 值。你可以將整段用來重新整理使用者介面的程式碼包在 try/catch 區塊中:
try {
DataGridItem dgi;
dgi = DataGrid1.SelectedItem;
•••
statusbar.Text = "..."
}
catch {
statusbar.Text = "No item selected"
}
如果你針對被選取列執行一些特定的格式化動作,也可以同時處理 ItemCreated 事件並測試新項目的型別是否為:
ListItemType.SelectedItem
目前為止,DataGrid 的選取機制尚未支援多列選取。利用這個控制項的程式設計介面達成多列選取並不算特別難。事實上,大概只須把 SelectedIndex 屬性轉成一個集合,並增加幾個特定方法就夠了。但是,從內部產生能力的角度,事情並不是這麼簡單。一個支援多列選取的 DataGrid 控制項不但酷而且還極為有用,但即使繼承自 .NET,這還是得耗費一大部分的心力。
你也許已經注意到,只要你能在某特定儲存格上按一下,你就能在 DataGrid 中選取一列。幾年前,Microsoft 曾經提出一個 ListView 控制項,也有著類似的行為。但是因為這個控制項不能在一列的任意處按下以選取該列,所以 Microsoft 很快就提出一個新樣式支援全列選取 (按在一列的任意處都能選取該列)。在 DataGrid 中類似的行為是可能的嗎?不,除非你自己設計。所有你能處理的事件都假設你是按在一個按鈕欄位上。一個一般的文字欄位不會對滑鼠動作有任何反應。
一個可行的解決方法是建立一個全為按鈕欄位的 DataGrid。這樣你就有兩個方法來處理事件。你可以讓所有欄位都有相同的命令名稱,或定義 OnItemCommand 事件的處理常式。每當你按下任何一個按鈕欄位時都會引發這個事件。但是要注意,這個事件總是比各欄的命令更早被處理。如果你打算處理 ItemCommand 事件,最好記得其方法原型如下:
void Grid_Click(
Object sender,
DataGridCommandEventArgs e)
DataGridCommandEventArgs 的 Item 屬性會指向你按下的 DataGridItem。要取得被選取列的索引值,可利用 DataGridItem 的 ItemIndex 屬性。

就地編輯
如果你打算在應用程式中使用 DataGrid,通常你真正需要的是一個如 Microsoft Excel 工作表般可編輯的 DataGrid。所以你應該不會太驚訝知道 ASP .NET 的 DataGrid 控制項提供了攔截程序,讓你能插入程式碼以啟動就地編輯。在這個情況下,主要的元件有:一個處理編輯命令列的按鈕欄位、一個以上的可讀寫儲存格以及三個處理基本事件 (如編輯開始、更新與編輯取消等) 的程序。
幾乎所有你需要的東西都已內建在 DataGrid 控制項中,或是容易自 DataGrid 取得的。但是 DataGrid 並不提供背後資料來源間接或直接的實際更新。待會兒我將討論這一項。現在,我們專注在如何修改這個方格使之能支援就地編輯。
首先,增加三個事件處理常式來管理基本作業。
<asp:DataGrid id=DataGrid1 runat="server"
•••
OnEditCommand="Grid_Edit"
OnUpdateCommand="Grid_Update"
OnCancelCommand="Grid_CancelEdit">
其次,在方格某處插入一個編輯按鈕欄位。這個欄位同時也是選取按鈕欄位。我會將這個欄位排在第一位,如果已經有選取按鈕欄位,則排在第二位。當然,你也可以依喜好將它排在最右邊甚至是中間。
<asp:EditCommandColumn
EditText="<img src=edit.gif>"
CancelText="<img src=cancel.gif>"
UpdateText="<img src=ok.gif>"
/>
一個編輯欄位是透過 EditCommandColumn 類別的物件所產生。它有一群屬性與方法,這其中你最需要熟悉的是 EditText、CancelText 與 UpdateText。這三個屬性的值均為 HTML 文字,DataGrid 將顯示這些文字以告知使用者開始編輯、取消與儲存變更的動作應按在哪裡。就像選取按鈕欄,你也可以用影像代替文字,只要將顯示影像的 HTML 碼指派給這些屬性即可。注意這些影像將含有框線,除非你在 IMG 標記中加入 border=0 這項屬性設定。另外我還發現一些常用的屬性設定,包括 align=absmiddle 與 alt="提示文字",這會替按鈕加上工具提示。
當你使用一個 EditCommandColumn (或其他按鈕欄位) 時,你就是建立了一個其項目為連結按鈕的按鈕欄位。換句話說,你會在某個超連結上按一下以引發某個特定函式。你透過 ButtonType 屬性來控制按鈕的樣式。其樣式選擇非常有限:LinkButton 或 PushButton。如果你使用如下的程式碼:
<asp:EditCommandColumn
ButtonType="PushButton"
EditText="..."
CancelText="..."
UpdateText="..."
/>
按鈕將具有立體外觀。這時你不能使用 HTML 文字。如果以下列字串指派 EditText 屬性值,那這個字串將變成按鈕的標題。
<img src=edit.gif>
處理事件

圖七內是一個具有可編輯方格的網頁所需要的最短程式碼。 DataGrid 的 EditItemIndex 代表你正在編輯的項目。如果其值為 -1,表示目前無任何項目在編輯中。將 EditItemText 設成 -1 也會取消任何進行中的編輯動作。所有的處理常式都有一個 DataGridCommandEventArgs 引數。要找到被編輯項目的索引值,你可以呼叫 DataGridItem 物件的 ItemIndex 屬性,透過 Item 進行存取。因此,要開始編輯一個列,需用以下程式碼:
DataGrid1.EditItemIndex = e.Item.ItemIndex;
ItemIndex 是一個相對於本頁的索引值,其中未含該列在資料集內絕對位置的任何資訊。如果你需要取得被選取項目的物件參照,利用 Item.Cells 或 Item.Controls 集合。這些集合所包含的資訊略有不同,且不能交互使用。 Item.Cells 包含組成方格列的所有 TableCell 物件 (記得,一個方格列是以 <TR> 方式產生的)。另一方面,Item.Controls 則含有各儲存格中的所有控制項。換言之,你可以確定 Cells[1] 就是指第二個 TableCell 物件,但是你不能確定 Controls[1] 的位置,因為其位置是由用來產生方格內各儲存格的範本所決定的。
要讓每件事都正確運作,你必須修改在圖七的程式碼,加入重新繫結 (事實上,所有你設計的事件處理常式都必須包含重新繫結資料來源的程式碼)。
DataGrid1.DataBind();
當你開始編輯時,你只須修改一列的版面配置。重新繫結是絕對必須的,因為它會重新填入與重畫 DataGrid 表格。
使用就地編輯,就表示資料來源有所更動。這表示你至少要在 UpdateCommand 中重新自儲存裝置讀取資料,也需讀取各欄位的新值。至於 EditCommand 與 CancelCommand,自資料來源重讀資料似乎不是太重要,因此你可以隱藏 DataBind 的呼叫。但是,就我所知,為了提供更具彈性且更一致的程式設計介面,在 EditCommand 與 CancelCommand 中均強制呼叫 DataBind。
當你開始編輯,方格會呈現一個文字方塊,而不是一般用來顯示欄位文字的標籤。文字方塊的初值與先前標籤上的文字相同。如你所看到的,這個步驟等於就是重新產生這個表格。接著,你開始使用這個文字方塊進行編輯。待要儲存變更時,你必須負責取出所有應讀欄位的值,並想個法子將這些資料送回資料來源。跟著你應該要反映出剛剛所做的變更。以下是 UpdateCommand 處理常式的慣用格式:
// Cancel editing
DataGrid1.EditItemIndex = -1;
// Update the data source
// to do...
// Refresh the grid
BindData();
處理 Update 命令比表面上看來更需要技巧。 DataGrid 所做的只是將標籤控制項代換成輸入方塊。你必須負責從這些文字方塊中取得正確資訊以更新資料來源。在圖八中你看到就地編輯在之前的方格中如何動作。第二個欄位 (Name) 並未對應到任何資料庫欄位。其值乃是由三個不同的欄位組合而成:TitleOfCourtesy、FirstName 與 LastName。如果你想分別編輯這三個欄位時又該如何?你有兩個選擇。你可以從單一輸入文字中拆出三段資訊,或是修改這個欄位的編輯範本 (待會討論)。不論是哪一個選擇,你都必須在 UpdateCommand 處理常式中加入該應用程式專有且不是太容易的程式碼。為了確定方格總是已適切地重新整理,你必須重新建立資料集並將之指派給 DataSource 屬性。
圖八 就地編輯
圖八 就地編輯

取消編輯同時也表示你必須將之恢復成舊值。這只需要呼叫一次 DataBind 就夠了,因為舊值仍然存在 DataGrid 所繫結的資料集中。 圖九 是到目前為止所有我介紹的網頁的指令碼。

DataGrid 的 版面配置
要啟用就地編輯,你必須實作一特殊欄位。這個欄位會透過 EditText 屬性顯示 HTML 文字。當你在該連結上按一下時,DataGrid 就進入編輯模式。這個欄位的內容會變成兩個連結,一個是儲存變更 (UpdateText) 另一個則是取消動作 (CancelText)。
你可以控制哪些欄位是可編輯的。所有欄位都預設是可編輯的,除非你將其 Readonly 屬性設成 True。例如,在圖八中,從欄位宣告可以看出 Employee ID 員工欄位是不可編輯的。
<asp:BoundColumn
DataField="employeeid"
HeaderText="ID"
Readonly="True" >
</asp:BoundColumn>
EditItemIndex 與 SelectedIndex 有著同樣的行為。換句話說,當索引值為 1 時,你指到方格中的第二列, 因為第一列的索引值為 0。見圖十

圖十 索引值與列號不符
圖十 索引值與列號不符

更新資料來源
在 UpdateCommand 處理常式中最重要的一件事就是將重要的資料回存至其資料來源中。 DataGrid 的 DataSource 屬性指向一個繼承自 ICollection 的物件。一般而言,這個物件是儲存在 ADO .NET DataSet 物件中某一個 DataTable 物件的一個檢視。
ICollection CreateDataSource()
{
// Define command and connection string
// Code here
SQLDataSetCommand oCMD;
oCMD = new SQLDataSetCommand(strCmd, strConn);
DataSet oDS = new DataSet();
oCMD.FillDataSet(oDS, "EmployeesList");
return oDS.Tables["EmployeesList"].DefaultView;
}
UpdateCommand 處理常式必須取得列集合,然後利用 DataTable 物件的程式設計介面來套用更新。這時,僅儲存在記憶體中的資料被更新。要連結至伺服器並確實更新資料來源,你必須利用 ADO .NET 所提供的工具,特別是 SQLDataSetCommand 的 Update 方法。注意,如果你的目標是 Microsoft SQL Server™ 以外的 OLE DB 供應者,你應該改用 ADODataSetCommand。同時也應注意這些類別的名稱在 ASP.NET Beta 2 中可能會改變。
Update 方法會將一個 DataSet 的內容傳送至資料來源。它所自動產生的 SQL 敘述通常利用 INSERT、DELETE 與 UPDATE 等命令變更資料集。但是,DataSetCommand 物件公開了三項屬性 (InsertCommand、UpdateCommand 與 DeleteCommand) 讓你決定該用哪些命令敘述來更新資料來源。

變更欄位範本
我所談到的仍只是 DataGrid 粗淺的一面。以上針對就地編輯的架構分析,只有在資料欄位與表格欄位是一對一的對應關係,且僅進行簡單的編輯動作時才成立。如果你需要檢查輸入值時該如何?雖然你可以靠 UpdateCommand 處理常式內的程式碼來進行檢查,但這個方法既沒有彈性也不具威力。更糟的是,只有在你要套用變更時才能進行檢查,這讓你無法剔除無效資料並繼續進行編輯。你可以不儲存資料,但是使用者必須再按一次按鈕才能重新進行編輯。如果你想限制輸入值僅限於下拉式方塊的項目又該如何?又或者你也許想利用核取方塊來輸入布林值,這又該如何達成呢?
不消說,在現實情況中你必須修改欄位範本以確定你使用了真正需要的控制項:驗證器、下拉式方塊、核取方塊與其他相關附屬元件 (標籤、影像與工具提示),使編輯工作變的更加的簡單。
別擔心,這正是我下個月的主題。

所有要給 Dino 的問題與意見請寄至 cutting@microsoft.com
posted on 2006-07-20 17:27  Field  阅读(999)  评论(0编辑  收藏  举报