Category: การพัฒนา ASP.NET Web Application

  • การย่อ-ยุบแถวข้อมูลบน GridView โดยประยุกต์ใช้ร่วมกับ jQuery และ Collapse ใน Bootstrap (C#)

               จากความเดิมตอนที่แล้ว เราได้พูดถึงวิธีการจัดการข้อมูลจำนวนมากด้วยการจัดกลุ่มหมวดหมู่ของข้อมูลใน GridView กันไปแล้ว ซึ่งหากผู้อ่านท่านใดต้องการทราบวิธีการจัดหมวดหมู่สามารถตามดูเนื้อหาในบทความได้จาก การจัดหมวดหมู่แถวของข้อมูลบน GridView ด้วย C# และในส่วนของบทความนี้จะเป็นเนื้อหาต่อยอดการทำงานจากการจัดหมวดหมู่ดังกล่าว โดยเพิ่มความสามารถให้หมวดหมู่หรือกลุ่มเหล่านั้นสามารถย่อ-ยุบได้ เพื่ออำนวยความสะดวกให้กับผู้ใช้ในการดูข้อมูลแยกส่วนกันชัดเจนมากยิ่งขึ้น หรือเพื่อตอบโจทย์ให้กับผู้ใช้บางท่านที่อาจมีความต้องการดูข้อมูลทีละส่วนได้ โดยจะนำ Component ที่ชื่อว่า Collapse ใน Bootstrap มาประยุกต์ใช้ในการแสดงผลร่วมกับ GridView และยังมี jQuery มาเป็นอีกหนึ่งตัวช่วยเพื่อให้สามารถแสดงผลตามที่ต้องการได้ โดยการอธิบายในบทความนี้ ทางผู้เขียนจะขอตัดตอนในส่วนของรายละเอียดขั้นตอนวิธีการจัดหมวดหมู่ไป และข้ามมาพูดถึงขั้นตอนที่ต้องจัดทำเพิ่มเติมในการทำย่อ-ยุบเลยละกันนะคะ

               ก่อนจะไปเริ่มในส่วนของการเขียนโค้ด เรามาทำความรู้จักกับ ค่าที่จำเป็นต้องใช้ในการระบุให้กับแถวหลัก(parent)เพื่อให้สามารถย่อยุบได้ กันก่อนนะคะ

    • data-toggle=”collapse
    • data-target=”.multi-collapse” เพื่อกำหนด target ที่เราต้องการให้ย่อยุบได้ โดยใช้สไตล์ชีทเป็นตัวช่วยเพื่อแยกแต่ละกลุ่มออกจากกัน ซึ่งในที่นี้จะตั้งชื่อสไตล์ชีท multi-collapse ตามด้วยรหัสของประเภทกลุ่มนั้น โดยต้องระบุสไตล์ชีทนี้ให้กับแถวย่อย(child)ด้วย
    • aria-controls=”demo1 demo2 demo3 demo4 demo5” เพื่อกำหนดพื้นที่ที่จะย่อยุบ โดยสามารถกำหนดได้มากกว่า 1 พื้นที่ ซึ่งจะแยกด้วยการเว้นวรรคชื่อ id ของแถวย่อย(child) ในตัวอย่างนี้ คือ แถวย่อยของแถวหลักนี้ ประกอบด้วย 5 แถว คือ แถวที่มี id ชื่อ demo1,demo2,demo3,demo4, demo5 นั่นเอง
    • class = “collapseToggle” เป็นการระบุสไตล์ชีทเพื่อใช้ในการระบุตำแหน่งในการเปลี่ยนไอคอนเวลากดย่อ-ยุบ โดยเรียกใช้งานผ่าน jQuery(ซึ่งจะกล่าวถึงในส่วนถัดไป)

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

    1. ปรับแก้ในส่วนของ GroupGv_DataBound เพิ่มเติม เพื่อกำหนดค่าที่ระบุไว้ข้างต้นในแถวของหมวดหมู่ที่แทรกเข้ามา

    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");
    
                    ////ตั้งชื่อ id ให้กับแถวย่อย(child) แต่ละแถว
                    row.Attributes.Add("id", "Name" + HidID.Value);
    
                   ////กำนดสไตล์ชีทของตัวแถวย่อยและระบุเพื่อให้ย่อยุบได้ ด้วย accordian-body collapse และระบุพื้นที่เพื่อให้อ้างถึงจากแถวหลัก(parent)ได้ 
                  /////ซึ่งเป็นในส่วนของ multi-collapse จะเป็น multi-collapse ตามด้วยชื่อรหัสประเภทนั่นเอง 
                  /////โดยแถวย่อย(child) ที่มีแถวหลัก(parent)เดียวกัน ค่าของ multi-collapse จะตรงกันและตรงกับค่า target ที่ระบุไว้ในแถวหลัก(parent)ด้วย
    
                    if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 
                       row.CssClass = "Blue accordian-body collapse multi-collapse" + HidCatID.Value;
                    else               
                      row.CssClass = "accordian-body collapse multi-collapse" + HidCatID.Value;
    
                    if (HidCatName.Value != lastCatName)
                    {
    
                       ////////วนเพื่อหาจำนวน child ในแต่ละประเภทเพื่อใช้ในการกำหนด aria-controls
    
                        var strList = GroupData.Select("CategoryID='" + HidCatID.Value + "'");
                        foreach (DataRow dr in strList)
                        {
                            AllName += " Name" + dr["ID"];
                        }
    
                       //// เป็นการหาผลรวมค่าของฟิลด์ 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);
    
                        /////กำหนดค่าต่างๆ (data-toggle  data-target  aria-controls )ให้กับแถว เพื่อให้สามารถย่อ-ยุบได้ 
    
                        newHeaderRow.Attributes.Add("data-toggle", "collapse");
                        newHeaderRow.Attributes.Add("data-target", ".multi-collapse" + HidCatID.Value);
                        newHeaderRow.Attributes.Add("aria-expanded", "true");
                        newHeaderRow.Attributes.Add("aria-controls", AllName);
    
                        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.Attributes.Add("class", "collapseToggle");
    
                        /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                        newCell.Text = "<i class='fas fa-plus mr-2'></i>" + 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));
                        newCellTotal.Attributes.Add("class", "collapseToggle");
                        table.Controls.AddAt(realIndex, newHeaderRow);
                    
                          AllName = "";
    
                    }
                    lastCatName = HidCatName.Value;
                }
            }

    2.จัดทำให้ไอคอนสามารถเปลี่ยนเป็น + หรือ – ได้ เมื่อกดย่อ-ยุบ ด้วย jQuery

     <script>
         $(document).ready(function () {
    
    ///เมื่อมีการคลิก element ที่มีสไตล์ชีท collapseToggle จะทำการเปลี่ยนค่าไอคอน หากเป็นภาพบวกจะเปลี่ยนเป็นลบ หากเป็นเครื่องหมายลบจะเปลี่ยนเป็นเครื่องหมายบวก
                $('.collapseToggle').click(function () {
                    $(this).find('i').toggleClass('fa-plus fa-minus');
                });
            });
    </script>

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

        <style>
            .collapseToggle {
                cursor: pointer;
            }
        </style>

    ผลลัพธ์

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

    1. เพิ่มคำว่า “show” เข้าไปในสไตล์ชีทตอนกำหนดให้แถวย่อย ดังนี้ค่ะ

    แบบเดิม

                    if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 
                       row.CssClass = "Blue accordian-body collapse multi-collapse" + HidCatID.Value;
                    else               
                      row.CssClass = "accordian-body collapse multi-collapse" + HidCatID.Value;

    แบบใหม่

                      if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 
    
                        row.CssClass = "Blue accordian-body collapse show multi-collapse" + HidCatID.Value;
                    else
                        row.CssClass = "accordian-body collapse show multi-collapse" + HidCatID.Value;

    2.ปรับแก้ให้ไอคอนแรกที่ต้องการแสดงเป็นเครื่องหมายลบ ดังนี้ค่ะ

    แบบเดิม

                       /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                        newCell.Text = "<i class='fas fa-plus mr-2'></i>" + string.Format(HidCatName.Value, "&nbsp;{0}", text);

    แบบใหม่

                        /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                        newCell.Text = "<i class='fas fa-minus mr-2'></i>" + string.Format(HidCatName.Value, "&nbsp;{0}", text);

    ผลลัพธ์

              จากตัวอย่าง การแสดงผลครั้งแรกก็จะเปลี่ยนเป็นขยายทั้งหมด และแสดงไอคอนเป็นเครื่องหมายลบ(-)ตั้งต้นไว้ให้ และสามารถย่อ-ยุบตามปกติได้แล้วค่ะ

    เพิ่มเติม

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

    1. สร้างปุ่ม 2 ปุ่ม เพื่อกดขยายทั้งหมด และย่อทั้งหมด

    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <div class="col pull-right">
                    <asp:LinkButton ID="lnkShowAll" runat="server" CssClass="btn btn-sm btn-info mb-1" OnClientClick="javascript:ShowHideAll('1');return false;"><i class="fas fa-eye"></i>แสดงทั้งหมด</asp:LinkButton>
                    <asp:LinkButton ID="lnkHideAll" CssClass="btn btn-sm btn-info mb-1" OnClientClick="ShowHideAll('0');return false;" runat="server"><i class="fas fa-eye-slash"></i>ซ่อนทั้งหมด</asp:LinkButton>
                </div>
    
    
            </ContentTemplate>
        </asp:UpdatePanel>

    2. เขียนฟังก์ชั่นในการซ่อน/แสดงทั้งหมด

    <script>
        
     function ShowHideAll(flag) {
    
              /////ล้างค่าการระบุการแสดงผลและการแสดงไอคอนทั้งหมด ทำให้ทุกแถวยุบอยู่ จนกว่าจะมีการสั่งให้ show
                $(".accordian-body").removeClass("show");
                $(".collapseToggle").find('i').removeClass("fa-plus").removeClass("fa-minus");
    
             /////กรณีต้องการให้แสดงจะเพิ่มสไตล์ชีท show ให้กับทุกแถว และแสดงไอคอนเป็น - ทั้งหมด เพื่อให้กดย่อได้
    
                if (flag == '1') {
                    $(".accordian-body").addClass("show");
                    $(".collapseToggle").find('i').addClass("fa-minus");
                }
            /////กรณีต้องการให้แสดงจะแสดงไอคอนเป็น + ทั้งหมด เพื่อให้กดขยายได้
                else
                    $(".collapseToggle").find('i').addClass("fa-plus");
                }
    
    </script>

    ผลลัพธ์

    หมายเหตุ : ในการทำงานนี้จะใช้ jQuery และ Bootstrap ร่วมด้วย ผู้ที่จะนำไปใช้งานอย่าลืมอ้างอิงไฟล์สไตล์ชีทและสคริปท์ของ Bootstrap รวมทั้งไฟล์ของ jQuery เพื่อให้โค้ดข้างต้นสามารถทำงานได้นะคะ

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

    แหล่งอ้างอิง

    https://getbootstrap.com/docs/4.0/components/collapse/
    https://www.geeksforgeeks.org/how-to-change-symbol-with-a-button-in-bootstrap-accordion/

  • เขียนโค้ดแก้ปัญหา browser block popup แบบง่ายๆ ใช้งานได้จริง

    เดี๋ยวนี้การเปิดหน้าเวบของผู้ใช้งานทั่วไปมักใช้มือถือแทนคอมพิวเตอร์แล้ว แล้วตอนนี้ browser ทั่วไปจะทำการ block popup JavaScript ใน function window.open ดังนั้นทำให้ง่ายที่สุดคือใช้ link ให้เป็นประโยชน์ โดยใช้ properties href ในการเรียกใช้ตามโค้ดตัวอย่างข้างล่างนี้

    เท่านี้สั้นๆ ง่ายๆ หวังว่าจะเป็นประโยชน์สำหรับคนเขียนโค้ดและผู้ใช้งานไม่ต้องปิด block popup ที่เครื่องตัวเองอีกต่อไป

  • การจัดหมวดหมู่แถวของข้อมูลบน 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

  • ขยับแถว (row) ขึ้น/ลง ใน ASP.NET Gridview ด้วย Code Behind

    เช่นเคยครับ จากบล็อก ขยับแถว (row) ขึ้น/ลง ใน ASP.NET Gridview ด้วย jQuery เป็นการเพิ่มฟีเจอร์ให้กับ ASP.NET Gridview ด้วยการประยุกต์ใช้ jQuery บทความต่อมา ก็จะเป็นการเพิ่มฟีเจอร์เดียวกัน แต่จะเป็นการพัฒนาด้วยโค้ดฝั่ง code behind

    เนื่องจากการทำงานฝั่ง code behind จะไม่สามารถเคลื่อนย้ายเร็คคอร์ดของ gridview ได้โดยตรง ดังนั้นเราจะทำทางอ้อม ด้วยการใช้ฟิลด์ในตารางฟิลด์หนึ่งมาเป็นข้อมูลสำหรับเรียงใน Gridview เมื่อต้องการย้ายเร็คคอร์ด เราก็จะเปลี่ยนตัวเลขในฟิลด์นี้ แล้วทำการเรียงข้อมูลใหม่ และแสดงผลข้อมูลใน Gridview อีกครั้ง ไปดูขั้นตอนต่างๆ กันเลยครับ

    1. ในส่วนของโค้ด HTML จะมีการปรับปรุงเพิ่มเล็กน้อยเฉพาะในส่วนของปุ่ม UP/DOWN ซึ่งเราจะเปลี่ยนไปใช้ปุ่มที่เป็นคอนโทรลของ ASP.NET เพื่อให้สามารถเขียนโปรแกรมควบคุมจากฝั่ง code behind ได้

    <asp:GridView ID="gvBib" runat="server" AutoGenerateColumns="False" OnRowCommand="gvBib_RowCommand" >
        <Columns>
            <asp:BoundField DataField="BIB_NO" HeaderText="Bib#" />
            <asp:BoundField DataField="TITLE" HeaderText="Title" />
            <asp:BoundField DataField="AUTHOR" HeaderText="Author" />
            <asp:BoundField DataField="CALL_NO" HeaderText="CallNo" />
            <asp:BoundField DataField="ISBN" HeaderText="ISBN" />
            <asp:TemplateField>
                <ItemTemplate>
                    <asp:Button ID="btnUp" runat="server" Text="UP" CommandName="up" CommandArgument="<%# ((GridViewRow) Container).RowIndex %>"></asp:Button>
                    <asp:Button ID="btnDown" runat="server" Text="DOWN" CommandName="down" CommandArgument="<%# ((GridViewRow) Container).RowIndex %>"></asp:Button>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

    โดย

    • CommndName ใช้สำหรับตรวจสอบว่าเป็นการเลือก up หรือ down
    • CommandArgument ใช้สำหรับระบุแถวที่คลิกเลือก

    2. ในส่วนของโค้ดที่ใช้สร้างข้อมูลเพื่อแสดงใน Gridview เราจะเพิ่มฟิลด์ขึ้นมาอีก 1 ฟิลด์ เพื่อใช้กำหนดลำดับการแสดงผล จากตัวอย่างโค้ด ก็คือฟิลด์ SEQ

    DataTable dt = new DataTable();
    dt.Columns.AddRange(new DataColumn[6] { new DataColumn("BIB_NO"), 
                                            new DataColumn("TITLE"), 
                                            new DataColumn("AUTHOR"),
                                            new DataColumn("CALL_NO"),
                                            new DataColumn("ISBN"),
                                            new DataColumn("SEQ")});
    dt.Rows.Add("001", "1 ทศวรรษ ดัชนีสุขภาพคนไทย", "สำนักงานคณะกรรมการสุขภาพแห่งชาติ (สช.)", "WA13 ห159 2556", "9786160822258", 1);
    dt.Rows.Add("002", "ความลับในร่างกายมนุษย์ที่เราไม่เคยรู้", "ฟรานซิส, เกวิน", "QS4 ฟ133a 2560", "9786168022887", 2);
    dt.Rows.Add("003", "The best ICU", "ดุสิต สถาวร", "WX218 B561 2560", "9786168122020", 3);
    dt.Rows.Add("004", "COVID-19 โรคระบาดแห่งศตวรรษ", "นำชัย ชีววิวรรธน์", "QW168.5.C8 น515c 2563", "9789740217060", 4);
    dt.Rows.Add("005", "Good health & smart life ในวัย 40+", "ไวต์, จอห์น", "WT104 ว967g 2560", "9786160827237", 5);
    gvBib.DataSource = dt;
    gvBib.DataBind();
    
    //เก็บข้อมูล
    ViewState["dtBook"] = dt;

    3. เพิ่มโค้ดในส่วนของ gvBib_RowCommand ซึ่งเป็น event ที่จะเกิดเมื่อมีการคลิกปุ่ม UP หรือ DOWN

    protected void gvBib_RowCommand(object sender, GridViewCommandEventArgs e)
    {
    	DataTable dt = (DataTable)ViewState["dtBook"];
    
            //เก็บแถวที่คลิกเลือก
    	int curIdx = Convert.ToInt32(e.CommandArgument);
    
    	if(e.CommandName == "up")   //ถ้ากดปุ่ม UP
    	{
    		//กำหนดลำดับใหม่ให้กับแถวก่อนหน้ากับแถวปัจจุบันให้สลับกัน
    		dt.Rows[curIdx - 1]["SEQ"] = curIdx;
    		dt.Rows[curIdx]["SEQ"] = curIdx - 1;
    	}
    	else if (e.CommandName == "down")   //ถ้ากดปุ่ม DOWN
    	{
    		//กำหนดลำดับใหม่ให้กับแถวถัดไปกับแถวปัจจุบันให้สลับกัน
    		dt.Rows[curIdx + 1]["SEQ"] = curIdx;
    		dt.Rows[curIdx]["SEQ"] = curIdx + 1;
    	}
    	dt.AcceptChanges();
    
    	//กำหนดให้ข้อมูลเรียงตามฟิลด์ SEQ
    	dt.DefaultView.Sort = "SEQ";
    
    	//เซ็ตข้อมูลที่มีการเรียงลำกับแล้วกลับไปให้ DataTable
    	dt = dt.DefaultView.ToTable();
    
    	//กำหนดข้อมูลที่เรียงลำดับใหม่แล้วให้กับ Gridview และแสดผล
    	gvBib.DataSource = dt;
    	gvBib.DataBind();
    
    	//เก็บข้อมูลกลับเข้า ViewState
    	ViewState["dtBook"] = dt;
    }

    เมื่อทดลองรัน จะปรากฏผลลัพธ์ดังรูปด้านล่าง

    เมื่อทดลองคลิกปุ่ม UP หรือ DOWN ระบบก็สามารถย้ายแถวที่ต้องการไปยังตำแหน่งที่ก่อนหน้าหรือถัดไปได้อย่างถูกต้อง ยกเว้นการคลิก UP ที่แถวแรก และ DOWN ที่แถวสุดท้าย จะเกิด Error ดังภาพ

    และ
    ซึ่งเกิดจากการเลื่อนไปยังตำแหน่งที่ไม่มีในข้อมูล จึงจะต้องปรับแก้โค้ดอีกเล็กน้อยดังนี้
    //เก็บแถวที่คลิกเลือก
    int curIdx = Convert.ToInt32(e.CommandArgument);
    
    if(e.CommandName == "up")   //ถ้ากดปุ่ม UP
    {
        //ตรวจสอบว่าไม่ใช่แถวแรกหรือไม่
        if (curIdx != 0)
        {
            //กำหนดลำดับใหม่ให้กับแถวก่อนหน้ากับแถวปัจจุบันให้สลับกัน
            dt.Rows[curIdx - 1]["SEQ"] = curIdx;
            dt.Rows[curIdx]["SEQ"] = curIdx - 1;
        }
    }
    else if (e.CommandName == "down")   //ถ้ากดปุ่ม DOWN
    {
        //ตรวจสอบว่าไม่ใช่แถวสุดท้ายหรือไม่
        if (curIdx != dt.Rows.Count - 1)
        {
            //กำหนดลำดับใหม่ให้กับแถวถัดไปกับแถวปัจจุบันให้สลับกัน
            dt.Rows[curIdx + 1]["SEQ"] = curIdx;
            dt.Rows[curIdx]["SEQ"] = curIdx + 1;
        }
    }

    โดยเราจะต้องตรวจสอบเพิ่มเติมก่อนว่า เมื่อคลิกปุ่ม UP จะต้องไม่ใช่แถวแรก และคลิกปุ่ม DOWN จะต้องไม่ใช่แถวสุดท้าย เมื่อทดสอบรันอีกครั้ง ก็พบว่าสามารถทำงานได้ปกติ เมื่อคลิกปุ่ม UP ที่แถวแรก หรือปุ่ม DOWN ที่แถวสุดท้าย ก็จะไม่เกิด Error

    ผู้เขียนหวังเป็นอย่างยิ่งว่าบทความนี้จะมีประโยชน์ไม่มากก็น้อย ที่ผู้อ่านน่าจะสามารถนำไปประยุกต์ใช้ในงานของตัวเอง

    จนกว่าจะพบกันใหม่ สวัสดีครับ


    แหล่งข้อมูลอ้างอิง

  • ขยับแถว (row) ขึ้น/ลง ใน ASP.NET Gridview ด้วย jQuery

    บางครั้งการแสดงผลข้อมูลใน Gridview จะมีความต้องการให้ผู้ใช้งานสามารถกำหนดลำดับข้อมูลได้ ในบทความนี้ผู้เขียนจะยกตัวอย่างการประยุกต์ใช้ jQuery มาเพิ่มความสามารถให้กับ Gridview ให้สามารถขยับแถวที่ต้องการไปอยู่ในตำแหน่งที่ต้องการได้

    โดยมีขั้นตอนดังต่อไปนี้

    1. เพิ่มโค้ด HTML สำหรับแสดงผลข้อมูล Gridview

    <asp:ScriptManager runat="server"></asp:ScriptManager>
    
    <asp:GridView ID="gvBib" runat="server" AutoGenerateColumns="False">
        <Columns>
    
            <asp:BoundField DataField="BIB_NO" HeaderText="Bib#" />
            <asp:BoundField DataField="TITLE" HeaderText="Title" />
            <asp:BoundField DataField="AUTHOR" HeaderText="Author" />
            <asp:BoundField DataField="CALL_NO" HeaderText="CallNo" />
            <asp:BoundField DataField="ISBN" HeaderText="ISBN" />
            <asp:TemplateField>
                <ItemTemplate>
                    <asp:UpdatePanel runat="server">
                        <ContentTemplate>
                            <button class="up">UP</button>
                            <button class="down">DOWN</button>
                        </ContentTemplate>
                    </asp:UpdatePanel>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

    จะสังเกตว่าจะมีฟิลด์ที่เป็น ItemTemplate สำหรับแสดงผลปุ่ม UP และปุ่ม DOWN สำหรับใช้ในการเลื่อน row นั้นๆ ขึ้นหรือลง

    และเหตุผลที่จะต้องมี UpdatePanel ครอบปุ่มไว้ เพราะทุกครั้งที่มีการกด จะเกิดการ Postback ทำให้ข้อมูลจะถูก Bind ซ้ำ การเรียงลำดับก็จะกลับคืนไปเหมือนเดิม จึงจำเป็นต้องทำ Partial Load เพื่อป้องกันการ Bind ข้อมูลซ้ำ

    2. เพิ่มโค้ดใน event Page_Load ในหน้า code behind เพื่อจำลองข้อมูลที่จะใช้แสดงใน GridView

    protected void Page_Load(object sender, EventArgs e)
    {
        if(!IsPostBack)
        {
            DataTable dt = new DataTable();
            dt.Columns.AddRange(new DataColumn[5] { new DataColumn("BIB_NO"), 
                                                    new DataColumn("TITLE"), 
                                                    new DataColumn("AUTHOR"),
                                                    new DataColumn("CALL_NO"),
                                                    new DataColumn("ISBN")});
            dt.Rows.Add("001", "1 ทศวรรษ ดัชนีสุขภาพคนไทย", "สำนักงานคณะกรรมการสุขภาพแห่งชาติ (สช.)", "WA13 ห159 2556", "9786160822258");
            dt.Rows.Add("002", "ความลับในร่างกายมนุษย์ที่เราไม่เคยรู้", "ฟรานซิส, เกวิน", "QS4 ฟ133a 2560", "9786168022887");
            dt.Rows.Add("003", "The best ICU", "ดุสิต สถาวร", "WX218 B561 2560", "9786168122020");
            dt.Rows.Add("004", "COVID-19 โรคระบาดแห่งศตวรรษ", "นำชัย ชีววิวรรธน์", "QW168.5.C8 น515c 2563", "9789740217060");
            dt.Rows.Add("005", "Good health & smart life ในวัย 40+", "ไวต์, จอห์น", "WT104 ว967g 2560", "9786160827237");
            gvBib.DataSource = dt;
            gvBib.DataBind();
        }
        
    }

    เนื่องจากโค้ดตัวอย่างมีการใช้งาน class DataTable และ DataColumn ซึ่งอยู่ใน namespace System.Data เพราะฉะนั้นจะต้อง Import namespace นี้ด้วย

    using System.Data;

    3. เพิ่มโค้ด jQuery สำหรับควบคุมการทำงานปุ่ม UP/DOWN โดยหลักการทำงานคือ เมื่อคลิกปุ่ม UP หรือ DOWN แถวที่เลือก จะถูกขยับหรือลง 1 เรคคอร์ด

    //เมื่อคลิกปุ่ม UP
    $(document).on("click", ".up", function () {
    
        //เก็บเร็คคอร์ดที่ถูกคลิกเอาไว้
        var curRow = $(this).closest('tr');
    
    
        //ย้ายเร็คคอร์ดปัจจุบัน ไปอยู่ในตำแหน่งก่อนหน้า เร็คคอร์ดที่อยู่ก่อนหน้า
        curRow.insertBefore(curRow.prev());
    
    });
    
    //เมื่อคลิกปุ่ม DOWN
    $(document).on("click", ".down", function () {
        var curRow = $(this).closest('tr');
        curRow.insertAfter(curRow.next());
    });

    เมื่อทดลองรัน จะปรากฏผลลัพธ์ดังรูปด้านล่าง

    เมื่อทดลองกดปุ่ม UP ในช่อง Bib#005 เรคคอร์ดนั้นก็จะขยับขึ้นไปอยู่บน Bib#004 ดังรูป
    แต่ถ้าทดลองกดปุ่ม UP ในเรคคอร์ด Bib#001 เรคคอร์ดนี้จะถูกเลื่อนขึ้นไปอยู่เหนือเรคคอร์ดที่เป็น Header ของตารางซึ่งไม่ถูกต้อง
    4. เราจะต้องแก้ไขโค้ด jQuery เพิ่มเติม โดยแก้ไขเฉพาะกรณีคลิกปุ่ม UP เพื่อตรวจสอบก่อนว่า เรคคอร์ดที่คลิกเป็นเรคคอร์ดบนสุด (ยกเว้น header) หรือยัง
    //เมื่อคลิกปุ่ม UP
    $(document).on("click", ".up", function () {
    
        //เก็บเร็คคอร์ดที่ถูกคลิกเอาไว้
        var curRow = $(this).closest("tr");
    
        //เก็บเร็คคอร์ดแรกสุดของตารางเอาไว้ ซึ่งก็คือเรคคอร์ดหัวตาราง 
        //ที่เราจะไม่อนุญาตให้เอาเรคคอร์ดอื่นมาแทรก
        var firstRow = $("[id*=gvBib] tr:first");
    
        //ตรวจสอบว่าเร็คอคอร์ดก่อนหน้าของเร็คคอร์ดที่เรากดปุ่ม คือเร็คคอร็ดที่เป็นหัวตารางหรือไม่
        //ถ้าไม่ใช่ก็จะทำการย้ายเร็คคอร์ด
        if (curRow.prev().html() != firstRow.html() ) {
    
            //ย้ายเร็คคอร์ดปัจจุบัน ไปอยู่ในตำแหน่งก่อนหน้า เร็คคอร์ดที่อยู่ก่อนหน้า
            curRow.insertBefore(curRow.prev());
        }
    });

    เมื่อทดสอบอีกครั้ง ก็จะพบว่า ไม่สามารถกด UP เรคคอร์ดที่อยู่ใต้ Header ได้อีกแล้ว

    ก็หวังเป็นอย่างยิ่งว่าบทความนี้จะมีประโยชน์ไม่มากก็น้อยต่อผู้อ่าน ที่สามารถนำไปประยุกต์ใช้กับงานของตัวเองได้ สวัสดีครับ


    แหล่งข้อมูลอ้างอิง

    • https://stackoverflow.com/questions/16524497/jquery-to-move-row-up-and-down
    • https://stackoverflow.com/questions/36243730/jquery-to-compare-rows-in-two-tables
  • วิธีการรวมไฟล์ pdf หลายไฟล์และรูปภาพมาแสดงในครั้งเดียวด้วย iTextSharp (#C)

              ที่มาของบทความนี้ เนื่องด้วยงานที่ผู้เขียนได้รับมอบหมายให้พัฒนาอยู่นั้น มีส่วนหนึ่งที่เป็นการแนบไฟล์เอกสาร/หลักฐานทั้งในรูปแบบไฟล์ PDF และรูปภาพ เข้ามาจากผู้ใช้งาน ซึ่งเอกสารเหล่านี้จะต้องมีส่วนของการแสดงผลให้ทางฝั่งเจ้าหน้าที่ทำการตรวจสอบเอกสาร/หลักฐานดังกล่าวด้วย โดยเดิมทีจะมีการแสดงผลแยกเป็นรายการให้เจ้าหน้าที่เพื่อคลิกดูรายละเอียดทีละรายการ ดังภาพ

              ซึ่งในการทำงานจริงแล้วนั้นพบว่า การตรวจสอบเอกสาร/หลักฐานต่างๆที่ผู้ใช้แนบมาสามารถทำได้ยากและต้องใช้เวลา เนื่องจากเจ้าหน้าที่จะต้องทำการคลิกดูรายละเอียด/ดาวน์โหลดไฟล์คราวละ 1 ไฟล์เพื่อตรวจสอบ ซึ่งไฟล์จะแยกกันอยู่ หากต้องการดาวน์โหลดก็ต้องดาวน์โหลดทีละไฟล์ ดังภาพ

    จึงเป็นที่มาของการค้นหาวิธีการแสดงผลไฟล์ทั้งหมดให้เจ้าหน้าที่สามารถตรวจสอบและดูรายละเอียดข้อมูลเอกสารไฟล์แนบได้ในคราวเดียว ซึ่งผู้เขียนก็ได้นำวิธีการดังกล่าวมาแบ่งปันให้กับผู้อ่านที่มีความสนใจและประสบปัญหาคล้ายกันอยู่  ซึ่งจะมีวิธีการดังนี้ค่ะ

    1.ดึงข้อมูลไฟล์แนบเอกสารทั้งหมดของผู้ใช้

      DataTable dtFile = new DataTable();
    
        //เป็นส่วนของการสมมุติการดึงข้อมูลการแนบไฟล์ของผู้ใช้ออกมาในรูปแบบ Datatable
            dtFile.Columns.AddRange(new DataColumn[2] { 
                             new DataColumn("CITIZEN_ID", typeof(string)),
                            new DataColumn("FILE_PATH",typeof(string))});
            dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Document1.pdf");
            dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Document2.pdf");
            dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Document3.pdf");
            dtFile.Rows.Add("xxxxxxxxxxxx", "resumeFiles/Image.png");
             
    
    //สร้างตัวแปรในการเก็บค่าชื่อไฟล์ เพื่อใช้ในการส่งเป็นพารามิเตอร์ในการรวมไฟล์
            List<string> listFiles = new List<string>();
            string path = Server.MapPath("../registPDF");
    
    //ตั้งชื่อไฟล์ที่จะรวมไฟล์ทั้งหมดไว้ และประกาศตัวแปรต่างๆที่จำเป็นต้องใช้ในการสร้างและรวมไฟล์
            string genName = Guid.NewGuid().ToString() + ".pdf";
    
    //ตัวแปร tmpPath เป็นตัวแปรที่เป็นชื่อไฟล์ที่สร้างขึ้นเพื่อรวมไฟล์ทั้งหมด
    
            string tmpPath = "../registPDF/" + genName;
            string[] files = Directory.GetFiles(path);
            string[] fileName;
            string DestName = "";
    
    //กำหนดนามสกุลที่ต้องการตรวจสอบว่าเป็นไฟล์รูปภาพหรือไม่
            string[] ImgExt = { "png","jpg","jpeg","gif"};
    
           /// เป็นการลบ Temporary file ที่ถูกสร้างขุึ้นเพื่อรวมไฟล์ เพื่อไม่ให้มีไฟล์ที่ไม่ใช้งานค้างอยู่ในเซิร์ฟเวอร์มากเกินไป *****
            foreach (string file in files)
            {
                FileInfo fi = new FileInfo(file);
                if (fi.LastAccessTime < DateTime.Now.AddMinutes(-30))
                    fi.Delete();
            }
          
    
          //วนค่าเพื่อเก็บตัวแปรชื่อไฟล์แนบก่อนรวมไฟล์
    
            for (int i = 0; i <= dtFile.Rows.Count - 1; i++)
            {
    
         //ตรวจสอบว่ามีไฟล์ดังกล่าวอยู่จริงหรือไม่บนเซิร์ฟเวอร์ตามที่อยู่ไฟล์ที่อ้างถึง
                if (File.Exists(Server.MapPath("../" + dtFile.Rows[i]["FILE_PATH"].ToString())))
                {
    
         //ตรวจสอบชนิดของไฟล์ว่ามีนามสกุลอะไร หากไม่ใช่ไฟล์ PDF จะต้องทำการแปลงและสร้างเป็นไฟล์ PDF ก่อนส่งไปรวมไฟล์
                    fileName = dtFile.Rows[i]["FILE_PATH"].ToString().Split('.');
                    if (fileName[1] == "pdf")
                        listFiles.Add(Server.MapPath("../" + dtFile.Rows[i]["FILE_PATH"].ToString()));
                    else 
                    {
    
         //กรณีที่พบว่าไม่ใช่ไฟล์ PDF จะตรวจสอบว่าเป็นไฟล์รูปภาพหรือไม่ หากใช่จะทำการแปลงและสร้างเป็นไฟล์ PDF ก่อนส่งไปรวมไฟล์
                        if (ImgExt.Contains(fileName[1].ToLower()))
                        {
                            DestName = "../registPDF/"  + Guid.NewGuid().ToString() + ".pdf";
                            ConvertImageToPdf(Server.MapPath("../" + dtFile.Rows[i]["FILE_PATH"].ToString()), Server.MapPath(DestName));
                            listFiles.Add(Server.MapPath( DestName));
                        }
                    }
                } 
     
            }
    
    //เรียกใช้งานเมธอดในการรวมไฟล์ โดยส่งค่าลิสต์ของชื่อไฟล์ PDF ทั้งหมดที่ต้องการรวม และชื่อไฟล์ปลายทางที่จะใช้ในการรวม
    
            CombineMultiplePDFs(listFiles.ToArray(), Server.MapPath(tmpPath));
    
    
    //แสดงผล PDF ไฟล์ที่ได้ทำการรวมเรียบร้อยแล้วด้วย Literal โดยตัวแปร tmpPath คือที่อยู่ของไฟล์ใหม่ที่ทำการรวมไฟล์ PDF ทั้งหมดเรียบร้อยแล้ว
    
    
            StringBuilder strObj = new StringBuilder();
    
            strObj.Append("<object id=\"pdfContainer\" type=\"application/pdf\"");
            strObj.AppendFormat(" data=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\" style=\" z-index:1000; width: 99%;  height: 600px;\">", tmpPath);
            strObj.AppendFormat(" <param name=\"src\" value=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\"> ", tmpPath);
            strObj.Append(" </object>");
    
            ltrPDF.Text = strObj.ToString();
            dtFile = null;
            

    หมายเหตุ ในการทำงานข้างต้น ควรมีการลบไฟล์(Temporary file)ที่ถูกสร้างไว้ก่อนหน้านี้ที่ไม่ใช้งานออกไปด้วย เนื่องจากการรวมไฟล์ด้วยวิธีนี้จะมีการสร้างไฟล์รวมตัวใหม่ขึ้นมาและนำไปแสดงผลให้ผู้ใช้ ซึ่งหลังจากผู้ใช้งานไม่ใช้งานแล้วหากเราไม่ทำการลบทิ้งจะเกิดเป็นไฟล์ขยะบนเซิร์ฟเวอร์จำนวนมากได้

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

    2. เขียนเมธอดในการรวมไฟล์ดังกล่าวและสร้างเป็นไฟล์ PDF ตัวใหม่

        public static void CombineMultiplePDFs(string[] fileNames, string outFile)
        {
            // ขั้นที่ 1: สร้าง document ที่จะรวมไฟล์ทั้งหมดขึ้นมา
            Document document = new Document();
            // สร้าง FileStream object ที่จะใช้งานและต้องอย่าลืม dispose เมื่อใช้งานเรียบร้อยแล้ว
            using (FileStream newFileStream = new FileStream(outFile, FileMode.Create))
            {
                // ขั้นที่ 2: สร้างตัว
                PdfCopy writer = new PdfCopy(document, newFileStream);
                if (writer == null)
                {
                    return;
                }
    
                //ขั้นที่ 3:เปิดการใช้งานตัว document
                document.Open();
    
              // วนเพื่ออ่านค่าชื่อไฟล์ และทำการเพิ่มข้อมูลลงในเอกสารตัวใหม่ที่จะรวมไฟล์ทั้งหมด
    
               foreach (string fileName in fileNames)
                {
                    // สร้างตัว reader จากเอกสารแนบที่กำลังวน
                    PdfReader reader = new PdfReader(fileName);
                    reader.ConsolidateNamedDestinations();
    
                    // ขั้นที่ 4: ทำการเพิ่มหน้าข้อมูลจาก reader ให้กับตัว writer ทีละหน้า
                    for (int i = 1; i <= reader.NumberOfPages; i++)
                    {
                        PdfImportedPage page = writer.GetImportedPage(reader, i);
                        writer.AddPage(page);
                    }
    
                    PRAcroForm form = reader.AcroForm;
                    if (form != null)
                    {
                        writer.CopyAcroForm(reader);
                    }
    
                    reader.Close();
                }
    
                // ขั้นที่ 5: ปิดการทำงาน document และ writer
                writer.Close();
                document.Close();
            } 
        }
    

    3.เขียนเมธอดในแปลงไฟล์รูปภาพให้เป็นไฟล์ PDF

     public static void ConvertImageToPdf(string srcFilename, string dstFilename)
        {
            iTextSharp.text.Rectangle pageSize = null;
    
            using (var srcImage = new Bitmap(srcFilename))
            {
                pageSize = new iTextSharp.text.Rectangle(0, 0, srcImage.Width, srcImage.Height);
            }
            using (var ms = new MemoryStream())
            {
                var document = new iTextSharp.text.Document(pageSize, 0, 0, 0, 0);
                iTextSharp.text.pdf.PdfWriter.GetInstance(document, ms).SetFullCompression();
                document.Open();
                var image = iTextSharp.text.Image.GetInstance(srcFilename);
                document.Add(image);
                document.Close();
    
                File.WriteAllBytes(dstFilename, ms.ToArray());
            }
        }

    4.แสดงผลไฟล์ PDF ที่รวมเรียบร้อยแล้ว ด้วย Literal

            StringBuilder strObj = new StringBuilder();
    
            strObj.Append("<object id=\"pdfContainer\" type=\"application/pdf\"");
            strObj.AppendFormat(" data=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\" style=\" z-index:1000; width: 99%;  height: 600px;\">", tmpPath);
            strObj.AppendFormat(" <param name=\"src\" value=\"{0}#toolbar=1&amp;navpanes=0&amp;scrollbar=1\"> ", tmpPath);
            strObj.Append(" </object>");
    
            ltrPDF.Text = strObj.ToString();
    

    หมายเหตุ ในการใช้งานโค้ดข้างต้น มีไลบรารีที่จำเป็นต้องใช้งาน ดังนี้ค่ะ

    using System.IO;
    using iTextSharp.text;
    using iTextSharp.text.pdf;
    using System.Data;
    using System.Text;
    using System.Drawing;

    ผลลัพธ์ ตัวอย่างไฟล์ที่ได้จากการรวมไฟล์เอกสาร PDF 3 ไฟล์ และไฟล์รูปภาพ 1 ไฟล์

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

    แหล่งอ้างอิง

    https://stackoverflow.com/questions/6029142/merging-multiple-pdfs-using-itextsharp-in-c-net

    https://alandjackson.wordpress.com/2013/09/27/convert-an-image-to-a-pdf-in-c-using-itextsharp/

  • ซ่อน/แสดง คอลัมน์ใน ASP.NET GridView จาก Code Behind

    จากบทความ ซ่อน/แสดง คอลัมน์ใน ASP.NET GridView ด้วย jQuery ท่านผู้อ่านที่ได้เข้าไปอ่านแล้วอาจจะมีคำถามว่าถ้าไม่อยากใช้ jQuery ล่ะ เนื่องด้วยสาเหตุอะไรก็แล้วแต่ วันนี้ผมก็จะมานำเสนอการซ่อน/แสดงคอลัมน์ใน ASP.NET GridView อีกวิธี ซึ่งจะเป็นการควบคุมจาก code behind ที่เป็นภาษา C# หรือ VB.NET ในที่นี่ผู้เขียนจะใช้ภาษา C#

    ซึ่งจะมีขั้นตอนต่างๆ ดังต่อไปนี้

    1. ตัวอย่างโค้ด HTML จะมีการแก้ไขเพิ่มขึ้นอีกเล็กน้อยจากบทความเดิม

    <asp:CheckBox ID="chkCallNo" Text=" Show CallNo from code behind" runat="server" Checked="true"
                  AutoPostBack="true" OnCheckedChanged="chkCallNo_CheckedChanged" />
    <hr />
    
    <asp:GridView ID="gvBib" runat="server" AutoGenerateColumns="False">
        <Columns>
            <asp:BoundField DataField="BIB_NO" HeaderText="Bib#" />
            <asp:BoundField DataField="TITLE" HeaderText="Title" />
            <asp:BoundField DataField="AUTHOR" HeaderText="Author" />
            <asp:BoundField DataField="CALL_NO" HeaderText="CallNo" />
            <asp:BoundField DataField="ISBN" HeaderText="ISBN" />
        </Columns>
    </asp:GridView>

    โดยสิ่งที่เพิ่มขึ้นมาจะอยู่ที่ CheckBox คือ

    • AutoPostBack=”true” เพื่อให้มีการ PostBack ทุกครั้งที่มีการคลิก Checkbox
    • OnCheckedChanged=”chkCallNo_CheckedChanged” คือ event ที่อยู่ใน code behind ที่จะถูกเรียกใช้เมื่อมีการคลิก

    2. โค้ดในส่วนของการจำลองข้อมูล สามารถใช้โค้ดเดิมจากบทความเก่าได้เลย

    if(!IsPostBack)
    {
        DataTable dt = new DataTable();
        dt.Columns.AddRange(new DataColumn[5] { new DataColumn("BIB_NO"), 
                                                new DataColumn("TITLE"), 
                                                new DataColumn("AUTHOR"),
                                                new DataColumn("CALL_NO"),
                                                new DataColumn("ISBN")});
        dt.Rows.Add(1, "1 ทศวรรษ ดัชนีสุขภาพคนไทย", "สำนักงานคณะกรรมการสุขภาพแห่งชาติ (สช.)", "WA13 ห159 2556", "");
        dt.Rows.Add(2, "ความลับในร่างกายมนุษย์ที่เราไม่เคยรู้", "ฟรานซิส, เกวิน", "QS4 ฟ133a 2560", "9786168022887");
        dt.Rows.Add(3, "The best ICU", "ดุสิต สถาวร", "WX218 B561 2560", "9786168122020");
        dt.Rows.Add(4, "COVID-19 โรคระบาดแห่งศตวรรษ", "นำชัย ชีววิวรรธน์", "QW168.5.C8 น515c 2563", "9789740217060");
        dt.Rows.Add(5, "Good health & smart life ในวัย 40+", "ไวต์, จอห์น", "WT104 ว967g 2560", "9786160827237");
        gvBib.DataSource = dt;
        gvBib.DataBind();
    }

    3. เพิ่มโค้ดใน chkCallNo_CheckedChanged เพื่อควบคุมการซ่อน/แสดงคอลัมน์ที่ต้องการ

    protected void chkCallNo_CheckedChanged(object sender, EventArgs e)
    {
        gvBib.Columns[3].Visible = chkCallNo.Checked;
    }

    โดย gvBib.Columns[3].Visible จะเป็นการกำหนดให้คอลัมน์ที่ 3 ของ Gridview แสดงผลหรือไม่ ซึ่งก็คือคอลัมน์ CallNo นั่นเอง (จะเริ่มนับตั้งแต่ 0)

    4. ทดสอบการใช้งาน

    5. จะเห็นว่าระบบสามารถทำงานได้ตามความต้องการ คอลัมน์จะซ่อน/แสดงได้ตามเงื่อนไขที่เราเลือกจาก checkbox แต่จะสังเกตเห็นว่า ทุกครั้งที่มีการเลือก checkbox ระบบจะ refresh หน้าจอใหม่ทุกครั้ง ซึ่งถ้าในหน้าจอที่เรากำลังทำงานมีข้อมูลอื่นๆ อีกหลายอย่าง ก็จะถูกโหลดซ้ำโดยไม่จำเป็น เพื่อแก้ไขปัญหานี้ ASP.NET จะมีเครื่องมือที่เรียกว่า UpdatePanel ซึ่งจะทำงานในแบบ Partial Load ได้ตามตามเงื่อนไขที่เรากำหนด โดยเราจะต้องปรับแก้โค้ด HTML เพิ่มเติมดังนี้

    <asp:ScriptManager runat="server"></asp:ScriptManager>
    
    <asp:CheckBox ID="chkCallNo" Text=" Show CallNo from code behind" runat="server" AutoPostBack="true" Checked="true" OnCheckedChanged="chkCallNo_CheckedChanged" />
    <hr />
    
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <asp:GridView ID="gvBib" runat="server" AutoGenerateColumns="False">
                <Columns>
                    <asp:BoundField DataField="BIB_NO" HeaderText="Bib#" />
                    <asp:BoundField DataField="TITLE" HeaderText="Title" />
                    <asp:BoundField DataField="AUTHOR" HeaderText="Author" />
                    <asp:BoundField DataField="CALL_NO" HeaderText="CallNo" />
                    <asp:BoundField DataField="ISBN" HeaderText="ISBN" />
                </Columns>
            </asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="chkCallNo" EventName="CheckedChanged" />
        </Triggers>
    </asp:UpdatePanel>

    สิ่งที่เพิ่มเติมเข้ามาคือ

    • ScriptManager สำหรับใช้ควบคุมการทำงาน UpdatePanel
    • UpdatePanel ใช้ครอบ component หรือพื้นที่ที่เราต้องให้สามารถทำ Partial Load ได้ ในที่นี่คือเราครอบ Gridview นั่นเอง
    • Triggers จะเป็นการกำหนดเพื่อให้เกิด Partial Load ตาม control และ event ที่ได้ระบุเอาไว้ ในที่นี้คือ จะโหลดเมื่อ chkCallNo เกิด event CheckdChanged ซึ่งก็คือเหตุการณ์คลิกนั่นเอง

    ทดสอบ run ก็จะได้ผลลัพธ์ดังรูปด้านล่าง

    จะเห็นว่าหลังจากมีการปรับแก้โค้ดไปแล้ว ทุกครั้งที่มีการคลิก CheckBox และมีการซ่อน/แสดงคอลัมน์ จะไม่มีการ refresh หน้าจออีกแล้ว เพราะ UpdatePanel ที่เราได้เพิ่มเข้าไปจะควบคุมให้มีการโหลดข้อมูลใหม่เฉพาะ component ที่อยู่ภายใน UpdatePanel เท่านั้น

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


    แหล่งข้อมูลอ้างอิง

  • ซ่อน/แสดง คอลัมน์ใน ASP.NET GridView ด้วย jQuery

    การแสดงผลข้อมูลใน Gridviews ในบางสถานการณ์เราต้องการซ่อนข้อมูลบางคอลัมน์ออกไปก่อนตามเงื่อนไขใดๆ และเมื่อเงื่อนไขเปลี่ยนเราก็จะแสดงข้อมูลในคอลัมน์ที่ถูกซ่อนนั้นออกมา

    ในบทความนี้ผู้เขียนจะแสดงตัวอย่างการ ซ่อน/แสดง คอลัมน์ใน ASP.NET GridView ด้วยชุดคำสั่ง jQuery โดยมีขั้นตอนดังต่อไปนี้

    1. เพิ่มโค้ด HTML ดังตัวอย่างด้างล่าง ซึ่งเป็นโค้ดที่แสดง Checkbox เพื่อใช้เลือกแสดงคอลัมน์ CallNo และ ASP.NET GridView ที่มีข้อมูล 5 คอลัมน์

    <asp:CheckBox ID="chkCallNo" Text=" Show CallNo" runat="server" Checked="true" />
    
    <hr />
    <asp:GridView ID="gvBib" runat="server" AutoGenerateColumns="False" >
    	<Columns>
    		<asp:BoundField DataField="BIB_NO" HeaderText="Bib#" />
    		<asp:BoundField DataField="TITLE" HeaderText="Title" />
    		<asp:BoundField DataField="Author" HeaderText="Author" />
    		<asp:BoundField DataField="CALL_NO" HeaderText="CallNo" />
    		<asp:BoundField DataField="ISBN" HeaderText="ISBN" />
    	</Columns>
    </asp:GridView>

    2. เพิ่มโค้ดใน event Page_Load ในหน้า code behind เพื่อจำลองข้อมูลที่จะใช้แสดงใน GridView

    protected void Page_Load(object sender, EventArgs e)
    {
        if(!IsPostBack)
        {
            DataTable dt = new DataTable();
            dt.Columns.AddRange(new DataColumn[5] { new DataColumn("BIB_NO"), 
                                                    new DataColumn("TITLE"), 
                                                    new DataColumn("AUTHOR"),
                                                    new DataColumn("CALL_NO"),
                                                    new DataColumn("ISBN")});
            dt.Rows.Add(1, "1 ทศวรรษ ดัชนีสุขภาพคนไทย", "สำนักงานคณะกรรมการสุขภาพแห่งชาติ (สช.)", "WA13 ห159 2556", "");
            dt.Rows.Add(2, "ความลับในร่างกายมนุษย์ที่เราไม่เคยรู้", "ฟรานซิส, เกวิน", "QS4 ฟ133a 2560", "9786168022887");
            dt.Rows.Add(3, "The best ICU", "ดุสิต สถาวร", "WX218 B561 2560", "9786168122020");
            dt.Rows.Add(4, "COVID-19 โรคระบาดแห่งศตวรรษ", "นำชัย ชีววิวรรธน์", "QW168.5.C8 น515c 2563", "9789740217060");
            dt.Rows.Add(5, "Good health & smart life ในวัย 40+", "ไวต์, จอห์น", "WT104 ว967g 2560", "9786160827237");
            gvBib.DataSource = dt;
            gvBib.DataBind();
        }
        
    }

    เนื่องจากโค้ดตัวอย่างมีการใช้งาน class DataTable และ DataColumn ซึ่งอยู่ใน namespace System.Data เพราะฉะนั้นจะต้อง Import namespace นี้ด้วย

    using System.Data;

    3. เพิ่มโค้ด jQuery เพื่อควบคุมการแสดง/ซ่อน คอลัมน์ เมื่อมีการคลิก เลือก/ยกเลิก เช็คบ๊อก (โดยในบทความนี้ผู้เขียนขออนุญาตข้ามขั้นตอนการติดตั้ง jQuery ออกไป)

    $(function () {
    
        //bind event click ให้กับ checkbox
        $("[id*=chkCallNo]").click(function () {
    		
    	//เก็บสถานะของ checkbox ว่าเลือกอยู่หรือไม่
            var isChecked = $(this).is(":checked");
    		
    	//เก็บ element ของหัวคอลัมน์ CallNo
            var th = $("[id*=gvBib] th:contains('CallNo')");
    	
            //ถ้า checkbox ถูกเลือก จะแสดงคอลัมน์ CallNo และถ้าไม่ ก็จะซ่อนคอลัมน์
            th.css("display", isChecked ? "" : "none");
    	
            //วนลูปเรคอร์ดที่เหลือทั้งหมด
            $("[id*=gvBib] tr").each(function () {
    	    
                //ใช้ลำดับของหัวคอลัมน์ CallNo (th.index) มาหาคอลัมน์ CallNo ที่อยู่ record ข้อมูล 
                //และกำหนดให้ซ่อนหรือแสดงตามการเลือก checkbox
                $(this).find("td").eq(th.index()).css("display", isChecked ? "" : "none");
            });
        });
    
    });

    ทดสอบ run ก็จะได้ผลลัพธ์ดังรูปด้านล่าง

    และเมื่อคลิกเช็คบ๊อก Show CallNo ออก คอลัมน์ CallNo ใน Gridview ก็จะหายไป

    จากตัวอย่างโค้ดในบทความนี้ จะเห็นว่าการซ่อนหรือแสดงคอลัมน์ด้วย jQuery ไม่ได้มีความยุ่งยาก สามารถนำไปประยุกต์ใช้กับงานของท่านได้

    ซึ่งผู้เขียนหวังเป็นอย่างยิ่งว่าบทความนี้น่าจะมีประโยชน์กับท่านผู้อ่านไม่มากก็น้อย สวัสดีครับ


    แหล่งข้อมูลอ้างอิง

  • จะทำอย่างไรให้สามารถดึงข้อมูลมาแสดงผลด้วย Progress bar โดยใช้ .Net (C#)

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

    แบบแถบละสี

    1. ดึงข้อมูลจากฐานข้อมูล และจัดเตรียม Tag Html ที่จะใช้ในการแสดงผล
        private void getData()
        {
            ////////////////////เป็นการสมมุติการดึงข้อมูลมาใส่ Datatable ที่ชื่อว่า dtProgress ซึ่งเป็นจำนวนของผลไม้แต่ละชนิด
    
            StringBuilder strProgress = new StringBuilder();
            DataTable dtProgress = new DataTable();
            dtProgress.Columns.AddRange(new DataColumn[2] { 
                             new DataColumn("Percent", typeof(int)),
                            new DataColumn("Name",typeof(string))});
    
            dtProgress.Rows.Add(25, "Orange");
            dtProgress.Rows.Add(56, "Grape");
            dtProgress.Rows.Add(45, "Mango");
            dtProgress.Rows.Add(100, "Banana");
          
          ////////////////////เป็นการวนลูปค่าเพื่อสร้างแท็ก html ในการแสดงผลแถบ Progress bar
             int i = 0;
             for ( i = 0; i <= dtProgress.Rows.Count -1; i++) 
             {
             
         ////////////////////เป็นการแสดงชื่อผลไม้แต่ละชนิดบนแถบ Progress bar
                strProgress.Append("<h3 class=\"progress-title\">" + dtProgress.Rows[i]["Name"] + "</h3>");
                strProgress.Append("<div class=\"progress-outer\">");
                strProgress.Append("<div class=\"progress\">");
    
         ////////////////////เป็นการแสดงกำหนดขนาดให้กับแถบสี Progress bar ตามข้อมูล % ในแถวที่วน และมีการ ดึงค่าสไตล์ชีทจากการเรียกใช้ฟังก์ชั่น getCss()
    
    ตามเงื่อนไขของจำนวน % ด้วย
                strProgress.Append("<div class=\"progress-bar progress-bar-striped " + getCss(int.Parse(dtProgress.Rows[i]["Percent"].ToString())) + " \" style=\"width:" + dtProgress.Rows[i]["Percent"] + "%;\"></div>");
    
    
         ////////////////////แสดงจำนวน % ของแต่ละแถบ Progress bar
    
                strProgress.Append("<div class=\"progress-value\"><span>" + dtProgress.Rows[i]["Percent"] + "</span>%</div>");
                strProgress.Append("</div></div>");
             }
    
         ////////////////////นำค่า Tag Html ที่เตรียมไว้ มาแสดงผลด้วย Literal
    
             ltrProgressBar.Text = strProgress.ToString();
    
        }

    2. เมธอดในการแปลงค่าสไตล์ชีทเพื่อปรับสีตามจำนวนที่ส่งมาเป็นพารามิเตอร์

    private string getCss(int Percent)
        {
            string ReturnResult = "";
    
            if (Percent >= 0 && Percent <= 25) 
            {
                ReturnResult = "progress-bar-danger";
            }
            else if (Percent > 25&& Percent <= 50) 
            {
                ReturnResult = "progress-bar-warning"; 
            }
            else if (Percent > 50 && Percent <= 75)
            {
                ReturnResult = "progress-bar-info";
            }
            else if (Percent > 75 && Percent <= 100)
            {
                ReturnResult = "progress-bar-success";
            }
            return ReturnResult;
        }

    จากโค้ดข้างต้นจะเป็นการกำหนดสไตล์ชีทที่จะใช้ ซึ่งจะขึ้นอยู่กับจำนวนค่า % ที่ส่งมาเป็นพารามิเตอร์ โดยจะแบ่งออกเป็น 4 ช่วง

    • สีแดง ช่วงตั้งแต่ 0 – 25 %
    • สีส้ม ช่วงตั้งแต่ 26 – 50 %
    • สีฟ้า ช่วงตั้งแต่ 51 – 75 %
    • สีเขียว ช่วงตั้งแต่ 76 – 100 %

    ตัวอย่างการเรียกใช้งาน

        protected void Page_Load(object sender, EventArgs e)
        {
            getData();
        }

    ผลลัพธ์

    แบบหลายสีในแถบเดียวกัน(แบบที่ 1)

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

    1. ดึงข้อมูลจากฐานข้อมูล และจัดเตรียม Tag Html ที่จะใช้ในการแสดงผล
        private void getMultiFruitColorData()
        {
            StringBuilder strProgress = new StringBuilder();
            DataTable dtProgress = new DataTable();
            dtProgress.Columns.AddRange(new DataColumn[2] { 
                             new DataColumn("Percent", typeof(int)),
                            new DataColumn("Name",typeof(string))});
            dtProgress.Rows.Add(25, "Orange");
            dtProgress.Rows.Add(12, "Grape");
            dtProgress.Rows.Add(7, "Mango");
            dtProgress.Rows.Add(25, "Banana");
    
            int i = 0;
            int percent = 0;
            string CssStr = "";
            int Total = 0;
            
                strProgress.Append("<h3 class=\"progress-title\">Multiple-fruits</h3>");
                strProgress.Append("<div class=\"progress-outer\">");
                strProgress.Append("<div class=\"progress\">");
            for (i = 0; i <= dtProgress.Rows.Count - 1; i++)
            {
                percent = int.Parse(dtProgress.Rows[i]["Percent"].ToString());
    
    
    ////////////////////คำนวณผลรวม % ของผลไม้ทุกชนิดตามการวนรอบที่จะแสดงในแถบ Progress bar
    
                Total += percent;
                switch (i) 
                {
                    case 0: CssStr = "progress-bar-danger"; break;
                    case 1: CssStr = "progress-bar-warning"; break;
                    case 2: CssStr = "progress-bar-info"; break;
                    case 3: CssStr = "progress-bar-success"; break;
                }
    
    ////////////////////เป็นการแสงค่า % ของผลไม้แต่ละชนิดในแถบ Progress bar เดียวกัน โดยแสดงชื่อผลไม้ และจำนวน % ของผลไม้แต่ละชนิดด้วย
    
                strProgress.Append("<div class=\"progress-bar progress-bar-striped " + CssStr + "\" style=\"width:" + percent + "%;\">" + dtProgress.Rows[i]["Name"] + "(" + percent + "%)</div>");
               
            }  
    
     ////////////////////เป็นการแสงผลรวม % ของผลไม้ทุกชนิดในแถบ Progress bar
    
            strProgress.Append("<div class=\"progress-value\"><span>" + Total + "</span>%</div></div>");
            strProgress.Append("</div></div>");
    
            ltrProgressBar.Text = strProgress.ToString();
    
        }
    

              จากโค้ดตัวอย่างข้างต้น จะเห็นว่าการสร้างแท็ก Html จะแตกต่างจากแบบแรก คือจะมีการสร้างใน <div class=\”progress\”> เดียวกัน ซึ่งมีหลักการคล้ายกับการสร้าง Progress bar อย่างง่ายหลายสีในแถบเดียวกันที่เคยกล่าวไว้แล้วในบทความก่อนหน้านั่นเอง

    ผลลัพธ์

    แบบหลายสีในแถบเดียวกันและแสดงหลายแถบ Progress Bar(แบบที่ 2)

              ในตัวอย่างนี้ เป็นการแสดงผลแถบสีแยกตามช่วงของข้อมูลบน Progress bar แต่ละแถบ โดยการแสดงผลจะแบ่งสีตามปริมาณข้อมูลในแต่ละช่วง ดังนี้

    สีแดง ช่วงตั้งแต่ 0 – 25 % สีส้ม ช่วงตั้งแต่ 26 – 50 % สีฟ้า ช่วงตั้งแต่ 51 – 75 % สีเขียว ช่วงตั้งแต่ 76 – 100 %

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

    1. ดึงข้อมูลจากฐานข้อมูล และจัดเตรียม Tag Html ที่จะใช้ในการแสดงผล
        private void getMultiColorData()
        {
            StringBuilder strProgress = new StringBuilder();
            DataTable dtProgress = new DataTable();
            dtProgress.Columns.AddRange(new DataColumn[2] { 
                             new DataColumn("Percent", typeof(int)),
                            new DataColumn("Name",typeof(string))});
    
            dtProgress.Rows.Add(25, "Orange");
            dtProgress.Rows.Add(56, "Grape");
            dtProgress.Rows.Add(45, "Mango");
            dtProgress.Rows.Add(100, "Banana");
    
            int i = 0;
            int j = 0;
            int maxRange = 0;
            int percent = 0;
            for (i = 0; i <= dtProgress.Rows.Count - 1; i++)
            {
                percent= int.Parse(dtProgress.Rows[i]["Percent"].ToString());
    
    
    ////////////////////วนเพื่อสร้าง Progress bar ใหม่ในผลไม้แต่ละชนิด
    
                strProgress.Append("<h3 class=\"progress-title\">" + dtProgress.Rows[i]["Name"] + "</h3>");
                strProgress.Append("<div class=\"progress-outer\">");
                strProgress.Append("<div class=\"progress\">");
                maxRange = 0;
    
    ////////////////////คำนวณหาว่าค่าของ % ตกอยู่ในช่วงใด 1-4(เนื่องจากแบ่งออกเป็นช่วงละ 25 % และรวมเป็น 100%)
    
    
                if (percent >= 0 && percent <= 25)
                {
                    maxRange = 1;
                }
                else if (percent > 25 && percent <= 50)
                {
                    maxRange = 2;
                }
                else if (percent > 50 && percent <= 75)
                {
                    maxRange = 3;
                }
                else if (percent > 75 && percent <= 100)
                {
                    maxRange = 4;
                }
    
    ////////////////////วนลูปเพื่อแสดงผลสีในแต่ละช่วงบน Progress bar โดยมีการเรียกใช้งานเมธอด getCssRange()
    
                for (j = 1; j <= maxRange; j++)
                {
                    strProgress.Append(getCssRange(percent, j, maxRange));
                 }
                strProgress.Append("<div class=\"progress-value\"><span>" + dtProgress.Rows[i]["Percent"] + "</span>%</div></div>");
                strProgress.Append("</div></div>");
            }
    
            ltrProgressBar.Text = strProgress.ToString();
    
        }

    2. สร้างเมธอดที่ใช้ในการสร้างแท็ก Html ในการแสดงผลแถบสี ซึ่งในส่วนของกระบวนการคำนวณแถบสีจะไม่ขอลงในรายละเอียด แต่แสดงไว้ให้เห็นภาพการทำงานหลักๆเท่านั้น ดังนี้ค่ะ

        private string getCssRange(int Percent,int Range,int MaxRange)
        {
            string ReturnResult = "";
    
            switch (Range)
            {
                case 1:
                    if (MaxRange > Range)
                        ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-danger\" style=\"width:25%;\"></div>";
    
                    else 
                    {
                        Percent = Percent >= 25 ? 25 : Percent - (25 * (Range - 1));
                    ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-danger\" style=\"width:" + Percent + "%;\"></div>";
                    }
                    break;
                case 2:
                    if (MaxRange > Range)
                        ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-warning\" style=\"width:25%;\"></div>";
    
                    else
                    {
                        Percent = Percent >= 50 ? 50 : Percent - (25 * (Range - 1));
                        ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-warning\" style=\"width:" + Percent + "%;\"></div>";
                    }
                   
                    break;
    
                case 3:
                    if (MaxRange > Range)
                        ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-info\" style=\"width:25%;\"></div>";
    
                    else
                    {
                        Percent = Percent >= 75 ? 75 : Percent- (25* (Range-1));
                        ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-info\" style=\"width:" + Percent + "%;\"></div>";
                    }
                     break;
    
                case 4:
    
                     if (Percent==100)
                         ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-success\" style=\"width:25%;\"></div>";
                     else
                     {
                         Percent =   Percent - (25 * (Range - 1));
                         ReturnResult = "<div class=\"progress-bar progress-bar-striped progress-bar-success \" style=\"width:" + Percent + "%;\"></div>";
                     }
                      break;
    
            }
             return ReturnResult;
        }
    

    ผลลัพธ์

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

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

    แหล่งข้อมูลอ้างอิง :

    https://bestjquery.com/tutorial/progress-bar/demo78/

    https://www.jquery-az.com/boots/demo.php?ex=51.0_5