ความผิดพลาดพื้นฐานที่เกี่ยวกับ Datagrid
ใช้ได้กับ
Microsoft ASP.NET
สรุป:
เรียนรู้ความผิดพลาดพื้นฐานเมื่อตอนที่พัฒนาคอนโทรล Datagrid ของ ASP.NET
คอนโทรล Datagrid ใน Microsoft ASP.NET จัดเป็นคอนโทรลที่มีประสิทธิภาพและมีประโยชน์มากที่สุดตัวหนึ่ง แม้ว่าเราสามารถใช้คอนโทรลตัวนี้ได้โดยง่ายก็ตาม แต่ในบางครั้ง Datagrid ก็อาจก่อให้เกิดเรื่องหงุดหงิดได้เช่นกัน ความผิดพลาดต่างๆเหล่านี้เป็นเรื่องที่ผู้คนจำนวนมากทำตั้งแต่นักพัฒนา .NET มือใหม่ไปจนถึงผู้ที่มีประสบการณ์แล้วเจอ คุณมักจะพบกับคนเหล่านี้เข้ามาตั้งคำถามเกี่ยวกับปัญหาเหล่านี้ตามนิวส์กรุปและฟอรัมต่างๆที่เกี่ยวข้องกับ ASP.NET แต่ถ้าหากคุณปฏิบัติตามขั้นตอนง่ายๆในบทความนี้แล้ว คุณจะหลีกเลี่ยงความผิดพลาดต่างๆเหล่านี้ได้ แถมยังช่วยประหยัดเวลาในการพัฒนาโปรแกรมของคุณอีกด้วย
สร้างข้อมูลตารางโดยไม่ใช้ Datagrid
คนที่คุ้นเคยกับ ASP.NET มานานแล้วมักจะเขียนโค้ดในลักษณะนี้
Response.Write("<table>")
While MyDataReader.Read()
Response.Write("<tr>")
Response.Write("<td>")
Response.Write(MyDataReader(0))
Response.Write("</td>")
Response.Write("</tr>")
Loop
Response.Write("</table>")
โค้ดด้านบนอาจจะดัดแปลงให้ง่ายขึ้นดังนี้
<asp:datagrid runat="server" datasources="MyDataReader"/> บวกกับการเรียก DataBind() method เท่านั้นเอง แม้ว่าคุณต้องการควบคุมเอาท์พุด HTML เป็นพิเศษก็ตาม คุณสามารถใช้ดาต้าเว็บคอนโทรลตัวใดตัวหนึ่งเมื่อแสดงผลยูซเซอร์อินเทอร์เฟซซ้ำโดยอิงกับชุดเรกคอร์ดชุดใดชุดหนึ่งก็ได้
ลืมตรวจเช็ค IsPostBack ใน Page_Load event
ความผิดพลาดที่พบมากอีกชนิดหนึ่งก็คือการลืมตรวจเช็คเงื่อนไข IsPostBack ของเพจก่อนที่จะทำการเชื่อมข้อมูล ตัวอย่างเช่นเมื่อ Datagrid อยู่ในโหมด Edit การลืมตรวจเช็คจะส่งผลทำให้ค่าที่ถูกแก้ไขแล้วถูกค่าเดิมจากแหล่งข้อมูลทับไป อย่างไรก็ตามกฎข้อนี้ก็มีข้อยกเว้นด้วยเช่นกัน ดูในหัวข้อใช้ "ใช้ ViewState ที่มีขนาดใหญ่เกินไป"
โค้ดด้านล่างเป็น Page_Load event ปกติ ซึ่งใส่ขั้นตอนการตรวจเช็ค IsPostBack ลงไปด้วย ส่วน BindGrid() เป็นรูทีนที่รวบรวมและกำหนดแหล่งข้อมูลของ Datagrid รวมทั้งเรียก DataBind()method ด้วย
Sub Page_Load
If Not IsPostBack Then
BindGrid()
End If
End Sub
ติดยึดอยู่กับการสร้างคอลัมน์อัตโนมัติ เมื่อสภาพการใช้งานต้องการความยืดหยุ่นกว่านั้น
ถ้าหากสถานการณ์ Datagrid ต้องการการฟอร์แมทพิเศษใดๆหรือต้องใช้เว็บคอนโทรลอื่นๆภายใน DataGrid ของคุณ ถ้าอย่างนั้นคุณจำเป็นต้องปิดการทำงานของ AutoGenerateColumns เสียก่อน การปล่อยให้ property ตัวนี้กำหนดให้เป็น True (ค่าปกติ) มีประโยชน์สำหรับสถานการณ์ Datagrid ที่เรียบง่ายที่สุดเท่านั้น แต่ถ้าหากเป็นแอพพลิเคชันที่มีการใช้งานจริง คุณต้องกำหนดให้ AutoGenerateColumns เป็น false และกำหนดคอลัมน์อย่างชัดเจนเอาไว้ในหัวข้อ <columns></columns> ของการกำหนด DataGrid ผู้ใช้ Microsoft Visual Studio .NET สามารถใช้ Property Builder เพื่อสร้างคอลัมน์เหล่านี้เป็นกราฟิกได้ด้วย
หมายเหตุ: ถ้าหากคุณปล่อยให้ AutoGenerateColumns มีค่าเป็น true แล้วกำหนดคอลัมน์ในหัวข้อ <columns> ของ Datagrid แล้วละก็ คุณจะมีคอลัมน์ซ้ำซ้อนกัน คอลัมน์ที่กำหนดเอาไว้จะปรากฏขึ้นมาก่อน ตามด้วยคอลัมน์ที่สร้างขึ้นมาเองโดยอัตโนมัติ
พยายามอ้างอิงคอนโทรลที่อยู่ใน Datagrid item โดยใช้ ID เพียงอย่างเดียว
ผู้คนจำนวนมากไม่ทราบว่าถ้าหากคุณมีคอนโทรลอยู่ภายใน ItemTemplate ของ TemplateColumn ใน Datagrid (อาทิเช่นคอนโทรล TextBox ที่มี ID ของ "MyTextBox") คุณไม่สามารถอ้างอิงไปยังคอนโทรลดังกล่าวโดยตรงใน codebehind หรือหัวข้อ <script> ในเพจ ASPX โดยใช้โค้ดลักษณะนี้ได้
Dim MyValue As String = MyTextBox.Text
โค้ดดังกล่าวจะแจ้งข้อความว่า "ชื่อ `MyTextBox' ไม่ได้กำหนดเอาไว้"
เนื่องจาก Datagrid ประกอบด้วยแถว (item) จำนวนมาก ที่จริงแล้วจึงมี "MyTextBox" instance แยกต่างหากสำหรับแต่ละแถวในแหล่งข้อมูลของคุณด้วย ASP.NET จะเติม ID ของ naming container ในโครงสร้างนำหน้า ID ของคอนโทรล ดังนั้น TextBox จะมี ID ที่ไม่ซ้ำกับคอนโทรลอื่นๆที่อยู่ในเพจ ตัวอย่างเช่นถ้าหาก MyTextBox อยู่ใน DataGrid1 แล้ว ID ที่สร้างขึ้นมาจะเป็น DataGrid1:_ctl2:MyTextBox โดยที่ "_ctl2" เป็นตัวแทนของแถวปัจจุบันที่เก็บ MyTextBox เอาไว้ ส่วน MyTextBox instance อื่นๆที่อยู่ในเพจอาจจะมี ID อย่าง DataGrid1:_ctl3:MyTextBox, DataGrid1:_ctl4:MyTextBox และอื่นๆไปเรื่อยๆ ถ้าหากต้องการดึงค่าของ "MyTextBox" ที่คุณต้องการ คุณจำเป็นต้องเรียก FindControl method ใน DataGridItem ที่เหมาะสม โดยที่ DataGridItem ดังกล่าวจะเป็น parent naming container ของ TextBox
HTML
<asp:Datagrid runat="server" id="Datagrid1">
<Columns>
<asp:TemplateColumn>
<ItemTemplate>
<asp:TextBox runat="server" id="MyTextBox"/>
</ItemTemplate>
</asp:TemplateColumn>
</Columns>
Code
Sub DataGrid1_UpdateCommand(sender As Object, _
e As DataGridCommandEventArgs)
Dim MyValue As String = _
CType(e.Item.FindControl("MyTextBox"), TextBox).Text
'Do something with MyValue
End Sub
การเรียก CType ในผลลัพธ์ของ FindControl call เป็นการส่งค่ากลับคืนจาก type Object ไปยัง type TextBox ซึ่งจะเปิดโอกาสให้เรียกใช้ .Text property ได้
ไม่ได้ใช้การสร้างเพจอย่างที่ควรจะทำ
ผู้ใช้ของคุณอาจไม่ต้องการเลื่อนหน้าจอเพื่อดูเรกคอร์ดเป็นพันๆเรกคอร์ดในเพจเดียวแต่อย่างใด คุณต้องตรวจสอบให้แน่ใจว่าแอพพลิเคชันของคุณถูกออกแบบมารับมือกับสถานการณ์การส่งเรกคอร์ดจำนวนมากกลับไปได้ คุณควรอ่านบทความ "การจัดเพจ DataGrid อย่างรวดเร็ว" เพื่อศึกษาว่าคุณจะจัดเพจใน DataGrid ได้อย่างไร นอกจากนั้นคุณยังสามารถอ่านบทความเรื่อง "การสร้าง DataGrid ที่จัดเรียงข้อมูลได้และจัดเพจได้" ของ Scott Mitchell ดูก็ได้ ลืมทำ DataBind() call สำหรับ Datagrid event แต่ละครั้ง จนก่อให้เกิด postback
มีผู้ตั้งคำถามดังนี้ "เมื่อฉันคลิกที่ลิงก์ Edit ในแถวหนึ่งของ Datagrid เพจถูกโพสต์กลับมา แต่ในตอนนี้ไม่มีข้อมูล มีอะไรผิดพลาดเกิดขึ้น?" ปัญหาก็คือข้อมูลจะเชื่อมโยงกับกริดในตอนแรกที่มีการเรียกเพจเท่านั้น ถ้าหากพูดถึงเหตุการณ์แต่ละอย่างของ Datagrid (Edit, Update, Cancel, Page, Sort) คุณต้องตรวจสอบให้แน่ใจว่ามีการกำหนด Datasource property ของ Datagrid เอาไว้ด้วย (เว้นเสียแต่ว่ามีการกำหนดเอาไว้ใน <asp:Datagrid>) แล้วเรียก DataBind() method ใน Datagrid
สร้างไดนามิกคอนโทรล Datagrid หรือคอลัมน์ภายใน Datagrid ตอนรันไทม์โดยไม่จำเป็น
มีเหตุผลทางเทคนิคบางประการที่การสร้างคอนโทรล ASP.NET ตอนรันไทม์เป็นสิ่งที่จำเป็นและเหมาะสมแล้ว บางทีอาจเป็นเพราะต้องมีการกำหนดยูซเซอร์อินเทอร์เฟซตอนรันไทม์หลังจากที่มีการเลือกออปชันบางอย่างในเพจแล้ว หรือคุณกำลังสร้างคอมโพสิตเซิร์ฟเวอร์คอนโทรลที่จำเป็นต้องมีการสร้างคอนโทรลลูกขึ้นมาพร้อมๆกัน เนื่องจากไม่มีวิธีการอื่นๆที่จะสร้างคอนโทรลดังกล่าวขึ้นมา ถ้าหากคุณตกอยู่ในสถานการณ์ดังกล่าว คุณควรตระหนักว่าไดนามิกคอนโทรลของคุณไม่ได้ถูกเก็บไว้ขณะที่ส่งเพจไป คุณจำเป็นต้องสร้างไดนามิกคอนโทรลขึ้นมาใหม่ทุกครั้งที่มีการ postback (ในช่วงต้นของวงจรชีวิตของเพจ) อาทิเช่นในช่วง Page_Init event เป็นต้น สิ่งที่คุณควรจำเอาไว้ก็คือสร้างคอนโทรลแต่เนิ่นๆ และสร้างคอนโทรลบ่อยๆ ถ้าหากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับการสร้างไดนามิกคอนโทรล ให้เข้าไปในบทความหมวด Microsoft Knowledge Base เรื่อง "วิธีการสร้างไดนามิกคอนโทรลใน ASP.NET โดยใช้ Visual Basic .NET"
อย่างไรก็ตามถ้าหากแอพพลิเคชัน Datagrid ของคุณไม่จำเป็นต้องใช้ไดนามิกคอนโทรล คุณควรหลีกเลี่ยงการสร้างมันขึ้นมาจะดีกว่า เนื่องจากไดนามิก Datagrids แม้สามารถสร้างขึ้นมาได้ แต่มักจะก่อให้เกิดเรื่องปวดหัวอย่างมาก หรือพูดอีกแง่หนึ่งก็คืออย่าสร้างไดนามิกคอนโทรลเพียงเพราะไม่ต้องการให้การสร้างคอนโทรลทั้งหมดไม่ได้ไปแออัดอยู่ในไฟล์ ASPX เท่านั้น
ใช้ ViewState ที่มีขนาดใหญ่เกินไป
คอนโทรล Datagrid มีจุดอ่อนอยู่ตรงที่เมื่อมีการเพิ่มส่วน ViewState ลงไปในเพจจะทำให้เพจที่ส่งไปให้ผู้ใช้มีขนาดโดยรวมเพิ่มขึ้นอย่างมาก วิธีการง่ายๆสำหรับแก้ปัญหาขนาดของเพจที่เพิ่มขึ้นก็คือปิดการทำงานของ ViewState เสีย ซึ่งอาจจะปิดทั้งเพจหรือเฉพาะบางคอนโทรลเท่านั้นก็ได้ ตัวอย่างเช่นถ้าหากเพจของคุณไม่ต้องโพสต์กลับมา คุณสามารถปิด ViewState ทั้งเพจได้อย่างปลอดภัย มิฉะนั้นแล้วให้ปิด ViewState สำหรับคอนโทรลบางอันที่ข้อมูลสภาพไม่ได้มีการเปลี่ยนแปลงระหว่างโพสต์กลับมา หรือสำหรับคอนโทรลบางอันที่ไม่จำเป็นต้องใช้ฟิลด์ซ่อนเพื่อคอยติดตามสภาพของตนเอง
เมื่อปิด ViewState ของคอนโทรล Datagrid บางอัน หรือเพจที่มี Datagrid อยู่ คุณจำเป็นต้องทำขั้นตอนพิเศษบางอย่าง ถ้าหาก Datagrid ของคุณเป็นตัวเริ่มการโพสต์ข้อมูลกลับ ก่อนอื่นคุณจำเป็นต้องมีการเชื่อมข้อมูลซ้ำของ Datagrid ใน Page_Load ทุกครั้งที่มีการโพสต์ข้อมูลกลับ วิธีการนี้ตรงข้ามกับความเชื่อเก่าๆ (ที่ระบุเอาไว้ในข้อ 2 ด้านบน) แต่ขั้นตอนนี้เป็นสิ่งที่จำเป็นเมื่อมีการปิด ViewState เพื่อทำให้ Datagrid event อื่นๆทำงานอย่างถูกต้องหลังจาก Page_Load นอกจากนั้นคุณยังจำเป็นต้องใส่ Datagrid property บางอย่างด้วยมือลงไปใน ViewState ด้วย ถ้าหากคุณทำงานกับ Datagrid events บางอย่าง (หรือทั้งหมด) ตามตัวอย่างด้านล่าง ตัวอย่างเช่นเมื่อมีการแก้ไข Datagrid ที่ปิด ViewState เอาไว้ คุณสามารถเก็บ EditItemIndex เอาไว้ใน ViewState ได้ เมื่อ Datagrid อยู่ในโหมดแก้ไขตราบเท่าที่มีการใส่ EditItemIndex ซ้ำลงไปใน ViewState ก่อนในตอนที่เชื่อมโยงกับ Datagrid เป็นครั้งแรกในช่วง Page_Load (ดูโค้ดตัวอย่างด้านล่าง)
ตารางที่ 1 Datagrid event ที่ต้องขึ้นอยู่กับ ViewState
event
ขึ้นอยู่กับ ViewState หรือไม่
ฟิลด์ที่ต้องจัดเก็บเอาไว้ใน ViewState
ItemCreated
N/A
ItemDataBound
N/A
SortCommand
มี
SortExpression
EditCommand
มี
EditItemIndex
PageIndexChanged
มี
CurrentPageIndex
SelectedIndexChanged
N/A
ตัวอย่างที่ 1: โค้ดตัวอย่างสำหรับ Datagrid ที่เปิดการทำงานของ Editing, Sorting และ Paging แต่ปิดการทำงานของ ViewState
Sub Page_Load
If Not ViewState("EditItemIndex") Is Nothing Then
Datagrid1.EditItemIndex = ViewState("EditItemIndex")
End If
If Not ViewState("CurrentPageIndex") Is Nothing Then
Datagrid1.CurrentPageIndex = ViewState("CurrentPageIndex")
End If
BindGrid()
End Sub
Sub BindGrid()
Dim DV As DataView
DV = GetDataSource()
DV.Sort = ViewState("SortBLOCKED EXPRESSION
Datagrid1.DataSource = DV
Datagrid1.DataBind()
End Sub
Sub Datagrid1_SortCommand(s As Object, _
e As DataGridSortCommandEventArgs)
ViewState("SortBLOCKED EXPRESSION = e.SortExpression
BindGrid()
End Sub
Sub Datagrid1_EditCommand(s As Object, _
e As DatagridCommandEventArgs)
Datagrid1.EditItemIndex = e.Item.ItemIndex
ViewState("EditItemIndex") = e.Item.ItemIndex
BindGrid()
End Sub
Sub Datagrid1_PageIndexChanged(s as Object, _
e As DataGridPageChangedEventArgs)
Datagrid1.CurrentPageIndex = e.NewPageIndex
ViewState("CurrentPageIndex") = e.NewPageIndex
BindGrid()
End Sub
เมื่อตอนที่ใช้ ItemDataBound หรือ ItemCreated events แต่ลืมตรวจสอบ ListItemType ที่เหมาะสม
คอนโทรล Datagrid มี event สองแบบที่ใช้กับแถวข้อมูลแต่ละแถว นั่นก็คือ ItemCreated จะเกิดขึ้นเมื่อแต่ละแถวถูกเพิ่มลงไปใน Datagrid และ ItemDataBound จะเกิดขึ้นเมื่อข้อมูลถูกเชื่อมโยงกับแต่ละแถว event เหล่านี้มีประโยชน์สำหรับการแก้ไขหน้าตาหรือข้อมูลของแต่ละเซล และถูกเพิ่มลงไปในตารางเอาท์พุดของ Datagrid ตัวอย่างประเภทหนึ่งก็คือการแก้ไขสีแบกกราวน์ของแต่ละเซล โดยอิงกับช่วงค่าที่เป็นตัวเลข อย่างไรก็ตามสิ่งสำคัญที่ต้องจำเอาไว้ก็คือ event เหล่านี้ใช้กับ item ทุกประเภทของ Datagrid ได้ อาทิเช่น Header, Footer และ Pager เป็นต้น ถ้าหากคุณไม่ได้ตรวจสอบประเภทของ item ก่อนที่จะอ้างอิงถึงข้อมูลของ item ในช่วง ItemDataBound event แล้ว จะมีความผิดพลาดเกิดขึ้นกับ item แรกซึ่งปกติมักจะเป็นแถวหัวเรื่อง ดังนั้น item แรกจะกลายเป็น Pager แทน ถ้าหากมีการเปิดการทำงานของ Paging สำหรับ Datagrid เอาไว้ และมีการกำหนดให้แสดงผลด้านบนสุด ตัวอย่างต่อไปนี้เป็นการแสดงการตรวจสอบ ListItemType อย่างเหมาะสมก่อนที่จะอ้างอิงข้อมูลของ item และคุณต้องไม่ลืม AlternatingItem ด้วย
Sub DataGrid1_ItemDataBound(source As Object, _
e As DataGridItemEventArgs)
If (e.Item.ItemType = ListItemType.Item Or _
e.Item.ItemType = ListItemType.AlternatingItem) Then
If e.Item.DataItem("ForumDate") < DateTime.Today Then
e.Item.Cells(1).BackColor =
System.Drawing.Color.FromName("#ffccff")
End If
End If
End Sub
ใช้ Datagrid มากเกินไป เมื่อคุณจำเป็นต้องควบคุมผลลัพธ์ HTML ให้มากขึ้น ดังนั้นคุณควรใช้ Repeater น่าจะดีกว่า
โปรแกรมเมอร์ขี้เกียจมักชื่นชอบการใช้คอนโทรล Datagrid เนื่องจากคอนโทรลตัวนี้ทำงานส่วนใหญ่ให้คุณแล้ว ในขณะที่พวกที่ชอบควบคุมสิ่งต่างๆมากเป็นพิเศษมักชอบใช้ Repeater มากกว่า ถ้าหากคุณต้องการควบคุม HTML ที่สร้างขึ้นมาแบบเบ็ดเสร็จ คอนโทรล Repeater จะช่วยคุณได้ นอกจากนั้นคอนโทรล Repeater ยังทำให้ผลลัพธ์มีประสิทธิภาพเหนือกว่าเล็กน้อยอีกด้วย เนื่องจากมันไม่ต้องทำงานแบบสูญเปล่าไปกับคุณสมบัติต่างๆที่มีอยู่ใน Datagrid แต่ในกรณีที่ต้องประนีประนอม คุณอาจลองใช้คอนโทรล DataList ดูก็ได้ เนื่องจากคอนโทรลตัวนี้มีคุณสมบัติการแก้ไขและการจัดเรียงด้วยเช่นกัน แถมยังมีปมเด่นในเรื่องการเรียกเรกคอร์ดซ้ำตามแนวนอนอีกด้วย
แหล่งข้อมูล
- Viewstate บน DotNetJohn
- แบบฝึกสอนการใช้ Datagrid อย่างรวดเร็ว
- คำถามยอดนิยมเกี่ยวกับ Datagrid
- วิธีสร้าง Datagrid ที่จัดเพจและจัดเรียงข้อมูลได้