การจัดหมวดหมู่แถวของข้อมูลบน GridView ด้วย C#

          ในการดึงข้อมูลมาแสดงผลในรูปแบบตาราง GridView อาจเป็นหนึ่งในทางเลือกของนักพัฒนาที่จะหยิบไปใช้เป็นเครื่องมือในการแสดงผล แต่ในกรณีที่ข้อมูลมีจำนวนมากอาจทำให้ผู้ใช้รู้สึกตาลายและอ่านยากไปสักหน่อย ผู้พัฒนาจึงต้องพยายามหาวิธีจัดการข้อมูลในการแสดงผลให้สามารถอ่านง่ายและสบายตามากยิ่งขึ้น ดังนั้น ในกรณีที่ข้อมูลมีจำนวนมาก การจำแนกประเภท หรือแยกออกเป็นกลุ่มย่อยๆก็เป็นอีกหนึ่งในทางเลือกที่จะช่วยแก้ปัญหาดังกล่าวได้ ดังนั้น ในบทความนี้ผู้เขียนจะขอแนะนำวิธีการแสดงผลของข้อมูลบน GridView แบบจำแนกออกตามกลุ่ม เพื่ออำนวยความสะดวกให้กับผู้ใช้ในการดูข้อมูลแยกส่วนกัน เพื่อเพิ่มความสามารถให้กับ GridView ซึ่งเราเองใช้งานอยู่และน่าจะคุ้นเคยกันดีอยู่แล้ว ซึ่งจะมีขั้นตอนดังนี้ค่ะ

ขั้นตอนในการพัฒนา

  1. เตรียมข้อมูลในการแสดงผล โดยจากตัวอย่างนี้ จะทำการสมมุติข้อมูลของดอกไม้ ผลไม้ และต้นไม้ และมีข้อมูลเพื่อใช้ในการแยกประเภทไว้ด้วย เพื่อให้เห็นภาพมากขึ้นค่ะ
//// ประกาศตัวแปร GroupData
 เป็น ViewState เพื่อใช้ในงานข้อมูลในส่วนการแทรกแถวประเภทกลุ่มใน Event อื่นด้วย 

public DataTable  GroupData

        {
            get
            {
                if (ViewState["GroupData"] == null)
                {
                    ViewState["GroupData"] = new DataTable();
                }
                return (DataTable)ViewState["GroupData"];
            }
            set { ViewState["GroupData"] = value; }
        }

//// ผู้อ่านสามารถเรียกใช้ฟังก์ชั่น Getdata() การดึงข้อมูลนี้ในตอน Page_Load เพื่อดูเป็นตัวอย่างได้ค่ะ
protected void Getdata() 
        {
            
            GroupData.Columns.AddRange(new DataColumn[5] {
                         new DataColumn("CategoryName", typeof(string)),
                         new DataColumn("Name", typeof(string)),
                         new DataColumn("ID", typeof(string)),
                          new DataColumn("Amt", typeof(int)),
                        new DataColumn("CategoryID",typeof(string))});

            GroupData.Rows.Add("Flower", "Rose", "1",2500, "01");
            GroupData.Rows.Add("Flower", "Lotus", "3",150, "01");
            GroupData.Rows.Add("Fruit", "Grape", "2",350, "02");
            GroupData.Rows.Add("Fruit", "Mango", "4",1750, "02");
            GroupData.Rows.Add("Fruit", "Orange", "5",2240, "02");
            GroupData.Rows.Add("Tree", "Cactus", "6",370, "03");
            GroupData.Rows.Add("Tree", "Hazelnut Tree", "6",2250, "03");
            ////นำข้อมูลใน Datatable ชื่อ GroupData แสดงผลใน GridView
            GroupGv.DataSource = GroupData;
            GroupGv.DataBind();
        }

หมายเหตุ : การประกาศตัวแปร และการดึงข้อมูลเป็นเพียงการยกตัวอย่างง่ายๆ เพื่อให้ผู้ใช้เข้าใจเท่านั้น ในการทำงานจริงผู้อ่านสามารถใช้วิธีการประกาศตัวแปรและเรียกใช้แบบอื่น หรือดึงข้อมูลจากส่วนอื่นได้ ขึ้นอยู่กับความเหมาะสมและความถนัดค่ะ

2. เตรียม GridView ที่จะใช้ในการแสดงผล โดยจะขอยกตัวอย่างให้ดูการแสดงผล GridView แบบทั่วไปก่อนมีการจัดกลุ่ม เพื่อให้เห็นความแตกต่าง ดังนี้ค่ะ

    <asp:GridView ID="GroupGv" runat="server" AutoGenerateColumns="False"
        DataKeyNames="ID"  CssClass="table table-sm table-hover Blue" Width="100%">
        <AlternatingRowStyle CssClass="Blue" />
        <EmptyDataRowStyle CssClass="Blue" />
        <EmptyDataTemplate>
            <br />
            <div style="text-align: center">
                <i class="fas fa-exclamation-circle"></i>&nbsp;&nbsp; ไม่พบข้อมูล<br />
                &nbsp;
            </div>

        </EmptyDataTemplate>
        <EmptyDataRowStyle HorizontalAlign="Center" />
        <HeaderStyle CssClass="Blue" />

        <Columns>
            <asp:TemplateField HeaderText="Name">
                <ItemTemplate>
                    <asp:HiddenField ID="hdCatID" runat="server" Value='<%# Eval("CategoryID") %>' />
                    <asp:HiddenField ID="hdID" runat="server" Value='<%# Eval("ID") %>' />
                    <asp:HiddenField ID="HidCatName" runat="server" Value='<%# Eval("CategoryName") %>' />
                    <asp:Label ID="lblName" runat="server" Text='<%# Eval("Name") %>'></asp:Label>
                </ItemTemplate>
                 <ItemStyle  Width="50%"  />
            </asp:TemplateField> <asp:BoundField HeaderText="Category Name" DataField="CategoryName">
            <ItemStyle  Width="30%"/>
             </asp:BoundField>
              <asp:BoundField HeaderText="Amount" DataField="Amt"  DataFormatString="{0:#,##0}" >
            <ItemStyle HorizontalAlign="Right"  Width="20%"/>
             </asp:BoundField>
           
        </Columns>
    </asp:GridView>

เพิ่มเติม : ข้อมูลจำนวนเป็นข้อมูลที่เป็นตัวเลข จึงได้ทำการจัด Format รูปแบบของข้อมูลให้แสดงผลแบบตัวเลข ด้วยการระบุ DataFormatString=”{0:#,##0}” เช่น หากข้อมูล 2500 จะแสดง 2,500 ให้อัตโนมัติ

ผลลัพธ์(ก่อนทำการจัดกลุ่ม)

3. เพิ่ม Event ที่ชื่อว่า OnDataBound=”GroupGv_DataBound” ให้กับ GridView เพื่อแสดงผลข้อมูลแบบกลุ่ม และตัดคอลัมน์ประเภท(Category Name)ออกไป เนื่องจากเราจะนำไปใช้แสดงผลในการจัดกลุ่ม

    <asp:GridView ID="GroupGv" runat="server" AutoGenerateColumns="False"
        DataKeyNames="ID"  CssClass="table table-sm table-hover Blue" Width="100%" OnDataBound="GroupGv_DataBound">
        <AlternatingRowStyle CssClass="Blue"  />
        <EmptyDataRowStyle CssClass="Blue" />
        <EmptyDataTemplate>
            <br />
            <div style="text-align: center">
                <i class="fas fa-exclamation-circle"></i>&nbsp;&nbsp; ไม่พบข้อมูล<br />
                &nbsp;
            </div>

        </EmptyDataTemplate>
        <EmptyDataRowStyle HorizontalAlign="Center" />
        <HeaderStyle CssClass="Blue" />

        <Columns>
            <asp:TemplateField HeaderText="Name">
                <ItemTemplate>
                     <%--นำค่าไปใช้ตอนแทรกแถวหมวดหมู่ที่ต้องการจัดกลุ่ม--%>
                    <asp:HiddenField ID="hdCatID" runat="server" Value='<%# Eval("CategoryID") %>' />
                    <asp:HiddenField ID="hdID" runat="server" Value='<%# Eval("ID") %>' />
                    <asp:HiddenField ID="HidCatName" runat="server" Value='<%# Eval("CategoryName") %>' />
                    <asp:Label ID="lblName" runat="server" Text='<%# Eval("Name") %>'></asp:Label>
                </ItemTemplate>
                 <ItemStyle  Width="70%"  />
            </asp:TemplateField> 
              <asp:BoundField HeaderText="Amount" DataField="Amt"  DataFormatString="{0:#,##0}">
            <ItemStyle HorizontalAlign="Right"  Width="30%"/>
             </asp:BoundField>
           
        </Columns>
    </asp:GridView>

4. เพิ่มโค้ดในส่วนของฝั่งเซิร์ฟเวอร์(C#) ให้กับ Event ของ GridView ที่เราเพิ่มในข้อ 3. เพื่อจัดกลุ่ม ดังนี้ค่ะ

        string lastCatName = "";
        string AllName = "";

        protected void GroupGv_DataBound(object sender, EventArgs e)
        {
            lastCatName = "";
            Table table = (Table)GroupGv.Controls[0];

           ////////วนเพื่อแทรกแถวชื่อแต่ละประเภทของข้อมูล เช่น ดอกไม้ ต้นไม้ หรือผลไม้ 
            foreach (GridViewRow row in GroupGv.Rows)
            {
              
              
                HiddenField HidCatName = (HiddenField)row.FindControl("HidCatName");
                HiddenField HidCatID = (HiddenField)row.FindControl("hdCatID");
                HiddenField HidID = (HiddenField)row.FindControl("hdID");
                
        
           ////////หากพบว่าเป็นประเภทใหม่จะทำการสร้างแถวและเพิ่มแทรกเข้าไป โดยมีการกำหนดค่าต่างๆ เช่น ข้อความที่จะแสดง สีพื้นหลัง สีตัวอักษร และค่า ColumnSpan เป็นต้น

 
                if (HidCatName.Value != lastCatName)
                {
                   
                    int realIndex = table.Rows.GetRowIndex(row);
                    string text = HidCatName.Value;
                    GridViewRow newHeaderRow = new GridViewRow(realIndex, 0, DataControlRowType.Header, DataControlRowState.Normal);


                    /////สร้าง TableCell และระบุค่าต่างๆ ก่อนนำไปเพิ่มในแถว newHeaderRow ที่เพิ่งสร้าง
                   TableCell newCell = new TableCell();
                    newHeaderRow.Cells.AddAt(0, newCell);

                    ///กำหนด ColumnSpan เท่ากับจำนวนคอลัมน์ทั้งหมดใน GridView (GroupGv.Columns.Count)เพื่อให้คอลัมน์ที่ต้องการเพิ่มยาวครอบคลุมทั้งแถว
                    newCell.ColumnSpan = GroupGv.Columns.Count;
                    newCell.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                    newCell.ForeColor = System.Drawing.Color.White;
                    newCell.Font.Bold = true;
                    newCell.Text = string.Format(HidCatName.Value, "&nbsp;{0}", text);

                    ////เพิ่มแถวที่ต้องการแทรกเข้าไปในตารางหรือ GridView ที่เรากำลังจัดการอยู่นั่นเอง
                    table.Controls.AddAt(realIndex, newHeaderRow);
                   
                }
                lastCatName = HidCatName.Value;
            }
        }         

ผลลัพธ์ (หลังมีการจัดกลุ่ม)

เพิ่มเติม : จากตัวอย่างข้างต้น เนื่องด้วยข้อมูลเป็นการจัดกลุ่มและมีข้อมูลเชิงตัวเลข ผู้เขียนจึงขอแนะนำเพิ่มเติมในส่วนของการแสดงจำนวนรวมแยกในแต่ละกลุ่มไว้ด้วย โดยเพิ่มเติมโค้ดในส่วนของ GroupGv_DataBound ดังนี้ค่ะ

 protected void GroupGv_DataBound(object sender, EventArgs e)
        {
            lastCatName = "";
            Table table = (Table)GroupGv.Controls[0];

            foreach (GridViewRow row in GroupGv.Rows)
            {
              
                HiddenField HidCatName = (HiddenField)row.FindControl("HidCatName");
                HiddenField HidCatID = (HiddenField)row.FindControl("hdCatID");
                HiddenField HidID = (HiddenField)row.FindControl("hdID");


                if (HidCatName.Value != lastCatName)
                {

                   //// เป็นการหาผลรวมค่าของฟิลด์ Amt ซึ่งหมายถึงจำนวน โดยเป็นการรวมค่าฟิลด์แยกตามแต่ละ CategoryID นั่นเอง

                    var sumOfValuesInCategory = GroupData.AsEnumerable().Where(x => x.Field<string>("CategoryID") == HidCatID.Value).Sum(x => x.Field<int>("Amt")).ToString();
                   

                    int realIndex = table.Rows.GetRowIndex(row);
                    string text = HidCatName.Value;
                    GridViewRow newHeaderRow = new GridViewRow(realIndex, 0, DataControlRowType.Header, DataControlRowState.Normal);
                    TableCell newCell = new TableCell();
                    newHeaderRow.Cells.AddAt(0, newCell);

                   /////ปรับแก้การระบุค่า ColumnSpan จากเดิมที่รวมกันทุกคอลัมน์(GroupGv.Columns.Count) 
                   //// แต่กรณีนี้ต้องเว้นคอลัมน์ไว้แสดงผลจำนวนรวมแต่ละประเภทด้วย

                    newCell.ColumnSpan =1;
                    newCell.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                    newCell.ForeColor = System.Drawing.Color.White;
                    newCell.Font.Bold = true;
                    newCell.Text = string.Format(HidCatName.Value, "&nbsp;{0}", text);

                    ///สร้าง TableCell หรือคอลัมน์ใหม่ เพื่อแสดงผลข้อมูลจำนวนรวมของแต่ละประเภท 

                    TableCell newCellTotal = new TableCell();


                    /////เพิ่ม TableCell ในแถวที่กำลังสร้างและระบุค่าต่างๆ
                    newHeaderRow.Cells.AddAt(1, newCellTotal);
                    newCellTotal.ColumnSpan = 1;
                    newCellTotal.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                    newCellTotal.ForeColor = System.Drawing.Color.White;
                    newCellTotal.Font.Bold = true;
                    newCellTotal.HorizontalAlign = HorizontalAlign.Right;


                    ////นำค่าผลรวมในตัวแปร sumOfValuesInCategory ที่คำนวณได้ข้างต้นมาจัดรูปแบบก่อนแสดงผลใน TableCell สร้าง

                    newCellTotal.Text = string.Format("{0:#,##0}",int.Parse(sumOfValuesInCategory));
                   
                    table.Controls.AddAt(realIndex, newHeaderRow);
                
                }
                lastCatName = HidCatName.Value;
            }
        }

ผลลัพธ์

          เพียงเท่านี้ท่านก็จะสามารถจัดกลุ่มข้อมูล GridView ของท่าน เพื่อลดปัญหาการดูข้อมูลแบบตารางที่มีแถวข้อมูลจำนวนมากได้บ้างแล้ว ซึ่งผู้เขียนหวังเป็นอย่างยิ่งว่าผู้อ่านจะสามารถนำแนวทาง เกร็ดเล็กๆน้อยๆนี้ไปประยุกต์ใช้กับงานของท่านได้ไม่มากก็น้อยนะคะ หากมีส่วนใดผิดพลาดทางผู้เขียนก็ขออภัยมา ณ ที่นี้ด้วยค่ะ ขอบคุณที่ติดตามนะคะ ^^

แหล่งอ้างอิง
https://stackoverflow.com/questions/61773421/sum-column-where-condition-with-datatable