Author: wanwarang.j

  • อยากดึงข้อมูลมาแสดงใน TreeView จะทำอย่างไรดี?

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

    ขั้นตอนในการดึงข้อมูลจากฐานข้อมูลมาแสดงใน TreeView

    1. สร้าง TreeView ที่ต้องการใช้ในการแสดงผลข้อมูล
      ตัวอย่าง code ในฝั่ง Client
      <body>
      <form id="form1" runat="server">
      <asp:TreeView ID="TvOrganization" runat="server" >
      </asp:TreeView>
      </form>
      </body>
    2. ติดต่อฐานข้อมูลเพื่อใช้ในการแสดงผล ซึ่งในตัวอย่างนี้จะขอสมมุติข้อมูลจาก datatable ที่สร้างขึ้นแทน
      ตัวอย่าง code ในฝั่ง Server (C#)
      • ฟังก์ชั่นตอน Page_Load
      protected void Page_Load(object sender, EventArgs e)
          {
              if (!this.IsPostBack)
              {   ///////ดึงข้อมูลวิทยาเขต โดยในการใช้งานจริงจะเป็นการติดต่อกับฐานข้อมูลเพื่อดึงค่าวิทยาเขตมาแสดง
                  DataTable dt = this.GetCampusData();
              
      ///////เรียกใช้งานฟังก์ชั่น PopulateTreeView() เพื่อนำค่าข้อมูลวิทยาเขตมาแสดงผลใน TreeView 
      โดยในกรณีนี้จะเริ่มสร้างจาก root node โดยส่งค่าพารามิเตอร์ของ parentId เป็น 0
                  this.PopulateTreeView(dt, 0, null);
              }
          }
      • ฟังก์ชั่นดึงข้อมูลวิทยาเขตและคณะ/หน่วยงาน
      private DataTable GetCampusData() {
       ///////เตรียมข้อมูลวิทยาเขต เนื่องจากในกรณีนี้เป็นการสมมุติโครงสร้างและข้อมูลวิทยาเขตโดยสร้างเป็น DataTable 
      เสมือนเป็นตารางวิทยาเขตในฐานข้อมูล
      DataTable dtCampus = new DataTable();
      dtCampus.Columns.AddRange(new DataColumn[2] {
      new DataColumn("CampID", typeof(int)), 
      new DataColumn("CampNameThai",typeof(string)) }); 
      dtCampus.Rows.Add("01", "วิทยาเขตหาดใหญ่"); 
      dtCampus.Rows.Add("02", "วิทยาเขตปัตตานี");
      dtCampus.Rows.Add("03", "วิทยาเขตภูเก็ต"); 
      dtCampus.Rows.Add("04", "วิทยาเขตสุราษฎร์ธานี");
      dtCampus.Rows.Add("05", "วิทยาเขตตรัง"); return dtCampus; } private DataTable GetFacultyData() { ///////เตรียมข้อมูลคณะ/หน่วยงาน ในกรณีนี้เป็นการสมมุติโครงสร้างข้อมูลคณะ/หน่วยงานและสร้างเป็น DataTable โดยมีการระบุรหัสของวิทยาเขตไว้ด้วย ซึ่งในการทำงานจริงจะเป็นการดึงข้อมูลจากตารางที่เก็บข้อมูลคณะ/หน่วยงานแทน DataTable dtFaculty = new DataTable(); dtFaculty.Columns.AddRange(new DataColumn[3] { new DataColumn("FacID", typeof(int)), new DataColumn("FacNameThai",typeof(string)) , new DataColumn("CampID",typeof(string)) }); dtFaculty.Rows.Add("01", "สำนักงานอธิการบดี","01"); dtFaculty.Rows.Add("02", "ศูนย์คอมพิวเตอร์", "01"); dtFaculty.Rows.Add("03", "วิศวกรรมศาสตร์", "01"); dtFaculty.Rows.Add("54", "คณะรัฐศาสตร์", "02"); dtFaculty.Rows.Add("07", "ศึกษาศาสตร์", "02"); dtFaculty.Rows.Add("36", "วิทยาการสื่อสาร", "02"); dtFaculty.Rows.Add("81", "ศูนย์การเรียนรู้", "03"); dtFaculty.Rows.Add("56", "คณะศิลปศาสตร์และวิทยาศาสตร์", "04"); dtFaculty.Rows.Add("44", "คณะเทคโนโลยีและการจัดการ", "04"); dtFaculty.Rows.Add("46", "คณะพาณิชยศาสตร์และการจัดการ", "05"); dtFaculty.Rows.Add("66", "คณะสถาปัตยกรรมศาสตร์", "05"); return dtFaculty; }
      • ฟังก์ชั่นที่ใช้ในการวนแสดงข้อมูลที่ได้จากวิทยาเขตและคณะ/หน่วยงานมาแสดงใน TreeView
      private void PopulateTreeView(DataTable dtParent, int parentId, TreeNode treeNode)
      {
      ///////ดึงค่าข้อมูลคณะ/หน่วยงาน
      DataTable dtChild = this.GetFacultyData();
      DataRow[] drChild;
      
      ///////วนค่าข้อมูลวิทยาเขตจาก dtParent ที่เป็นพารามิเตอร์ที่ส่งมา
      foreach (DataRow row in dtParent.Rows)
      {
      
      if (parentId == 0) ///////กรณีที่เป็น root node ค่าของ parentId จะเท่ากับ 0 ซึ่งในที่นี้หมายถึง node วิทยาเขต นั่นเอง
      {
         TreeNode child = new TreeNode
        {  ///////กำหนดคุณสมบัติให้กับ root node ในส่วนของ Text,Value,ToolTip,SelectAction ตอน runtime 
      ขึ้นอยู่กับเงื่อนไขและจุดประสงค์ที่เราต้องการให้แสดงผล โดยในที่นี้จะนำค่าข้อมูลจากวิทยาเขตมาวนแสดง
      
      ///////สามารถกำหนด Style ตอนระบุ Text ได้ด้วย โดยในกรณีนี้ใส่ style ในแท็ก <span></span> นั่นเอง
          Text = "<span style='color:blue;font-weight:bold'>" + row["CampNameThai"].ToString() + "</span>" ,  
      
      ///////กำหนดค่าของ node เป็นรหัสวิทยาเขต
          Value = row["CampID"].ToString(),
      
      //////ระบุให้ ToolTip เป็นชื่อวิทยาเขต[รหัสวิทยาเขต]
          ToolTip = row["CampNameThai"].ToString() + "[" + row["CampID"].ToString() +"]", 
      
      ///////ระบุคุณสมบัติไม่ให้เลือกได้
          SelectAction = TreeNodeSelectAction.None, 
         };
      ///////เมื่อระบุคุณสมบัติและกำหนดค่าเรียบร้อยแล้วจะเพิ่ม node ดังกล่าวลงใน TreeView ที่ชื่อว่า TvOrganization
        TvOrganization.Nodes.Add(child); 
      
      ///////ดึงข้อมูลคณะ/หน่วยงานตามวิทยาเขตที่กำลังวนค่าซึ่งก็คือ row["CampID"].ToString()
        drChild = dtChild.Select("CampID=" + row["CampID"].ToString());
      
      ///////เรียกใช้ฟังก์ชั่น PopulateTreeView() โดยส่งค่าข้อมูลคณะ/หน่วยงานของวิทยาเขตที่กำลังวน(ตัวแปร drChild.CopyToDataTable())
      เพื่อวนสร้าง node ย่อยเพิ่มลงไปใน node วิทยาเขตที่กำลังจัดการ(ตัวแปร child)
      PopulateTreeView(drChild.CopyToDataTable(), int.Parse(child.Value), child); }
      else ///////กรณีที่เป็นการจัดการ node ย่อย ซึ่งในที่นี้หมายถึง node คณะ/หน่วยงาน { TreeNode child = new TreeNode { ///////กำหนดคุณสมบัติให้กับ node ย่อย
      ในส่วนของ Text,Value,ToolTip,SelectAction,ImageUrl,NavigateUrl,Target ตอน runtime ขึ้นอยู่กับเงื่อนไขและจุดประสงค์ที่เราต้องการให้แสดงผล โดยในที่นี้จะนำค่าข้อมูลจากคณะ/หน่วยงานมาวนแสดง
      ///////แสดงค่าข้อมูลคณะ/หน่วยงานที่กำลังวน Text = row["FacNameThai"].ToString() , ///////แสดงค่าข้อมูลรหัสคณะ/หน่วยงานที่กำลังวน Value = row["FacID"].ToString(), //////ระบุให้ ToolTip เป็นชื่อคณะ/หน่วยงาน[รหัสคณะ/หน่วยงาน] ToolTip = row["FacNameThai"].ToString() + "[" + row["FacID"].ToString() + "]", ///////ระบุคุณสมบัติไม่ให้สามารถคลิกเลือก node ได้ SelectAction = TreeNodeSelectAction.Select, ///////ระบุ url ของเพจที่ต้องการให้ลิงค์ไปโดยส่งค่าพารามิเตอร์ของรหัสคณะ/หน่วยงานไปด้วยกรณีที่ต้องการนำค่าข้อมูลไปใช้ต่อในเพจดังกล่าว NavigateUrl = "BasicTreeviewAll.aspx?FacId=" + row["FacID"].ToString(), ///////กำหนดให้เปิดเพจใหม่ Target="_blank", ///////กำหนดให้แสดงรูปภาพดาวหน้าข้อความใน node ดังกล่าว ImageUrl = "~/images/star.png", }; ///////เมื่อระบุคุณสมบัติและกำหนดค่าเรียบร้อยแล้วจะเพิ่ม node ดังกล่าวลงใน ตัวแปร treeNode(ซึ่งก็คือ node แม่ที่ถูกส่งมานั่นเอง) ซึ่งเป็นพารามิเตอร์ที่ส่งมาตอนเรียกใช้ฟังก์ชั่น PopulateTreeView() แต่ถ้าเป็นกรณี เรียกใช้ฟังก์ชั่นเพื่อสร้าง root node ค่า treeNode ที่ส่งมานี้จะเป็น null treeNode.ChildNodes.Add(child); } } }

      หมายเหตุ :
      1) การกำหนดคุณสมบัติให้กับ node ตอน RunTime เราสามารถเลือกระบุคุณสมบัติได้เช่นเดียวกับตอนกำหนดฝั่ง Client ที่เคยกล่าวไว้ในบทความก่อน โดย properties ที่สามารถระบุได้ขึ้นอยู่กับความต้องการของผู้ใช้ มีดังนี้

      2) การประยุกต์ใช้งานและการดึงข้อมูลที่จะนำมาวนแสดงสามารถปรับเปลี่ยนในแบบที่ท่านต้องการ เพื่อเพิ่มประสิทธิภาพในการทำงานของโปรแกรมให้รวดเร็วขึ้นได้ โดยกรณีนี้เพียงยกตัวอย่างเพื่อเน้นให้ดูการวนมาแสดงใน TreeView เท่านั้น

      ตัวอย่างแสดงผลลัพธ์ของการดึงข้อมูลมาแสดงใน TreeView จากโค้ดข้างต้น


              นอกจากนี้ ท่านยังสามารถเพิ่มความสามารถให้กับ TreeView ของท่านได้อีกหน่อยในกรณีที่ TreeView ที่ต้องการจัดการมี check box และต้องการให้สามารถเลือก node แม่และสามารถเลือก node ลูกทั้งหมดด้วยได้ (ลักษณะคล้ายกับแบบ Select All นั่นเอง) โดยใช้  jQuery เข้ามาช่วย โดยเพิ่ม code ที่เป็น jQuery  ดังนี้ ค่ะ

    1. อ้างอิงไฟล์ที่ใช้ในการทำงานร่วมกับ jQuery
      <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    2. ส่วนของ jQuery ที่ใช้ในการเลือกแบบ Select All ให้กับ node แม่และลูก
      <script type="text/javascript">
          $(function () {
              $("[id*=TvOrganization] 
      ////ทุกครั้งที่คอนโทรลที่มีชนิดเป็น checkbox ถูกคลิก
      input[type=checkbox]").bind("click", function () {
      ////จะหา tag table ที่ใกล้ที่สุด เพื่อแยกระหว่าง node แม่(วิทยาเขต)และลูก(คณะ/หน่วยงาน)
                  var table = $(this).closest("table");
                  if (table.next().length > 0 && table.next()[0].tagName == "DIV") {
                    ////เป็นการตรวจสอบว่าเป็น node ที่เป็นแม่หรือไม่
      โดยตรวจสอบว่าต้องเจอแท็ก table ถัดไป และแท็ก table ดังกล่าวต้องมีแท็ก DIV อยู่ด้วย
      var childDiv = table.next();

      ////นำค่าการเช็คของ check box ดังกล่าว มาใส่ไว้ในตัวแปร isChecked var isChecked = $(this).is(":checked");

      ////นำค่าจากตัวแปร isChecked ไปเซ็ทการเช็คหรือไม่เช็คใน check box ใน node ลูกทั้งหมด $("input[type=checkbox]", childDiv).each(function () { if (isChecked) { $(this).attr("checked", "checked"); } else { $(this).removeAttr("checked"); } }); } else { ////กรณีที่เป็น node ย่อยหรือ node ลูก var parentDIV = $(this).closest("DIV");

       ////ถ้าค่าของจำนวน node ย่อย เท่ากับจำนวนของการเช็คใน node ย่อยจะไปเช็ค check box ตัวแม่ด้วย
      แต่ถ้าไม่เท่ากันจะไปติ๊กเลือกออกจาก check box ตัวแม่ให้อัตโนมัติ
      if ($("input[type=checkbox]", parentDIV).length ==
      $("input[type=checkbox]:checked", parentDIV).length)
      { $("input[type=checkbox]", parentDIV.prev()).attr("checked", "checked"); } else { $("input[type=checkbox]", parentDIV.prev()).removeAttr("checked"); } } }); }) </script>

      คำอธิบายเพิ่มเติม :
             จากโค้ดของ jQuery เบื้องต้น ที่มีการแยกระหว่างส่วนของ node แม่และลูก เนื่องจากเมื่อดูจากโครงสร้าง Tag HTML ของ TreeView ที่ถูกสร้างขึ้นเมื่อทำการรันโปรแกรมเพื่อแสดงผล จะสังเกตว่าจะมีการสร้างเป็น Tag HTML ในลักษณะดังนี้

      ////ส่วนของการแสดงผล node แม่(วิทยาเขต) โดยในที่นี้คือ วิทยาเขตตรัง
      <table cellpadding="0" cellspacing="0" style="border-width:0;">
      <tbody>
      <tr>
      <td><a id="TvOrganizationn13"
      href="javascript:TreeView_ToggleNode(TvOrganization_Data,13,document.getElementById('TvOrganizationn13'),' '
      ,document.getElementById('TvOrganizationn13Nodes'))">
      <img src="xxxx" alt="Collapse <span style='color:blue;font-weight:bold'>วิทยาเขตตรัง</span>"
      style="border-width:0;">
      </a></td>
      <td style="white-space:nowrap;"><input type="checkbox" name="TvOrganizationn13CheckBox"
      id="TvOrganizationn13CheckBox"
      title="วิทยาเขตตรัง[5]"><span class="TvOrganization_0" title="วิทยาเขตตรัง[5]" id="TvOrganizationt13">
      <span style="color:blue;font-weight:bold">วิทยาเขตตรัง</span></span></td>
      </tr>
      </tbody>
      </table>

      ////ส่วนของการแสดงผล node ลูก(คณะ/หน่วยงาน) จะมีแท็ก DIV คั่นอยู่ ก่อนจะเจอแท็ก <table></table>ของ node ลูก

      <div id="TvOrganizationn13Nodes" style="display:block;">
      ////ในที่นี้จะมี 2 คณะ/หน่วยงานในวิทยาเขตตรัง คือ คณะพาณิชยศาสตร์และการจัดการ และคณะสถาปัตยกรรมศาสตร์
      ////คณะพาณิชยศาสตร์และการจัดการ
      <table cellpadding="0" cellspacing="0" style="border-width:0;">
      <tbody>
      <tr>
      <td><div style="width:20px;height:1px"></div></td>
      <td><img src="xxx" alt=""></td>
      <td><a href="BasicTreeviewAll.aspx?FacId=46" target="_blank"
      onclick="javascript:TreeView_SelectNode(TvOrganization_Data, this,'TvOrganizationt14');"
      title="คณะพาณิชยศาสตร์และการจัดการ[46]" id="TvOrganizationt14i" tabindex="-1">
      <img src="images/star.png" alt="" style="border-width:0;"></a></td>
      <td style="white-space:nowrap;"><input type="checkbox" name="TvOrganizationn14CheckBox"
      id="TvOrganizationn14CheckBox" title="คณะพาณิชยศาสตร์และการจัดการ[46]">
      <a class="TvOrganization_0" href="BasicTreeviewAll.aspx?FacId=46" target="_blank"
      onclick="javascript:TreeView_SelectNode(TvOrganization_Data, this,'TvOrganizationt14');"
      title="คณะพาณิชยศาสตร์และการจัดการ[46]" id="TvOrganizationt14">คณะพาณิชยศาสตร์และการจัดการ</a></td>
      </tr>
      </tbody>
      </table>
      ////คณะคณะสถาปัตยกรรมศาสตร์
      <table cellpadding="0" cellspacing="0" style="border-width:0;">
      <tbody>
      <tr>
      <td><div style="width:20px;height:1px"></div></td>
      <td><img src="xxxx" alt=""></td>
      <td><a href="BasicTreeviewAll.aspx?FacId=66" target="_blank"
      onclick="javascript:TreeView_SelectNode(TvOrganization_Data, this,'TvOrganizationt15');"
      title="คณะสถาปัตยกรรมศาสตร์[66]" id="TvOrganizationt15i" tabindex="-1">
      <img src="images/star.png" alt="" style="border-width:0;"></a></td>
      <td style="white-space:nowrap;"><input type="checkbox" name="TvOrganizationn15CheckBox"
      id="TvOrganizationn15CheckBox" title="คณะสถาปัตยกรรมศาสตร์[66]">
      <a class="TvOrganization_0" href="BasicTreeviewAll.aspx?FacId=66" target="_blank"
      onclick="javascript:TreeView_SelectNode(TvOrganization_Data, this,'TvOrganizationt15');"
      title="คณะสถาปัตยกรรมศาสตร์[66]" id="TvOrganizationt15">คณะสถาปัตยกรรมศาสตร์</a></td>
      </tr>
      </tbody>
      </table>
      </div>

                จากแถบสีที่เน้นไว้ใน tag HTML ข้างต้น จะเห็นว่า  node แม่(วิทยาเขต)จะถูกสร้างเป็นแท็ก <table> </table> คลุมและถูกคั่นกับ node ลูก ด้วยแท็ก <div> </div> ก่อน node ลูกจะสร้างแท็ก <table> </table> คลุม node ของตัวเอง ทำให้ตอนจัดการกับ node แต่ละแบบใน jQuery จึงแยกส่วน node แม่และลูกด้วยแท็กดังกล่าวนั่นเองค่ะ

    3. ส่วนของ body ในการแสดงผล
      <body>
          <form id="form1" runat="server">
              <asp:TreeView ID="TvOrganization" runat="server" ShowCheckBoxes="All" >
              </asp:TreeView>
          </form>
      </body>

      หมายเหตุ : กรณีที่ต้องการใช้งานเกี่ยวกับ check box อย่าลืมกำหนดคุณสมบัติให้กับ TreeView เพื่อให้สามารถแสดง check box ไว้ด้วยนะคะ ซึ่งก็คือ การกำหนดค่าให้กับ ShowCheckBoxes นั่นเองค่ะ

    4. ตัวอย่างผลลัพธ์
      แบบที่ 1 เลือกที่  node วิทยาเขต จะเลือกที่ node คณะ/หน่วยงานในวิทยาเขตนั้นให้ทั้งหมด

      แบบที่ 2
      หากเลือกออกจากคณะ/หน่วยงานใดหน่วยงานหนึ่ง จะทำการเลือกออกที่  node วิทยาเขตให้อัตโนมัติ

               เพียงเท่านี้ท่านก็จะสามารถดึงข้อมูลจากฐานข้อมูลมาแสดงใน TreeView ของท่านได้แล้ว และนี่ก็อาจจะเป็นเพียงหนึ่งในวิธีการดึงข้อมูลมาแสดงใน TreeView ในเบื้องต้นที่ผู้เขียนศึกษาและนำมาประยุกต์ให้ดูเป็นตัวอย่างเท่านั้น หวังว่าผู้อ่านจะสามารถนำไปเป็นแนวทางในการนำไปประยุกต์ใช้กับงานของท่านได้ นอกจากนี้ท่านยังสามารถผนวกส่วนของการกำหนดคุณสมบัติที่ฝั่ง client กับการกำหนดคุณสมบัติตอนฝั่งเซิร์ฟเวอร์(C#) ร่วมกันได้ เพื่อเพิ่มประสิทธิภาพและลูกเล่นให้กับ TreeView ของท่านมากยิ่งขึ้นค่ะ หากมีส่วนใดผิดพลาดทางผู้เขียนขออภัยไว้ ณ ที่นี้ด้วยนะคะ ขอบคุณค่ะ

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

  • มาทำความรู้จักกับพื้นฐานการใช้งาน “TreeView” สำหรับมือใหม่กันดีกว่า

              เชื่อว่านักพัฒนาโปรแกรม Web application ด้วย .Net หลายๆท่านอาจจะเคยได้ยินชื่อของเจ้า “TreeView” กันมาบ้างแล้ว หรือบางท่านก็อาจจะเกือบลืมเจ้าเครื่องมือตัวนี้ไปแล้วก็ตามเพราะมันอาจจะไม่ใช่เครื่องมือที่ถูกหยิบมาใช้บ่อยนัก ในบทความนี้ผู้เขียนจึงขอหยิบยกเจ้า “TreeView” มาปัดฝุ่น แนะนำลูกเล่นการใช้งาน การกำหนดคุณสมบัติต่างๆ และวิธีการใช้งานกันอย่างคร่าวๆก่อน เพื่อเป็นการปูพื้นฐานให้กับมือใหม่หัดใช้ TreeView และรื้อความทรงจำให้กับผู้ที่เคยใช้ TreeView มาก่อนหน้านี้ เผื่อความสามารถที่ซ่อนอยู่จะไปเตะตาตรงใจท่านใดที่กำลังมองหาการทำงานแบบนี้อยู่พอดี จนอยากนำเครื่องมือตัวนี้ไปประยุกต์ใช้เป็นอีกหนึ่งทางเลือกในการแสดงผลข้อมูลในงานพัฒนาของท่านกันอีกครั้งได้ค่ะ
              แต่ก่อนจะพูดถึงลูกเล่นการทำงานของ TreeView คงต้องเกริ่นนำกันก่อนว่าเจ้าเครื่องมือตัวนี้ถูกจัดอยู่ในจำพวก Navigation Control ซึ่งบางคนอาจเกิดคำถามว่า ข้อมูลแบบใดบ้างจึงจะเหมาะนำมาใช้งานแสดงผลกับเจ้า TreeView นี้ ซึ่งโดยความเห็นส่วนตัวแล้วนั้น ผู้เขียนคิดว่าข้อมูลที่เหมาะจะมาแสดงผลด้วย TreeView ควรจะเป็นข้อมูลที่มีลักษณะเป็นแบบ Hierarchy หรือเป็นลักษณะโครงสร้าง มีลักษณะข้อมูลเป็นลำดับชั้นย่อยๆ เช่น โครงสร้างหน่วยงาน โครงสร้างองค์กร หรือข้อมูลที่เป็นประเภทหลักและประเภทย่อย เป็นต้น และเพื่อไม่ให้เป็นการเสียเวลา เรามาเรียนรู้วิธีจัดการกับ TreeView ของเรากันเลยดีกว่าค่ะ

    ขั้นตอนสร้าง TreeView อย่างง่ายจาก Tool box ของ Visual studio .Net สามารถทำได้ดังนี้

    1. สร้าง TreeView โดยเลือกจากแท็บเครื่องมือ ในกลุ่ม Navigation มาวางในหน้าจอ design ของเรา ดังรูป


    2. การเพิ่มและจัดการ node ให้กับ TreeView ได้โดยการคลิกเลือก “Edit Nodes…” ดังภาพ


    3. การจัดการเพิ่ม-ลดข้อมูล node ในระดับต่างๆ จะแสดงภาพดังหน้าจอต่อไปนี้ เพื่อใช้ในการจัดการเพิ่ม-ลดข้อมูล node ในระดับต่างๆ ดังนี้



      โดยขั้นตอนการเพิ่ม – ลด node อย่างคร่าวๆ มีดังนี้
      • การเพิ่ม node ที่เป็น root (ลำดับแรกสุด) โดยการกดปุ่ม
      • การเพิ่ม node ระดับย่อย(child node) โดยการกดปุ่ม
      • การลบ node ที่ไม่ต้องการ สามารถทำได้โดยการกดปุ่ม
      • ปรับเปลี่ยนระดับความลึกของ node โดยคลิกเลือก node ที่ต้องการจัดการและกดปุ่ม เพื่อเลื่อนระดับความลึกมากน้อยได้ตามต้องการ ซึ่งจะเห็นได้ว่ามีการทำงานคล้ายกับการเยื้องย่อหน้าใน MS Word ที่เราคุ้นเคยกันดี
      • การเลื่อนลำดับบน-ล่างให้กับ node สามารถทำได้โดยกดปุ่ม

        ตัวอย่าง หน้าจอการจัดการ node ใน TreeView



                      จากภาพตัวอย่าง มีการเพิ่มและจัดการ node ข้อมูลโครงสร้างองค์กรตั้งแต่ระดับมหาวิทยาลัย วิทยาเขต และคณะ โดยการกำหนดค่าตายตัวจากหน้าจอ design ด้วยการระบุเอง ไม่ได้ดึงจากฐานข้อมูล ซึ่งท่านสามารถทดลองเพิ่มและจัดการได้ด้วยตัวเองเพื่อให้เข้าใจการทำงานมากขึ้น โดยลองทำตามขั้นตอนการจัดการ node ข้างต้น
                      นอกจากการเพิ่ม-ลด node แล้ว ท่านยังสามารถกำหนดคุณสมบัติให้กับ node ได้ด้วย โดยคลิกข้อมูล node ที่ต้องการจัดการจากฝั่งซ้ายมือ และกำหนดคุณสมบัติของ node ดังกล่าวในฝั่งขวามือ ซึ่งรายละเอียดการกำหนดคุณสมบัติต่างๆ ในแต่ละ node จะขอพูดในหัวข้อถัดไป (ข้อ 4.)ค่ะ

    4. กำหนดคุณสมบัติให้กับแต่ละ node เราสามารถกำหนดคุณสมบัติ (Properties) เฉพาะให้กับแต่ละ node ได้ โดยจะขอพูดถึงคร่าวๆ ที่คิดว่าน่าจะใช้อยู่บ่อยๆและน่าสนใจ ดังนี้
      • ShowCheckBox : เป็นการระบุว่าจะให้ node ดังกล่าวแสดงช่องให้เลือกหรือไม่
      • Checked : เป็นการระบุว่า ต้องการให้มีการทำเครื่องหมายถูกไว้ในกล่อง check box หรือไม่ ซึ่งต้องมีการกำหนดควบคู่กับ ShowCheckBox = “true” นั่นเอง
      • Text : ระบุว่าต้องการให้ node ดังกล่าวแสดงข้อความอะไร
      • Value : ระบุค่าที่จะกำหนดให้กับ node ดังกล่าว และสามารถเลือกดึงค่าไปใช้งานได้(ในกรณีที่จะนำไปพัฒนาโปรแกรมต่อ)
      • NavigateUrl : กำหนดว่าเมื่อกด node ดังกล่าวจะให้ไปยังหน้าจอใด โดยระบุชื่อเพจหน้าจอนั้น
      • Target : เป็นการระบุการเปิดเพจในกรณีที่มีการกำหนด NavigateUrl ไว้ว่าจะให้เปิดเพจใหม่ หรืออยู่ในหน้าเดิมนั่นเอง
      • ImageUrl : เป็นการระบุว่าต้องการให้แสดงเป็นรูปภาพอะไรหน้าข้อความใน node นั้นๆ
      • Selected : เป็นการระบุว่าจะให้ node ดังกล่าวสามารถคลิกลิงค์ได้หรือไม่
      • SelectAction : กำหนด action ที่จะให้ทำตอนกดเลือกลิงค์ใน node นั้น
        1) Expand : เมื่อหากต้องการให้คลิกแล้วขยาย node ย่อย ซึ่งเมื่อคลิกแล้วจะย่อ-ขยาย node ย่อยได้
        2) SelectExpand : จะเป็นการระบุให้มีการขยาย node เช่นกัน แต่จะต่างกันตรงที่เมื่อขยาย node ย่อยแล้วจะไม่ย่อ-ขยายให้อีกแบบ Expand นั่นหมายถึงว่าจะสามารถคลิกลิงค์ของ node เพื่อขยายได้เท่านั้น แต่จะไม่สามารถย่อกลับมาได้นั่นเอง
        3) None : เมื่อไม่ต้องการให้คลิกได้
      • ToolTip : เป็นการระบุข้อความที่ต้องการให้แสดงเมื่อชี้ไปที่ node นั้นๆ
      • Expanded : เป็นการกำหนดว่า ตอนเริ่มต้นจะให้ node ย่อยของ node ดังกล่าวขยายไว้หรือไม่

        ตัวอย่าง ผลลัพธ์จากการกำหนดลองกำหนดค่าต่างๆให้กับบาง node


                      จากภาพ จะเห็นว่าแต่ละ node มีการแสดงผลแตกต่างกันตามที่เราระบุ เช่น บาง node มีการกำหนดให้แสดงภาพหน้าข้อความ บาง node ไม่สามารถคลิกลิงค์ได้ เช่น มหาวิทยาลัยสงขลานครินทร์ และบาง node มี check box อยู่หน้าข้อความให้เลือก และบาง node มีการยุบ node ย่อยไม่แสดงในตอนเริ่มต้น เป็นต้น
    5. กำหนดคุณสมบัติให้กับทุก node จาก properties ของคอนโทรล TreeView ซึ่งจะแตกต่างจากการกำหนดในแต่ละ node ในข้อ 4. เนื่องจากวิธีการกำหนดคุณสมบัติแบบนี้ จะเหมาะกับกรณีที่ไม่ต้องการระบุคุณสมบัติแยกในแต่ละ node แต่ต้องการให้เป็นไปในแบบเดียวกันทั้ง TreeView ทำให้ลดระยะเวลาที่จะต้องกำหนดรูปแบบให้กับทุก node ที่ต้องการให้แสดงผลเหมือนกัน โดยจะขอยกตัวอย่างให้กับคุณสมบัติบางตัวที่อาจจะจำเป็นต้องใช้ ดังนี้
      • ShowCheckBox : เป็นการกำหนดคุณสมบัติคล้ายกับที่กำหนดให้กับแต่ละ node แต่หากมากำหนดที่ตรงนี้ จะทำให้ทุก node แสดงหรือซ่อนกล่อง check box ได้จากจุดเดียว โดยมีให้เลือก 4 ตัวเลือก ดังนี้

        1) All : กำหนดให้ทุก node แสดง check box

        ตัวอย่าง ผลลัพธ์จากการกำหนด ShowCheckBox =”All”


        จากภาพจะเห็นว่า มี check box อยู่ในแต่ละ node ทั้งหมด โดยการกำหนดที่คุณสมบัติ ShowCheckBox =”All” นี้ที่จุดเดียว โดยไม่ต้องไปกำหนดที่แต่ละ node

        2) Leaf : กำหนดให้แสดง check box เฉพาะ node ลูกที่ไม่มี node ย่อย

        ตัวอย่าง ผลลัพธ์จากการกำหนด ShowCheckBox =”Leaf”


        จากภาพจะเห็นว่า จะแสดงผล check box เฉพาะ node ที่เป็นลูก และไม่มี node ย่อยอีก ซึ่งเรียก node แบบนี้ว่า “Leaf” นั่นเอง

        3) None : ไม่แสดง Checkbox ใน node ใดๆ เลย

        ตัวอย่าง ผลลัพธ์จากการกำหนด ShowCheckBox =”None”


        จากภาพจะเห็นว่า ไม่แสดงผล check box ใน node ใดๆเลย

        4) Parent : เป็นการแสดง check box เฉพาะตำแหน่ง node ที่มีลูก หรือ child node ที่ไม่ใช่ root node นั่นเอง

        ตัวอย่าง ผลลัพธ์จากการกำหนด ShowCheckBox =”Parent”


        จากภาพจะเห็นว่า จะแสดง check box เฉพาะ node ที่มีลูกในระดับย่อย และต้องไม่ใช่ root node นั่นเอง

        5) Root : จะแสดง check box เฉพาะตำแหน่ง root หรือ node ระดับแรกสุดเท่านั้น

        ตัวอย่าง ผลลัพธ์จากการกำหนด ShowCheckBox =”Root”

        จากภาพจะเห็นว่า จะแสดงผล check box ใน node ที่อยู่ในระดับ root หรือระดับแรกสุดนั่นเอง

      • ShowExpandCollapse : เป็นการระบุว่าต้องการให้แสดงตัวย่อ-ขยาย ที่เป็นเครื่องหมาย + และ – เพื่อย่อขยาย TreeView หรือไม่ หากกำหนดเป็น false จะไม่สามารถย่อ-ขยาย TreeView ดังกล่าวได้ โดยค่าตั้งต้นจะเป็น true ซึ่งหมายถึงสามารถย่อ-ขยายได้นั่นเอง

        ตัวอย่าง การกำหนดคุณสมบัติ ShowExpandCollapse =”false”

        จากภาพจะเห็นว่า ไม่สามารถย่อ-ขยายข้อมูลของ TreeView แต่ละ node ได้เลย

      • BorderStyle : เป็นการกำหนดรูปแบบของเส้นขอบของ TreeView ที่เราต้องการได้ เช่น solid dashed dotted เป็นต้น

        ตัวอย่าง การกำหนดคุณสมบัติของเส้นขอบ BorderStyle เป็น Double


      • CollapseImageUrl : เป็นการระบุ url ภาพที่ต้องการนำมาใช้แสดงแทนเครื่องหมาย – เพื่อให้กดแล้วย่อ node ย่อย
      • ExpandImageUrl : เป็นการระบุ url ภาพที่ต้องการนำมาใช้แสดงแทนเครื่องหมาย + เพื่อให้กดแล้วขยาย node ย่อย
      • NoExpandImageUrl : เป็นการระบุ url ภาพที่ต้องการนำมาใช้แสดงในกรณีที่ node ดังกล่าวไม่มี node ย่อย

        ตัวอย่าง ผลลัพธ์ของการระบุค่าของ CollapseImageUrl ให้เป็นรูปเครื่องหมายลบ กำหนดค่า ExpandImageUrl ให้เป็น url ของรูปที่เป็นเครื่องหมายบวก และกำหนด NoExpandImageUrl เป็นรูปไอคอนสีฟ้า ดังรูป

        หมายเหตุ : รูปภาพที่กำหนดในการย่อ-ขยายจะแสดงผลเมื่อ ShowLines เป็น false เท่านั้น

      • ImageSet : เป็นการกำหนดภาพที่ต้องการให้แสดงหน้า node ลักษณะคล้ายกับ bullet ดังตัวอย่างในภาพ



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



        เมื่อเราคลิกเลือก “Auto Format…” จะปรากฎภาพหน้าจอดังนี้



        ซึ่งจะเห็นได้ว่า มีลักษณะการจัดการเดียวกับตอนเรากำหนดคุณสมบัติจาก ImageSet นั่นเอง

      • การกำหนด style ให้กับ node แต่ละแบบ ไม่ว่าจะเป็น LeafNodeStyle NodeStyle ParentNodeStyle หรือ RootNodeStyle ท่านก็สามารถกำหนดรูปแบบและตกแต่งความสวยงามให้กับ node แต่ละแบบได้ เช่น การใส่พื้นหลัง สีตัวอักษร หรือ แม้กระทั่งการระบุ style sheet ที่จัดทำไว้ก็สามารถทำได้เช่นกัน

      • LevelStyles : เป็นการกำหนด Style ให้กับแต่ละ level โดยจะเรียงตามลำดับ style ของแต่ละ node เริ่มจาก root ตามลำดับ ดังภาพ



        ตัวอย่าง ผลลัพธ์ของการกำหนด LevelStyles 3 ระดับข้างต้น โดยกำหนดให้ Level แรก มีสีตัวอักษรเป็นสีม่วง และ Level ที่ 2 มีสีตัวอักษรเป็นสีชมพู และ Level ที่ 3 มีตัวอักษรสีเขียว ดังภาพ



      • ExpandDepth : เป็นการกำหนดว่าจะให้การแสดงผล TreeView ขยายเริ่มต้นที่กี่ระดับ โดยค่าตั้งต้นจะเป็นแบบ FullyExpand ซึ่งหมายถึงขยายทั้งหมดตอนเริ่มต้น

        ตัวอย่าง ผลลัพธ์การระบุ ExpandDepth = “1”

      • DataSourceID : เป็นการระบุแหล่งข้อมูลให้กับการแสดงผล TreeView ให้มาจากการดึงข้อมูล ไม่ใช่การระบุค่า node ตายตัวจากหน้าเพจ
      • NodeIndent : ระยะการเยื้องหน้าของ node แต่ละระดับ โดยมีค่าตั้งต้นเท่ากับ 20 ซึ่งท่านสามารถลดหรือเพิ่มได้ตามความเหมาะสม
      • ShowLines : เป็นการระบุว่าต้องการให้แสดงเส้นโยงเชื่อมของ node แต่ละระดับหรือไม่
      • SelectedNodeStyle : เป็นการกำหนด style ให้กับ node ที่โดนเลือกคลิก เช่น การระบุพื้นหลัง หรือการกำหนดสีตัวอักษร เป็นต้น
        ตัวอย่าง ผลลัพธ์การระบุ SelectedNodeStyle ให้พื้นหลังเป็นสีม่วงและตัวอักษรสีขาว โดยเมื่อคลิกที่ node ใดก็ตามก็จะแสดงผลดังที่ได้ระบุไว้ที่ node นั้น

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

  • การปรับเปลี่ยนรูปแบบการแสดงผลเส้นทางระหว่างพิกัดจุดบนแผนที่ Google Map APIs ด้วย DirectionsTravelMode

              ก่อนที่เราจะไปเริ่มเนื้อหาของบทความี้ ผู้เขียนต้องขอท้าวความเดิมตอนที่แล้วของบทความก่อน ซึ่งผู้เขียนได้พูดถึงวิธีการแสดงผลเส้นทางทางระหว่างพิกัดจุดบนแผนที่ Google Map APIs ด้วย DirectionsService ในเบื้องต้นไว้ (สามารถศึกษาเพิ่มเติมได้จากบทความ : การแสดงเส้นทางระหว่างพิกัดจุดบนแผนที่ Google Map APIs ด้วย DirectionsService ในเบื้องต้น )  สำหรับในบทความนี้ผู้เขียนจึงอยากต่อยอดการทำงาน และเพิ่มลูกเล่นให้กับการแสดงผลแผนที่ด้วยการปรับเปลี่ยนการกำหนดรูปแบบการแสดงผลของเส้นทางให้ผู้เยี่ยมชมเว็บไซต์  ซึ่งเราจะให้ผู้ใช้สามารถเลือกรูปแบบในการแสดงผลได้ว่า ต้องการดูเส้นทางในรูปแบบใดตามรูปแบบ Mode ที่ผู้ใช้เลือกมา เช่น เส้นทางเดิน ทางถนน หรือขนส่งสาธารณะ เป็นต้น โดยวิธีการดังกล่าวนี้เหมาะกับเว็บไซต์ที่มีความเกี่ยวข้องกับความต้องการที่ต้องการทราบเส้นทางในแต่ละรูปแบบ เพื่อเป็นประโยชน์ในการนำไปประยุกต์ใช้กับงานของแต่ละท่านได้ค่ะ

       ตัวอย่างผลลัพธ์ที่ได้จากบทความที่แล้ว 

    ซึ่งจากตัวอย่างโค้ดในบทความที่แล้ว การกำหนด DirectionsTravelMode เป็น DRIVIING ซึ่งในความเป็นจริงแล้วยังมีรูปแบบอื่นๆให้เลือกใช้ด้วยกันทั้งหมด 4 รูปแบบ ดังนี้

    DRIVING (Default):เป็นโหมดตั้งต้นให้หากไม่ได้มีการกำหนดไว้ ซึ่งเป็นโหมดที่แสดงเส้นทางการขับขี่ด้วยยานพาหนะ
    BICYCLING: เป็นโหมดสำหรับเส้นทางที่เตรียมไว้สำหรับผู้ขับขี่จักรยาน
    TRANSIT: เป็นโหมดสำหรับแสดงเส้นทางระบบขนส่งสาธารณะ เช่น  รถบัส รถไฟ หรือแม้แต่เครื่องบิน เป็นต้น
    WALKING: เป็นโหมดสำหรับแสดงเส้นทางการเดินถนน

              ซึ่งผู้เขียนจะขอยกตัวอย่างในส่วนของการเลือกรูปแบบเส้นทางและผลลัพธ์ของแต่ละรูปแบบ ดังตัวอย่างโค้ดต่อไปนี้

    • การกำหนดรูปแบบของเส้นทางในการแสดงผลบนแผนที่
    1. โค้ด Javascript
       <script type="text/javascript">
      function initialize() {
       // ประกาศเรียกใช้งาน DirectionsService เพื่อการแสดงผลให้มีลักษณะเป็น Global
       window.directionsService = new google.maps.DirectionsService();
       window.directionsDisplay = new google.maps.DirectionsRenderer();
       var map;
       var bounds = new google.maps.LatLngBounds();
       var mapOptions = {
       mapTypeId: 'roadmap'
       };
      
      // แสดงผลแผนที่ในหน้าจอตาม Element ที่กำหนด
       map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
       map.setTilt(45);
      
      // กำหนดพิกัดจุดให้กับจุดเริ่มต้นและเส้นสุด
       window.markers = [
       ['Place1', 51.501546, -0.142000],
       ['Place2', 51.512051,-0.091225]
       ];
      // ให้แสดงผลสิ่งที่ตั้งค่าไว้ในพื้นที่แผนที่ที่กำหนด
       directionsDisplay.setMap(map);
      // กำหนดค่าเริ่มต้นของรูปแบบให้เป็นเส้นทางการเดิน (walking mode)
       calcRoute();
      }
      // เป็นฟังก์ชั่นในการคำนวณเส้นทางระหว่างสองจุดพิกัดบนแผนที่และเลือกกำหนดค่า Mode ในการแสดงผล
       function calcRoute() {
        var selectedMode = document.getElementById('travelType').value;
        var request = {
       
        origin: new google.maps.LatLng(markers[0][1], markers[0][2]),
       
        destination: new google.maps.LatLng(markers[1][1], markers[1][2]),
      
      //// //กำหนดรูปแบบการแสดงผลเส้นทางโดยเริ่มต้นให้เป็นเส้นทางการเดิน(ตามรายการแรกที่เลือก)
        travelMode: google.maps.TravelMode[selectedMode]
        };
        
       directionsService.route(request, function(response, status) {
       if (status == google.maps.DirectionsStatus.OK) {
       directionsDisplay.setDirections(response);
       }
       });
       }
       </script>
    2. ส่วนของการแสดงผลใน body
      <body> 
      <div id="map_wrapper">
       <div id="map_canvas" class="mapping"></div>
      </div>
      <div id="travel_selector">
       <p><strong>Mode of Travel: </strong>
       <select id="travelType" onchange="calcRoute();">
       <option value="WALKING">Walking</option>
       <option value="BICYCLING">Bicycling</option>
       <option value="DRIVING">Driving</option>
       <option value="TRANSIT">Transit</option>
       </select></p>
      </div>
      </body>
    3. ผลลัพธ์ในแต่ละรูปแบบ
    • แบบที่ 1 DRIVING Mode :

     

    • แบบที่ 2 BICYCLING Mode:

    • แบบที่ 3 TRANSIT Mode:


    • แบบที่ 4 WALKING Mode:


                จากตัวอย่างจะเห็นได้ว่าเป็นพิกัดของประเทศในต่างแดน เนื่องจากก่อนหน้านี้ ผู้เขียนลองใช้พิกัดจุดบนพื้นที่ประเทศไทย แต่พบว่ามีบาง Mode ยังไม่รองรับ เช่น เส้นทางจักรยาน ซึ่งเมื่อเลือกกำหนดโหมดดังกล่าวจะไม่เห็นการเปลี่ยนแปลงใดๆ จึงหันไปยกตัวอย่างจาก London แทนเพื่อให้ผู้อ่านได้เห็นความแตกต่างในการแสดงผลแต่ละแบบได้ชัดเจนขึ้น แต่เพื่อไม่ให้เป็นการน้อยเนื้อต่ำใจกัน ผู้เขียนก็ได้ลองเอาพิกัดของพื้นที่ในประเทศไทยมาลองทำดูให้ผู้อ่านได้เห็นแถมมาด้วยว่าพื้นที่ของเราจะเห็นเส้นทางเป็นยังไงในแต่ละแบบกันบ้าง ดังนี้ค่ะ
      ตัวอย่างการแสดงเส้นทางระหว่างสนามบินสุวรรณภูมิและเขตจตุจักร
      1) แบบ Transit mode

       

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

      2) แบบ Walking Mode


      3) แบบ Driving Mode

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


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

     

  • การแสดงเส้นทางระหว่างพิกัดจุดบนแผนที่ Google Map APIs ด้วย DirectionsService ในเบื้องต้น

              หลังจากที่ก่อนหน้านี้ ผู้เขียนเคยได้มีโอกาสศึกษาบทความเกี่ยวกับความสามารถของ Google Map APIs มาบ้างแล้วนั้น ผู้เขียนเห็นว่าเรื่องเกี่ยวกับแผนที่นับเป็นอีกหัวข้อที่น่าสนใจในการพัฒนาเว็บไซต์ในปัจจุบัน โดยเฉพาะเว็บไซต์ที่มีส่วนเกี่ยวข้องกับสถานที่ และที่ตั้ง เพราะถือเป็นการเพิ่มลูกเล่นให้มีความตื่นตาตื่นใจยิ่งขึ้น โดยความสามารถของแผนที่ที่ใน Google Map APIs มีมาให้ใช้กันได้ในหลายรูปแบบ ขึ้นอยู่กับความต้องการของผู้พัฒนาแต่ละคน อาทิ การกำหนดพิกัดจุด การแสดงเส้นทาง การค้นหาละติจูด-ลองจิจูดจากการคลิกบนแผนที่ หรือจนกระทั่งการดึงพิกัดมาจากฐานข้อมูลเพื่อมาแสดงผล เป็นต้น โดยในความเป็นจริงแล้ว ลูกเล่นเกี่ยวกับการทำงานกับแผนที่ยังมีอีกมากมาย แต่ในบทความนี้ ผู้เขียนจะขอพูดถึงในส่วนของการแสดงเส้นทางระหว่างจุดสองจุดในแบบเบื้องต้นเพื่อทำความเข้าใจก่อนนำไปประยุกต์ใช้ ดังนี้

    การแสดงเส้นทางโดยเบื้องต้นเมื่อทราบจุดพิกัดละติจูด-ลองจิจูดบนแผนที่

    1. อ้างอิงไฟล์ที่ใช้ในการทำงานร่วมกับ Google Map APIs
      <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&language=en" async defer></script>
      
    2. ส่วนของ Javascript ที่ใช้ในการแสดงเส้นทางระหว่างพิกัด 2 จุดบนแผนที่
      <script type="text/javascript">
      /////////การกำหนดค่าละติจูด-ลองจิจูดระหว่าง 2 ตำแหน่ง ในที่นี้คือ มหาวิทยาลัยสงขลานครินทร์ และสวนสาธารณะหาดใหญ่
      var markers = [
       {
       "title": 'มหาวิทยาลัยสงขลานครินทร์',
       "lat": '7.006941', 
       "lng": '100.498412',
       "description": '<b>มหาวิทยาลัยสงขลานครินทร์:</b> (อังกฤษ: Prince of Songkla University; อักษรย่อ: ม.อ.) เป็นมหาวิทยาลัยแห่งแรกในภาคใต้ของประเทศไทย ตาม พระราชบัญญัติมหาวิทยาลัยสงขลานครินทร์ พ.ศ. ๒๕๑๑ ก่อตั้งในปี พ.ศ. 2510 ต่อมา พระบาทสมเด็จพระปรมินทรมหาภูมิพลอดุลยเดชได้พระราชทานชื่อเมื่อวันที่ 22 กันยายน พ.ศ. 2510 จึงถือว่าวันที่ 22 กันยายนของทุกปี เป็นวันสงขลานครินทร์'
       }
       ,
       {
       "title": 'สวนสาธารณะหาดใหญ่',
       "lat": '7.042899', 
       "lng": '100.507664',
       "description": '<b>สวนสาธารณะเทศบาลนครหาดใหญ่:</b> เป็นแหล่งพักผ่อนหย่อนใจของคนเมืองแห่งนี้ มีทัศนียภาพที่สวยงาม เต็มไปด้วยด้วยแมกไม้เขียวขจี มีอ่างเก็บน้ำขนาดใหญ่ สัตว์น้ำนานาชนิดมากมาย สมบูรณ์ด้วยธรรมชาติอันบริสุทธิ์ ที่รอคอยต้อนรับนักท่องเที่ยวให้ได้สัมผัสกับความร่มรื่นของพรรณไม้นานาชนิด'
       }
       
       ];
      ///////เป็นการกำหนดค่าต่างๆตอนที่ฟอร์มมีการโหลด
      
      window.onload = function () {
      
      //////กำหนดค่ากึ่งกลางการแสดงผลแผนที่ ระยะใกล้ และชนิดการแสดงผลของแผนที่
       var mapOptions = {
       center: new google.maps.LatLng(markers[0].lat, markers[0].lng),
       zoom: 10,
       mapTypeId: google.maps.MapTypeId.ROADMAP
       };
      
      //////ประกาศ Element ที่จะใช้ในการแสดงผล
       var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
       var infoWindow = new google.maps.InfoWindow();
      
       var lat_lng = new Array(); ///สร้างตัวแปรอาร์เรย์เพื่อเก็บค่าละติจูด-ลองจิจูด
       var latlngbounds = new google.maps.LatLngBounds();
      
      /// วนพื่อเก็บค่าละติจูด-ลองจิจูดในตัวแปรอาร์เรย์ และเก็บค่าในการพิกัดจุดของแต่ละจุดที่มี
       for (i = 0; i < markers.length; i++) {
       var data = markers[i]
       var myLatlng = new google.maps.LatLng(data.lat, data.lng);
       lat_lng.push(myLatlng);
      
      
       var marker = new google.maps.Marker({
       position: myLatlng,
       map: map,
       title: data.title
       });
      
       latlngbounds.extend(marker.position);
      
      //////กำหนดเมื่อมีอีเวนต์คลิก ให้แสดงผลรายละเอียดที่กำหนดไว้ในตัวแปรของ marker ที่กำหนดไว้ในแต่ละตำแหน่ง
       (function (marker, data) {
       google.maps.event.addListener(marker, "click", function (e) {
       infoWindow.setContent(data.description);
       infoWindow.open(map, marker);
       });
       })(marker, data);
       }
       map.setCenter(latlngbounds.getCenter());
       map.fitBounds(latlngbounds);
       
       //***********ส่วนของการแสดงเส้นทาง****************//
       
       //ประกาศ Path Array
       var path = new google.maps.MVCArray();
       
       //ประกาศการใช้งาน Direction Service
       var service = new google.maps.DirectionsService();
       
       //กำหนดสีของเส้นทางที่ต้องการ
       var poly = new google.maps.Polyline({ map: map, strokeColor: '#4986E7' });
       
       //วนลูปเพื่อวาดเส้นทางระหว่างจุดที่มีในแผนที่
       for (var i = 0; i < lat_lng.length; i++) {
       if ((i + 1) < lat_lng.length) {
       var src = lat_lng[i];
       var des = lat_lng[i + 1];
       path.push(src);
       poly.setPath(path);
       service.route({
       origin: src,
       destination: des,
       travelMode: google.maps.DirectionsTravelMode.DRIVING
      /////กำหนด Mode ในการแสดงผลเส้นทาง(จะกล่าวเพิ่มเติมในภายหลัง)
       }, function (result, status) {
       if (status == google.maps.DirectionsStatus.OK) {
       for (var i = 0, len = result.routes[0].overview_path.length; i < len; i++) 
      {
       path.push(result.routes[0].overview_path[i]);
       }
       }
       });
       }
       }
      
      </script>
    3. ส่วนของ body ในการแสดงผล
      <body>
       <form id="form1" runat="server">
      <div id="dvMap" style="width: 500px; height: 500px">
      </div>
       </form>
      </body>
    4. ตัวอย่างผลลัพธ์ที่ได้ หมายเหตุ: หากต้องการเปลี่ยนสีเส้นทางในการแสดงผล สามารถทำได้โดยการกำหนดสี จากโค้ดดังตัวอย่าง คือ
      var poly = new google.maps.Polyline({ map: map, strokeColor: ‘#4986E7‘ }); ซึ่งสามารถเปลี่ยน strokeColor ในส่วนของตัวอักษรสีน้ำเงินให้เป็นสีที่ท่านต้องการได้

              เอาล่ะค่ะ เราก็ได้เห็นวิธีการและตัวอย่างการแสดงเส้นทางระหว่างจุดพิกัดบนแผนที่กันไปพอหอมปากหอมคอกันแล้วนะคะ หวังว่าผู้อ่านแต่ละท่านจะสามารถนำเกร็ดเล็กเกร็ดน้อยเหล่านี้ไปประยุกต์ใช้งานต่อได้ไม่มากก็น้อย หากมีข้อผิดพลาดประการใดผู้เขียนขออภัยไว้ ณ ที่นี้ด้วยนะคะ ไว้เจอกันใหม่บทความหน้าเกี่ยวกับเรื่อง “การปรับเปลี่ยนรูปแบบการแสดงผลเส้นทางระหว่างพิกัดจุดบนแผนที่ Google Map APIs ด้วย DirectionsTravelMode” นะคะ ขอบคุณค่ะ ^^

    หมายเหตุ: หากผู้ใดสนใจบทความอื่นๆเกี่ยวกับการพัฒนาโปรแกรมด้วยแผนที่ Google Map APIs ที่ผู้เขียนเคยเขียนไว้ก่อนหน้านี้ สามารถติดตามได้จากลิงค์ต่อไปนี้ค่ะ
    1) ทำอย่างไรให้สามารถกำหนดจุดพิกัดบนแผนที่ Google map แบบจุดเดียวและหลายจุดจากฐานข้อมูลได้ด้วย ASP.NET C#
    2) ทำอย่างไรให้สามารถกำหนดจุดพิกัดบนแผนที่ Google map แบบจุดเดียวและหลายจุดจากฐานข้อมูลได้ด้วย ASP.NET C# (ภาคต่อ)
    3) การดึงค่าละติจูดและลองจิจูดของสถานที่ด้วย Places search box บน Google Maps
    4) การค้นหาสถานที่ด้วย Places search box และแสดงผลภาพจากข้อมูล Street View บน Google Maps


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

 

  • การค้นหาสถานที่ด้วย Places search box และแสดงผลภาพจากข้อมูล Street View บน Google Maps

           ความเดิมจากตอนที่แล้วของบทความ “การดึงค่าละติจูดและลองจิจูดของสถานที่ด้วย Places search box บน Google Maps” เพื่อแก้ปัญหาในการดึงค่าละติจูด-ลองจิจูดจากชื่อสถานที่ที่ผู้ใช้พิมพ์ค้นหาไว้ โดยมีการนำ “Places search box” มาใส่ไว้ในแผนที่ที่เราต้องการแทนนั้น ถือเป็นตัวช่วยให้กับผู้ใช้ในระดับหนึ่ง  แต่สำหรับในบทความนี้ ผู้เขียนจะขอต่อยอดความรู้ดังกล่าว โดยได้ศึกษาและนำวิธีการแสดงผลภาพจากข้อมูล Street view มาประยุกต์ใช้ร่วมด้วย เพื่อให้ผู้ใช้สามารถมองเห็นสถานที่จริงที่เลือกได้จากแผนที่ได้ด้วย ซึ่งจะยิ่งช่วยอำนวยความสะดวกให้กับผู้ใช้ ทำให้ผู้ใช้สามารถตรวจสอบได้ว่าสถานที่ดังกล่าวเป็นสถานที่ที่ต้องการจะดึงค่าละติจูด-ลองจิจูดจริงหรือไม่ได้อีกทางหนึ่งนั่นเอง ซึ่งวิธีการบางส่วนจะขอยกยอดมาจากบทความก่อนหน้านี้ เกี่ยวกับเรื่องการดึงค่าละติจูด-ลองจิจูดจากการค้นหาโดยใช้ Places search box และจะขอเพิ่มเติมความสามารถในการแสดงผลภาพ Street View  รวมทั้งสามารถคลิกจุดพิกัดใหม่บนแผนที่ เพื่อค้นหาตำแหน่ง สถานที่ตั้ง และรายละเอียด รวมถึงภาพจาก Street View ได้อีกด้วย เพื่อให้การดึงค่าพิกัดใกล้เคียงความจริงมากที่สุด โดยมีวิธีการดังนี้

    • ส่วนของสไตล์ชีทในการแสดงผล ขึ้นกับการตกแต่งของผู้พัฒนาแต่ละท่าน
    <!—ส่วนของ Style Sheets->
    <style>
    html, body {
     height: 100%;
     margin: 0;
     padding: 0;
    }
    #map {
     width: 100%;
     height: 400px;
    }
    .controls {
     margin-top: 10px;
     border: 1px solid transparent;
     border-radius: 2px 0 0 2px;
     box-sizing: border-box;
     -moz-box-sizing: border-box;
     height: 32px;
     outline: none;
     box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
    }
    #searchInput {
     background-color: #fff;
     font-family: Roboto;
     font-size: 15px;
     font-weight: 300;
     margin-left: 12px;
     padding: 0 11px 0 13px;
     text-overflow: ellipsis;
     width: 80%;
    }
    #searchInput:focus {
     border-color: #4d90fe;
    }
    
    </style>
    
    • ส่วนของการอ้างอิง libraries เพื่อใช้งาน Google API
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCRbMoDPc_mTv3D3QPqe0Ar84nSvRhA8nk&libraries=places&callback=initMap" async defer></script>
    
    • ส่วนของการประกาศค่าเริ่มต้นและตัวแปร รวมถึงการเรียกใช้งานฟังก์ชั่นเพื่อใช้ในการแสดงผล
      • ฟังก์ชั่น initMap() ซึ่งจะเรียกใช้งานตอนเริ่มต้นโหลดหน้าจอขึ้นมา
    <script>
     var panorama;
     var map;
     var markers = [];
    /***** function ในการประกาศค่าเริ่มต้นให้กับแผนที่*****/
     function initMap() {
    
    /***** ประกาศตำแหน่งพิกัดกึ่งกลางให้กับการแสดงผลแผนที่ ในที่นี้กำหนดเป็นกรุงเทพมหานคร*****/
     var center_point = { lat: 13.7563309, lng: 100.50176510000006 };
     var sv = new google.maps.StreetViewService();
    
    /***** เป็นส่วนของการแสดงผลภาพจาก Street View *****/
     panorama = new google.maps.StreetViewPanorama(document.getElementById('pano'));
    
     /***** กำหนดรายละเอียดคุณสมบัติของแผนที่*****/
     var map = new google.maps.Map(document.getElementById('map'), {
     center: {lat: center_point.lat, lng: center_point.lng},
     zoom: 13,
     streetViewControl: false // เป็นส่วนที่ Set เพิ่มเติมจากปกติ
     });
    
    sv.getPanorama({ location: center_point, radius: 50 }, processSVData);
    
    /***** กำหนด event เมื่อมีการคลิกแผนที่*****/
     map.addListener('click', function(event) {
     
    /***** ล้างข้อมูลการกำหนดจุด เพื่อให้มีการกำหนดจุดพิกัดเพียงจุดเดียว*****/
     for (i = 0; i < markers.length; i++) {
     markers[i].setMap(null);
     }
    
    /***** ดึงค่าข้อมูลจาก Street View ตามพิกัดที่คลิกบนแผนที่ *****/
     sv.getPanorama({ location: event.latLng, radius: 50 }, processSVData);
     var geocoder = new google.maps.Geocoder();
     geocoder.geocode({
     'latLng': event.latLng
     }, function(results, status) {
     if (status == google.maps.GeocoderStatus.OK) {
    
    /***** แสดงผลข้อมูลรายละเอียดสถานที่*****/
     if (results[0]) {
        var place = results[0];
         for (var i = 0; i < place.address_components.length; i++) {
           if (place.address_components[i].types[0] == 'postal_code') {
             document.getElementById('postal_code').innerHTML = place.address_components[i].long_name;
          }
     if (place.address_components[i].types[0] == 'country') {
     document.getElementById('country').innerHTML = place.address_components[i].long_name;
        }
      }
     document.getElementById('location').innerHTML = results[0].formatted_address;
     document.getElementById('lat').innerHTML = results[0].geometry.location.lat();
     document.getElementById('lon').innerHTML = results[0].geometry.location.lng();
        }
       }
     });
     });
     /***** กำหนดตำแหน่งที่ตั้งของ control ที่จะวางในแผนที่*****/
     var input = document.getElementById('searchInput');
     map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
    
     /***** เพิ่ม Feature ให้กับ textbox ให้สามารถพิมพ์ค้นหาสถานที่ได้*****/
     var autocomplete = new google.maps.places.Autocomplete(input);
     autocomplete.bindTo('bounds', map);
    
     var infowindow = new google.maps.InfoWindow();
     
     /***** กำหนดคุณสมบัติให้กับตัวพิกัดจุดหรือ marker *****/
     var marker = new google.maps.Marker({
     map: map,
     anchorPoint: new google.maps.Point(0, -29)
     });
    markers.push(marker); //เก็บค่าการกำหนดจุดพิกัดไว้ในตัวแปร markers เพื่อใช้ล้างข้อมูลการกำหนดจุดได้
    
     /***** ทำงานกับ event place_changed หรือเมื่อมีการเปลี่ยนแปลงค่าสถานที่ที่ค้นหา*****/
     autocomplete.addListener('place_changed', function() {
     infowindow.close();
     marker.setVisible(false);
     var place = autocomplete.getPlace();
     if (!place.geometry) {
     window.alert("ไม่ค้นพบพิกัดจากสถานที่ดังกล่าว");
     return;
     }
    
     /***** แสดงผลบนแผนที่เมื่อพบข้อมูลที่ต้องการค้นหา *****/
     if (place.geometry.viewport) {
     map.fitBounds(place.geometry.viewport);
     } else {
     map.setCenter(place.geometry.location);
     sv.getPanorama({ location: place.geometry.location, radius: 50 }, processSVData);
     map.setZoom(17);
     }
     marker.setIcon(({
     url: place.icon,
     size: new google.maps.Size(71, 71),
     origin: new google.maps.Point(0, 0),
     anchor: new google.maps.Point(17, 34),
     scaledSize: new google.maps.Size(35, 35)
     }));
     marker.setPosition(place.geometry.location);
     marker.setVisible(true);
     
     /***** แสดงรายละเอียดผลลัพธ์การค้นหา *****/
     var address = '';
     if (place.address_components) {
     address = [
     (place.address_components[0] && place.address_components[0].short_name || ''),
     (place.address_components[1] && place.address_components[1].short_name || ''),
     (place.address_components[2] && place.address_components[2].short_name || '')
     ].join(' ');
     }
     /***** แสดงรายละเอียดผลลัพธ์การค้นหาเป็น popup โดยมีชื่อและสถานที่ดังกล่าว *****/
     infowindow.setContent('<div><strong>' + place.name + '</strong><br>' + address);
     infowindow.open(map, marker);
    
     /***** แสดงรายละเอียดผลลัพธ์การค้นหา ซึ่งประกอบด้วย ที่อยู่ รหัสไปรษณีย์ ประเทศ ละติจูดและลองจิจูด *****/
     for (var i = 0; i < place.address_components.length; i++) {
     if(place.address_components[i].types[0] == 'postal_code'){
     document.getElementById('postal_code').innerHTML = place.address_components[i].long_name;
     }
     if(place.address_components[i].types[0] == 'country'){
     document.getElementById('country').innerHTML = place.address_components[i].long_name;
     }
     }
     document.getElementById('location').innerHTML = place.formatted_address;
     document.getElementById('lat').innerHTML = place.geometry.location.lat();
     document.getElementById('lon').innerHTML = place.geometry.location.lng();
     });
    }
    </script>
    
    • ฟังก์ชั่น processSVData() เป็นฟังก์ชั่นที่ใช้ในการนำค่าของ Street view มาแสดงผล
    <script>
    function processSVData(data, status) {
     if (status === 'OK') {
     
      marker = new google.maps.Marker({
     position: data.location.latLng,
     map: map,
     title: data.location.description
     });
     markers.push(marker);
     panorama.setPano(data.location.pano);
     panorama.setPov({
     heading: 270,
     pitch: 0
     });
     panorama.setVisible(true);
    
     marker.addListener('click', function() {
     
     var markerPanoID = data.location.pano;
     // Set the Pano to use the passed panoID.
     panorama.setPano(markerPanoID);
     panorama.setPov({
     heading: 270,
     pitch: 0
     });
     panorama.setVisible(true);
     });
     } else {
     console.error('ไม่พบข้อมูล Street view ตามสถานที่ดังกล่าว');
     }
     }
    </script>
    
    • ส่วนของการแสดงผล (ใน tag body)
    <body>
    
    <!--ส่วนของการแสดงรายละเอียดผลลัพธ์ที่ได้->
     <table style="width: 100%">
     <tr>
     <td>
    <!--ส่วนของ Element ที่ใช้ในการแสดงผล->
     <input id="searchInput" class="controls" type="text" placeholder="Enter a location">
    <div id="map" style="width: 60%;height:360px;float:left"></div>
    <div id="pano" style="width: 40%;height:360px; float:left"> </div><br />
     </td>
     
     </tr>
     <tr>
     <td>
     <div id="Div1" style="width: 100%;" >
     <ul id="geoData">
     <li>ที่อยู่: <span id="location"></span></li>
     <li>รหัสไปรษณีย์ : <span id="postal_code"></span></li>
     <li>ประเทศ: <span id="country"></span></li>
     <li>ละติจูด: <span id="lat"></span></li>
     <li>ลองจิจูด: <span id="lon"></span></li>
    </ul>
    </div>
    
     </td>
     </tr>
     
     </table>
     <!--ส่วนของการประกาศค่าเริ่มต้นและตัวแปร รวมถึงการเรียกใช้งานฟังก์ชั่นเพื่อใช้ในการแสดงผลที่กล่าวไว้ก่อนหน้านี้-> <script> /***** function ในการประกาศค่าเริ่มต้นให้กับแผนที่ และอื่นๆตามที่กล่าวไว้แล้วข้างต้น*****/ </script> <!--สิ้นสุดส่วนของการประกาศค่าเริ่มต้นและตัวแปร รวมถึงการเรียกใช้งานฟังก์ชั่นเพื่อใช้ในการแสดงผล-> </body>

    ผลลัพธ์ที่ได้

    1. เมื่อเปิดหน้าจอขึ้นมา จะแสดงพิกัดที่จังหวัดกรุงเทพมหานครเป็นค่าตั้งต้น และฝั่งขวาเป็นภาพที่ดึงข้อมูลมาได้จากพิกัดดังกล่าวจากข้อมูล Street Views

    2. พิมพ์ค้นหาชื่อสถานที่ และเลือกสถานที่ที่ต้องการ

    3. เมื่อเลือกสถานที่เรียบร้อย จะมีการกำหนดพิกัดจุดตามชื่อสถานที่ที่เลือกไว้ และแสดงผลภาพจาก Street View รวมทั้งแสดงรายละเอียดในส่วนล่างของหน้าจอ ดังภาพ

           นอกจากการค้นหาพิกัดและข้อมูลภาพจาก Street View โดยใช้วิธีการพิมพ์ค้นหาแล้วนั้น ผู้ใช้ยังสามารถค้นหาพิกัดใหม่ได้จากการคลิกบนแผนที่ได้อีกด้วย ซึ่งจะแสดงผลลัพธ์ได้เช่นกัน ดังภาพ

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

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

    https://www.tutorialspoint.com/google_maps/google_maps_symbols.htm

    http://www.codexworld.com/autocomplete-places-search-box-google-maps-javascript-api/

          https://developers.google.com/maps/documentation/javascript/examples/streetview-service

  • การดึงค่าละติจูดและลองจิจูดของสถานที่ด้วย Places search box บน Google Maps

           ก่อนจะพูดถึงเนื้อหาของบทความนี้ ผู้เขียนขอท้าวความไปถึงบทความก่อนหน้าที่เป็นเนื้อหาที่เกี่ยวข้องกับการดึงค่าละติจูด-ลองจิจูดของสถานที่ เพื่อมากำหนดจุดพิกัดบนแผนที่ หรือที่เรารู้จักกันดีในนามของ Google maps (ซึ่งสามารถหาอ่านได้จากลิงค์ ทำอย่างไรให้สามารถกำหนดจุดพิกัดบนแผนที่ Google map แบบจุดเดียวและหลายจุดจากฐานข้อมูลได้ด้วย ASP.NET C# (ภาคต่อ) และหากท่านต้องการศึกษาเกี่ยวกับวิธีการกำหนดพิกัดบนแผนที่เพิ่มเติมสามารถหาอ่านได้จากลิงค์ ทำอย่างไรให้สามารถกำหนดจุดพิกัดบนแผนที่ Google map แบบจุดเดียวและหลายจุดจากฐานข้อมูลได้ด้วย ASP.NET C# เช่นกัน) แต่หลังจากที่ผู้เขียนได้นำไปทดลองใช้งานการดึงค่าพิกัดที่ค้นหา พบว่าเกิดปัญหาในการค้นหาพิกัดของสถานที่ในบางกรณี คือ ไม่สามารถค้นหาพิกัดของบางสถานที่ที่ต้องการได้ และในบางครั้งผู้ใช้เลือกสถานที่ที่จะดึงค่าพิกัดมาใช้งานผิด เนื่องจากชื่อสถานที่อาจคล้ายกัน แต่ตั้งอยู่กันคนละประเทศ หรือทวีป โดยผู้ใช้อาจไม่เห็นภาพว่าสถานที่ดังกล่าวอยู่ส่วนใดของแผนที่ จึงทำให้พิกัดที่ได้มีความผิดพลาดหรือคลาดเคลื่อนได้ ผู้เขียนจึงได้ลองศึกษาเพิ่มเติม และปรับเปลี่ยนวิธีการ เพื่อให้การดึงค่าพิกัดเป็นไปได้ง่าย และอำนวยความสะดวกต่อผู้ใช้งานมากขึ้น รวมทั้งเพิ่มช่องทางในการค้นหาข้อมูลพิกัดได้มากขึ้นและมีความถูกต้องแม่นยำขึ้น

    ตัวอย่างภาพการทำงานของการดึงค่าพิกัดที่ผู้เขียนเคยเขียนไว้ก่อนหน้านี้

    แบบที่ 1 การเรียกใช้เซอร์วิสของ Google Geocoding API โดยการส่งพารามิเตอร์เป็นที่อยู่ของสถานที่ดังกล่าว

       

    แบบที่ 2 แบบใช้ place Autocomplete ซึ่งเป็น feature ของ Google Places API ที่จะช่วยในการค้นหาที่อยู่จากชื่อสถานที่ได้และประยุกต์เพิ่มเติมเพื่อดึงค่ามาแสดงเมื่อมีการเลือกรายการสถานที่นั้นๆ

         

           จากปัญหาดังกล่าวข้างต้น ผู้เขียนได้ปรับเปลี่ยนวิธีการในการดึงค่าละติจูด-ลองจิจูดจากชื่อสถานที่ที่ผู้ใช้พิมพ์ค้นหาไว้ โดยมีการนำ “Places search box” มาใส่ไว้ในแผนที่ที่เราต้องการแทน เพื่อให้ผู้ใช้สามารถมองเห็นสถานที่จริงที่เลือกได้จากแผนที่ ซึ่งสามารถตรวจสอบได้ว่าสถานที่ดังกล่าวเป็นสถานที่ที่ต้องการจะดึงค่าละติจูด-ลองจิจูดจริงหรือไม่อีกทางหนึ่งนั่นเอง โดยมีวิธีการดังนี้

    • ส่วนของสไตล์ชีทในการแสดงผล ขึ้นกับการตกแต่งของผู้พัฒนาแต่ละท่าน
    <!—ส่วนของ Style Sheets-->
    <style>
    html, body {
     height: 100%;
     margin: 0;
     padding: 0;
    }
    #map {
     width: 100%;
     height: 400px;
    }
    .controls {
     margin-top: 10px;
     border: 1px solid transparent;
     border-radius: 2px 0 0 2px;
     box-sizing: border-box;
     -moz-box-sizing: border-box;
     height: 32px;
     outline: none;
     box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
    }
    #searchInput {
     background-color: #fff;
     font-family: Roboto;
     font-size: 15px;
     font-weight: 300;
     margin-left: 12px;
     padding: 0 11px 0 13px;
     text-overflow: ellipsis;
     width: 50%;
    }
    #searchInput:focus {
     border-color: #4d90fe;
    }
    
    </style>
    
    • ส่วนของการอ้างอิง libraries เพื่อใช้งาน Google API
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCRbMoDPc_mTv3D3QPqe0Ar84nSvRhA8nk&libraries=places&callback=initMap" async defer></script>
    
    • ส่วนของการประกาศค่าเริ่มต้นและตัวแปร รวมถึงการเรียกใช้งานฟังก์ชั่นเพื่อใช้ในการแสดงผล
    <script>
    /***** function ในการประกาศค่าเริ่มต้นให้กับแผนที่*****/
     function initMap() {
     
     /***** กำหนดรายละเอียดคุณสมบัติของแผนที่*****/
     var map = new google.maps.Map(document.getElementById('map'), {
     center: {lat: -33.8688, lng: 151.2195},
     zoom: 13
     });
    
     /***** กำหนดตำแหน่งที่ตั้งของ control ที่จะวางในแผนที่*****/
     var input = document.getElementById('searchInput');
     map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
    
     /***** เพิ่ม Feature ให้กับ textbox ให้สามารถพิมพ์ค้นหาสถานที่ได้*****/
     var autocomplete = new google.maps.places.Autocomplete(input);
     autocomplete.bindTo('bounds', map);
    
     var infowindow = new google.maps.InfoWindow();
     
     /***** กำหนดคุณสมบัติให้กับตัวพิกัดจุดหรือ marker *****/
     var marker = new google.maps.Marker({
     map: map,
     anchorPoint: new google.maps.Point(0, -29)
     });
    
     /***** ทำงานกับ event place_changed หรือเมื่อมีการเปลี่ยนแปลงค่าสถานที่ที่ค้นหา*****/
     autocomplete.addListener('place_changed', function() {
     infowindow.close();
     marker.setVisible(false);
     var place = autocomplete.getPlace();
     if (!place.geometry) {
     window.alert("ไม่ค้นพบพิกัดจากสถานที่ดังกล่าว");
     return;
     }
    
     /***** แสดงผลบนแผนที่เมื่อพบข้อมูลที่ต้องการค้นหา *****/
     if (place.geometry.viewport) {
     map.fitBounds(place.geometry.viewport);
     } else {
     map.setCenter(place.geometry.location);
     map.setZoom(17);
     }
     marker.setIcon(({
     url: place.icon,
     size: new google.maps.Size(71, 71),
     origin: new google.maps.Point(0, 0),
     anchor: new google.maps.Point(17, 34),
     scaledSize: new google.maps.Size(35, 35)
     }));
     marker.setPosition(place.geometry.location);
     marker.setVisible(true);
     
     /***** แสดงรายละเอียดผลลัพธ์การค้นหา *****/
     var address = '';
     if (place.address_components) {
     address = [
     (place.address_components[0] && place.address_components[0].short_name || ''),
     (place.address_components[1] && place.address_components[1].short_name || ''),
     (place.address_components[2] && place.address_components[2].short_name || '')
     ].join(' ');
     }
     /***** แสดงรายละเอียดผลลัพธ์การค้นหาเป็น popup โดยมีชื่อและสถานที่ดังกล่าว *****/
     infowindow.setContent('<div><strong>' + place.name + '</strong><br>' + address);
     infowindow.open(map, marker);
    
     /***** แสดงรายละเอียดผลลัพธ์การค้นหา ซึ่งประกอบด้วย ที่อยู่ รหัสไปรษณีย์ ประเทศ ละติจูดและลองจิจูด *****/
     for (var i = 0; i < place.address_components.length; i++) {
     if(place.address_components[i].types[0] == 'postal_code'){
     document.getElementById('postal_code').innerHTML = place.address_components[i].long_name;
     }
     if(place.address_components[i].types[0] == 'country'){
     document.getElementById('country').innerHTML = place.address_components[i].long_name;
     }
     }
     document.getElementById('location').innerHTML = place.formatted_address;
     document.getElementById('lat').innerHTML = place.geometry.location.lat();
     document.getElementById('lon').innerHTML = place.geometry.location.lng();
     });
    }
    </script>
    • ส่วนของการแสดงผล (ใน tag body)
    <body>
    <!--ส่วนของ Element ที่ใช้ในการแสดงผล-->
    <input id="searchInput" class="controls" type="text" placeholder="Enter a location">
    <div id="map"></div>
    <!--ส่วนของการแสดงรายละเอียดผลลัพธ์ที่ได้-->
    <ul id="geoData">
     <li>ที่อยู่: <span id="location"></span></li>
     <li>รหัสไปรษณีย์: <span id="postal_code"></span></li>
     <li>ประเทศ: <span id="country"></span></li>
     <li>ละติจูด: <span id="lat"></span></li>
     <li>ลองจิจูด: <span id="lon"></span></li>
    </ul>
    
    <!--ส่วนของการประกาศค่าเริ่มต้นและตัวแปร รวมถึงการเรียกใช้งานฟังก์ชั่นเพื่อใช้ในการแสดงผลที่กล่าวไว้ก่อนหน้านี้-->
                                                                
    <script>
    /***** function ในการประกาศค่าเริ่มต้นให้กับแผนที่ และอื่นๆตามที่กล่าวไว้แล้วข้างต้น*****/
    </script>
    <!--สิ้นสุดส่วนของการประกาศค่าเริ่มต้นและตัวแปร รวมถึงการเรียกใช้งานฟังก์ชั่นเพื่อใช้ในการแสดงผล->
     </body>

    ผลลัพธ์ที่ได้

    1. เมื่อพิมพ์ชื่อสถานที่ เช่น มหาวิทยาลัยสงขลานครินทร์ จะมีรายการชื่อสถานที่ให้ผู้ใช้เลือก

    2. เมื่อเลือกชื่อสถานที่ที่ต้องการ เช่น มหาวิทยาลัยสงขลานครินทร์ จะมีการแสดงรายละเอียดของสถานที่ดังกล่าวดังภาพตามหมายเลข 1,2 และ 3 ตามลำดับ

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

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

    https://www.tutorialspoint.com/google_maps/google_maps_symbols.htm

    http://www.codexworld.com/autocomplete-places-search-box-google-maps-javascript-api/

  • ทำอย่างไรให้เว็บไซต์ที่เราพัฒนาสามารถอัพโหลดไฟล์แบบคราวละหลายไฟล์ได้โดยไม่จำกัดจำนวน ด้วย ASP.NET(C#)

              ในการพัฒนาเว็บไซต์ บางครั้งอาจมีความจำเป็น หรือความต้องการจากผู้ใช้ที่ต้องการให้เว็บไซต์ดังกล่าวมีฟังก์ชั่นการทำงานในส่วนของการอัพโหลดไฟล์เพื่อแนบไฟล์เข้าไปในระบบและบันทึกลงฐานข้อมูล เพื่อให้สามารถเรียกดูข้อมูลการแนบไฟล์ดังกล่าวได้ในภายหลัง การอัพโหลดไฟล์จึงถือเป็นอีกหนึ่งฟังก์ชั่นการทำงานที่นักพัฒนาเว็บไซต์ควรทราบไว้ ซึ่งลักษณะการทำงานโดยทั่วไปส่วนใหญ่เราจะใช้ Control ที่มีเรียกว่า “FileUpload” แบบอัพโหลดครั้งละ 1 ไฟล์ และหากมีมากกว่านั้นก็จะมีการสร้างตัว FileUpload มาวางไว้ในหน้าจอเพิ่มตามจำนวนที่ต้องการแบบตายตัว เช่น หากในหน้าจอดังกล่าวต้องการสามารถให้ทำการอัพโหลดไฟล์ได้ไม่เกิน 5 ไฟล์ต่อการอัพโหลดแต่ละครั้งก็จะมีการลากคอนโทรล FileUpload มาวางไว้ในหน้าจอจำนวน 5 ตัว เพื่อให้สามารถรองรับความต้องการในการอัพโหลดไฟล์ของผู้ใช้ได้ แต่ผู้เขียนพบว่าการทำงานดังกล่าวอาจไม่รองรับความต้องการในการทำงานของผู้ใช้ที่จะเพิ่มไฟล์ได้ครั้งละหลายๆไฟล์โดยไม่จำกัดและผู้พัฒนาไม่จำเป็นต้องระบุหรือสร้างคอนโทรล FileUpload มาวางในหน้าจอในจำนวนที่ตายตัวโดยไม่จำเป็น โดยผู้ใช้สามารถคลิกปุ่มเพื่อเพิ่มจำนวนในการอัพโหลดไฟล์แต่ละครั้งได้เองเพื่อความยืดหยุ่นในการใช้งาน ในบทความนี้ ผู้เขียนจึงขอเสนอแนวทางในการพัฒนาเว็บไซต์โดยใช้ ASP.NET(C#) ในแบบที่มีการอัพโหลดไฟล์ได้คราวละหลายๆไฟล์ในแบบไม่ต้องจำกัดจำนวนคอนโทรล FileUpload ในหน้าจอโดยมีการระบุจำนวนไฟล์ที่สามารถอัพโหลดได้ในแต่ละครั้งโดยผู้พัฒนาแบบตายตัวอย่างเช่นที่กล่าวไว้ข้างต้น

              หลังจากที่ได้มีการศึกษาเพิ่มเติม ผู้เขียนพบว่าใน .NET Framework เวอร์ชั่น 4.5 นั้นจะมีการเพิ่ม Feature การทำงานในส่วนนี้ให้กับคอนโทรล FileUpload ไว้แล้วผ่าน Properties ที่เรียกว่า AllowMultiple ซึ่งจะทำให้สะดวกต่อการพัฒนาและสามารถลดปัญหาดังกล่าวข้างต้นได้ แต่สำหรับเวอร์ชั่นที่ต่ำกว่ายังคงต้องมีการปรับปรุงพัฒนาเพิ่มเติมเอง ในบทความนี้จึงขอยกตัวอย่างการพัฒนาทั้ง 2 แบบในเบื้องต้นโดยจะเน้นไปในแบบเวอร์ชั่นที่ต่ำกว่า 4.5 เพื่อให้ผู้อ่านได้นำไปเป็นแนวทางในการพัฒนาต่อไป ดังนี้
    1. การอัพโหลดไฟล์คราวละหลายๆไฟล์โดยใช้ .Net Framework เวอร์ชั่นที่ต่ำกว่า 4.5

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

    1) ออกแบบหน้าจอการทำงานในฝั่ง Client  ทำการออกแบบหน้าจอการทำงานไว้ในเบื้องต้น โดยมีการสร้างคอนโทรล FileUpload มาตั้งต้นไว้ 1 ตัว และมีปุ่มเพื่อให้ทำการเพิ่มคอนโทรล FileUpload ได้เองอัตโนมัติโดยตัวผู้ใช้เอง และปุ่มที่ใช้ในการอัพโหลดไฟล์จากคอนโทรล FileUpload ทั้งหมด ดังนี้

    <body>
    <form id="form1" runat="server">
    <div>
     <div id="fileUploadarea" class="Divborder">
      <div id='divfirstUpload'><br /><asp:FileUpload ID="fuMultiple" runat="server" CssClass="fileUpload" onchange="javascipt:FileValidate(this,5);" />&nbsp;  <input style="display:inline;" id="BtnRemove" type="button" value="Remove" onclick="DelFileUpload('divfirstUpload');" /> 
    </div> 
    </div>
    <br /> 
    <div>&nbsp; 
    <input style="display:inline; background-color: #A4EDFF; color: #333333; width: 150px; font-weight: bold;" id="btnAddMoreFiles" type="button" value="Add more files" onclick="AddMoreFilesWithMax(6);" />
    &nbsp; <asp:Button ID="BtnUpload" runat="server" onclick="BtnUpload_Click" Text="Upload" OnClientClick="return ValidateFileUpload();" BackColor="#3399FF" Font-Bold="True" ForeColor="White" Width="150px" /> 
    <br> 
    <asp:GridView ID="gvResult" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" Width="410px">
    <RowStyle BackColor="#EFF3FB" /> 
    <Columns> 
    <asp:BoundField DataField="Name" HeaderText="ชื่อไฟล์"> 
    <ItemStyle Width="200px" /> </asp:BoundField> 
    <asp:BoundField DataField="FileSize" HeaderText="ขนาดไฟล์(KB)"> 
    <ItemStyle HorizontalAlign="Right" Width="120px" /> 
    </asp:BoundField> 
    </Columns> 
    <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> 
    <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> 
    <EditRowStyle BackColor="#2461BF" /> <AlternatingRowStyle BackColor="White" /> 
    </asp:GridView> 
    </div> 
    </div> 
    </form> 
    </body>

    2) ในส่วนของจาวาสคริปต์ที่ใช้เป็นตัวช่วยในการตรวจสอบ โดยจะแยกเป็นแต่ละ function ดังนี้

    2.1) ฟังก์ชั่นที่ใช้ในการเพิ่มไฟล์ได้ไม่จำกัด โดยใช้ชื่อว่า AddMoreFiles()

    <script language="javascript" type="text/javascript">
    
    function AddMoreFiles() {
    
      if (!document.getElementById && !document.createElement)
        return false;
    ////อ้างอิงถึง div ที่มีชื่อว่าfileUploadarea
      var fileUploadarea = document.getElementById("fileUploadarea"); 
      if (!fileUploadarea) return false; 
       var newLine = document.createElement("br"); fileUploadarea.appendChild(newLine); 
       var newFile = document.createElement("input"); 
       newFile.type = "file"; newFile.setAttribute("class", "fileUpload");
    
    /////กำหนดค่าเริ่มต้นให้กับตัวแปร AddMoreFiles.lastAssignedId 
    เพื่อใช้ในการกำหนด ID ให้กับคอนโทรลที่สร้างขึ้น และเป็นตัวนับ
      if (!AddMoreFiles.lastAssignedId) 
        AddMoreFiles.lastAssignedId = 1;
    
     //////กำหนดค่าแอททริบิวต์ต่างๆให้กับ FileUpload ที่ทำการสร้างใหม่ ในทีนี้คือ id และ name
        newFile.setAttribute("id", "FileUpload" + AddMoreFiles.lastAssignedId);  
        newFile.setAttribute("name", "FileUpload" + AddMoreFiles.lastAssignedId); 
    
     //////สร้าง div ขึ้นมาใหม่ และกำหนดค่าแอททริบิวต์ต่างๆให้กับ div ที่ทำการสร้างใหม่ 
    ในทีนี้คือ id และ เพิ่มคอนโทรลที่สร้าง tag ไว้ด้านบนเข้ามาไว้ใน div 
        var div = document.createElement("div"); 
        div.appendChild(newFile); 
        div.setAttribute("id", "div" + AddMoreFiles.lastAssignedId); 
    
    //////เพิ่ม div ที่สร้างใหม่ไปยัง div ที่มีชื่อว่า fileUploadarea และเพิ่มค่าของตัวแปร AddMoreFiles.lastAssignedId   
        fileUploadarea.appendChild(div); 
        AddMoreFiles.lastAssignedId++; 
    }

    2.2) ฟังก์ชั่นที่ใช้ในการเพิ่มไฟล์ได้โดยมีการระบุจำนวนสูงสุดที่ยอมให้อัพโหลดแต่ละครั้ง ในกรณีที่ต้องการระบุค่าจำนวนไฟล์ที่ต้องการให้ผู้ใช้สามารถอัพโหลดได้สูงสุดในแต่ละครั้ง สามารถปรับแก้เพิ่มเติมจากฟังก์ชั่นก่อนหน้านี้ โดยการส่งพารามิเตอร์จำนวนมากสุดที่ยอมให้สร้างตัวอัพโหลดไฟล์ได้ โดยใช้ชื่อฟังก์ชั่นว่า AddMoreFilesWithMax(x) ดังนี้

    function AddMoreFilesWithMax(x) 
    { //////กำหนดค่าให้กับ AddMoreFiles.lastAssignedId 
    เพื่อเป็นค่าตั้งต้นในการไปเซทค่า id และ name ของคอนโทรลที่สร้างใหม่ 
    โดยในที่นี้เริ่มต้นที่ 2 เนื่องจากเดิมมีคอนโทรลอัพโหลดไฟล์อยู่เดิมแล้ว 1 ตัว
      if (!AddMoreFiles.lastAssignedId)
          AddMoreFiles.lastAssignedId = 2;
    
       if (!document.getElementById && !document.createElement)
          return false; 
      var fileUploadarea = document.getElementById("fileUploadarea");
        if (!fileUploadarea) return false;
    
    /////เป็นการตรวจสอบเงื่อนไขว่ายังไม่เกินจำนวนมากสุดที่ระบุไว้ผ่านพารามิเตอร์ x
          if (AddMoreFiles.lastAssignedId <= x) 
          { 
          var newLine = document.createElement("br"); 
    
          //สร้างปุ่ม Remove เพื่อใช้ลบคอนโทรล FileUpload ที่สร้างขึ้นออกโดยผู้ใช้ 
          var newbtnDel = document.createElement("input"); 
          newbtnDel.setAttribute("id", "btnDelUpload" + AddMoreFiles.lastAssignedId);
          newbtnDel.setAttribute("name", "btnDelUpload" + AddMoreFiles.lastAssignedId); 
          newbtnDel.setAttribute("value", "Remove");   
          newbtnDel.type = "button"; 
    
         //////สร้างคอนโทรล FileUpload ขึ้นใหม่และกำหนดแอททริบิวต์ต่างๆให้
          var newFile = document.createElement("input"); 
          newFile.type = "file"; 
          newFile.setAttribute("class", "fileUpload");
          newFile.setAttribute("id", "FileUpload" + AddMoreFiles.lastAssignedId); 
          newFile.setAttribute("name", "FileUpload" + AddMoreFiles.lastAssignedId);
    
    //////เป็นส่วนของการเรียกฟังก์ชั่นในการตรวจสอบไฟล์ที่ทำการอัพโหลดว่าถูกต้องตรงตามเงื่อนไขหรือไม่
         เมื่อมีการเลือกไฟล์ใดๆจากผู้ใช้ ซึ่งจะพูดถึงในส่วนถัดไปในการสร้างฟังก์ชั่น FileValidate(ctrl,maxVal) 
          newFile.setAttribute('onchange', 'javascipt:FileValidate(this,5);');
    
    //////สร้าง div ขึ้นมาใหม่ และกำหนดค่าแอททริบิวต์ต่างๆให้กับ div ที่ทำการสร้างใหม่ 
    ในทีนี้คือ id และ เพิ่มคอนโทรลที่สร้าง tag ไว้ด้านบนเข้ามาไว้ใน div 
          var div = document.createElement("div");
          div.appendChild(newLine);
          div.appendChild(newFile);
    //////เพิ่มช่องว่างระหว่างคอนโทรลอัพโหลดไฟล์และปุ่ม Remove เพื่อความสวยงาม
          div.appendChild(document.createTextNode('\u00A0'));
          div.appendChild( newbtnDel);
          div.setAttribute("id", "div" + AddMoreFiles.lastAssignedId);
    
    //////เพิ่ม event การคลิกปุ่ม Remove เพื่อให้ไปเรียกใช้ฟังก์ชั่น DelFileUpload
    โดยส่งค่า id ของ div ที่ต้องการให้ลบออกซึ่งจะกล่าวถึงในส่วนถัดไปของการสร้างฟังก์ชั่น DelFileUpload(divid);
          newbtnDel.setAttribute('onclick', 'javascipt:DelFileUpload(\'' + "div" + AddMoreFiles.lastAssignedId + '\');');
    
          fileUploadarea.appendChild(div);
          AddMoreFiles.lastAssignedId++; } 
    
    ///////กรณีที่มีจำนวนตัวอัพโฟลดไฟล์ที่ถูกสร้างขึ้นเองเกินกว่าที่กำหนด(ค่าพารามิเตอร์ x) 
    จะแสดงข้อความให้ทราบ
     else { 
           alert("ขออภัย ท่านสามารถอัพโหลดไฟล์ได้สูงสุดครั้งละไม่เกิน " + x + " ไฟล์ต่อการอัพโหลดแต่ละครั้ง กรุณาลองใหม่อีกครั้ง"); 
          return false;
           } 
          }

    2.3) ฟังก์ชั่นที่ใช้ในการตรวจสอบขนาดของไฟล์ โดยมีการระบุขนาดสูงสุดที่ยอมให้อัพโหลดแต่ละครั้ง โดยใช้ชื่อฟังก์ชั่นว่า FileValidate(ctrlFile,MaxSize)

    ////เป็นการประกาศตัวแปรที่ใช้ในการตรวจสอบชนิดของไฟล์ที่ยอมให้อัพโหลดได้
    var _validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png", ".pdf"];
    function FileValidate(ctrlFile,MaxSize) 
    {//////พารามิเตอร์ที่รับเข้ามาคือตัวคอนโทรลของการอัพโหลดไฟล์ที่ต้องการตรวจสอบ(ctrlFile) 
    และขนาดสูงสุดของไฟล์ที่ยอมให้อัพโหลดในแต่ละคอนโทรล(MaxSize)
    
    ////ดึงค่าชื่อของไฟล์ที่อัพโหลดที่ต้องการตรวจสอบ
      var sFileName = ctrlFile.value;
    
    /////ตรวจสอบเมื่อพบชื่อไฟล์ที่ดึง
       if (sFileName.length > 0) 
       { 
         var blnValid = false; 
    //////////ตรวจสอบนามสกุลของไฟล์ที่ดึงมาจากชื่อไฟล์กับค่าของนามสกุลไฟล์ที่ยอมให้อัพโหลด
    จากตัวแปร _validFileExtensions
         for (var j = 0; j < _validFileExtensions.length; j++) 
          { var sCurExtension = _validFileExtensions[j]; 
           if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) 
           { 
             blnValid = true; 
             break; 
            } 
          }   
    //////กรณีไฟล์ดังกล่าวมีนามสกุลที่แตกต่างจากที่กำหนดจะแสดงข้อความแจ้งเตือน 
    และล้างค่าของชื่อไฟล์ในคอนโทรลอัพโหลดไฟล์ตัวดังกล่าว
         if (!blnValid) 
         { alert("ไม่สามารถอัพโหลดไฟล์ดังกล่าวได้ เนื่องจากรองรับเฉพาะไฟล์ที่มีนามสกุลดังนี้เท่านั้น: " + _validFileExtensions.join(", ")); 
          ctrlFile.value = ""; 
          return false; 
         } 
         else 
         {
    /////หากไฟล์ที่อัพโหลดมีนามสกุลไฟล์ตามที่ระบุ จะทำการตรวจสอบขนาดของไฟล์ว่าไม่เกินจากขนาดสูงสุด
    ที่กำหนดหรือไม่ โดยมีการคำนวณหน่วยเป็น MB
           var fileSize = parseFloat(ctrlFile.files[0].size / 1048576).toFixed(2);
    
    /////หากขนาดของไฟล์เกินกว่าที่กำหนดจะแสดงข้อความแจ้งเตือน และล้างค่าไฟล์ที่ต้องการอัพโหลด
    ในคอนโทรลอัพโหลดไฟล์ดังกล่าว
           if (fileSize > MaxSize) 
            { alert(" ขออภัย ขนาดของไฟล์ที่ต้องการอัพโหลดมีขนาดใหญ่เกินกว่าทีกำหนด(" + MaxSize + " MB)"); 
              ctrlFile.value = ""; 
              return false;   
           } 
         }   
        }   
      return true; 
      }

    2.4) ฟังก์ชั่นที่ใช้ในการตรวจสอบว่ามีการเลือกไฟล์ที่ต้องการอัพโหลดแล้วหรือไม่ เมื่อมีการกดปุ่ม “Upload” โดยใช้ชื่อฟังก์ชั่นว่า ValidateFileUpload() 

    function ValidateFileUpload() { 
    //////เป็นการค้นหาคอนโทรลไฟล์อัพโหลดที่มีในหน้าจอโดยใช้ tag input 
    และตรวจสอบที่มี type เป็น "file" ใส่ไว้ในตัวแปร arrInputs
        var arrInputs = document.getElementsByTagName("input");
        var blnValid; 
        var oInput;
        for (var i = 0; i < arrInputs.length; i++)
         { oInput = arrInputs[i]; 
          if (oInput.type == "file") 
          { 
           var sFileName = oInput.value; 
    
     ///////หากพบตัวอัพโหลดไฟล์ตัวใดที่ยังไม่ได้ทำการเลือกไฟล์ไว้ จะทำการแสดงข้อความแจ้งเตือนให้ทราบ
           if (sFileName.length == 0) 
             { blnValid = false; 
              alert("ขออภัย ไม่สามารถอัพโหลดไฟล์ได้ เนื่องจากพบว่ามีบางไฟล์ไม่ได้ถูกเลือก กรุณาลองใหม่อีกครั้ง"); 
              return false;   
             } 
          }   
         }   
    return true; 
    }

    2.5) ฟังก์ชั่นที่ใช้ในการลบคอนโทรลที่ใช้ในการอัพโหลดไฟล์ โดยใช้ชื่อฟังก์ชั่นว่า DelFileUpload(dv) ซึ่งจะส่งพารามิเตอร์เป็น id ของ div ตัวที่ต้องการให้ลบ และถูกเรียกใช้เมื่อมีการกดปุ่ม “Remove”

    function DelFileUpload(dv) 
    { ////ค้นหา div ตาม id ที่ส่งเข้ามา
       var elem = document.getElementById(dv); 
      ///ทำการลบ div นั้นออกจากหน้าจอและลดค่าของ AddMoreFiles.lastAssignedId ลง 1
    เพื่อใช้ในการคำนวณค่าสูงสุดและกำหนด tag ของ id และ name ของคอนโทรลที่สร้างใหม่ต่อไป
       elem.parentNode.removeChild(elem); 
       AddMoreFiles.lastAssignedId = AddMoreFiles.lastAssignedId - 1; 
    }

    3) ในฝั่งเซฺิร์ฟเวอร์ (C#)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    /////เป็น namespace ที่ต้องอ้างอิงเพิ่มเพื่อใช้ในการจัดการข้อมูลกับ datatable
    using System.Data;
    /////เป็น namespace ที่ต้องอ้างอิงเพิ่มเพื่อใช้ในการจัดการเกี่ยวกับการอัพโหลดไฟล์
    using System.IO;
    public partial class MultipleUpload : System.Web.UI.Page
    {
    protected void BtnUpload_Click(object sender, EventArgs e)
    {
      try
      {
    
     //////สมมุติเพื่อนำไปประยุกต์ใช้กับการบันทึกลงฐานข้อมูลต่อไป 
    โดยสร้างโครงสร้าง datatable ที่ชื่อว่า dtFiles 
         DataTable dtFiles = new DataTable();
         dtFiles.Columns.AddRange(new DataColumn[3] { new DataColumn("Id", typeof(int)),
         new DataColumn("Name", typeof(string)),
         new DataColumn("FileSize",typeof(string)) });
    
    ////เป็นการดึงคอนโทรลในการอัพโหลดไฟล์ที่ถูกสร้างขึ้นและวนรอบในการบันทึกไฟล์ดังกล่าว
     HttpFileCollection hfc = Request.Files;
     for (int i = 0; i < hfc.Count; i++)
     {
        HttpPostedFile hpf = hfc[i];
       if (hpf.ContentLength > 0)
        {
    //////บันทึกไฟล์ตามที่ระบุไว้
        hpf.SaveAs(Server.MapPath("~/uploads/") + System.IO.Path.GetFileName(hpf.FileName));
    
    ////เพิ่มข้อมูลลงไปใน datatable ที่สร้างไว้ สำหรับการใช้งานจริงอาจเป็นการติดต่อเพื่อบันทึกลงฐานข้อมูล 
        dtFiles.Rows.Add(i,hpf.FileName, hpf.ContentLength / 1024);
         }
       }
     if (dtFiles.Rows.Count > 0) 
     {
    //////ดึงข้อมูลจาก datatable มาแสดงในกริดวิว
        gvResult.DataSource = dtFiles;
        gvResult.DataBind();
     
     }
       }
      }
      catch (Exception)
      {
       throw;
       }
      }
    }
    

    เพิ่มเติม :
    หากต้องการตกแต่งเพื่อความสวยงามสามารถใส่ StyleSheet เพิ่มเติมได้ ดังตัวอย่างต่อไปนี้

    <style type="text/css">
      .fileUpload
      {
       width:255px;
       font-size:11px;
       color:#000000;
       border:solid;
       border-width:1px;
       border-color:#7f9db9;
       height:17px;
      }
     .Divborder
      {
       border: 2px solid;
       border-radius: 5px;
       padding:10px;
       width:390px;
      }
    </style>
    

    ตัวอย่างผลลัพธ์ที่ได้

    1) แสดงหน้าจอ โดยแรกเริ่มมีอัพโหลดไฟล์ตั้งต้น 1 ตัว

    uploadsingle

    2) แสดงหน้าจอเมื่อมีการกดปุ่ม “Add more files” uploadmulti2

    3) แสดงผลลัพธ์หลังจากบันทึกข้อมูลเรียบร้อยแล้วmultipleResult2

    2. การอัพโหลดไฟล์คราวละหลายๆไฟล์โดยใช้ .Net Framework เวอร์ชั่น 4.5  ซึ่งในบทความนี้จะไม่ลงรายละเอียดมากนัก ผู้อ่านสามารถนำไปประยุกต์เพิ่มเติมได้ โดยจะอธิบายทีละขั้นตอนคร่าวๆ ดังนี้

    1) การออกแบบในหน้าจอฝั่ง Client

    <body>
    <form id="form1" runat="server">
    <div>
    <asp:FileUpload ID="file_upload" runat="server" AllowMultiple="true" />
    <asp:Button ID="btnFileUpload" runat="server" Text="Upload" OnClick="btnFileUpload_Click" />
    <asp:Label ID="lblUploadStatus" runat="server"></asp:Label><br />
     <asp:GridView ID="gvResult" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" Width="410px">
     <AlternatingRowStyle BackColor="White" />
     <Columns>
     <asp:BoundField DataField="Name" HeaderText="ชื่อไฟล์">
     <ItemStyle Width="300px" />
     </asp:BoundField>
     <asp:BoundField DataField="filesize" HeaderText="ขนาดไฟล์(KB)">
     <ItemStyle HorizontalAlign="Right" Width="110px" />
     </asp:BoundField>
     </Columns>
     <EditRowStyle BackColor="#2461BF" />
     <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
     <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
     <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
     <RowStyle BackColor="#EFF3FB" />
     <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
     <SortedAscendingCellStyle BackColor="#F5F7FB" />
     <SortedAscendingHeaderStyle BackColor="#6D95E1" />
     <SortedDescendingCellStyle BackColor="#E9EBEF" />
     <SortedDescendingHeaderStyle BackColor="#4870BE" />
     </asp:GridView>
    </div>
    </form>
    </body>

    2) การพัฒนาในฝั่งเซิร์ฟเวอร์(C#)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.IO;
    using System.Data;
    namespace WebAppTest
    {
     public partial class MultipleUpload : System.Web.UI.Page
     {
     protected void btnFileUpload_Click(object sender, EventArgs e)
     {
     try
     {
    ///////การตรวจสอบขนาดไฟล์และไฟล์ต้องเป็นชนิด image/jpeg เท่านั้น
     if (file_upload.HasFile && file_upload.PostedFiles.All(x => x.ContentType == "image/jpeg" && x.ContentLength < 102400))
     {
    ///////ประกาศตัวแปร และกำหนดโครงสร้างของ datatable
     int i = 1;
     DataTable dtFiles = new DataTable();
     dtFiles.Columns.AddRange(new DataColumn[3] { new DataColumn("Id", typeof(int)),
     new DataColumn("Name", typeof(string)),
     new DataColumn("FileSize",typeof(string)) });
     foreach (var file in file_upload.PostedFiles)
     {
    //////บันทึกไฟล์ที่เลือก
       file_upload.SaveAs(Server.MapPath("~/Fileupload/") + Path.GetFileName(file.FileName));
    ////วนบันทึกข้อมูลไฟล์ที่อัพโหลดลงใน datatable  
        dtFiles.Rows.Add(i, file.FileName, file.ContentLength / 1024);
     }
    
    ////แสดงข้อความเมื่อการอัพโหลดเสร็จสมบูรณ์
       lblUploadStatus.Text = "บันทึกสำเร็จ";
    
    ////แสดงค่าที่ได้ลงในกริดวิว
     if (dtFiles.Rows.Count > 0)
     {
     gvResult.DataSource = dtFiles;
     gvResult.DataBind();
    
     }
     }
     else
     {
    ////แสดงข้อความแจ้งเตือนกรณีเกิดปัญหาไฟล์มีขนาดใหญ๋เกินกว่าที่กำหนดและไม่ใช่ชนิด image/jpeg
       lblUploadStatus.Text = "กรุณาเลือกไฟล์ที่ต้องการอัพโหลดให้เหมาะสม";
     }
     }
     catch (Exception ex)
     {
    ////แสดงข้อความแจ้งเตือนกรณีเกิดปัญหาในการอัพโหลดไฟล์
       lblUploadStatus.Text = "เกิดข้อผิดพลาดในการอัพโหลดไฟล์ :" + ex.Message;
     }
     }
     }
    }

    ตัวอย่างผลลัพธ์ที่ได้

    1) ก่อนทำการอัพโหลดไฟล์

    multiple4_5_0

    2) หลังอัพโหลดไฟล์ทั้งหมดเรียบร้อยแล้ว

    multiple4_5

    หมายเหตุ : หากกำหนดค่าให้กับ Properties ที่ชื่อว่า AllowMultiple=”false ตอนเลือกไฟล์ที่จะอัพโหลดจะสามารถเลือกได้เพียงไฟล์เดียวเท่านั้น

              จากบทความและตัวอย่างข้างต้น จะเห็นว่า ลักษณะผลลัพธ์ที่ได้จะคล้ายคลึงกัน แต่จะแตกต่างกันในส่วนของความซับซ้อนในการตรวจสอบความถูกต้องของข้อมูลไฟล์แนบที่รับเข้ามา ซึ่งผู้พัฒนาเองสามารถเลือกใช้วิธีที่ตนถนัดและขึ้นกับเวอร์ชั่นของ .NET Framework ที่กำลังพัฒนา รวมถึงเงื่อนไขของการตรวจสอบให้ตรงตามความต้องการของผู้ใช้มากที่สุด โดยเนื้อหาในบทความนี้เป็นเพียงแนวทางหนึ่งให้กับท่านในเบื้องต้นเท่านั้น ท่านสามารถนำไปประยุกต์ต่อยอดเพิ่มเติมได้ และผู้เขียนหวังเป็นอย่างยิ่งว่าบทความนี้จะเป็นประโยชน์กับผู้พัฒนาที่กำลังค้นหาวิธีการอัพโหลดไฟล์คราวละหลายไฟล์นี้อยู่เช่นกัน หากมีเนื้อหาส่วนใดผิดพลาด ผู้เขียนขออภัยไว้ ณ ที่นี้ด้วย ขอบคุณค่ะ ^^
    แหล่งข้อมูลอ้างอิง :
    http://www.aspsnippets.com/Articles/Uploading-Multiple-Files-using-JavaScript-Dynamic-FileUpload-Controls-in-ASP.Net.aspx
    http://www.codeproject.com/Articles/667604/Upload-multiple-files-in-asp-net
    http://www.c-sharpcorner.com/UploadFile/99bb20/upload-multiple-files-using-fileupload-control-in-Asp-Net-4/

  • ทำความรู้จักและเรียนรู้การใช้งานเบื้องต้นกับ StringBuilder ใน .NET Framework(C#)

              โดยปกติแล้วนั้น ผู้พัฒนาโปรแกรมโดยใช้ .NET Framework มักจัดการข้อมูลที่เป็นอักษรหรือข้อความ (String) ด้วยคลาสของ String ที่มีใน .NET Framework ซึ่งประกอบไปด้วยฟังก์ชั่นหรือเมธอดที่หลากหลายที่ติดมากับตัวคลาส เพื่อเตรียมมาไว้ให้ใช้งาน และสามารถรองรับความต้องการในการจัดการข้อมูลของผู้ใช้แต่ละคน ซึ่งมีวิธีการใช้งานง่าย ไม่ยุ่งยากซับซ้อน มีมาตรฐานการใช้งานที่รู้จักโดยทั่วถึงกัน สะดวกและรวดเร็วทำให้ผู้พัฒนาสามารถเลือกวิธีจัดการข้อมูลได้อย่างมีประสิทธิภาพ อย่างไรก็ตาม แม้ว่าคลาสของ String จะมีเมธอดให้เราได้เลือกใช้กันอย่างมากมายและมีประสิทธิภาพอยู่แล้ว แต่ปัญหาอย่างหนึ่งที่พบในการใช้งานกับตัวแปรของคลาส String คือ การเชื่อมต่อหรือเปลี่ยนแปลงแก้ไขค่าข้อความของตัวแปรชนิด String ในแต่ละครั้ง จะไม่สามารถเปลี่ยนรูป หรือกลับไปแก้ไขค่าของตัวแปรบนพื้นที่หน่วยความจำเดิมที่ถูกจองไว้ให้กับตัวแปรได้ หรืออาจพูดในทางโปรแกรมแบบง่ายๆได้ว่า หากเราต้องการเปลี่ยนแปลงค่าของตัวแปรชนิด String เราจะไม่สามารถกลับไปแก้ไขค่าใน object ของตัวแปรที่ถูกสร้างขึ้นเดิมในหน่วยความจำ หรือ Memory ที่สร้างไว้ในตอนแรกได้ แต่จะมีการสร้าง object ตัวใหม่ขึ้นมา ทุกครั้งที่มีการแก้ไข/จัดการข้อมูลค่า หรือมีการใช้งานเมธอดในคลาส System.String และใช้วิธีให้ pointer ของตัวแปรชี้ไปยังตำแหน่งของ object ตัวใหม่ที่มีค่าของตัวแปรที่ถูกแก้ไขภายหลังแทน ซึ่งหากมีกรณีที่มีการเปลี่ยนแปลงค่าข้อมูล หรือเชื่อมต่อข้อความในตัวแปรดังกล่าวจำนวนหลายครั้ง หรือมีการวนลูปซ้ำในการเปลี่ยนแปลงค่าเป็นจำนวนมาก จะถือเป็นการใช้งานทรัพยากรหน่วยความจำอย่างสิ้นเปลือง เนื่องจาก object ของตัวแปรจะถูกสร้างขึ้นใหม่เรื่อยๆและมีการจองพื้นที่ให้กับ object ตัวที่ถูกสร้างขึ้นใหม่ตามจำนวนครั้งที่ทำการแก้ไขหรือเชื่อมต่อข้อความนั่นเอง ดังภาพ


    ภาพการจองพื้นที่ในหน่วยความจำของตัวแปรชนิด String
    [ที่มาของภาพ : http://www.tutorialsteacher.com/csharp/csharp-stringbuilder]

                        จากภาพตัวอย่างจะเห็นได้ว่า เดิมทีมีการกำหนดค่าให้กับตัวแปรเป็นข้อความ “Hello World!!” แต่เมื่อมีการปรับแก้ค่าของตัวแปร จะมีการสร้าง object ตัวใหม่ โดยจะเก็บค่าที่มีการเปลี่ยนแปลงของข้อความไปเป็น “Hello World!! From Tutorials Teacher” และเลื่อนตำแหน่งของ pointer ของตัวแปรที่จะชี้ไปเพื่อให้ได้ค่าใหม่นั่นเอง
              จากปัญหาดังกล่าว .NET Framework ก็ได้จัดเตรียมคลาสที่มีชื่อว่า “StringBuilder” ขึ้นมา ซึ่งเป็นคลาสที่ใช้ในการจัดการกับข้อมูลชนิดข้อความเช่นเดียวกับคลาส String โดยยินยอมให้มีการแก้ไขและปรับเปลี่ยนค่าในตัวแปรข้อความ(string) ดังกล่าวได้ใน object ตัวเดิมบนพื้นที่หน่วยความจำเดิม โดยไม่ต้องสร้าง object และทำการจองพื้นที่หน่วยความจำขึ้นใหม่ทุกครั้งที่มีการเปลี่ยนแปลงค่าของข้อความดังเช่นในคลาส String นั่นเอง ซึ่งจะทำให้สามารถประหยัดการใช้ทรัพยากรหน่วยความจำได้ในกรณีที่มีการเชื่อมต่อข้อความหรือเปลี่ยนแปลงค่าของตัวแปรจำนวนหลายครั้งได้

     ภาพการจองพื้นที่ในหน่วยความจำของตัวแปรชนิด StringBuilder

    [ที่มาของภาพ : http://www.tutorialsteacher.com/csharp/csharp-stringbuilder]

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

     

    หลักการทำงานและการจองพื้นที่ในหน่วยความจำของตัวแปรชนิด StringBuilder

              โดยปกติแล้วนั้น ค่าของ “StringBuilder.Length” จะเป็นค่าของจำนวนตัวอักษรที่มีในตัวแปร object ของ StringBuilder และจะถูกเพิ่มขึ้นเรื่อยๆ เมื่อมีการเพิ่มตัวอักษรหรืออักขระเข้าไปในตัวแปรนั้นโดยไม่มีการจองพื้นที่หน่วยความจำเพิ่ม จนกว่าค่าของ Length เท่ากับจำนวนของความจุที่จองพื้นที่หน่วยความจำไว้ ซึ่งก็หมายถึงค่าของ “Capacity” นั่นเอง และหากการเพิ่มตัวอักษรดังกล่าวทำให้ค่าของ Length มากกว่าค่าของ Capacity ใน object นั้นๆ จะมีการจองพื้นที่หน่วยความจำเพิ่มเป็นเท่าตัว เช่น จากเดิม 16 ตัวอักษรจะถูกเพิ่มเป็น 32 ตัวอักษร โดยจะสามารถเปลี่ยนแปลงได้ไม่เกินค่าความจุสูงสุด หรือที่เรียกว่า “MaxCapacity” ซึ่งถ้าหากมีการเพิ่มตัวอักษรที่เกินค่าของ MaxCapacity แล้วนั้นจะทำให้เกิดข้อผิดพลาดได้ แต่หากไม่มีการกำหนดค่าให้กับ Capacity และ MaxCapacity แล้วนั้น ค่าตั้งต้นของ Capactity เริ่มต้นจะอยู่ที่ 16 ตัวอักษร และค่าของ MaxCapacity จะอยู่ที่ประมาณ 2 พันล้านตัวอักษร หรือเทียบเท่ากับค่าสูงสุดของ Int32.MaxValue นั่นเอง

    การอ้างอิง Namespace
    โดย Namespace ที่ต้องอ้างอิงเพิ่มเติมในการใช้งานคลาส StringBuilder มีดังนี้

    • using System;
    • using System.Text;

    การประกาศตัวแปรของคลาส StringBuilder
    ในการประกาศตัวแปร object ของคลาส StringBuilder จะใช้หลักการเดียวกันกับการประกาศตัวแปร object ของคลาสโดยทั่วไป แต่สามารถประกาศโดยมีการระบุค่าอื่นเพิ่มเติมโดยใช้ Constructor ได้ ซึ่งจะขอยกตัวอย่างในกรณีที่มีการใช้งานกันโดยทั่วไป ดังนี้
    แบบเดียวกับการประกาศ object ของคลาสทั่วไป
    ตัวอย่าง

    StringBuilder sb = new StringBuilder();

    แบบมีการกำหนดค่าตั้งต้น
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!!");

    คำอธิบาย : เป็นการสร้างตัวแปร object แบบมีการกำหนดค่าเริ่มต้นให้มีค่าเท่ากับ Hello World!!
    แบบมีการระบุขนาดในการจองพื้นที่ของหน่วยความจำ(Capacity)  แม้ว่าตัวแปรของคลาส StringBuilder สามารถเพิ่มและขยายได้ไม่จำกัดโดยไม่ต้องสร้าง object ตัวใหม่ แต่ผู้พัฒนาสามารถระบุขนาดสูงสุดของจำนวนตัวอักษรที่ตัวแปรจะสามารถรองรับได้ โดยค่าดังกล่าวที่ระบุนี้ เรียกว่า “Capacity” และเรียกค่าของความยาวตัวอักษรที่มีในตัวแปรนั้นๆ โดยใช้ properties ที่มีชื่อว่า “Length” โดยหากมีการกำหนดค่าของ Capacity ให้กับตัวแปรคลาส StringBuilder เมื่อมีการแก้ไขค่าของตัวแปรจะไม่ถูกจองพื้นที่ใหม่จนกว่าความยาวของตัวอักษรจะถึงค่าของ Capacity ที่กำหนด จึงจะมีการจองพื้นที่ใหม่ให้อัตโนมัติในขนาดเดียวกับ Capacity ที่กำหนดไว้เดิมเป็นเท่าตัว แต่หากไม่ได้ทำการกำหนดค่าของ Capacity ไว้ จะมีค่า default เท่ากับ 16 ซึ่งสามารถกำหนดค่าของ Capacity ได้ดังนี้

    StringBuilder sb = new StringBuilder(25);

    คำอธิบาย : เป็นการสร้างตัวแปร object แบบมีการกำหนดขนาดของตัวแปรให้มีขนาดความจุ หรือ Capacity ไว้ที่ 25 ตัวอักษร แต่หากมีการกำหนดค่าของข้อมูลที่มีความยาวตัวอักษรมากกว่าขนาดที่กำหนด จะมีการขยายพื้นที่ความจุให้กับขนาดตัวแปรอัตโนมัติ นอกจากจะสามารถกำหนดขนาดของตัวแปร StringBuilder โดยใช้ Constructor แล้วนั้นยังสามารถระบุผ่าน Properties ที่มีชื่อว่า Capacityได้อีกด้วย ดังนี้

    sb.Capacity = 25;

    แบบกำหนดค่าเริ่มต้นและระบุขนาดความจุของตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!", 25);

    คำอธิบาย : เป็นการประกาศตัวแปรขนาด 25 ตัวอักษร และมีค่าเริ่มต้นเป็นคำว่า “Hello World!!”

    แบบกำหนดค่าขนาดความจุและความจุสูงสุดของพื้นที่หน่วยความจำให้กับตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder( 25,200);

    คำอธิบาย : เป็นจองพื้นที่ในการประกาศตัวแปร(Capacity)ขนาด 25 ตัวอักษร และมีค่าความจุสูงสุด(MaxCapacity)ได้ไม่เกิน 200 ตัวอักษร

    แบบกำหนดค่าเริ่มต้นที่มีการตัดข้อความ(substring) และระบุขนาดความจุของตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!",0,5, 25);

    คำอธิบาย : เป็นจองพื้นที่ในการประกาศตัวแปรขนาด 25 ตัวอักษร และมีค่าเริ่มต้นเป็นคำว่า “Hello”

    เนื่องจากมีการ substring ค่าข้อความ “Hello World!!” ตั้งแต่ตำแหน่งที่ 0 มา 5 ตัวอักษร จึงกลายเป็นคำว่า “Hello” นั่นเอง

    เมธอดที่จำเป็นและควรรู้ในการใช้งานคลาส String builder

    โดยแบ่งตามลักษณะการทำงาน เพื่อง่ายต่อความเข้าใจและการนำไปใช้ ดังนี้

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

    StringBuilder sb = new StringBuilder("Hello World!");
    sb.Append(" Nice to meet you!!");
    Console.WriteLine(sb);

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นว่าเดิมค่าของตัวแปรมีค่า “Hello World!” แต่เมื่อมีการเรียกใช้เทธอด Append() จะทำการต่อข้อความจากเดิมจนกลายเป็น “Hello World! Nice to meet you!!

    ตัวอย่างที่ 2

    StringBuilder sb = new StringBuilder("Hello World");
    sb.Append(" !?!?",2,2);
    Console.WriteLine(sb);

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นว่าเดิมค่าของตัวแปรมีค่า “Hello World” แต่เมื่อมีการเรียกใช้เมธอด Append() แบบมีการ substring ร่วมด้วย โดยจะเริ่มทำการตัดตัวอักษรจากข้อความ “ !?!?” จากตำแหน่งลำดับ index ที่ 2 ไป 2 ตัวอักษร ซึ่งนั่นก็คือ “?!” หลังจากนั้นก็จะเชื่อมต่อด้วยข้อความเดิมจนกลายเป็น “Hello World!?!

    AppendLine() เป็นเมธอดที่ใช้ในการแทรกบรรทัดใหม่เข้าไปให้กับตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello, I am Kate.");
    sb.AppendLine();
    sb.Append("Nice to meet you.");
    Console.WriteLine(sb);
    
    //หรือ
    StringBuilder sb = new StringBuilder("Hello, I am Kate.");
    sb.AppendLine("Nice to meet you.");
    Console.WriteLine(sb);
    
    

    คำอธิบาย : จากตัวอย่างข้างต้น จะเป็นการเพิ่มการขึ้นบรรทัดใหม่ให้กับข้อความของตัวแปร จากค่า “Hello, I am Kate.” ด้วยการขึ้นบรรทัดใหม่ ตามด้วยข้อความ “Nice to meet you.” โดยแสดงทั้งตัวอย่างที่มีการเพิ่มบรรทัดโดยใช้เมธอด AppendLine() ก่อน แล้วจึงเชื่อมข้อความที่เหลือด้วยเมธอด Append() หรืออาจรวบวิธีการขึ้นบรรทัดใหม่ต่อด้วยข้อความด้วยการใช้เมธอด Appendline() แบบมีการส่งค่าพารามิเตอร์ของข้อความที่ต้องการเชื่อมต่อหลังจากขึ้นบรรทัดใหม่ได้เลยดังตัวอย่าง

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

    int MyInt = 25;
    StringBuilder sb = new StringBuilder("Your total is ");
    sb.AppendFormat("{0:C} ", MyInt);
    Console.WriteLine(sb);
    

    คำอธิบาย จากตัวอย่าง เป็นการจัดรูปแบบของข้อมูลที่จะนำมาทำการเชื่อมต่อกับตัวแปรเดิม โดยผลลัพธ์ที่ได้ คือ Your total is $25.00 นั่นเอง

    Insert() เป็นเมธอดที่ใช้ในการเพิ่มค่าข้อความแทรกไปยังตัวแปร โดยมีการกำหนดตำแหน่งเริ่มต้นที่ต้องการให้แทรกเพิ่มในค่าข้อมูลดังกล่าวเข้าไปในตัวแปร StringBuilder นั้นด้วย
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello, I am Kate.");
    sb.Insert(5," Mr.Kim");
    Console.WriteLine(MyStringBuilder);

    คำอธิบาย : จากตัวอย่างข้างต้น จะเป็นการแทรกข้อความ “ Mr.Kim” ลงในตัวแปร sb ที่มีค่าเดิมเป็น
    Hello, I am Kate.” จนได้ผลลัพธ์ คือ “Hello Mr.Kim, I am Kate.”

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

    Clear() เป็นเมธอดที่ใช้ในการล้างค่าตัวอักษรในข้อความของตัวแปร String builder ดังกล่าว และกำหนดค่าให้กับความยาวตัวอักษรหรือ properties ที่มีชื่อว่า “Length เป็น 0
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!");
    sb.Clear();
    Console.WriteLine(sb);

    คำอธิบาย : จากตัวอย่างข้างต้นจะเป็นการล้างค่าข้อมูลให้กับตัวแปร sb จากเดิมที่มีค่าตั้งต้นเป็น “Hello World!” จะเหลือเป็นค่าว่าง หรือ String.Empty และหากต้องการให้แสดงผลจะไม่แสดงข้อความใดๆขึ้นมา แต่ยังคงจองพื้นที่หน่วยความจำไว้ให้กับตัวแปรนี้แม้จะไม่มีค่าข้อมูลใดๆก็ตาม และจะมีค่า Length เป็น 0 นั่นเอง

    Remove() เป็นเมธอดที่ใช้ในการตัดค่าของข้อความในตัวแปรออก โดยมีการกำหนดตำแหน่งเริ่มต้นและความยาวของตัวอักษรในข้อความที่ต้องการตัด โดยนับตำแหน่งจากตัวอักษรตัวแรกเป็นค่าตำแหน่งลำดับที่ 0(ซึ่งเป็นการอ้างอิงตามหลักการนับตำแหน่งของ index โดยเริ่มถือว่าตัวอักษรตัวแรกเป็น index ลำดับที่ 0 นั่นเอง)
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!");
    sb.Remove(5,7);
    Console.WriteLine(sb);
    

    คำอธิบาย : จากตัวอย่างข้างต้น เดิมตัวแปรมีค่าข้อความเป็น “Hello World!” แต่เมื่อใช้เมธอด Remove() ในการตัดค่าข้อความตั้งแต่ตำแหน่งของลำดับ index ที่ 5 (หรือตำแหน่งที่ 4 เมื่อเริ่มต้นนับ 1 จากตัวอักษรแรกของข้อความ) ไปจำนวน 7 ตัวอักษร โดยผลลัพธ์ที่ได้ เป็นดังนี้ “Hello

    • การตัดค่าบางส่วนออกโดยกำหนดค่า properties ที่ชื่อว่า Length ให้กับตัวแปร

    ตัวอย่าง

    StringBuilder sb1= new StringBuilder("Hello World!");
    sb1.Length--;
    //หรือ
    sb1.Length = sb1.Length-1;
    //หรือ
    sb1.Remove(sb1.Length-1,1);
    //เป็นการตัดค่าตัวอักษรตัวสุดท้ายออกจากข้อความ จะได้ผลลัพธ์ คือ Hello World 
    
    StringBuilder sb2= new StringBuilder("Hello World!"); 
    sb2.Length=0; 
    //เป็นการล้างค่าข้อมูลในตัวแปร sb2 
    ซึ่งจะมีลักษณะการทำงานเช่นเดียวกับการใช้เมธอด Clear() ที่กล่าวไว้แล้วข้างต้นนั่นเอง

    คำอธิบาย : จากตัวอย่างข้างต้น ในส่วนของ sb1 นั้นจะเป็นการตัดค่าตัวอักษรตัวสุดท้ายออกจากข้อความ “Hello World!” ซึ่งก็คือตัวอักษร “!” โดยจะได้ผลลัพธ์ เป็น “Hello World” และสำหรับกรณีของ sb2 จะเป็นการล้างค่าข้อมูลในตัวแปร sb2 ซึ่งจะมีลักษณะการทำงานเช่นเดียวกับการใช้เมธอด Clear() ที่กล่าวไว้แล้วข้างต้นนั่นเอง

    3. การเปลี่ยนแปลงค่าของตัวแปร  ดังนี้

    • Replace() เป็นเมธอดที่ใช้ในการแทนที่ข้อความ โดยมีลักษณะการทำงานเช่นเดียวกับเมธอด Replace() ที่มีในคลาส String ที่จะต้องมีการกำหนดค่าใหม่ที่ต้องการให้แทนที่ และค่าที่ต้องการแทนที่นั่นเอง
    ตัวอย่าง

    StringBuilder sb= new StringBuilder("Hello World!");
    sb.Replace('World', 'Kate');
    Console.WriteLine(sb);
    

    คำอธิบาย จากตัวอย่างข้างต้น จะเป็นการแทนที่ค่าของข้อความเดิมคือ “World” ในคำว่า “Hello World!” ด้วยค่า “Kate” ซึ่งจะได้ผลลัพธ์ คือ “Hello Kate!

    4. การแปลงค่าจากตัวแปรชนิด StringBuilder เป็น String  ดังนี้

    ToString() เป็นเมธอดที่ใช้ในการแปลงค่าตัวแปรแบบ StringBuilder มาเป็นค่าของตัวแปรแบบคลาส String เพื่อนำไปแสดงผลหรือนำมาประยุกต์ใช้งานกับเมธอดที่มีในคลาส String ต่อไป
    ตัวอย่าง

    StringBuilder sb= new StringBuilder("Hello World!");
    Console.WriteLine(sb.ToString());
    //การนำไปใช้กับเมธอดหรือฟังก์ชั่นที่มีในคลาส String
    int a = sb.ToString().IndexOf("a");
    //เป็นการค้นหาตำแหน่งของอักษร a ในตัวแปร sb ที่ถูกแปลงเป็น String 
    ซึ่งผลลัพธ์ที่ได้จะเท่ากับ -1 เนื่องจากไม่มีตัวอักษร a ในข้อความดังกล่าว

    คำอธิบาย : จากตัวอย่างข้างต้นเป็นการแปลงค่าของตัวแปรในคลาส StringBuilder ให้เป็นคลาส String ซึ่งหลังจากที่ได้ทำการแปลงค่าแล้วนั้น จะสามารถใช้งานเมธอหรือฟังก์ชั่นที่มีในคลาส String ได้ เช่น IndexOf หรือ StartsWith  เป็นต้น

    5. การอ้างอิงและเข้าถึงตัวอักษรที่อยู่ในข้อความ  ดังนี้

    ตัวอย่าง

    StringBuilder sb= new StringBuilder("Hello World!");
    Console.WriteLine(sb[6]);
    //ผลลัพธ์ที่ได้คือ "W"
    sb[2] = 'k';
    //ผลลัพธ์ที่ได้คือ "Heklo World!"

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

    6. การค้นหา  ในการค้นข้อมูลของตัวแปรแบบ StringBuilder นั้น โดยทั่วไปแล้วจะไม่มีเมธอดที่ใช้ในการค้นหาข้อมูลโดยตรง ดังเช่นที่มีในคลาส String เช่น IndexOf หรือ StartsWith ดังนั้นเราสามารถประยุกต์วิธีการค้นหาตามลักษณะของข้อมูลและการจัดการได้ ดังนี้

    • การค้นหาค่าที่ได้กลับมาในรูปแบบ Stringจากการเรียกใช้เมธอด ToString() โดยใช้เมธอดที่มีเช่นเดียวกับในคลาส String ซึ่งเป็นวิธีที่ง่าย และสามารถเปลี่ยนแปลงข้อมูลของตัวแปรได้โดยไม่สิ้นเปลืองทรัพยากร
    • แปลงค่าของตัวแปรแบบ StringBuilder ให้เป็นแบบ String และใช้เมธอดในการค้นหาที่มีในคลาส String ซึ่งวิธีการนี้เหมาะกับการทำงานที่ไม่มีการเปลี่ยนแปลงภายหลังจากการแปลงค่ามากนัก เพื่อลดการสิ้นเปลืองทรัพยากรในการแปลงค่าภายหลัง
    • ค้นหาข้อมูลให้เสร็จก่อนทำการนำมาแปลงค่าเป็นแบบ StringBuilder โดยวิธีนี้จะต้องไม่สนใจค่าลำดับของตัวอักษรที่ได้จากการค้นหานั้นๆ
    • ค้นหาโดยใช้ Properties ทีชื่อว่า Chars โดยวิธีนี้เหมาะกับข้อมูลที่มีอักษรจำนวนไม่มาก และเงื่อนไขในการค้นหาไม่ซับซ้อน

    เพิ่มเติม
    • หลักการเบื้องต้นในการพิจารณาการใช้งานกับการจัดการข้อมูลของตัวแปรข้อความ(String)ที่ใช้เมธอดการทำงานในแบบเดียวกัน หากเป็นการใช้งานที่มีการเชื่อมต่อข้อความในตัวแปรเดิมที่มากกว่า 4 ครั้ง ผู้พัฒนาควรหันมาใช้ตัวแปรแบบคลาส StringBuilder เนื่องจากจะทำให้ประหยัดทรัพยากรหน่วยความจำมากกว่า แต่หากจำนวนครั้งในการเชื่อมต่อน้อยกว่านั้นควรใช้คลาสของ String ตามเดิมเพื่อความสะดวกและรวดเร็ว เนื่องจากมีเมธอดรองรับการจัดการข้อความที่หลากหลายและไม่ถือเป็นการสิ้นเปลืองพื้นที่หน่วยความจำมากนัก

    • ในการเรียกใช้งานเมธอดต่างๆ สามารถนำมาเขียนรวมกันใน statement เดียวกันได้ ดังนี้

    ตัวอย่างที่ 1

          StringBuilder sb = new StringBuilder();
          sb.Append("This is the beginning of a sentence, ");
          sb.Replace("the beginning of ", "");
          sb.Insert(sb.ToString().IndexOf("a ") + 2, "complete ");
          sb.Replace(",", ".");
          Console.WriteLine(sb.ToString());
    

    คำอธิบาย : จากตัวอย่างข้างต้นเป็นการเชื่อมต่อข้อความเริ่มต้น คือ “This is the beginning of a sentence, ” และแทนที่คำว่า  “the beginning of” ด้วย “”(ค่าว่าง) และแทรกข้อความคำว่า “complete ” เข้าไปในตำแหน่งซึ่งหามาได้จากการเรียกใช้เมธอด ToString() และตามด้วยเมธอด IndexOf ของตำแหน่งข้อความ “a ” ถัดไปอีก 2 ตำแหน่งด้วยค่า “complete ” และแทนที่เครื่องหมาย “,” ด้วย “.”

    สามารถเขียนรวมเป็น statement เดียวกันได้ดังนี้

    ตัวอย่างที่ 2

          StringBuilder sb = new StringBuilder("This is the beginning of a sentence, ");
          sb.Replace("the beginning of ", "").Insert(sb.ToString().IndexOf("a ") + 2, 
                                                     "complete ").Replace(",", ".");
          Console.WriteLine(sb.ToString());

    ซึ่งจะให้ผลลัพธ์เช่นเดียวกับตัวอย่างที่ 1

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

    แหล่งข้อมูลอ้างอิง :
    http://www.dotnetperls.com/stringbuilder
    https://msdn.microsoft.com/en-us/library/2839d5h5(v=vs.110).aspx
    http://www.tutorialsteacher.com/csharp/csharp-stringbuilder
    https://blog.udemy.com/stringbuilder-c-sharp/

  • การเรียกใช้งานเมธอดในฝั่งเซิร์ฟเวอร์/เว็บเซอร์วิสแบบ Ajax ด้วย jQuery (C#)

              ก่อนจะพูดถึงวิธีการเรียกใช้งานเมธอดในฝั่งเซิร์ฟเวอร์/เว็บเซอร์วิสแบบ Ajax ด้วย jQuery ผู้เขียนขอเกริ่นนำเกี่ยวกับที่มาที่ไปเกี่ยวกับแนวคิดแบบ Ajax เพื่อให้ผู้อ่านบางท่านที่ยังอาจงงๆได้ทำความเข้าใจเสียก่อน ว่าโดยปกติแล้วนั้น ในการพัฒนาเว็บไซต์(Web application)ของ ASP.NET จะประกอบด้วย 2 ส่วน คือ Client side และ Server side ซึ่งการทำงานในส่วนของ Client side จะหมายถึงส่วนของ browser หรือหน้าจอการทำงานของผู้ใช้ เช่น Google Chrome Firefox และ Internet explorer ส่วนในฝั่งเซิร์ฟเวอร์จะมีการรับคำร้องขอ(request) จากฝั่ง Client ทำการประมวลผลและส่งค่าผลลัพธ์คืนกลับมายังฝั่ง Client อีกครั้งเพื่อให้ผู้ใช้เห็นผลลัพธ์การตอบกลับนั้นได้ ซึ่งโดยปกติแล้วนั้นการพัฒนาจะประกอบด้วยโค้ด 2 ส่วน คือ

    • Server-side code ซึ่งเป็นเทคโนโลยีของการทำงานในการติดต่อไปยังฝั่งเซิร์ฟเวอร์ และถูกนำมาใช้ในการพัฒนา Web application ซึ่งใน ASP.NET นั้นนับว่าได้รับความนิยมเป็นอย่างมาก โดยจะมีการใช้งานผ่านทาง .NET Framework ในรูปแบบของภาษา C# VB หรือภาษาอื่นๆที่มีใน .NET ในการประมวลผลเพื่อติดต่อกับฐานข้อมูล หรือแหล่งข้อมูลอื่นๆจากคำร้องขอในฝั่ง Client และทำการส่งผลลัพธ์ที่ได้กลับมายังฝั่ง Client อีกครั้ง หลังจากมีการส่งผลลัพธ์ตอบกลับมาเรียบร้อยแล้วนั้น จะมีการ render หน้าจอดังกล่าวขึ้นอีกครั้งเพื่อแสดงผลลัพธ์ที่ได้ ทำให้เพจหรือหน้าจอที่ใช้งานมีการโหลดหรือ refresh เกิดขึ้น ซึ่งอาจส่งผลเสียทำให้ผู้ใช้รู้สึกล่าช้าในการรอการตอบกลับการประมวลผลพร้อมทั้งการแสดงผลจากฝั่งเซิร์ฟเวอร์ โดยโค้ดในลักษณะนี้ผู้พัฒนาบางท่านอาจรู้จักกันในนามของ code behind นั่นเอง
    • Client-side code อาจเรียกได้ว่าเป็น client-side script ที่ถูกฝังไว้ในฝั่ง client และมีการประมวลผลในส่วนของ Browser ของผู้ใช้ ซึ่งสคริปต์ที่นิยมใช้ในการพัฒนาโดยส่วนใหญ่จะเป็น JavaScript และโต้ตอบกลับมาโดยตรงด้วย element ที่มีใน HTML เช่น textbox ปุ่ม หรือตาราง เป็นต้น นอกจากนี้ยังมีการใช้งานของโค้ดภาษา HTML และ CSS(Cascading Style Sheets) ร่วมด้วย แต่การพัฒนาในส่วนนี้ต้องมีการคำนึงถึงภาษาและโค้ดที่รองรับในแต่ละ browser ของผู้ใช้ด้วย ซึ่งการพัฒนาด้วยโค้ดในส่วนนี้ จะทำให้การตอบโต้กับผู้ใช้งานเกิดขึ้นภายในเวลาอันสั้น เกิด overhead น้อย แต่ก็มีข้อเสียเกี่ยวกับการเลือกใช้ภาษาในการพัฒนาให้ครอบคลุมในความหลากหลายของ browser ที่ผู้ใช้ใช้งานในการเปิดเว็บไซต์ได้

    Ajax (Asynchronous JavaScript and XML)
              เทคโนโลยีและแนวคิดแบบ Ajax ได้เข้ามามีบทบาทในการเปลี่ยนแปลงแนวคิดและหลักการทำงานโดยทั่วไปที่มีอยู่เดิมของ web application เล็กน้อย คือจะมีการส่งคำร้องขอจากฝั่ง Client ไปยังฝั่งเซิร์ฟเวอร์โดยตรงเพื่อตอบโต้กับ object ที่มีในฝั่งเซิร์ฟเวอร์ เช่น ฐานข้อมูล หรือไฟล์ เป็นต้น โดยปราศจากการ postback โดยแนวคิดของ Ajax จะเกี่ยวข้องกับเทคโนโลยีที่มีอยู่เดิม เช่น ข้อมูลในฝั่งเซิร์ฟเวอร์ เว็บเซอร์วิส และการเขียนสคริปต์ในฝั่ง client โดย client-side script จะใช้ในการเรียกใช้เว็บเซอร์วิสเพื่อประมวลผลกับฐานข้อมูลตามคำร้องขอ ซึ่งคำร้องขอดังกล่าวอาจเป็นการบันทึกหรืออ่านค่าข้อมูลจากฐานข้อมูล และการเรียกใช้แบบ Ajax นี้จะเป็นลักษณะ Asynchronous คือ เมื่อผู้ใช้มีการส่งคำร้องขอไปยังเว็บเซอร์วิส ในหน้าจอการทำงานจะไม่ถูกล็อคไว้ และสามารถทำงานในส่วนอื่นต่อได้โดยไม่จำเป็นต้องรอกระบวนการทำงานให้เสร็จทีละส่วน สามารถทำคู่ขนานกันไปได้ และหากส่วนใดเสร็จสิ้นการประมวลผลแล้วนั้นตัวเว็บเซอร์วิสก็จะส่งผลลัพธ์กลับมาให้แสดงยังฝั่งผู้ใช้เอง
              โดยเทคโนโลยีที่ถือว่ามีความสำคัญและจำเป็นสำหรับ client-side script นั่นก็คือ jQuery ที่เรารู้จักกันดี ซึ่งเป็นที่นิยม เนื่องจากประมวลผลได้เร็ว มีขนาดเล็ก และมีคุณสมบัติอีกหลายประการที่จะเข้ามาช่วยทำให้การพัฒนาโปรแกรมมีประสิทธิภาพมากยิ่ง
    จากข้อความข้างต้น แสดงให้เห็นว่าการพัฒนาเว็บไซต์โดยหลีกเลี่ยงการ postback เมื่อมีการเรียกใช้งานไปยังฝั่งเซิร์ฟเวอร์ ถือเป็นอีกทางเลือกของนักพัฒนาที่จะหยิบมาใช้ เพื่อลด over head ให้กับตัว web server และสามารถตอบสนองต่อผู้ใช้ได้รวดเร็วขึ้น ลดจำนวนในการโหลดเพจ และรอการตอบสนองทุกครั้งที่มีการส่งคำร้องขอ ผู้เขียนจึงขอแนะนำวิธีการเรียกใช้หรือส่งคำร้องขอไปยังฝั่งเซิร์ฟเวอร์แบบ Ajax ด้วย jQuery ดังนี้

    • การเรียกใช้เมธอดในฝั่งเซิร์ฟเวอร์ตามแนวคิดของ Ajax ด้วย jQueryหลักการเขียนเพื่อเรียกใช้เมธอดในฝั่งเซิร์ฟเวอร์/เว็บเซอร์วิส
      CheckUserNameAvailability-2
      ที่มาของภาพ :http://www.aspsnippets.com/Articles/Call-ASPNet-Page-Method-using-jQuery-AJAX-Example.aspx
       

      • ตัวอย่างที่ 1 : การเรียกใช้งานเมธอดแบบส่งค่ากลับเป็น string แบบไม่มีการส่งค่าพารามิเตอร์
        ฝั่ง Client(aspx)
       function CallServerMethodString() {
                          $.ajax({
                              type: "POST", //ชนิดในการส่งค่า
                              contentType: "application/json",
                              ///ที่อยู่ของเมธอดที่ต้องการเรียก (ชื่อเพจ/ชื่อเมธอด)
                              url: "TestPageMethod.aspx/getDataRetString", 
                              dataType: "json", ///ส่งค่าแบบ json
                              beforeSend: function () {
                              ///  เป็นส่วนการแสดง loading progress bar ในขณะที่กำลังทำงานอยู่
                                  $("#loadingmessage").show();
                              },
                             //////เป็นส่วนที่เกิดขึ้น เมื่อเกิดข้อผิดพลาดในการเรียกใช้เมธอด
                              error: function (XMLHttpRequest, textStatus, errorThrown) {
                                  alert("Request: " + XMLHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
                              },
                            //////เป็นส่วนที่เกิดขึ้น เมื่อการเรียกใช้เมธอดเสร็จสิ้นสมบูรณ์
                              success: function (response) {
      ///ล้างค่าข้อมูลใน div และนำผลลัพธ์ที่ได้จากการเรียกใช้(response.d) มาแสดงใน div ที่กำหนด
                                  $("#dvResult").html("");
                                  $("#dvResult").append(response.d);
      ///ซ่อน loading progress bar เมื่อเสร็จสิ้นการทำงาน
                                  $("#loadingmessage").hide(); 
                              }
      
                          });
                      }
      

      ฝั่ง Server (c#)

       [System.Web.Services.WebMethod]
       public static string getDataRetString()
       {
           string Result = "";
           string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
           var results = from p in digits
                            select p;
           foreach (var d in digits)
           {
               Result= Result+ "Result is :" + d.ToString() + "<br>";
           }
              return Result;
          }
       }
      

      คำอธิบาย : เป็นตัวอย่างการสร้างเมธอดในฝั่งเซิร์ฟเวอร์ โดยในการสร้างเมธอดต้องมีการกำหนด [System.Web.Services.WebMethod] ไว้ในส่วนบนของเมธอด และมีชนิดเป็น static โดยในส่วนนี้ตามความเป็นจริงจะเป็นส่วนที่อาจมีการดึงมาจากฐานข้อมูล เพื่อคืนค่ากลับไปให้ฝั่ง Client แสดงผล แต่ในที่นี้จะขอสร้างเป็นเพียงข้อมูลสมมุติของอาร์เรย์ที่เป็นข้อความขึ้นมาเท่านั้น

      ผลลัพธ์

      result1

      • ตัวอย่างที่ 2 : แบบส่งค่ากลับเป็น object โดยมีการส่งพารามิเตอร์มายังฝั่งเซิร์ฟเวอร์ด้วย
        ฝั่ง Client (aspx)
      <script type="text/javascript">
       function CallServerMethodObject() {
                          $.ajax({
                              type: "POST",
                              contentType: "application/json",
                              url: "TestPageMethod.aspx/getDataRetObj",
               ///ส่งค่าพารามิเตอร์จากการเลือกประเภทสถานศึกษาให้กับเมธอดฝั่งเซิร์ฟเวอร์
                              data: "{'CategoryID' : " + ddlMain.val() + "}",
                              dataType: "json", ///ส่งค่าแบบ json
              /// เป็นส่วนการแสดง loading progress bar ในขณะที่กำลังทำงานอยู่
                              beforeSend: function () {
                                  $("#loadingmessage").show();
                              }, 
                              error: function (XMLHttpRequest, textStatus, errorThrown) {
                                  alert("Request: " + XMLHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
                              },
              ///เป็นส่วนที่จะทำงานเมื่อมีการเรียกใช้เมธอดเสร็จสิ้นโดยไม่มีข้อผิดพลาดใดๆ
                              success: function (response) {
                  ///ประกาศตัวแปรของคอนโทรล dropdownlist ที่ชื่อว่า ddlResult และล้างค่าข้อมูล
                                  var ddlResult = $("[id*=ddlResult]");
                                  ddlResult.empty().append('กรุณาเลือกชื่อสถานศึกษา');
                   ///วนรอบเพื่อดึงค่าจาก obj ที่ถูกส่งกลับมาเพื่อ populate ลงใน dropdownlist 
                                 $.each(response.d, function (index, item) {
                             ddlResult.append($("").val(item.SubCategory).html(item.title));
                                  });
                   ////ซ่อน loading progress bar เมื่อการทำงานสิ้นสุด
                                $("#loadingmessage").hide();
                              }
                          });
                      }
        </script>
       <asp:DropDownList ID="ddlMain" runat="server" onchange="CallServerMethodObject()" >
       <asp:ListItem Selected="True" Value="0">กรุณาเลือกประเภทสถานศึกษา</asp:ListItem>
       <asp:ListItem Value="1">มหาวิทยาลัย</asp:ListItem>
       <asp:ListItem Value="2">โรงเรียน</asp:ListItem>
       </asp:DropDownList>
       <asp:DropDownList ID="ddlResult" runat="server" >
      <asp:ListItem Value="0">กรุณาเลือกชื่อสถานศึกษา</asp:ListItem>
      </asp:DropDownList>
      

      คำอธิบาย : เป็นตัวอย่างการสร้าง Cascading dropdownlist อย่างง่าย โดยหากมีการเลือกประเภทสถานศึกษา จะมีการเรียกใช้ฟังก์ชั่น CallServerMethodObject() และส่งค่าที่เลือก(รหัสประเภทสถานศึกษา) ให้กับการเรียกใช้เมธอดในฝั่งเซิร์ฟเวอร์ในพารามิเตอร์ที่ชื่อว่า CategoryID เพื่อดึงค่าของชื่อสถานศึกษาตามประเภทที่เลือก

      ฝั่ง server (c#)

       [System.Web.Services.WebMethod]
          public static object getDataRetObj()
          {
      ///สมมุติให้ในส่วนนี้ เป็นการสร้างข้อมูลที่เป็น object และนำผลลัพธ์ที่ได้ส่งกลับไปยั่งฝั่ง Client เพื่อแสดงผล
        var obj = new { Category = 1, SubCategory=1, title = "มหาวิทยาลัยสงขลานครินทร์"};
        var objList = (new[] { obj }).ToList();
        objList.Add(new { Category = 1, SubCategory=2,  title = "มหาวิทยาลัยราชภัฎสงขลา" });
        objList.Add(new { Category = 2, SubCategory=3, title = "โรงเรียนมหาวชิราวุธ"});
        objList.Add(new { Category = 2, SubCategory=4, title = "โรงเรียนหาดใหญ่วิทยาลัย" });
      ////ดึงค่าชื่อสถานศึกษาตามรหัสประเภทสถานศึกษา CategoryID
              var results = from p in objList.Where(p=>p.Category==int.Parse(CategoryID))
                            select p;
      
              return results;
          }

      คำอธิบาย : เป็นตัวอย่างการสร้างเมธอดในฝั่งเซิร์ฟเวอร์ โดยในการสร้างเมธอดต้องมีการกำหนด [System.Web.Services.WebMethod] ไว้ในส่วนบนของเมธอด และมีชนิดเป็น static โดยในส่วนนี้ตามความเป็นจริงจะเป็นส่วนที่อาจมีการดึงมาจากฐานข้อมูลตามเงื่อนไขของพารามิเตอร์ที่ส่งมา เพื่อคืนค่ากลับไปให้ฝั่ง Client แสดงผล แต่ในที่นี้จะขอสร้างเป็นเพียงข้อมูลสมมุติของ object ขึ้นมาเท่านั้น

      ผลลัพธ์

      result3

    • การเรียกใช้เว็บเซอร์วิสตามแนวคิดของ Ajax ด้วย jQuery
      • ตัวอย่างที่ 1 : เป็นการเรียกใช้เมธอดจากเซอร์วิสในการแสดงผล

      ฝั่ง Client(aspx)

     <script type="text/javascript">
     function CallWebservice() {
                        var ddlMain = $("[id*=ddlMain]");
    
                        $.ajax({
                            type: "POST",
                            contentType: "application/json",
                            url: "WebServiceTest.asmx/getDataRetOddEven",
                            ///ส่งค่าพารามิเตอร์จากการเลือกประเภทชนิดข้อมูลตัวเลขให้กับเมธอดที่สร้างในเซอร์วิส
                            data: "{'CategoryID' : " + ddlMain.val() + "}",
                            dataType: "json",
                            beforeSend: function () {
                                $("#loadingmessage").show();
                            }, error: function (XMLHttpRequest, textStatus, errorThrown) {
                                alert("Request: " + XMLHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
                            },
                            success: function (response) {
                                $("#dvResult").html("");
                                $("#dvResult").append(response.d);
                                $("#loadingmessage").hide();
                            }
    
                        });
                    }
     </script>
     <asp:DropDownList ID="ddlMain" runat="server" onchange="CallWebservice()" >
     <asp:ListItem Selected="True" Value="-1">กรุณาเลือกประเภทตัวเลข</asp:ListItem>
     <asp:ListItem Value="0">เลขคู่</asp:ListItem>
     <asp:ListItem Value="1">เลขคี่</asp:ListItem>
     </asp:DropDownList>
    

    คำอธิบาย : เป็นตัวอย่างการเรียกใช้เมธอดจากเว็บเซอร์วิส เพื่อทำการดึงค่าข้อมูลตัวเลขตามประเภทของตัวเลขที่เลือกจาก dropdownlist ซึ่งในตัวอย่างนี้จะเป็นการเรียกใช้เมธอดที่มีชื่อว่า getDataRetOddEven ในเว็บเซอร์วิส WebServiceTest.asmx โดยลักษณะการเรียกใช้จะคล้ายกับตัวอย่างของการเรียกใช้เมธอดจากฝั่งเซิร์ฟเวอร์ที่กล่าวไว้แล้วก่อนหน้านี้ มีเพียงส่วนของการกำหนด url ที่เมธอดนั้นอยู่เท่านั้นที่ต้องให้มาอ้างจากเมธอดในเว็บเซอร์วิสแทน

    ฝั่งเว็บเซอร์วิส (WebServiceTest.asmx)

     [WebMethod]
        public string getDataRetOddEven(string CategoryID)
        {
             string result = "";
             int[] numbers = { 0, 1, 2, 3,4, 5, 6, 7,8, 9 };
    
             var results = from p in numbers.Where(p => p % 2 == int.Parse(CategoryID))
                          select p;
             foreach (var d in results)
             {
                 result = result + "Result is :" + d.ToString() + "
    ";
             }
             return result;
        }
    

    คำอธิบาย : เป็นตัวอย่างการสร้างเมธอดในเว็บเซอร์วิสเพื่อรับค่าชนิดของตัวเลข(คู่/คี่) เพื่อนำมาใช้ในการดึงข้อมูลตามเงื่อนไข โดยในการสร้างเมธอดต้องมีการกำหนด [WebMethod] ไว้ในส่วนบนของเมธอด และมีเพิ่ม [System.Web.Script.Services.ScriptService] ในส่วนบนของไฟล์ เพื่อให้สามารถรันสคริปต์ผ่านเว็บเซอร์วิสได้ โดยในส่วนนี้ตามความเป็นจริงจะเป็นส่วนที่อาจมีการดึงมาจากฐานข้อมูล เพื่อคืนค่ากลับไปให้ฝั่ง Client แสดงผล แต่ในที่นี้จะขอสร้างเป็นเพียงข้อมูลสมมุติของอาร์เรย์ที่เป็นข้อความขึ้นมาเท่านั้น
    หมายเหตุ : 

    ผลลัพธ์

    result4result5

    เพิ่มเติม : 
    Namespace ที่ต้องอ้างอิงเพิ่มเติมหากมีการใช้งานเกี่ยวกับ LINQ คือ using System.Linq;

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

    แหล่งข้อมูลอ้างอิง :
    http://www.seguetech.com/blog/2013/05/01/client-side-server-side-code-difference
    http://www.aspsnippets.com/Articles/Call-ASPNet-Page-Method-using-jQuery-AJAX-Example.aspx