บทความ

Common Datagrid Mistakes

เรตติ้ง
เขียนโดย admin เมื่อวันที่ 23 January 2008 ตอน 08:35

ความผิดพลาดพื้นฐานที่เกี่ยวกับ 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 ที่จัดเพจและจัดเรียงข้อมูลได้
Filed under:
No Comments

Leave a Comment

(required)  
(optional)
(required)  
Add