Category: Developer

งานพัฒนาระบบ, เขียนโปรแกรม

  • Export ข้อมูลไฟล์ Excel ในแบบ Single และ Multiple sheet ด้วย ASP.NET(C#)

                ความเดิมตอนที่แล้ว ผู้เขียนได้พูดถึงความสำคัญของข้อมูล และวิธีการ Import ข้อมูลจากไฟล์ Excel กันไปพอสมควร สำหรับในบทความนี้ ผู้เขียนจะขอพูดถึงวิธีการส่งออกข้อมูล หรือที่เรามักเรียกกันติดปากว่า “Export ข้อมูล” กันบ้าง เพื่อให้ผู้พัฒนาที่มีความสนใจสามารถพัฒนาโปรแกรมได้ครบวงจรทั้งแบบนำเข้าและส่งออกข้อมูล โดยผู้เขียนจะไม่ขอพูดถึงในรายละเอียดที่ได้กล่าวไว้แล้วก่อนหน้า ผู้อ่านสามารถอ่านรายละเอียดเพิ่มเติมได้ในบทความ “เรียนรู้วิธีการ Import ข้อมูลในรูปแบบไฟล์ Excel ด้วย ASP.NET (C#)” สำหรับในบทความนี้ผู้เขียนจะเน้นในส่วนของการ Export ข้อมูลเท่านั้น ซึ่งจะมีการอธิบายใน 2 ลักษณะเช่นกัน คือ แบบ Single sheet และแบบ Multiple sheet เพื่อให้เห็นเป็นแนวทางและสามารถนำไปต่อยอดการพัฒนาเพิ่มเติมสำหรับแต่ละท่านได้

    กรณีส่งออกข้อมูล(Export) ซึ่งในบทความนี้จะแบ่งเป็น 2 ลักษณะ ดังนี้

    • การส่งออกข้อมูลแบบ Single-sheet ซึ่งมีขั้นตอนการทำงานไม่ซับซ้อนมากนัก ดังนี้
    protected void btnExport_Click(object sender, EventArgs e)
    
    {
    //// ตารางสมมติ สร้างขึ้นเพื่อให้ผู้พัฒนาเห็นภาพ หากเป็นกรณีใช้งานจริงจะเป็นข้อมูลที่ดึงจากฐานข้อมูลเพื่อ Export ในรูปแบบไฟล์ Excel
    DataTable table = new DataTable();
    table.Columns.Add("Name", typeof(string));
    table.Columns.Add("Latitude", typeof(decimal));
    table.Columns.Add("Longitude", typeof(decimal));
    table.Columns.Add("Description", typeof(string));
    table.Rows.Add("University1", 7.006923, 100.500238, "Desc1");
    table.Rows.Add("University2", 7.172661, 100.613726, "Desc2");
    
    
    
    StringBuilder sb = new StringBuilder();
    if (table.Rows.Count > 0)
    {
    string fileName = Path.Combine(Server.MapPath("~/ImportDocument"), DateTime.Now.ToString("ddMMyyyyhhmmss") + ".xls");
    
    //// ลักษณะการตั้งค่าเพื่อเชื่อมต่อด้วย OleDb ซึ่งในกรณีนี้ไฟล์ Excel จะต้องมีนามสกุลเป็น .xls แต่หากเป็นนามสกุลแบบ .xlsx ต้องเปลี่ยนการกำหนดค่าให้เป็น
    conString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + 
    fileName + ";Extended Properties='Excel 12.0;HDR=YES;IMEX=1;';";  แทน
    
    string conString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileName + ";Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=2\"";
    
    
     using (OleDbConnection con = new OleDbConnection(conString))
    {
    ////เขียนคำสั่งในการสร้างตาราง ซึ่งในที่นี้คือ WorkSheet ที่ต้องการ พร้อมทั้งกำหนดชื่อและชนิดของข้อมูลในแต่ละคอลัมน์
    string strCreateTab = "Create table University (" +
    " [Name] varchar(50), " +
    " [Latitude] double, " +
    " [Longitude] double, " +
    " [Description] varchar(200)) ";
    
    
    
    if (con.State == ConnectionState.Closed)
    {
    con.Open();
    }
    ////รันคำสั่งที่เขียนในการสร้างตาราง
    OleDbCommand cmd = new OleDbCommand(strCreateTab, con);
    cmd.ExecuteNonQuery();
    
    ////เขียนคำสั่งในการเพิ่มข้อมูล(insert) ข้อมูลในแต่ละฟิลด์ รวมทั้งประกาศพารามิเตอร์ที่ใช้ในการรับค่าข้อมูลที่อ่านได้
    string strInsert = "Insert into University([Name],[Latitude]," +
    " [Longitude], [Description]" +
    ") values(?,?,?,?)";
    
    OleDbCommand cmdIns = new OleDbCommand(strInsert, con);
    cmdIns.Parameters.Add("?", OleDbType.VarChar, 50);
    cmdIns.Parameters.Add("?", OleDbType.Double);
    cmdIns.Parameters.Add("?", OleDbType.Double);
    cmdIns.Parameters.Add("?", OleDbType.VarChar, 200);
    
    ////วนค่าที่ได้จากฐานข้อมูลและกำหนดค่าให้กับพารามิเตอร์และรันคำสั่งในการเพิ่มข้อมูลทีละรายการ
    foreach (DataRow  i in table.Rows)
    {
    cmdIns.Parameters[0].Value = i["Name"];
    cmdIns.Parameters[1].Value = i["Latitude"];
    cmdIns.Parameters[2].Value = i["Longitude"];
    cmdIns.Parameters[3].Value = i["Description"];
    cmdIns.ExecuteNonQuery();
    
    }
    }
    
    ////สร้างไฟล์ที่ต้องการดาวน์โหลดจากการอ่านที่ได้ทั้งหมด เพื่อบันทึกเป็นไฟล์ Excel ที่มีชื่อว่า University.xls
    
    byte[] content = File.ReadAllBytes(fileName);
    HttpContext context = HttpContext.Current;
    context.Response.BinaryWrite(content);
    context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    context.Response.AppendHeader("Content-Disposition", "attachment; filename=University.xls");
    Context.Response.End();
    }
    }
    • การส่งออกข้อมูลแบบ Multiple sheet ใช้ในกรณีที่มีข้อมูลที่ Export จำนวนมากและต้องการแบ่งข้อมูลที่มีอยู่ออกเป็น WorkSheet ย่อย ซึ่งหลักการทำงานจะคล้ายกับการส่งออกข้อมูลแบบ Single-sheet แต่จะซับซ้อนกว่าในส่วนของการวนค่าจากในฐานข้อมูลมาใส่ใน  WorkSheet โดยมีการกำหนดจำนวนแถวที่จะให้บันทึกในแต่ละ WorkSheet หากเกินค่าที่กำหนดจะสร้าง WorkSheet ใหม่และบันทึกข้อมูลลงไปใน WorkSheet นั้น
    static StringBuilder sqlInsert = new StringBuilder();
    static StringBuilder strScript = new StringBuilder();
    
    public static void ExportToMultipleXLSheets( System.String OutputFileName)
    {
    string connstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
    mOutputFileName + ";Extended Properties='Excel 8.0'";
    OleDbConnection xlConn = new OleDbConnection(connstr);
    
    try
    {
    xlConn.Open();
    ////ตารางสมมติเช่นเดียวกับที่ได้กล่าวไว้ในแบบการส่งออกข้อมูลแบบ Single sheet
    DataTable table = new DataTable();
    table.Columns.Add("EmployeeID", typeof(string));
    table.Columns.Add("Name", typeof(string));
    table.Columns.Add("LastName", typeof(string));
    table.Columns.Add("Position", typeof(string));
    
    table.Rows.Add("0001", "Jack", "Rayman", "Programmer");
    table.Rows.Add("0002", "John", "Wicky", "Programmer");
    table.Rows.Add("0003", "Jenifer", "Wincy", "Programmer");
    table.Rows.Add("0004", "Kate", "Wrapper", "Manager");
    table.Rows.Add("0005", "Bella", "Cole", "Programmer");
    table.Rows.Add("0006", "Sandy", "Stick", "Programmer");
    table.Rows.Add("0007", "Tom", "Runner", "Programmer");
    
    ////เป็นการเรียกใช้เมธอดที่ใช้ในการเตรียมคำสั่งที่จะใช้วนสร้างตาราง/Worksheet
    PrepareScript(table);
    
    ////เป็นเมธอดที่ใช้ในการเริ่มต้นทำงานคำสั่งในการ Export โดยจะมีการคำนวณจำนวนแถวของข้อมูลว่าเกินที่ระบุไว้หรือไม่ หากเกินกำหนดจะสร้าง WorkSheet ใหม่นั่นเอง
    StartExport(table, xlConn);
    
    if (xlConn != null)
    {
    if (xlConn.State == ConnectionState.Open) xlConn.Close();
    xlConn.Dispose();
    }
    
    ////อ่านค่าและ Export ไปยังไฟล์ที่มีชื่อว่า ExportMultiSheetData.xls
    byte[] content = File.ReadAllBytes(mOutputFileName);
    HttpContext context = HttpContext.Current;
    context.Response.BinaryWrite(content);
    context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    context.Response.AppendHeader("Content-Disposition", "attachment; filename=ExportMultiSheetData.xls"  );
    context.Response.End();
    
    }
    catch (Exception exp)
    {
    throw new Exception("ImportToMultipleXLSheets", exp.InnerException);
    }
    finally
    {
    if (xlConn != null)
    {
    if (xlConn.State == ConnectionState.Open) xlConn.Close();
    xlConn.Dispose();
    }
    }
    }
    o เมธอดในการเตรียมคำสั่งที่ใช้ในการสร้างตารางและเพิ่มข้อมูล
    private static string PrepareScript(DataTable DTable)
    {
    // เตรียมคำสั่งในการสร้าง WorkSheet
    sqlInsert.Length = 0;
    strScript.Length = 0;
    
    ////วนค่าเพื่ออ่านค่าคอลัมน์ที่มีในแต่ละตาราง
    for (int i = 0; i < DTable.Columns.Count; i++)
    {
    sqlInsert.Append( "[" + DTable.Columns[i].ColumnName + "],");
    strScript.Append("[" + DTable.Columns[i].ColumnName.Replace("'", "''") + "]");
    
    ////กำหนดชนิดของข้อมูลแต่ละคอลัมน์
    if (DTable.Columns[i].DataType.ToString().ToLower().Contains("int") || DTable.Columns[i].DataType.ToString().ToLower().Contains("decimal"))
    strScript.Append(" double");
    else
    strScript.Append(" text");
    strScript.Append(", ");
    }
    
    sqlInsert.Remove(sqlInsert.Length - 1, 1);
    strScript.Remove(strScript.Length - 2, 1);
    strScript.Append(") ");
    ////ผลที่ได้ : 
    sqlInsert >> [EmployeeID],[Name],[LastName],[Position]
    strScript >> [EmployeeID] text, [Name] text, [LastName] text, [Position] text ) 
    เพื่อนำไปใช้ในการ run คำสั่งการทำงานสร้าง (create table) และเพิ่มข้อมูล (insert)
    
    return strScript.ToString();
    }
    o เมธอดในการสร้างตาราง/WorkSheet ตามคำสั่งที่เตรียมไว้
    private static void CreateXLSheets(DataTable DTable,
    OleDbConnection xlConn, System.String XLSheetName)
    {
    // สร้าง WorkSheet ใหม่
    System.Text.StringBuilder SqlFinalScript = new System.Text.StringBuilder();
    
    OleDbCommand cmdXl = new OleDbCommand();
    try
    {
    SqlFinalScript.Length = 0;
    cmdXl.Connection = xlConn;
    
    ///สร้างคำสั่งในการสร้างตาราง/WorkSheet ขึ้นใหม่ โดยตั้งชื่อตามพารามิเตอร์ที่ได้รับมา
    SqlFinalScript.Append("CREATE TABLE " + XLSheetName + " (");
    SqlFinalScript.Append(strScript.ToString());
    cmdXl.CommandText = SqlFinalScript.ToString();
    cmdXl.ExecuteNonQuery();
    }
    catch (Exception xlSheetExp)
    {
    throw (new Exception("CreateXLSheetException", xlSheetExp.InnerException));
    }
    finally
    {
    cmdXl.Dispose();
    }
    }
    
    private static void StartExport(DataTable DTable, OleDbConnection xlConn)
    {
    Int64 rowNo = 0, xlSheetIndex = 1, TotalNoOfRecords = 0;
    System.String NewXLSheetName = "Sheet";
    System.Text.StringBuilder strInsert = new System.Text.StringBuilder();
    TotalNoOfRecords = DTable.Rows.Count;
    OleDbCommand cmdXl = new OleDbCommand();
    cmdXl.Connection = xlConn;
    for (int count = 0; count < DTable.Rows.Count; count++)
    {
    strInsert.Length = 0;
    ////กรณีที่เป็นแถวแรกจะทำการสร้าง WorkSheet ขึ้นใหม่ โดยเรียกใช้เมธอด CreateXLSheets
    if (rowNo == 0 )
    { CreateXLSheets(DTable, xlConn, NewXLSheetName + xlSheetIndex);
    }
    rowNo += 1;
    
    ////กรณีที่จำนวนแถวใน 1 WorkSheet เกินที่กำหนดไว้จะสร้างชีทใหม่ ในกรณีนี้คือจะสร้าง WorkSheet ใหม่ทุกๆ 3 แถว และเพิ่มลำดับของ index ของ WorkSheet คราวละ 1
    if (TotalNoOfRecords > 3 && rowNo > 3
    {
    xlSheetIndex += 1;
    CreateXLSheets(DTable, xlConn, NewXLSheetName + xlSheetIndex);
    rowNo = 1;
    }
    
    ////เขียนคำสั่งในการเพิ่มข้อมูลไปยัง WorkSheet ที่กำหนด โดยมีการแทนที่ค่าของคำสั่งที่เตรียมไว้แล้วก่อนหน้านี้ในตอนเรียกใช้เมธอด PrepareScript
    strInsert.Append("Insert Into [" + NewXLSheetName + xlSheetIndex.ToString() +  "$](" + sqlInsert.ToString() + ") Values (");
    
    
    foreach (DataColumn dCol in DTable.Columns)
    {
    if (dCol.DataType.ToString().ToLower().Contains("int"))
    {
    if (DTable.Rows[count][dCol.Ordinal].ToString() == "")
    strInsert.Append("NULL");
    else
    strInsert.Append(DTable.Rows[count][dCol.Ordinal]);
    }
    else if (dCol.DataType.ToString().ToLower().ToLower().Contains("decimal"))
    {
    if (DTable.Rows[count][dCol.Ordinal].ToString() == "")
    strInsert.Append("NULL");
    else
    strInsert.Append(DTable.Rows[count][dCol.Ordinal]);
    }
    else
    strInsert.Append("\"" +
    DTable.Rows[count][dCol.Ordinal].ToString().Replace("'",
    "''") + "\"");
    strInsert.Append(",");
    }
    strInsert.Remove(strInsert.Length - 1, 1);
    strInsert.Append(");");
    
    ////run คำสั่งที่สร้างขึ้นในการเพิ่มข้อมูลทีละรายการ
    cmdXl.CommandText = strInsert.ToString();
    cmdXl.ExecuteNonQuery();
    
    }
    }
    
    o เขียนการทำงานเมื่อกดปุ่ม “Export”
    protected void btnExport_Click(object sender, EventArgs e)
    { 
     string fileName = Path.Combine(Server.MapPath("~/ImportDocument"), Guid.NewGuid().ToString() + ".xls");
    //เรียกใช้เมธอดในการ Export ข้อมูลแบบ Multiple sheet
    ExportToMultipleXLSheets(fileName);
    
    }

    หลักการทำงานคร่าวๆในการ Export ข้อมูลใน 2 ลักษณะจะมีหลักการพื้นฐานคล้ายกัน ซึ่งจะสรุปได้ดังนี้

    • ดึงข้อมูลจากฐานข้อมูลมาเก็บไว้ใน Datadatable หรือ Dataset
    • กำหนดชื่อไฟล์ที่จะใช้ในการเชื่อมต่อกับ OleDb
    • เชื่อมต่อกับ OleDb โดยการกำหนดค่าต่างๆ เพื่อใช้ในการทำงานกับไฟล์ Excel นั้นๆ
    • เตรียมกำหนดคำสั่ง sql command ในการสร้างโครงสร้างตาราง(create table) และ เพิ่มข้อมูล (insert) ในตารางที่สร้างไว้ หากเป็นกรณีที่ต้องการสร้างแบบ Multiple sheet
      ก็จะทำการเตรียมชุดคำสั่งไว้ และนำไปวน run คำสั่งนั้นๆตามจำนวนรอบที่มีการสร้าง WorkSheet ใหม่นั่นเอง
    • สั่ง run คำสั่งดังกล่าว หากจำนวนแถวของข้อมูลเกินกว่าที่กำหนดต่อ 1 WorkSheet (ซึ่งในกรณีสมมติให้เป็น 3 แถว) จะทำการสร้าง WorkSheet ใหม่
    • อ่านค่าที่ได้จากไฟล์ที่เชื่อมต่อกับ OleDb และผ่านกระบวนการ Export ข้อมูล  มาเขียนเป็นไฟล์ Excel เพื่อให้ผู้ใช้สามารถบันทึกข้อมูลในรูปแบบไฟล์ที่ Export ได้
    • หากเป็นการ Export ข้อมูลแบบ Multiple sheet จำนวนของ WorkSheet จะขึ้นอยู่กับข้อมูลที่ดึงมาและจำนวนที่กำหนดไว้ต่อ 1 WorkSheet นั่นเอง
    หมายเหตุ : Namespace ที่ต้องอ้างอิงเพิ่มเติมในการใช้งานโค้ดที่กล่าวไว้ข้างต้น มีดังนี้
                –  System.IO
                –  System.Data.OleDb
                –  System.Data
                –  System.Text

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

    แหล่งข้อมูลอ้างอิง :
    http://www.codeproject.com/Articles/33271/Import-and-Export-to-Multiple-Worksheets
    http://dotnetawesome.blogspot.com/2013/11/how-to-import-export-database-data-from_18.html

  • เรียนรู้วิธีการ Import ข้อมูลในรูปแบบไฟล์ Excel ในแบบ Single และ Multiple sheet ด้วย ASP.NET (C#)

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

    กรณีนำเข้าข้อมูล(Import) ซึ่งในบทความนี้จะแบ่งเป็น 2 ลักษณะ ดังนี้

    • การนำเข้าข้อมูลแบบ Single sheet ถือเป็นการนำเข้าในแบบทั่วไป ไม่ซับซ้อนมากนัก
    ฝั่ง Client
    <%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="Excel.aspx.cs" Inherits="ExcelTest" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
    <title></title>
    </head>
    <body>
    <form id="form1" runat="server">
    <asp:FileUpload ID="FileUpload1" runat="server" />
    <div>
    <asp:GridView ID="GridView" runat="server">
    </asp:GridView>
    <asp:Button ID="btnImport" runat="server" 
    onclick="btnImport_Click" Text="Import" />
    </div>
    </form>
    </body>
    </html>
    ฝั่งเซิร์ฟเวอร์
    protected void btnImport_Click(object sender, EventArgs e)
    {
    try
    {
    ////เป็นการกำหนดชื่อของไฟล์ที่ต้องการจะบันทึกลงเซิร์ฟเวอร์ ซึ่งมีการระบุพาธรวมทั้งนามสกุลของไฟล์ตามไฟล์ที่รับเข้ามา
    string fileName =  Path.Combine(Server.MapPath("~/ImportDocument"), Guid.NewGuid().ToString() + Path.GetExtension(FileUpload1.PostedFile.FileName));
    
    ////บันทึกไฟล์ดังกล่าวลงเซิร์ฟเวอร์
    FileUpload1.PostedFile.SaveAs(fileName);
    
    string conString = "";
    
    string ext = Path.GetExtension(FileUpload1.PostedFile.FileName);
    
    ////เป็นส่วนของเงื่อนไขในการตั้งค่า ConnectionString ในการอ่านไฟล์ Excel ด้วย OleDb ซึ่งจะแยกด้วยนามสกุลของไฟล์ Excel ที่รับมา
    if (Path.GetExtension(ext) == ".xls")
    {
    conString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
     fileName + ";Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=2\"";
    }
    else if (Path.GetExtension(ext) == ".xlsx")
    {
    conString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + 
    fileName + ";Extended Properties='Excel 12.0;HDR=YES;IMEX=1;';";
    }
    
    ////เป็นการเปิดการเชื่อมต่อผ่าน OleDb
    OleDbConnection con = new OleDbConnection(conString);
    
    if (con.State == System.Data.ConnectionState.Closed)
    {
    con.Open();
    }
    
    
    DataTable dtExcelSchema;
    
    dtExcelSchema = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
     null);
    ////ดึงค่าชื่อของ Worksheet ที่อ่านมาจากไฟล์ Excel ที่รับเข้ามา
    string SheetName = dtExcelSchema.Rows[0]["TABLE_NAME"].ToString();
    
    ////เขียนคำสั่งในการดึงข้อมูลจาก Worksheet ดังกล่าว ซึ่งลักษณะการทำงานจะคล้ายกับการเขียนคำสั่ง sql command ในการดึงข้อมูลตารางโดยทั่วไป และเปรียบ Worksheet นั้นเป็นตาราง
    string query = "Select *  from [" + SheetName + "]";
    
    OleDbCommand cmd = new OleDbCommand(query, con);
    OleDbDataAdapter da = new OleDbDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    da.Dispose();
    con.Close();
    con.Dispose();
    ////เป็นการสมมติโครงการสร้างตาราง หากเป็นการทำงานจริงส่วนนี้จะหมายถึงตารางในฐานข้อมูลของแต่ละระบบ
    DataTable table = new DataTable();
    table.Columns.Add("EmployeeID", typeof(string));
    table.Columns.Add("EmployeeName", typeof(string));
    ////เป็นการวนค่าเพื่อบันทึกลงฐานข้อมูล แต่ในกรณีนี้จะเป็นเพียงแค่การเพิ่มแถวข้อมูลลงใน datatable ที่ชื่อ table เท่านั้น
    foreach (DataRow dr in ds.Tables[0].Rows)
    {
    //// dr["EmployeeID"].ToString() ชื่อของค่าฟิลด์ต้องตรงกับชื่อของคอลัมน์ใน Worksheet ที่อ่านมาจากไฟล์ Excel
    table.Rows.Add(dr["EmployeeID"].ToString(), dr["EmployeeName"].ToString());
    }
    ////นำค่าที่ได้แสดงในกริดวิว
    GridView.DataSource = table;
    GridView.DataBind();
    
    }
    catch (Exception)
    {
    throw;
    }
    }
    • การนำเข้าข้อมูลแบบ Multiple Sheet จะใช้ในกรณีที่มีจำนวนของ Worksheet ไม่จำกัด ขึ้นอยู่กับข้อมูล ซึ่งจะมีความซับซ้อนกว่าแบบแรก โดยหลักการทำงานโดยสรุปจะเป็นในลักษณะของการดึงข้อมูล Worksheet ที่มีทั้งหมดในไฟล์ Excel ที่อ่านได้ และนำไปเพื่อวนอ่านค่าข้อมูลในแต่ละชีทและนำค่าเหล่านั้นลงฐานข้อมูล ซึ่งจะอธิบายเป็นส่วนๆดังนี้
    1. การดึงข้อมูลชื่อ Worksheet ที่มีทั้งหมดในไฟล์ที่รับเข้ามา
     public static string[] getExcelSheets(string mFile)
    {
    try
    {
    string strXlsConnString;
    strXlsConnString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + mFile + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'";
    OleDbConnection xlsConn = new OleDbConnection(strXlsConnString);
    xlsConn.Open();
    
    ////เป็นการดึงค่าชื่อ Worksheet ของไฟล์ excel ที่กำลังอ่าน โดยตารางหรือชีทใน Excel จะมีสัญลักษณ์ $ ต่อท้ายชื่อเสมอ
    DataTable xlTable = new DataTable();
    xlTable = xlsConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    System.String strExcelSheetNames = "";
    string sheetName;
    for (int lngStart = 0; lngStart < xlTable.Rows.Count; 
    lngStart++)
    {
    ////เป็นการเอา '' ออกจากชื่อตาราง/worksheet ที่ดึงมาได้
    sheetName = xlTable.Rows[lngStart][2].ToString().Replace("'", "");
    
    ////เป็นการคัดกรองเฉพาะตัวที่เป็นตารางหรือworksheet เนื่องจากจบด้วย $
    if (sheetName.EndsWith("$"))
    {
    ////เป็นการเชื่อมตัวสุดท้ายด้วย ~ เพื่อใช้ในการตัดคำในขั้นตอนถัดไป
    strExcelSheetNames += sheetName.Substring(0, sheetName.Length - 1) + "~";
    }
    }
    ////เป็นการตัด  ~ ตัวสุดท้ายออกจากการเชื่อมคำ
    if (strExcelSheetNames.EndsWith("~"))
    {
    strExcelSheetNames = strExcelSheetNames.Substring(0,
    strExcelSheetNames.Length - 1);
    }
    xlsConn.Close();
    xlsConn.Dispose();
    char[] chrDelimter = { '~' };
    ////เป็นการตัดคำด้วย ~ และส่งค่าตัวแปร array ของ string ที่เป็นชื่อ worksheet ทั้งหมดที่อ่านได้กลับไป
    return strExcelSheetNames.Split(chrDelimter);
    
    }
    catch (Exception exp)
    {
    throw new Exception("Error while listing the excel" +
    " sheets from upload file " + exp.Message, exp);
    }
    }
    2. การอ่านค่าข้อมูลใน Worksheet ที่มีทั้งหมดในไฟล์ที่รับเข้ามา โดยมีการส่งค่าของชื่อ Worksheet และชื่อของไฟล์ที่อ่าน รวมทั้งฟิลด์เพิ่มเติมที่ต้องการระบุในการอ่านค่า ซึ่งจะทำงานในลักษณะเดียวกับการ Import แบบ Single sheet นั่นเอง
    public static DataSet getXLData(string xlSheetName,
    string xlFileName, string AdditionalFields)
    
    {
    try
    {
    string connstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" 
    +  xlFileName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'";
    OleDbConnection xlConn = new OleDbConnection(connstr);
    DataSet xlTDS = new DataSet("xlDataSet");
    xlConn.Open();
    OleDbDataAdapter xlDA = new OleDbDataAdapter("Select" + AdditionalFields +  " * from [" + xlSheetName + "$] ", xlConn);
    xlDA.Fill(xlTDS);
    xlConn.Close();
    xlConn.Dispose();
    
    ////เป็นการลบแถวที่มีค่าว่างออกจากการอ่านข้อมูลในไฟล์
    RemoveEmptyRows(xlTDS.Tables[0], (AdditionalFields.Length -
    
    AdditionalFields.ToLower().Replace(" as ", "").Length) / 4);
    
    return xlTDS;
    }
    catch (Exception e)
    {
    throw new Exception("Error while reading data from excel sheet", e);
    }
    }
    
    public static void RemoveEmptyRows(DataTable dtbl,
    
    System.Int32 intNumberOfFieldsToIgnore) ////เป็นการตรวจสอบค่าว่างในแต่ละแถว
    System.String strFilter = "";
    System.Int32 intAvgColsToCheck =
    Convert.ToInt32((dtbl.Columns.Count - intNumberOfFieldsToIgnore) * 0.75);
    if (intAvgColsToCheck < 3)
    {
    intAvgColsToCheck = dtbl.Columns.Count;
    }
    System.Int32 lngEnd = dtbl.Columns.Count;
    lngEnd = lngEnd - intAvgColsToCheck;
    
    ////เป็นการเชื่อมเงื่อนไขในการดึงข้อมูลว่าให้ฟิลด์ใดบ้างที่ห้ามเป็นค่าว่าง ในที่นี้จะทำการวนดูคอลัมน์ที่มีใน worksheet นั้นๆ และเชื่อมเป็นเงื่อนไข
    for (int lngStartColumn = dtbl.Columns.Count;
    lngStartColumn > lngEnd; lngStartColumn--)
    {
    strFilter += "[" + dtbl.Columns[lngStartColumn - 1].ColumnName +
    "] IS NULL AND ";
    
    }
    
    ////ทำในกรณีที่มีอย่างน้อย 1 คอลัมน์ถูกเพิ่มเป็นเงื่อนไขในการตรวจสอบค่าว่าง และลบคำว่า “AND” สุดท้ายออก เพื่อนำไปใช้งานในการกรองข้อมูลตามเงื่อนไขนี้
    if (strFilter.Length > 1)
    {
    strFilter = strFilter.Remove(strFilter.Length - 4);
    
    }
    DataRow[] drows = dtbl.Select(strFilter);
    ////ลบแถวเมื่อพบว่าค่าของฟิลด์นั้นๆ เป็นค่าว่าง
    foreach (DataRow drow in drows)
    {
    dtbl.Rows.Remove(drow);
    }
    }
    3. เขียนการทำงานเมื่อกดปุ่ม “Import”
    protected void btnImport_Click(object sender, EventArgs e)
    
    {
    ////เรียกใช้เมธอดในการบันทึกไฟล์ Excel ที่รับเข้ามาตามพาธของโฟลเดอร์ที่กำหนด
    string fileName = uploadXLFile(FileUpload, Server.MapPath("~/ImportDocument"));
    
    ////เรียกใช้เมธอดในการดึงค่าชื่อ WorkSheet ที่มีทั้งหมดในไฟล์ โดยมีตัวแปร array ชนิด string มารับข้อมูลดังกล่าว
    string[] listExcelSheet = getExcelSheets(fileName);
    
    DataSet DSTotal = new DataSet();
    ////วนลูปข้อมูล WorkSheet ตามชื่อในตัวแปร array และส่งค่าให้กับเมธอดที่ใช้ในการอ่านค่าข้อมูลในแต่ละ WorkSheet นั้น
    for (int i = 0; i < listExcelSheet.Count(); i++)
    {
    DataSet DS = getXLData(listExcelSheet[i], fileName, "");
    DSTotal.Merge(DS);
    
    }
    
    ////แสดงผลตัวอย่างข้อมูลที่อ่านได้ในกริดวิว ซึ่งในการใช้งานจริงในส่วนนี้ผู้พัฒนาจะต้องนำข้อมูลที่อ่านได้เหล่านี้วนบันทึกลงฐานข้อมูลเช่นเดียวกับที่กล่าวไว้ในการ Import ข้อมูลแบบ Single sheet นั่นเอง
    if (DSTotal.Tables[0].Rows.Count > 0)
    {
    GvData.DataSource = DSTotal.Tables[0];
    GvData.DataBind();
    
    }
    }
    }
    
     public static string uploadXLFile(FileUpload fileUpload, string mPath)
     {
     mPath = Path.Combine(mPath ,Guid.NewGuid().ToString() + Path.GetExtension(fileUpload.PostedFile.FileName));
     fileUpload.SaveAs(mPath);
     return mPath;
     }
    
    

     

    จะเห็นว่าจริงๆแล้วการทำงานใน 2 ลักษณะจะมีหลักการพื้นฐานคล้ายกัน ซึ่งจะสรุปได้ดังนี้

    • บันทึกไฟล์ Excel บนเซิร์ฟเวอร์ตามพาธที่กำหนดเพื่อให้สามารถเรียกอ่านค่าได้
    • เชื่อมต่อกับ OleDb โดยการกำหนดค่าต่างๆ เพื่อใช้ในการอ่านค่าจากไฟล์ Excel นั้นๆ
    • กำหนดคำสั่ง sql command ในการดึงข้อมูลจาก WorkSheet ซึ่งต้องมีการระบุชื่อของ WorkSheet นั้นๆ
    • สั่ง run คำสั่งดังกล่าวและนำค่าที่ได้ไปประมวลผลต่อไป
    • หากเป็นกรณีแบบ Multiple sheet เราจะไม่สามารถทราบจำนวนและชื่อของ WorkSheet ตายตัว จึงต้องเพิ่มการทำงานที่ทำการวนค่าเพื่อดึงข้อมูลชื่อ WorkSheet และทำตามกระบวนการต่อไป
    หมายเหตุ : Namespace ที่ต้องอ้างอิงเพิ่มเติมในการใช้งานโค้ดที่กล่าวไว้ข้างต้น มีดังนี้
                –  System.IO
                –  System.Data.OleDb
                –  System.Data
                –  System.Text

                สำหรับในบทความนี้ผู้เขียนจะขอเสนอวิธีการ Import ข้อมูลด้วยไฟล์ Excel ไว้เพียงเท่านี้ก่อน หากมีผู้รู้ท่านใดมีข้อเสนอแนะที่ต้องการแลกเปลี่ยนความรู้ร่วมกัน สามารถชี้แจงเพิ่มเติมได้เป็นกรณีศึกษาเพื่อการเรียนรู้ หากผิดพลาดประการใด ผู้เขียนขออภัยไว้ ณ ที่นี้ค่ะ และสำหรับท่านผู้พัฒนาที่มีความสนใจเกี่ยวกับการ Export ข้อมูลไฟล์ Excel ด้วย ASP.NET(C#) สามารถติดตามต่อได้ใน Part II นะคะ

    แหล่งข้อมูลอ้างอิง :
     http://www.codeproject.com/Articles/33271/Import-and-Export-to-Multiple-Worksheets
    http://dotnetawesome.blogspot.com/2013/11/how-to-import-export-database-data-from_18.html

     

  • รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 3 จบ)

    สำหรับผู้ที่ต้องการอ่านบทความก่อนหน้านี้ ตามลิงค์ด้านล่างได้เลยครับ:
    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 1)
    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 2)

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

    แนวคิดที่ 21 : Try Exposing Options instead of hiding them.
    _____แนวคิดนี้กล่าวถึงการนำเอา DropdownList มาใช้สำหรับให้ผู้ใช้เลือกตัวเลือกสำคัญๆในหน้าจอนั้น อาจทำให้ผู้ใช้เลือกตัวเลือกไม่ตรงตามที่ต้องการหรือเกิดข้อผิดพลาดจากการคลิก ทำให้ข้อมูลที่ได้ไม่ตรงตามต้องการ หรือส่งผลให้เป็นการโน้มน้าวใจผู้ใช้ให้เลือกตัวเลือกที่ถูกตั้งค่าเริ่มต้นกรณีที่ผู้ใช้ยังตัดสินใจไม่ได้ หรือในกรณีตัวเลือกที่มีให้เลือกนั้นมีจำนวนน้อย นอกจากการใช้ Dropdownlist ก็อาจเปลี่ยนมาใช้ Radio Button แทนได้ จะทำให้ผู้ใช้สามารถเห็นตัวเลือกได้อย่างชัดเจน ลดขั้นตอนในการทำงานของผู้ใช้ได้อีกด้วย DropdownList จึงเหมาะกับกรณีที่มีตัวเลือกที่หลากหลายมากกว่าidea014

    แนวคิดที่ 22 : Try Showing State instead of being state agnostic.
    _____ในหัวข้อที่ผ่านมาได้มีการกล่าวถึงการออกแบบให้รองรับสำหรับการแสดงผลแบบตารางกรณีที่ไม่มีค่าข้อมูลหรือเท่ากับ 0 นั้นเอง ซึ่งในหัวข้อนี้จะกล่าวถึงการออกแบบสำหรับกรณีที่มีข้อมูลที่นอกจากจะนำข้อมูลมาแสดงผลแล้ว การแจ้งสถานะของข้อมูลในแต่ละรายการ ก็เป็นส่วนหนึ่งที่จะช่วยให้ผู้ใช้รับทราบถึงผลการทำงานได้ เช่นการแสดงผลของรายการอีเมล์พร้อมทั้งแสดงสถานะว่าอ่านแล้วหรือยังไม่อ่าน รายการเรียกเก็บภาษีพร้อมแสดงสถานะว่ารายการนี้ชำระแล้ว เป็นต้น ส่งผลให้ผู้ใช้รู้ว่าควรจะดำเนินการส่วนไหนต่อ หรือสิ่งที่ได้กระทำลงไปนั้นได้ผลลัพธ์เป็นเช่นไรidea017

    แนวคิดที่ 23 : Try Direct Manipulation instead of contextless menus.
    _____ก่อนหน้านี้มีแนวคิดเกี่ยวกับการให้รวมเมนูต่างๆที่ซ้ำกัน มาไว้ในที่เดียวกันทั้งหมด เพื่อผู้ใช้จะสะดวกในการเรียกใช้เมนู แต่ในกรณีของแนวคิดนี้จะเป็นการให้เราแยกเมนูต่างๆลงไปยังรายการนั้นๆ เมื่อข้อมูลในแต่ละรายการ มีการเรียกใช้เมนูที่ไม่เหมือนกันหรือผู้ใช้สามารถจัดการกับข้อมูลแต่ละรายการได้มากน้อยไม่เท่ากัน การออกแบบให้เมนูอยู่คู่กับรายการจึงเป็นสิ่งที่ทำให้ผู้ใช้ไม่สับสนได้ว่าทำไมบางรายการถึงกดใช้เมนูในแถบเมนูไม่ได้ และลดขั้นตอนที่ผู้ใช้ต้องทำการเลือกรายการก่อนถึงจะไปเรียกใช้เมนูได้ ถามว่าแนวคิดไหนผิด ผู้เขียนขอตอบว่าไม่มีแบบไหนผิด แต่ขึ้นอยู่กับการนำไปใช้งานมากกว่าครับidea019

    แนวคิดที่ 24 : Try Opt-Out instead of opt-in.
    _____ในการออกแบบเงื่อนไขแบบ 2 ตัวเลือกนั้น ปัจจุบันส่วนใหญ่นักออกแบบจะใช้ Checkbox มาใช้ในการเลือกเงื่อนไขกันอย่างแพร่หลาย เช่น ให้ติ๊กยอมรับผล หรือให้ติ๊กถ้าต้องการอีเมล์ตอบรับจากระบบ เป็นต้น แต่ในความเป็นจริงแล้ว การที่เราออกแบบโดยใช้ Checkbox จะทำให้ผู้ใช้เสียโอกาสต่างๆพอสมควรและเป็นการแสดงตัวเลือกได้ไม่ชัดเจนนักเนื่องจากบางครั้งผู้ใช้อาจลืมไม่ได้เลือกติ๊กรายการต่างๆ การเปลี่ยนมาใช้ Radio Button เราจะสามารถทราบถึงผลลัพธ์ ที่แน่นอนกว่าและลดกรณีที่ผู้ใช้ลืมไม่ได้ติ๊กเลือกรายการที่ต้องการได้ และไม่เป็นการเอารัดเอาเปรียบผู้ใช้อีกด้วยidea026

    แนวคิดที่ 25 : Try Smart Defaults instead of asking to do extra work.
    _____มาตั้งค่าเริ่มต้นกันเถอะ เมื่อพูดถึงค่าเริ่มต้นในการกรอกข้อมูลแล้ว อาจเป็นสิ่งที่ทำได้ค่อนข้างยาก ยิ่งเป็นระบบที่กลุ่มผู้ใช้หลากหลาย การกำหนดค่าเริ่มต้นจึงทำได้ยากมาก โดยส่วนใหญ่จะยึดค่าเริ่มต้นตามกลุ่มผู้ใช้งานส่วนใหญ่ ซึ่งค่าเหล่านี้ต้องได้มาจากการรวบรวมข้อมูลของกลุ่มผู้ใช้และใช้ประสบการณ์ของผู้ออกแบบมากพอสมควร ซึ่งข้อนี้ผู้เขียนคิดว่าต้องระวังเป็นพิเศษ ถ้าออกแบบมาดีตรงตามกลุ่มผู้ใช้ส่วนใหญ่ ก็จะส่งผลให้ผู้ใช้สะดวกสบายในการกรอกข้อมูลยิ่งขึ้นไปด้วย แถมเป็นการลดเวลาในการใช้งานระบบไปในตัว แต่ถ้าออกแบบมาไม่ดี จะส่งผลให้ผู้ใช้เสียเวลามากขึ้นตามไปด้วยเช่นกันidea028

    แนวคิดที่ 26 : Try Inline Validation instead of delaying errors.
    _____การกรอกข้อมูลมักจะมาคู่กับการออกแบบเงื่อนไขเพื่อรองรับความผิดพลาดจากการกรอกข้อมูล ซึ่งผู้ออกแบบสามารถใช้เครื่องมือ Validater มาช่วยในการตรวจสอบข้อมูลตามเงื่อนไขที่เราต้องการได้ และเมื่อผู้ใช้งานกรอกข้อมูลไม่ตรงตามเงื่อนไข ก็ควรที่จะแจ้งเตือนความผิดพลาดทันทีเมื่อจบการทำงานในส่วนของการกรอกข้อมูลช่องนั้นๆ แทนการแจ้งเตือนความผิดพลาดทั้งหมดภายหลัง เพื่อผู้ใช้ไม่ต้องเสียเวลาในการย้อนกลับมาตรวจสอบข้อมูลว่าส่วนไหนของรายการที่ผิดพลาดไป และลดความกลัวหรือกังวลให้กับผู้ใช้ได้กรณีที่ผู้ใช้ทราบผลการแจ้งเตือนของข้อมูลที่ผิดพลาดหลายรายการพร้อมๆกันidea033

    แนวคิดที่ 27 : Try Forgiving Inputs instead of being strict with data.
    _____แนวคิดนี้ต่อเนื่องมาจากข้อก่อนหน้านี้ที่พูดถึงการตั้งค่าเงื่อนไขเพื่อตรวจสอบข้อมูลที่ผู้ใช้กรอกเข้าสู่ระบบ ซึ่งควรออกแบบให้มีความหลากหลายในการกรอกข้อมูลแทนที่การจำกัดเงื่อนไขในการกรอกแบบตายตัว ซึ่งจะทำให้ผู้ใช้รู้สึกไม่เป็นมิตรกับระบบได้ แต่การออกแบบให้รองรับการกรอกข้อมูลหลายรูปแบบนั้น ค่อยข้างจะทำได้ยากและเสียเวลาในส่วนของการพัฒนาโปรแกรม เช่นการกรอกข้อมูลเบอร์โทรศัพท์ รูปแบบในการกรอกค่อนข้างมีหลากหลาย ไม่ว่าจะใส่วงเล็บ ใส่เครื่องหมายขีดกลาง แม้กระทั้งเว้นวรรคระหว่างกลุ่มตัวเลข ในด้านการเขียนโปรแกรมอาจเขียนได้ยากพอสมควร แต่สำหรับด้านการใช้งานถือว่าเป็นการอำนวยความสะดวกและลดเวลาในการกรอกข้อมูลแก่ผู้ใช้ได้เช่นกัน idea034

    แนวคิดที่ 28 : Try Progressive Disclosure instead of overwhelming.
    _____ในการออกแบบแบบสอบถามผ่านระบบนั้น ผู้ออกแบบไม่ควรสร้างแบบสอบถามที่แสดงคำถามทั้งหมดให้ผู้ใช้ตอบถึงแม้บางเงื่อนไข ผู้ใช้ไม่จำเป็นที่จะตอบคำถามในข้อนั้นๆ หรือคำถามดังกล่าวไม่เกี่ยวข้องกับผู้ใช้เลย การออกแบบให้การตอบคำถามข้อนั้นๆเป็นการนำไปสู่คำถามข้อถัดไปจะช่วยลดคำถามที่ไม่จำเป็นกับผู้ใช้ได้ ส่งผลให้ผู้ใช้ไม่เกิดความสับสนหรือรู้สึกไม่ดีกับการตอบแบบสอบถามidea043

    แนวคิดที่ 29 : Try Useful Calculations instead of asking to do math.
    _____แนวคิดนี้เป็นตัวช่วยที่เอื้ออำนวยความสะดวกให้กับผู้ใช้ได้คล้ายๆกับกรณีที่ให้มีการแจ้งเตือนสถานะของรายการข้อมูล โดยจะกล่าวถึงการคำนวนค่าต่างๆที่สามารถทำได้ในระบบให้ผู้ใช้ทราบได้ เช่นการแจ้งเตือนหมดอายุการเป็นสมาชิกในรายการต่างๆ การบอกระยะเวลาของอีเมล์ที่ได้รับแทนการบอกเป็นวันเวลาแบบตรงๆซึ่งผู้ใช้ต้องเสียเวลาในการคิดคำนวนว่าอีเมล์ดังกล่าวมาถึงนานแล้วหรือไม่ เราอาจนำเอาแนวคิดก่อนหน้านี้มาใช้ร่วมกันได้ เพื่อเพิ่มความสะดวกสบายให้แก่ผู้ใช้งานมากยิ่งขึ้นidea053

    แนวคิดที่ 30 : Try Responsive Layouts instead of static ones.
    _____เนื่องด้วยปัจจุบันความก้าวหน้าทางเทคโนโลยีและการให้บริการอินเตอร์เน็ตความเร็วสูงมีการพัฒนาอย่างต่อเนื่อง การเข้าถึงข้อมูลข่าวสารเป็นไปได้อย่างรวดเร็วและทั่วถึง รวมถึงในปัจจุบันได้มีเครื่องมือสื่อสารหลายขนาดและรูปแบบ ไม่ว่าจะเป็นโทรศัพท์ Tablet หรือแม้แต่โทรทัศน์เองก็สามารถใช้งานอินเตอร์เน็ตได้เช่นกัน ซึ่งมีคุณสมบัติที่แตกต่างกันออกไป เช่น ความกว้าง ความสูง ความละเอียดหน้าจอ เป็นต้น ทำให้การออกแบบหน้าจอต้องคำนึงถึงการแสดงผลที่รองรับกับอุปกรณ์ต่างๆเช่นกันแทนการออกแบบให้หน้าจอแสดงผลได้เพียงบนคอมพิวเตอร์อย่างเดียว ส่งผลให้ผู้ใช้ได้รับความสะดวกสบายในการใช้งานไม่ว่าจากคอมพิวเตอร์หรือโทรศัพท์ก็ตามidea070

    สรุปส่งท้าย
    _____สุดท้ายนี้ต้องขอขอบคุณทุกท่านที่อ่านมาจนจบทั้ง 3 ตอน ซึ่งจริงๆแล้วยังมีอีกหลายแนวคิดที่ผู้เขียนไม่ได้นำเอามาเขียนในชุดบทความนี้ ผู้ที่สนใจสามารถเข้าไปอ่านได้ที่ GoodUI.com เลยครับ และขออภัยถ้าอ่านแล้วไม่เข้าใจหรือผู้เขียนแปลความหมายผิดไปจากต้นฉบับ และถ้าต้องการเสริมในหัวข้อไหน สามารถมา Comment บอกกันได้ครับ จะรู้สึกเป็นเกียรติอย่างยิ่ง สุดท้ายนี้หวังว่าชุดบทความนี้จะเป็นประโยชน์กับผู้อ่านไม่มากก็น้อยนะครับ

    ขอบคุณครับ

  • รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 2)

    ใครยังไม่ได้อ่านตอนที่ 1 แนะนำให้อ่านก่อนครับ ที่: รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 1)

    มาต่อกันกับบทความรวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ ตอนที่ 2 ซึ่งในชุดบทความนี้จะมีทั้งหมด 3 ตอน เพื่อไม่ให้เป็นการเสียเวลาเราไปเริ่มข้อต่อไปกันเลยดีกว่าครับ

    แนวคิดที่ 11 : Try Merging Similar Functions instead of fragmenting the UI.
    _____แนวคิดนี้จะพูดถึงการจัดกลุ่มเมนูหรือลิงค์ที่มีการทำงานไปยังส่วนเดียวกันหรือเปิดไปทำงานในอีกหน้าจอ การออกแบบให้สิ่งเหล่านี้กระจายไปอยู่ตามจุดต่างๆของหน้าจอ อาจทำให้ดูเหมือนเข้าถึงได้ง่าย แต่จริงๆแล้วกลับส่งผลให้หน้าจอดูรกขึ้นมาโดยทันที และอาจจะทำให้ผู้ใช้งานสับสนได้กับเมนูที่มีซ้ำกันในหน้าจอ การย้ายเมนูหรือลิงค์ดังกล่าวมาอยู่รวมกันเป็นเมนูเดียวหรือสร้างเป็นเมนูย่อยจะทำให้หน้าจอของเรามีพื้นที่ใช้งานมากขึ้นด้วยครับidea003

    แนวคิดที่ 12 : Try Repeating Your Primary Action instead of showing it just once.
    _____สำหรับนักออกแบบ UI หลายท่าน การออกแบบให้มีปุ่มหรือลิงค์หลักกับผู้ใช้เพียงจุดเดียวเป็นสิ่งที่ดูเหมาะสมแล้ว แต่เมื่อนำไปใช้งานจริงบางกรณีอาจทำให้ผู้ใช้งานหน้าจอไม่สะดวกในการใช้งาน มักจะเกิดกับหน้าจอที่มีข้อมูลมากส่งผลให้เกิดสกอร์บาร์ขึ้น เมื่อผู้ใช้งานเลื่อนหน้าจอขึ้นลง ปุ่มหรือลิงค์หลักอาจหายไปจากหน้าจอได้ ส่งผลให้ผู้ใช้งานต้องเลื่อนหน้าจอไปมาเพื่อที่จะกลับไปกดปุ่มหรือลิงค์ดังกล่าว กรณีนี้ควรเพิ่มปุ่มหรือลิงค์ขึ้นมาในส่วนล่างสุดและบนสุดของหน้าจอ โดยปุ่มหรือลิงค์ดังกล่าวควรมีหน้าตาที่เหมือนหรือคล้ายกันเพื่อไม่ให้ผู้ใช้เกิดความสับสนidea005

    แนวคิดที่ 13 : Try Suggesting Continuity instead of false bottoms.
    _____จากที่กล่าวไปแล้วก่อนหน้านี้ในการออกแบบรูปแบบการแสดงผลของบทความแบบคอลัมน์เดียวนั้น กรณีที่บทความนั้นยาวมาก หรือเป็นบทความหลายๆเรื่องต่อๆกันไปนั้น ผู้ออกแบบหน้าจอควรมีการเพิ่มช่องว่างหรือลำดับที่บอกถึงการเปลี่ยนแปลงของบทความเมื่อมีการขึ้นเรื่องใหม่หรือย่อหน้าใหม่ แทนการออกแบบให้บทความนั้นๆยาวต่อกันไปเรื่อยๆ เมื่อผู้ใช้เข้ามาอ่านบทความแล้วจะได้ไม่เกิดความสับสนหรือหาจุดที่อ่านต่อไม่เจอเมื่อมีการเลื่อนสอร์บาร์ แต่ข้อควรระวังคือ อย่าใส่ช่องว่างระหว่างบทความมากจนเกินไป จนทำให้ผู้ใช้รุ้สึกว่าบทความนั้นๆจบแล้วidea015

    แนวคิดที่ 14 : Try Visual Hierarchy instead of dullness.
    _____แนวคิดนี้เป็นอีกวิธีหนึ่งที่น่าสนใจในการออกแบบหน้าจอแสดงผลบทความแบบคอมลัมน์เดียว คือการจัดตัวนำสายตาหรือก็คือการจัดหน้าจอแบบการย่อหน้าเป็นลำดับชั้น ถ้านึกภาพไม่ออกให้นึกถึงการใส่เลขที่หัวข้อข้อย่อยในเอกสารจำพวก MS Word จะมีการเลื่อนระดับย่อหน้าเข้าไปเรื่อยๆตามลำดับชั้นที่เล็กลงไป วิธีนี้จะง่ายกับผู้ที่กำลังอ่านบทความได้ เพราะผู้อ่านจะรับรู้ได้ว่าบทความนี้จะจบลงที่ส่วนไหนของหน้าจอและผู้ใช้อาจไม่ต้องเรียนรู้เพิ่มเติมเลย เราสามารถนำวิธีการนี้ไปใช้ร่วมกับการออกแบบในข้อก่อนหน้านี้ได้idea031

    แนวคิดที่ 15 : Try Grouping Related Items instead of disordering.
    _____การจัดกลุ่มรายการเครื่องมือการใช้งานพื้นฐานก็เป็นอีกสิ่งหนึ่งที่จำเป็นสำหรับการออกแบบเพื่อผู้ใช้โดยเฉพาะ ซึ่งการจัดกลุ่มควรยึดตามหลักพื้นฐานทั่วไปในชีวิตประจำวัน เช่นส้อมต้องคู่กับช้อน เป็นต้น หรือจัดกลุ่มตามประสบการ์ณการทำงานของผู้ใช้ เพราะเป็นสิ่งที่ผู้ใช้เข้าใจได้โดยไม่ต้องเรียนรู้เพิ่มเติม การจัดกลุ่มและเรียงลำดับเครื่องมือจึงช่วยให้ผู้ใช้เข้าใจการทำงานของเครื่องมือๆนั้นได้มากขึ้นและสะดวกยิ่งขึ้นidea032

    แนวคิดที่ 16 : Try Thanking instead of simply confirming completion.
    _____การขอบคุณดูเป็นเรื่องปกติทั่วไป แต่เมื่อนำมาใช้ใน UI ของเราจะเป็นสิ่งหนึ่งที่ช่วยยกระดับ UI ของเราให้ดูดียิ่งขึ้น โดยให้มีการออกแบบการแสดงผลข้อความขอบคุณเมื่อผู้ใช้มีการกระทำบางอย่างที่เราต้องการในหน้าจอนั้นๆ นอกจากการแสดงผลรับหรือรายงานผลว่าเสร็จสิ้นแล้ว การเพิ่มข้อความขอบคุณผู้ใช้ จะทำให้ผู้ใช้เกิดความรู้สีกว่าตัวเองมีคุณค่าและได้รับความสนใจจากระบบของเรา ส่งผลให้ความรู้สึกของผู้ใช้ต่อระบบในแง่ดีเพิ่มมากขึ้นตามไปด้วย และเป็นการกระตุ้นให้ผู้ใช้กลับมาใช้งานระบบของเราอีกครั้งidea052

    แนวคิดที่ 17 : Try Softer Prompts instead of modal windows.
    _____Popup ถือเป็นส่วนหนึ่งในการตอบโต้กับผู้ใช้ ซึ่งผู้เขียนได้กล่าวถึงไปแล้วในข้อก่อนหน้านี้ ซึ่งในปัจจุบัน popup ที่นักออกแบบนิยมใช้งานกันอย่างแพร่หลายก็คงไม่พ้น modal popup ข้อดีของมันคือไม่มีการเรียกใช้จาวาสคริปแบบ popup รุ่นก่อนๆส่งผลให้รองรับการทำงานทุกเบราเซอร์และจุดเด่นอีกอย่างคือการล๊อคหน้าจอไม่ให้ผู้ใช้งานทำงานในส่วนของเบื้องหลังได้กรณีที่ popup ทำงานอยู่ แต่จุดเด่นข้อนี้จะกลายเป็นข้อเสียทันทีเมื่อเรานำไปใช้แบบผิดวิธี เช่นการ popup ให้กรอกข้อมูล ที่มีความเกี่ยวเนื่องกับข้อมูลที่อยู่ด้านหลัง popup ซึ่งส่งผลให้ผู้ใช้ย้อนไปดูข้อมูลหรือนำข้อมูลดังกล่าวมาอ้างอิงในการตัดสินใจได้ หรือกรณี popup ทำการแจ้งเตือนอัตโนมัติเมื่อผู้ใช้ไม่ทำการบันทึกข้อมูลเป็นเวลานานๆ หรือผู้ใช้ที่กำลังจดจ่อกับการกรอกข้อมูลหรือกำลังทำงานบางอย่างกับหน้าจออยู่ เมื่อมีการแสดผล popup ขึ้นมา จะเป็นการรบกวนสมาธิหรือขัดจังหวะผู้ใช้งานได้ ส่งผลให้ผู้ใช้งานไม่พอใจหรือตกใจได้ ดังนั้นการนำ popup ประเภทนี้ไปใช้งาน ผู้ออกแบบควรพิจารณาว่าเหมาะสมกับหน้าจอนั้นๆด้วยหรือไม่idea045

    แนวคิดที่ 18 : Try Expectation Setting instead of being ignorant.
    _____การออกแบบหน้าจอให้มีการแจ้งเตือนสถานะของผู้ใช้ในปัจจุบันหรือการแสดงให้ผู้ใช้ทราบว่ากำลังทำงานอยู่ในขั้นตอนไหน และยังเหลืออีกกี่ขั้นตอน หรืออาจออกแบบไปถึงขั้นตอนที่บอกว่าจะได้อะไรในการทำงานในหน้าจอนี้ ส่งผลให้ผู้ใช้งานระบบรู้ถึงจุดประสงค์ของการทำงานในแต่ละขั้นตอนหรือหน้าจอได้ และยังทำให้ผู้ใช้ไม่รู้สึกเป็นกังวลว่ายังเหลือสิ่งที่ต้องทำอีกมากน้อยเพียงใดหรือทำไปเพื่ออะไรidea059

    แนวคิดที่ 19 : Try Providing Feedback instead of silence.
    _____สำหรับแนวคิดนี้ อาจจะดูเป็นเรื่องที่ขาดไม่ได้ในการออกแบบหน้าจอ เพื่อใช้ในการตอบโต้กับผู้ใช้ ทำให้ผู้ใช้รู้สึกว่าระบบมีความเป็นมนุษย์มากขึ้น ซ้ำยังช่วยให้ผู้ใช้ทราบถึงผลลัพธ์จากการกระทำบางอย่างของผู้ใช้ด้วย เช่นเมื่อมีการบันทึกหรือลบข้อมูล หลังจากทำกระบวนการเสร็จสิ้นแล้ว ก็ควรมีข้อความแจ้งเตือนบอกผู้ใช้ให้ทราบถึงผลลัพธ์ว่าสำเร็จลุล่วงหรือไม่ เป็นต้น ส่วนรูปแบบการแสดงผลตอบสนองกับผู้ใช้ก็สามารถออกแบบได้หลายรูปแบบไม่ว่าจะเป็น ในส่วนของ popup หรือแถบข้อความแจกเตือนในหน้าจอidea061

    แนวคิดที่ 20 : Try Explaining instead of assuming the obvious.
    _____แนวคิดข้อนี้จะพูดถึงเกี่ยวกับการใช้คำแนะนำมาช่วยขยายความหัวข้อของการกรอกฟอร์มข้อมูล โดยปกติการออกแบบฟอร์มกรอกข้อมูลจะประกอบไปด้วยส่วนของหัวข้อและส่วนของกล่องข้อความหรือเครื่องมืออื่นๆเช่น dropdownlist checkbox เป็นต้น เพื่อทำให้ฟอร์มนั้นๆดูกระชับน่าใช้งาน แต่บางครั้งคำที่เรานำมาเขียนเป็นหัวข้อนั้นเราคิดว่าอธิบายความหมายหรือสิ่งที่เราต้องการได้หมดครบถ้วนแล้ว แต่สำหรับผู้ใช้บางคำอาจทำให้เกิดความสับสนหรือเข้าใจผิด ส่งผลให้ข้อมูลที่ได้มาจากฟอร์มอาจผิดพลาด การใช้คำมาช่วยอธิบายเพิ่มเติมหรือเขียนเป็นคำถามปลายเปิด จะช่วยให้ผู้ใช้เข้าใจสิ่งที่เราต้องการจากผู้ใช้มากขึ้นidea068


    สำหรับบทความชุด “รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่” ตอนที่ 2 ขอจบแต่เพียเท่านี้ สามารถอ่านตอนที่ 3 ได้ตามลิงคด้านล่างเลยครับ

    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 3 จบ)

    ขอบคุณครับ

  • มาทำความรู้จักกับ Validator ใน ASP.NET กันเถอะ

                ในการพัฒนาโปรแกรมโดยทั่วไป สิ่งหนึ่งที่นักพัฒนาควรคำนึงถึงและให้ความสำคัญในการรับค่าข้อมูลจากผู้ใช้ คือ การตรวจสอบความถูกต้องของข้อมูลที่รับเข้า เพื่อให้มั่นใจว่าข้อมูลที่ได้มาจะเป็นประโยชน์ สามารถนำไปใช้ในการประมวลผลต่อไปได้ และลดปัญหาการเก็บข้อมูลขยะไว้ในฐานข้อมูลซึ่งอาจทำให้ฐานข้อมูลมีขนาดใหญ่เกินความจำเป็น อีกทั้งยังถือเป็นการป้องกันความผิดพลาดที่อาจจะเกิดขึ้นจากการเก็บข้อมูลที่ไม่ถูกต้อง ในกรณีเลวร้ายอาจมีผลกระทบทำให้ฐานข้อมูลเกิดความเสียหายได้ ดังนั้น การตรวจสอบความถูกต้องของข้อมูลก่อนบันทึกถือเป็นสิ่งหนึ่งที่นับว่ามีความสำคัญสำหรับนักพัฒนาที่จะต้องคำนึงถึงในการเก็บข้อมูลจากผู้ใช้
                วิธีที่ใช้ในการตรวจสอบสามารถทำได้หลายวิธี ซึ่งเดิมผู้พัฒนาจะต้องเขียน Client-Script (อาจจะเป็น JavaScript VBScript หรือ JQuery) เพื่อใช้ในการตรวจสอบเองทั้งหมด โดยวิธีการนี้ผู้พัฒนาจะต้องมีความรู้เกี่ยวกับ Client Script เหล่านั้นพอสมควร และอาจต้องใช้เวลานานในกรณีที่มีการตรวจสอบที่ซับซ้อน แต่นับว่าเป็นโชคดีของนักพัฒนาที่ใช้เครื่องมือ ASP.NET ในการพัฒนาโปรแกรม เนื่องจาก .NET framework ได้จัดเตรียมเครื่องมือในการตรวจสอบเหล่านี้ไว้ให้เรียบร้อยแล้วตั้งแต่เวอร์ชั่น Visual Studio .NET 2003 เพื่ออำนวยความสะดวกให้กับผู้พัฒนาสามารถหยิบมาใช้ได้โดยง่าย ทำให้การตรวจสอบไม่ใช่เรื่องยากและซับซ้อนอีกต่อไป และยังถือเป็นการประหยัดเวลา ไม่จำเป็นต้องเขียนสคริปต์ตรวจสอบเองทั้งหมด เพียงกำหนด properties ต่างๆที่จำเป็นให้ validator เหล่านั้นและเลือกใช้ให้เหมาะสมกับความต้องการก็จะสามารถตรวจสอบความถูกต้องของข้อมูลได้โดยง่ายดาย ผู้เขียนจึงเห็นว่าการทำความเข้าใจกับความสามารถของ validator เหล่านี้ถือเป็นสิ่งสำคัญเพื่อช่วยในการตัดสินใจเลือก validator มาใช้ให้เหมาะสมกับงานและความต้องการในการตรวจสอบนั้นๆ
                หากแบ่ง Validator control ที่มีใน ASP.NET ตามลักษณะการทำงาน สามารถแบ่งออกได้เป็น 2 ลักษณะ ดังนี้

    การตรวจสอบข้อมูลในฝั่ง Client โดยการตรวจสอบข้อมูลในลักษณะนี้ จะเกิดขึ้นทันทีเมื่อผู้ใช้มีการป้อนข้อมูลเข้ามาและการตอบโต้นี้จะยังไม่มีการส่งค่าไปยังฝั่ง server ซึ่งข้อดีของการตรวจสอบในลักษณะนี้ คือ สามารถโต้ตอบกับผู้ใช้ในเวลาอันรวดเร็วและแสดงให้ผู้ใช้ทราบได้ทันทีว่าเกิดข้อผิดพลาดในการป้อนข้อมูล โดยไม่ต้องรอให้มีการเรียกใช้ไปยังฝั่งเซิร์ฟเวอร์ก่อนจึงจะตอบสนองกลับมายังผู้ใช้ และยังถือเป็นการลดจำนวนครั้งในเรียกใช้ไปยังฝั่งเซิร์ฟเวอร์โดยไม่จำเป็น ซึ่งหลักการทำงานของ Validator ของ ASP.NET ในลักษณะนี้ คือ Validator จะทำหน้าที่แปลง Validation เป็น JavaScript และถูกทำงานที่ฝั่ง Client ซึ่งจะขึ้นอยู่กับการทำงานของ Validation Control แต่ละประเภทที่ใช้นั่นเอง
    การตรวจสอบข้อมูลในฝั่งเซิร์ฟเวอร์ โดยการตรวจสอบในลักษณะนี้ จะเกิดขึ้นที่ฝั่งเซิร์ฟเวอร์ ซึ่งมีข้อดี คือ สามารถเพิ่มความปลอดภัยและความแม่นยำในการตรวจสอบอีกระดับก่อนจะบันทึกข้อมูลเหล่านั้นลงฐานข้อมูลในกรณีที่อาจมีข้อมูลผิดพลาดที่ตกสำรวจมาจากการตรวจสอบในฝั่ง Client โดยการตรวจสอบในลักษณะนี้ ผู้พัฒนาจะเป็นผู้เขียนโค้ดในการตรวจสอบตามเงื่อนไขที่กำหนดขึ้นเอง ซึ่ง ASP.NET ก็ได้มีการเตรียม validation control เพื่อรองรับการตรวจสอบในลักษณะนี้ไว้แล้วเช่นกัน

    โดย Validation control ที่มีเตรียมไว้ใน ASP.NET ประกอบด้วย Validator ชนิดต่างๆ ดังนี้
    • RequiredFieldValidator
    • RegularExpressionValidator
    • RangeValidator
    • CompareValidator
    • CustomValidator
    • Validation Summary

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

    ID : เป็นการกำหนดชื่อ ID ให้กับตัว validator ซึ่งผู้เขียนแนะนำให้ตั้งชื่อให้สื่อความหมาย เพื่อง่ายต่อการต่อยอดในการพัฒนาร่วมกับผู้อื่น
    ControlToValidate : เป็นการกำหนดว่าจะให้ validator ดังกล่าวทำการควบคุมและตรวจสอบ control หรือ element ตัวใดในหน้าจอ
    Display : เป็นการกำหนดลักษณะในการแสดงผลของ validator ซึ่งมีด้วยกัน 3 แบบคือ

    o Static : แบบจองพื้นที่ในการแสดงผล validator กรณีมีข้อผิดพลาด แม้ไม่พบข้อผิดพลาด validator ก็จะยังคงจองพื้นที่ส่วนนั้นไว้เช่นกัน
    o Dynamic : จะแสดงผล validator เมื่อพบข้อผิดพลาด และเมื่อมีการแก้ไขข้อผิดพลาดดังกล่าวแล้ว Error message ของ validator ตัวดังกล่าวจะหายไปและลดพื้นที่ส่วนการแสดงผลให้เหมือนก่อนจะพบข้อผิดพลาด
    o None : ไม่แสดงผลบนหน้าจอ

    Error Message : เป็นการกำหนดข้อความที่ต้องการให้แสดง เมื่อผู้ใช้กรอกข้อมูลผิดพลาด ซึ่งถือเป็นการชี้แนะให้ผู้ใช้ทราบว่าความผิดพลาดดังกล่าวเกิดขึ้นจากสาเหตุใด และควรแก้ไขอย่างไร
    SetFocusOnError : เป็นการกำหนด Focus ให้กับ control ที่พบความผิดพลาด แต่หากในหน้าจอดังกล่าวพบข้อผิดพลาดหลายจุดและ validator ทุกตัวมีการกำหนดค่า SetFocusOnError เป็น true จะทำให้ Focus เลื่อนไปยังจุดที่มีข้อผิดพลาดจุดแรกที่พบทันที
    Text : เป็นการกำหนดในส่วนที่จะแสดงผล เมื่อ validator พบข้อผิดพลาดนั่นเอง ซึ่งโดยทั่วไปแล้วมักกำหนดให้แสดงเป็นเครื่องหมายดอกจันท์ หรือ * และวางไว้หลัง control ตัวที่ validator ดังกล่าวกำลังควบคุม เพื่อแจ้งให้ผู้ใช้ทราบว่าข้อมูลที่กรอกส่วนใดกำลังมีปัญหาหรือพบข้อผิดพลาดจากการตรวจสอบอยู่นั่นเอง
    ToolTip : เป็นการกำหนดข้อความที่ต้องการให้แสดงเมื่อผู้ใช้นำเมาส์ไปชี้ที่ validator ที่กำลังแสดงข้อผิดพลาดอยู่
    Validation group : เป็นการจัดกลุ่มให้กับ validator ที่ใช้ในการตรวจสอบ ซึ่งโดยปกติหากไม่กำหนดค่าในส่วนนี้จะถือว่า control ทั้งหมดในหน้าจอถูกจัดกลุ่มการควบคุมไว้เป็นกลุ่มเดียวกันทั้งหน้าจอ แต่หากมีบางกรณีที่ต้องการแยกกลุ่มในการตรวจสอบ ต้องมีการระบุ ValidationGroup ของปุ่มและ validator ที่ต้องการตรวจสอบเหล่านั้นให้เป็นกลุ่มเดียวกัน ซึ่งจะทำให้ validator ตัวอื่นที่ไม่ใช่กลุ่มดังกล่าวไม่ถูกนำมาตรวจสอบ เพราะถือว่าเป็นการตรวจสอบคนละส่วนกัน

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

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

    • InitialValue : ใช้เปรียบเทียบค่าจาก InitialValue ที่กำหนดใน validator กับค่าที่ผู้ใช้กรอกมาใน control ที่ validator ตัวนี้ควบคุมอยู่ ซึ่งโดยทั่วไปการใช้งาน RequiredFieldValidator อาจไม่จำเป็นต้องกำหนดค่าให้กับ  properties นี้ แต่ผู้เขียนจะขอยกตัวอย่างเพื่อให้ผู้พัฒนาเห็นภาพการทำงานของลักษณะการกำหนดค่า InitialValue  เพื่อนำไปประยุกต์ใช้งานได้ ดังนี้

    ตัวอย่างที่ 1 การเรียกใช้งาน RequiredFieldValidator  แบบทั่วไป(ไม่ได้กำหนดค่า InitialValue )

    <asp:TextBox ID="TxtEmail" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="ReqEmail" runat="server"
    ErrorMessage="Please enter your email-address" 
    ControlToValidate="TxtEmail" SetFocusOnError="True">*
    </asp:RequiredFieldValidator>

    ตัวอย่างที่ 2  เช่น กรณีที่มีการกำหนดค่า InitialValue และมีการเลือกประเภทจาก dropdownlist ที่มีชื่อว่า DdlType และบังคับเลือก

    <asp:DropDownList ID="DdlType" runat="server"> 
    <asp:ListItem Value="--Select--">--Select--</asp:ListItem>
    <asp:ListItem Value="Item1" />
    <asp:ListItem Value="Item2" />
    <asp:ListItem Value="Item3" />
    </asp:DropDownList>
    <asp:RequiredFieldValidator ID="ReqType" runat="server"
    ErrorMessage="Please select type" ControlToValidate="DdlType" 
    SetFocusOnError="True" InitialValue="--Select--" >*
    </asp:RequiredFieldValidator>

    จากตัวอย่างที่ 2 จะเห็นว่า ค่า InitialValue คือ “–Select–” นั่นหมายถึงว่า หากผู้ใช้เลือกรายการ “–Select–” จาก dropdownlist แล้วกดปุ่มบันทึก validator ดังกล่าวจะแสดงข้อผิดพลาดทันที

    RegularExpressionValidator

                เป็น validator ที่ใช้ในการตรวจสอบรูปแบบการกรอกของข้อมูล เช่น อีเมล์ หมายเลขโทรศัพท์ URL ของเว็บไซต์ หรือรหัสไปรษณีย์ เป็นต้น โดย properties  ที่สำคัญเพิ่มเติมจากที่กล่าวไว้แล้วในข้างต้นของ validator นี้คือ

    • ValidationExpression ซึ่งทาง .NET ได้มีการกำหนดรูปแบบ regular expression สำเร็จรูปในการตรวจสอบไว้ให้แล้วในเบื้องต้น แต่ผู้พัฒนาสามารถประยุกต์ใช้ และปรับแก้ให้รูปแบบของ regular expression ที่ใช้ในการตรวจสอบเป็นไปในแบบที่ต้องการได้

    ตัวอย่าง การใช้งาน ValidationExpression ในการตรวจสอบรูปแบบ URL ของเว็บไซต์

    <asp:TextBox ID="txtURL" runat="server"></asp:TextBox>
    <asp:RegularExpressionValidator ID="RegExURL" runat="server"
    ControlToValidate="txtURL" ErrorMessage="Internet URL 's format is incorrect" SetFocusOnError="True" ValidationExpression="http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)?">*</asp:RegularExpressionValidator>

    จากตัวอย่าง พบว่า regular expression ที่ใช้ในการตรวจสอบรูปแบบของข้อมูล คือ  http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)? ซึ่งเป็นแบบสำเร็จรูปที่ ASP.NET ได้เตรียมไว้ให้แล้ว

    RangeValidator

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

    • MaximumValue : เป็นการกำหนดค่าสูงสุดที่สามารถกรอกได้
    • MinimumValue : เป็นการกำหนดค่าข้อมูลต่ำสุดที่สามารถกรอกได้
    • Type :เป็นการกำหนดชนิดของข้อมูลที่จะทำการตรวจสอบช่วงของข้อมูลว่าให้มองเป็นชนิดใด โดยชนิดของข้อมูลประกอบด้วย
      • String : การตรวจสอบช่วงข้อมูลชนิดที่เป็นชนิด String นี้จะคำนวณค่าโดยเทียบกับ Ascii Code (http://www.lookuptables.com/)
      • Integer
      • Double
      • Date

    ตัวอย่างที่ 1 : การตรวจสอบช่วงของข้อมูลที่เป็นชนิด string

    <asp:DropDownList ID="DdlChoice" runat="server">
    
    <asp:ListItem Value="--Select--">--Select--</asp:ListItem>
    <asp:ListItem Value="A" >A</asp:ListItem>
    <asp:ListItem Value="B" >B</asp:ListItem>
    <asp:ListItem Value="C" >C</asp:ListItem>
    <asp:ListItem Value="D">D</asp:ListItem>
    <asp:ListItem Value="E">E</asp:ListItem>
    </asp:DropDownList>
    <asp:RangeValidator ID="RangeChoice" runat="server"
    ControlToValidate="DdlChoice" 
    ErrorMessage="Please select between A-D"
    MaximumValue="D" MinimumValue="A">*</asp:RangeValidator>

    จากตัวอย่างจะพบข้อผิดพลาดหากผู้ใช้เลือกรายการ “E” เนื่องจากเมื่อนำค่าดังกล่าวไปเปรียบเทียบกับค่าแบบ Ascii code แล้วจะมีค่าเกินช่วงที่กำหนดใน validator คือ A-D นั่นเอง

    ตัวอย่างที่ 2 : การตรวจสอบช่วงของข้อมูลที่เป็นชนิดตัวเลข

    Enter number (between 1 - 10) :
    <asp:TextBox ID="txtAmount" runat="server"></asp:TextBox>
    <asp:RangeValidator ID="RangeNumber" runat="server"
    ControlToValidate="txtAmount" 
    ErrorMessage="The data must be from 1 to 10"
    MaximumValue="10" MinimumValue="1" Type="Integer">*</asp:RangeValidator>

    CompareValidator

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

    • ValueToCompare : เป็น properties ที่ใช้ในกรณีที่ต้องการเปรียบเทียบค่าที่รับจากผู้ใช้กับค่าคงที่ที่ระบุ
    • ControlToCompare : เป็น properties ที่ใช้กำหนดคอนโทรลตัวที่ต้องการเปรียบเทียบค่า(ในกรณีที่มีการเปรียบเทียบค่าจาก 2 คอนโทรล)
    • Operator :เป็นpropertiesที่ใช้ในการกำหนดการปฏิบัติการในการเปรียบเทียบค่า มีดังนี้
      • Equal : ค่าของข้อมูลที่รับมาต้องมีค่าเท่ากันกับค่าที่ต้องการเปรียบเทียบ
      • NotEqual : ค่าของข้อมูลที่รับมาต้องไม่เท่ากับค่าที่ต้องการเปรียบเทียบ
      • GreaterThan : ค่าของข้อมูลที่รับมาต้องมีค่ามากกว่าค่าที่ต้องการเปรียบเทียบ
      • GreaterThanEqual : ค่าของข้อมูลที่รับมาต้องมีค่ามากกว่าหรือเท่ากับค่าที่ต้องการเปรียบเทียบ
      • LessThan : ค่าของข้อมูลที่รับมาต้องมีค่าน้อยกว่าค่าที่ต้องการเปรียบเทียบ
      • LessThanEqual : ค่าของข้อมูลที่รับมาต้องมีค่าน้อยกว่าหรือเท่ากับค่าที่ต้องการเปรียบเทียบ
      • DataTypeCheck : เป็นการระบุการตรวจสอบชนิดของข้อมูล และทำงานร่วมกับ properties ที่ชื่อว่า Type โดยการกำหนด properties นี้ จะส่งผลให้ค่าที่ถูกกำหนดไว้ให้กับ properties ที่มีชื่อว่า ValueToCompare และ ControlToCompare ไม่ทำงาน
    • Type : ชนิดของข้อมูลที่ต้องการตรวจสอบ
    หมายเหตุ : ค่าที่ต้องการเปรียบเทียบอาจเป็นค่าที่มาจากคอนโทรลอีกตัวที่ต้องการเปรียบเทียบ(ControlToCompare) หรือมาจากค่าที่กำหนดไว้ใน ValueToCompare นั่นเอง ซึ่งค่า properties ทั้งสองนี้ไม่ควรกำหนดร่วมกัน ควรเลือกใช้เพียงตัวใดตัวหนึ่งเท่านั้น แต่หากมีการกำหนดทั้ง 2 ค่าพร้อมกัน การทำงานของ ControlToCompare จะมีลำดับในการทำงานก่อน


    ตัวอย่างที่
    1
    การเปรียบเทียบค่าข้อมูล 2 ค่าจากคอนโทรล เช่น กรณีการพิมพ์รหัสผ่านและยืนยันรหัสผ่าน เป็นต้น

    Password :<asp:TextBox ID="txtPassword" runat="server">
    </asp:TextBox><br />
    Retype-password :<asp:TextBox ID="txtConfirmPassword" 
    runat="server"></asp:TextBox> 
    <asp:CompareValidator ID="CmpPassword" runat="server" 
    ErrorMessage="Password must be the same" 
    ControlToCompare="txtPassword" ControlToValidate="txtConfirmPassword">*</asp:CompareValidator>
    
    

    ตัวอย่างที่ 2 การเปรียบเทียบค่าของข้อมูลกับค่าคงที่ที่กำหนด กรณีที่ต้องกรอกข้อมูลที่มีค่ามากกว่าหรือเท่ากับ 18

    Age :<asp:TextBox ID="txtAge" runat="server"></asp:TextBox>
    <asp:CompareValidator ID="CmpAge" runat="server" 
    ControlToValidate="txtAge" 
    ErrorMessage="Age must greater than or equal 18 years old"
    Operator="GreaterThanEqual" ValueToCompare="18">*
    </asp:CompareValidator>
    
    

    ตัวอย่างที่ 3 การตรวจสอบชนิดของข้อมูล กรณีกรอกข้อมูลราคาต้องเป็นตัวเลขเท่านั้น

    Price : <asp:TextBox ID="txtPrice" runat="server"></asp:TextBox>
    
    <asp:CompareValidator ID="CmpType" runat="server" 
    ControlToValidate="txtPrice" ErrorMessage="Price must be number" 
    Operator="DataTypeCheck" Type="Double">*</asp:CompareValidator>

    CustomValidator

                หากการตรวจสอบด้วย validator ที่กล่าวมาก่อนหน้านี้ ยังไม่ครอบคลุมการตรวจสอบที่ต้องการ นักพัฒนาสามารถกำหนดเงื่อนไขในการตรวจสอบ และการทำงานนอกเหนือจากที่ validator ที่ ASP.NET เตรียมไว้ให้ขึ้นได้เอง โดยใช้ validator ประเภท CustomValidator ซึ่ง validator ประเภทนี้ จะมีช่องทางในการตรวจสอบสำหรับผู้พัฒนาเตรียมไว้ให้ทั้ง 2 ฝั่ง คือทั้งฝั่ง client และฝั่ง server โดยมี properties ที่จำเป็นเพิ่มเติมจาก properties หลัก ดังนี้

    • ClientValidationFunction : เป็นการกำหนดค่าของฟังก์ชั่นในฝั่ง client ที่ใช้ในการตรวจสอบข้อมูล
    • OnServerValidate : เป็นการกำหนดฟังก์ชั่น event หรือเมธอดที่ใช้ในการตรวจสอบข้อมูลในฝั่งเซิร์ฟเวอร์

    ตัวอย่างที่ 1 การใช้งาน custom validator ในการตรวจสอบค่าข้อมูลจากฝั่ง client

    <script type="text/javascript">
    function CheckData(oSrc, args) {
    if(args.Value.length >= 5 && args.Value.search('-') == -1)
    args.IsValid = true;
    else
    args.IsValid = false;
    oSrc.innerText = "(Your custom message here) ";
    }
    </script>
    
    <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
    <asp:CustomValidator ID="CustomUsername" runat="server" 
    ClientValidationFunction="CheckData" ControlToValidate="txtUserName" 
    ErrorMessage="Username must be greater than or equal 5 letters and cannot contain contain '-'">*</asp:CustomValidator>
    
    หมายเหตุ : หากต้องการเปลี่ยนแปลงข้อความผิดพลาดที่ต้องการแสดงสามารถทำได้โดยกำหนด      oSrc.innerText ในฟังก์ชั่น JavaScript นั้น

    ตัวอย่างที่ 2 การใช้งาน custom validator ในการตรวจสอบค่าข้อมูลจากทั้งฝั่ง client และฝั่งเซิร์ฟเวอร์

    ฝั่ง Client

    <script type="text/javascript">
    function CheckData(oSrc, args) {
    if(args.Value.length >= 5 && args.Value.search('-') == -1)
    args.IsValid = true;
    else
    args.IsValid = false;
    
    </script>
    <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
    <asp:CustomValidator ID="CustomUsername" runat="server"
    ClientValidationFunction="CheckData" 
    ControlToValidate="txtUserName"
    ErrorMessage="Username must greater than or equal 5 letters and cannot contain '-' " onservervalidate="CustomUsername_ServerValidate">*
    </asp:CustomValidator>
    
    

    ฝั่งเซิร์ฟเวอร์

    protected void CustomUsername_ServerValidate(object source, ServerValidateEventArgs args)
    {
    string username = args.Value;
    string[] Users = {"Amanda","Robert","John","Jennie"};
    if (Users.Contains(username))
    {
    args.IsValid = false;
    
    CustomUsername.ErrorMessage = "Username is already exists in database.";
    }
    else
    args.IsValid = true;
    }
    

                จากตัวอย่างจะมีการตรวจสอบค่าข้อมูลรหัสผู้ใช้จากฝั่ง client ก่อน ว่าต้องมีความยาวตั้งแต่ 5 ตัวอักขระขึ้นไปและต้องไม่ประกอบด้วย “-” หากมีเงื่อนไขครบตามกำหนดจะทำการตรวจสอบอีกครั้งในฝั่งเซิร์ฟเวอร์ว่าจะต้องไม่ซ้ำกับค่าที่มีอยู่แล้วจากข้อมูลรหัสผู้ใช้สมมติที่มีอยู่ และหากพบข้อผิดพลาดจะมีการแสดงข้อความแจ้งบอก

    Validation Summary

                เป็น validator ที่ใช้ในการสรุปข้อผิดพลาดทั้งหมดที่เกิดขึ้นในหน้าจอดังกล่าว ซึ่งอาจมีการระบุ ValidationGroup เพื่อแยกกลุ่มการตรวจสอบตามที่ได้กล่าวไว้แล้วข้างต้นได้ โดยการใช้งาน validator ตัวนี้จะประกอบด้วย properties ที่สำคัญ ดังนี้

    • DisplayMode :เป็นรูปแบบในการแสดงผลสรุปรวมข้อผิดพลาด ซึ่งประกอบด้วย
      • List : เป็นการแสดงผลแบบเว้นบรรทัด
      • BulletList : เป็นการแสดงผลแบบ bullet ซึ่งโดยปกติจะมีค่าตั้งต้นเป็นการแสดงผลในรูปแบบนี้
      • SingleParagraph : เป็นการแสดงผลข้อผิดพลาดแบบเว้นวรรคต่อกันในบรรทัดเดียวกันไปเรื่อยๆ

    ตัวอย่างผลลัพธ์การแสดงผลของ DisplayMode ในแต่ละแบบ

    DisplayMode

    • HeaderText : เป็นการระบุข้อความที่ต้องการให้แสดงในส่วนบนของข้อสรุปผิดพลาดทั้งหมดที่เกิดขึ้น
    • ShowMessageBox : เป็นการกำหนดว่าต้องการให้แสดงผลสรุปในรูป MessageBox หรือไม่ ซึ่ง properties ดังกล่าวจะไปทำการสร้างฟังก์ชั่นที่เรียกใช้งาน alert() ของ JavaScript เพื่อการแสดงผลสรุปข้อผิดพลาดนี้
    • ShowSummary : เป็นการแสดงผลรายการข้อผิดพลาดในรูปแบบ HTML ในตำแหน่งที่ validator ตัวนี้ถูกวางอยู่

    ตัวอย่างที่ 1 การใช้ ValidationSummary ในการสรุปข้อผิดพลาดในหน้าจอโดยแสดงผลในรูปแบบ MessageBox

    Username : <asp:TextBox ID="txtUserName" runat="server">
    </asp:TextBox>
    <asp:CustomValidator ID="CustomUsername" runat="server" 
    ClientValidationFunction="CheckData" ControlToValidate="txtUserName" 
    ErrorMessage="Username must greater than or equal 5 letters and cannot contain '-' "  onservervalidate="CustomUsername_ServerValidate">*
    </asp:CustomValidator><br />
    Password :
    <asp:TextBox ID="txtPass" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="ReqPass" runat="server" 
    ControlToValidate="txtPass" ErrorMessage="Password">*
    </asp:RequiredFieldValidator>
    <asp:ValidationSummary ID="ValidationSummary" runat="server"
    HeaderText="Errors:" ShowMessageBox="True" />
    
    

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

    แหล่งข้อมูลอ้างอิง :
    http://www.codeproject.com/Articles/3882/ASP-NET-Validators-Unclouded
    http://www.codeproject.com/Articles/334310/Understanding-ASP-NET-Validation-Techniques
    http://www.codeproject.com/Articles/7943/Validator-Controls-in-ASP-NET

  • รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 1)

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

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

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

    แนวคิดที่ 1 : Try A One Column Layout instead of multicolumns.
    _____การจัดรูปแบบบทความให้มีเพียงคอลัมน์เดียวจะช่วยทำให้เราสามารถควบคุมความต่อเนื่องของบทความได้ดี ช่วยอำนวยความสะดวกและสามารถกำหนดทิศทางการอ่านบทความของผู้อ่านได้อย่างแม่นยำ เนื่องจากมีเพียงการเลื่อนขึ้นและลงเท่านั้น ในขณะที่การจัดบทความแบบหลายคอลัมน์จะทำให้ผู้อ่านเกิดความสับสน ส่งผลให้ผู้อ่านเสียสมาธิหรือหมดความสนใจในบทความดังกล่าวได้idea001

    แนวคิดที่ 2 : Try Distinct Clickable/Selected Styles instead of blurring them.
    _____ในการออกแบบหน้าจอโดยเฉพาะส่วนของ links, buttons สิ่งที่กำลังถูกเลือก(chosen items) และข้อความ(text)หรือบทความ(content) ควรออกแบบให้ไปในรูปแบบเดียวกันหมดทุกๆหน้าจอ เพื่อช่วยให้ผู้ใช้งานไม่สับสนหรือต้องทำความเข้าใจเพิ่มเติมในรูปแบบพื้นฐานที่ได้ออกแบบไว้ ดังตัวอย่างภาพทางด้านซ้าย โดยผู้ออกแบบเลือกสีฟ้าแทนในส่วนของ links, buttonsและสีดำแทนส่วนที่กำลังถูกเลือก(chosen items) และสีเทาแทนข้อความโดยในแต่ละองค์ประกอบใช้รูปแบบเดียวกันภายในองค์ประกอบนั้น ส่วนในภาพทางด้านขวา เป็นการเลือกสีและรูปแบบที่หลากหลายในองค์ประกอบเดียวกันซึ่งจะส่งผลให้ผู้ใช้สับสนกับหน้าจอดังกล่าวได้idea006

    แนวคิดที่ 3 : Try More Contrast instead of similarity.
    _____การเพิ่มความน่าสนใจหรือการยกระดับความคมชัดในส่วนขององค์ประกอบสำคัญๆส่งผลให้เกิดความแตกต่างจากองค์ประกอบรวมอื่นๆในหน้าจอจะเป็นการยกระดับ UI ของคุณให้มีประสิทธ์ภาพมากขึ้น ไม่ว่าจะเป็นการใช้โทนสีที่เข้มขึ้น การไล่เฉดสีหรือการใส่เงาให้กับองค์ประกอบนั้นๆทำให้ผู้ใช้งานรับรู้ถึงองค์ประกอบสำคัญนั้นได้ทันทีจากการเข้าใช้งาน ช่วยให้ผู้ใช้งานสะดวกและเข้าใจการทำงานของหน้าจอได้ง่ายยิ่งขึ้นidea011

    แนวคิดที่ 4 : Try Fewer Borders instead of wasting attention.
    _____การจัดรูปแบบองค์ประกอบโดยใช้เส้นเป็นอีกสิ่งหนึ่งที่มีการนำมาใช้เพื่อเพิ่มจุดน่าสนใจให้กับตัวUI ทั้งยังสามารถนำมาจัดหรือแบ่งขอบเขตของกลุ่มองค์ประกอบในหน้าจอได้อย่างชัดเจน จนบางครั้งนักออกแบบก็ใช้งานการจัดองค์ประกอบแบบนี้มากจนเกินจำเป็นไปในแต่ละส่วนของหน้าจอส่งผลให้กลุ่มองค์ประกอบนั้นถูกตัดขาดออกจากกันอย่างสิ้นเชิงและทำให้การควบคุมทิศทางของหน้าจอผิดจากที่ได้ตั้งเอาไว้ ดังนั้นการเลือกใช้เส้น ควรใช้แค่พอจำเป็นจนไม่ทำให้หน้าจอดูรกจนเกินไป เราอาจจะใช้วิธีอื่นๆมาช่วยในการจัดกลุ่มองค์ประกอบได้ ไม่ว่าจะใช้ช่องว่างระหว่างกลุ่มองค์ประกอบ การเน้นตัวอักษรหรือสีเป็นต้นidea023

    แนวคิดที่ 5 : Try Designing For Zero Data instead of just data heavy cases.
    _____โดยทั่วไปแล้วเรามักจะออกแบบหน้าจอให้รองรับกับการแสดงข้อมูล ไม่ว่าเป็น 1, 10, 100 หรือเป็น 1000 ข้อมูลโดยบางทีเราอาจลืมออกแบบสำหรับกรณีที่ข้อมูลเป็น 0 ส่งผลให้เวลาแสดงหน้าจอ อาจเป็นหน้าจอว่างๆหรือมีการแจ้งเตือนว่าไม่พบรายการหรือข้อมูล โดยสำหรับนักออกแบบแล้วอาจคิดว่าไม่ส่งผลกระทบใดๆกับหน้าจอมากนัก แต่สำหรับผู้ใช้งานระบบที่เจอหน้าจอที่ว่างเปล่าแล้วอาจเกิดข้อสงสัยได้ว่าเกิดอะไรขึ้นหรือเกิดความสับสนว่าจะทำอะไรในขั้นตอนต่อไป ดังนั้นการออกแบบในส่วนของกรณีที่ไม่พบข้อมูลหรือรายการ อาจใส่ข้อความอธิบายถึงสาเหตุที่ทำให้ไม่พบข้อมูลหรือแนะนำขั้นตอนที่จะทำให้เกิดข้อมูลต่างๆได้ ส่งผลให้ผู้ใช้ไม่สะดุดและสะดวกกับการใช้งานระบบได้มากยิ่งขึ้นidea025

    แนวคิดที่ 6 : Try Conventions instead of reinventing the wheel.
    _____การสื่อสารกับผู้ใช้ถือเป็นอีกส่วนที่มีความสำคัญในการออกแบบหน้าจอ ซึ่งในการออกแบบนั้น เราควรออกแบบให้สอดคล้องกับการใช้งานของผู้ใช้ระบบหรือความเคยชินที่ผู้ใช้เคยได้ทำมาโดยตลอด ส่งผลให้หน้าจอที่ได้ออกแบบไว้ตอบสนองความต้องการและลดเวลาในการเรียนรู้หน้าจอเพิ่มเติม ซึ่งโดยทั่วไปแล้วจะมีรูปแบบหลักๆอยู่พอสมควร เช่นการให้มีปุ้มปิดหน้าจอมุมบนขวา ปุ่มกดถัดไปอยุ่ด้านขวาและย้อนกลับอยู่ด้านซ้าย สัญลักษณ์รูปเฟืองสื่อถึงการตั้งค่า เป็นต้นidea029

    แนวคิดที่ 7 : Try Bigger Click Areas instead of tiny ones.
    _____จากหัวข้อที่ 3 นี้ก็เป็นอีกแนวคิดหนึ่งที่จะเพิ่มความน่าสนใจให้กับองค์ประกอบประเภท links, buttons ได้ คือการเพิ่มขยายหรือขอบเขตในการกดองค์ประกอบนั้นๆ เพราะในปัจจุบัน หน้าจอที่ได้ออกแบบไว้ถูกนำไปใช้งานในอุปกรณ์ที่หลากหลายมากขึ้นการออกแบบให้สิ่งเหล่านี้มีขนาดที่เหมาะสมในหน้าจอหนึ่ง อาจจะไม่สะดวกที่จะใช้งานในอีกหน้าจอหนึ่ง หรือการออกแบบให้ปุ่มกดหรือลิงค์เล็กจนเกินไป อาจส่งผลให้ผู้ใช้ไม่สะดวกกับการหาหรือกดสิ่งเหล่านั้นได้การขยายขนาดหรือขอบเขตของการกดจะช่วยให้ผู้ใช้สะดวกมากอีกขึ้น และยังมีวิธีการเพิ่มข้อความให้มีความยาวมากขึ้น หรือใช้ไอคอนร่วมกับข้อความ เป็นต้นidea038

    แนวคิดที่ 8 : Try Icon Labels instead of opening for interpretation.
    _____ถ้าพุดถึงเรื่องของไอคอนแล้ว ไอคอนมีส่วนช่วยให้หน้าจอของเราดูดีขึ้นได้และยังทำให้ผู้ใช้งานสามารถเข้าใจถึงการทำงานของไอคอนนั้นได้เกือบทันที แต่ในบางครั้งกลุ่มผู้ใช้งานบางกลุ่ม อาจจะไม่สามารถตีความหมายของไอคอนตามวัตถุประสงค์การใช้งานที่เราได้ออกแบบเอาไว้ หรือไอคอนที่เรานำมาใช้ อาจไม่แสดงความหมายได้คลุมเครือ ดังนั้นวิธีที่จะช่วยให้ไอคอนสามารถแสดงวัตถุได้อย่างชัดเจนคือการเพิ่มข้อความควบคู่ไปกับตัวไอคอนด้วย จะทำให้ผู้ใช้งานหน้าจอเข้าใจได้ทันทีและไม่สับสนกับความหมายที่จะสื่อถึง และบางกรณีไอคอนที่นำมาใช้อาจเล็กหรือสีที่ใช้ดูกลมกลืนไปกับองค์ประกอบอื่นๆ การใส่ข้อความจึงเป็นการช่วยให้ไอคอนดูคมชัดมากขึ้นidea047

    แนวคิดที่ 9 : Try Natural Language instead of dry text.
    _____แนวคิดข้อนี้ออกจะแปลกตาสำหรับผู้เขียนสักหน่อย เพราะเป็นการนำภาษาธรรมชาติ (ภาษาพูด)มาใช้เป็นคำอธิบายแทนการใช้คำทางการหรือราชการที่ปัจจุบันเราใช้กันอย่างแพร่หลาย ซึ่งการนำภาษาธรรมชาติมาใช้ช่วยเขียนคำชี้แจ้ง จะทำให้ผู้ใช้เข้าใจถึงจุดหมายที่ผู้ใช้จะต้องกระทำกับหน้าจอ แต่ในเว็บไซต์ที่ใช้งานในเชิงราชการ อาจดูไม่ค่อยเหมาะสมหรือไม่เป็นที่ชอบใจของผู้ใช้งานได้ ข้อนี้จึงขึ้นอยู่กับว่าเราจะไปใช้ในลักษณะไหน มากน้อยเพียงไหนขึ้นอยุ่กับกลุ่มผู้ใช้งานระบบด้วย แต่อาจนำมาใช้ผสมกับคำที่เป็นทางการในบางจุดได้ เพื่อให้ผู้ใช้งานเข้าใจมากขึ้นและไม่ดูน่าเกลียดจนเกินไป แต่ในอนาคตอาจการเป็นที่นิยมแทนการใช้คำทางราชการก็เป็นได้idea048

    แนวคิดที่ 10 : Try Extra Padding instead of overcrowding elements.
    _____ช่องว่างสำคัญไฉน เมื่อพูดถึงช่องว่างนักออกแบบบางท่านอาจบอกว่าไม่ค่อยสำคัญมากนัก แต่จริงๆแล้วช่องว่างก็เป็นส่วนหนึ่งที่จะทำให้หน้าจอเราดูสะอาดตามากขึ้น และสามารถนำช่องว่างมาใช้สำหรับแยกกลุ่มองค์ประกอบได้ นอกจากการใช้เส้นแล้ว เราสามารถนำช่องว่างมาแยกข้อความในตารางให้รับรู้ได้ง่ายยิ่งขึ้นด้วย เพราะบางกรณีที่มีการแสดงผลแบบตาราง จะมีการนำข้อมูลจำนวนมากมาแสดงให้ผู้ใช้งานรับรู้ แต่กลับไม่ได้ออกแบบส่วนของการแบ่งแยกขอบเขตของcolumn หรือ row ไว้เลย ส่งผลให้ข้อมูลที่นำมาแสดงอาจติดกันยาวเหยียดจนผู้ใช้สับสนกับจุดสิ้นสุดของข้อมูลได้ การเพิ่มช่องว่างก็เป็นอีกวิธีที่สามารถนำมาใช้ได้ หรือเราอาจนำมาใช้ควบคู่กับเส้นก็เป็นอีกวิธีที่ดีเช่นกันidea063


    สำหรับบทความชุด “รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่” ตอนที่ 1 ขอจบที่ 10 ข้อแรกติดตามตอนที่ 2 ได้ตามลิงค์ด้านล่างเลยครับ

    รวมเทคนิคการออกแบบ UI ให้สวยงามสำหรับ Designer มือใหม่ (ตอนที่ 2)

    ขอบคุณครับ

  • ทำอย่างไรให้สามารถกำหนดจุดพิกัดบนแผนที่ Google map แบบจุดเดียวและหลายจุดจากฐานข้อมูลได้ด้วย ASP.NET C#

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

          โดยผู้เขียนขอเสนอวิธีการเบื้องต้นในการแสดงผลแบบกำหนดค่าตายตัวให้ผู้อ่านลองศึกษาการทำงานเพื่อทำความเข้าใจในเบื้องต้นก่อน ดังนี้

    การแสดงผลแบบจุดเดียว

    1. อ้างอิงพาธที่ตั้งของ Google API ซึ่งเป็นส่วนหนึ่งของการแสดงผลบนแผนที่ Google map และไฟล์จาวาสคริปต์ที่ใช้ในการแสดงผล(ถ้ามี)
    <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
     
    <script src="js/mapwithmarker.js" type="text/javascript"></script>
    1. กำหนดสไตล์ชีทที่ใช้ในการแสดงผล เมื่อมีการคลิกตำแหน่งที่ได้ทำการกำหนดพิกัดไว้
    <style type="text/css">  
    .labels { color: black; background-color: #FF8075; font-family: Arial; font-size: 11px; font-weight: bold; text-align: center; width: 12px; }  </style>
    1. กำหนดพิกัดที่ต้องการให้แผนที่ค้นหาจุดกึ่งกลางของการแสดงผล ซึ่งโดยปกติจะถือเอาจุดแรกที่ต้องการแสดงเป็นตำแหน่งกึ่งกลางของการแสดงผลตำแหน่งบนแผนที่นั้นๆ เพื่อให้ตำแหน่งดังกล่าวอยู่กึ่งกลางของแผนที่ที่ต้องการแสดงนั่นเอง
    var mapOptions = {  
    center: new google.maps.LatLng(ค่าละติจูด, ค่าลองจิจูด),  
    zoom: 12, 
    ///ขนาดที่ต้องการให้ซูมเป็นค่าตั้งต้น  
    mapTypeId: google.maps.MapTypeId.ROADMAP  };
    1. กำหนดส่วนที่ต้องการให้แสดงแผนที่ ว่าต้องการให้แสดงในส่วนใดของเว็บไซต์
    var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
     ///ในที่นี้พื้นที่ที่ต้องการให้แสดงผลในเว็บไซต์ คือ dvMap โดยนำค่าที่กำหนดกึ่งกลางไว้ในขั้นตอนที่ 3 (mapOptions) มาเป็นค่าพารามิเตอร์ในการแสดงผลด้วย
    
    1. การกำหนดจุดพิกัดที่ต้องการแสดงผล ซึ่งค่าที่ต้องการคือ ชื่อสถานที่ ค่าละติจูด ลองจิจูด และคำอธิบายในการแสดงผลตำแหน่งสถานที่ที่เราทำการกำหนดไว้ ดังนี้
      • กำหนดค่าพิกัดลองจิจูดและละติจูดของจุดที่เราต้องการกำหนดบนแผนที่
      var infoWindow = new google.maps.InfoWindow();
       var myLatlng = new google.maps.LatLng(ค่าละติจูด, ค่าลองจิจูด);
      
      
      • กำหนดค่าพิกัดตำแหน่งของจุดและพารามิเตอร์ต่างๆที่จำเป็นต้องใช้ในการกำหนดจุดที่เราต้องการกำหนดบนแผนที่
      var marker = new MarkerWithLabel({
       position: myLatlng, //เป็นการกำหนดค่าพิกัดตำแหน่งของจุดที่เราต้องการกำหนดบนแผนที่
       map: map, //เป็นการกำหนดพื้นที่ที่ต้องการแสดงแผนที่ ในที่นี้คือ dvMap
       title: title, //เป็นการกำหนดชื่อสถานที่
       labelContent:1,  //เป็นการกำหนดหมายเลขลำดับของตำแหน่งแสดงผล
       labelAnchor: new google.maps.Point(7, 30),
       labelClass: "labels", //เป็นการกำหนดรูปแบบในการแสดงผลด้วยสไตล์ชีท
       labelInBackground: false
       });
      
      
      • กำหนดการแสดงผลเมื่อผู้เยี่ยมชมมีการคลิกบนจุดดังกล่าว
      (function(marker) {
       google.maps.event.addListener(marker, "click", function(e) {
       infoWindow.setContent(description);
       //เป็นการกำหนดข้อความที่ต้องการแสดง เมื่อผู้เยี่ยมชมเว็บไซต์คลิกบนจุดดังกล่าว
       infoWindow.open(map, marker);
       });
       })(marker);
      
      
    1. สร้างพื้นที่ที่กำหนดการแสดงผลในส่วน body
    <div id="dvMap" style="width: 800px; height: 700px;">
     </div>
    1. แสดง code ทั้งหมดที่ใช้
    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     <html xmlns="http://www.w3.org/1999/xhtml">
     <head runat="server">
     <title>Google map Test for one point</title>
     <style type="text/css">
     .labels { color: black; background-color: #FF8075; font-family: Arial; font-size: 11px; font-weight: bold; text-align: center; width: 12px; }
     </style>
     <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
     <script src="js/mapwithmarker.js" type="text/javascript"></script>
     <script type="text/javascript">
     window.onload = function() {
     var mapOptions = {
     center: new google.maps.LatLng(7.006923, 100.500238),
     zoom: 12,
     mapTypeId: google.maps.MapTypeId.ROADMAP
     };
     var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
     var infoWindow = new google.maps.InfoWindow();
     var myLatlng = new google.maps.LatLng(7.006923, 100.500238);
     var marker = new MarkerWithLabel({
     position: myLatlng,
     map: map,
     title: 'มหาวิทยาลัยสงขลานครินทร์',
     labelContent: 1,
     labelAnchor: new google.maps.Point(7, 30),
     labelClass: "labels", // the CSS class for the label
     labelInBackground: false
     });
     (function(marker ) {
     google.maps.event.addListener(marker, "click", function(e) {
     infoWindow.setContent('มหาวิทยาลัยสงขลานครินทร์ วิทยาเขตหาดใหญ่');
     infoWindow.open(map, marker);
     });
     })(marker );
     }
     </script>
     </head>
     <body>
     <form id="form1" runat="server">
     <div id="dvMap" style="width: 800px; height: 700px;">
     </div>
     </form>
     </body>
     </html>

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

    แบบหลายจุดและดึงค่าละติจูด ลองจิจูดจากฐานข้อมูลมาแสดงผล

    โดยลักษณะการเขียนจะคล้ายๆกับแบบกำหนดจุดเพียงจุดเดียวอย่างที่กล่าวไว้แล้วในตอนต้น แต่จะแตกต่างกันในส่วนของการดึงข้อมูล ซึ่งจะเน้นเฉพาะส่วนที่แตกต่าง และแสดง code สรุปรวมให้ดูอีกครั้ง

    ไฟล์ default.aspx ในฝั่ง Client

    1. กำหนดค่าตัวแปรที่ใช้ในแสดงผลจาก Repeater ซึ่งเป็นเครื่องมือที่มีอยู่แล้วใน .NET โดยตัวแปรที่จะใช้ที่นี้ให้มีชื่อว่า markers และค่าที่ต้องการนำมาใช้จะเป็นฟิลด์ของ Name, Latitude, Longitude และ Description ซึ่งดึงมาจากฐานข้อมูล(โดยชื่อฟิลด์สามารถเปลี่ยนแปลงได้ตามข้อมูลที่มีอยู่จริงในฐานข้อมูลของแต่ละท่าน)
      <script type="text/javascript">
       var markers = [
       <asp:Repeater ID="rptMarkers" runat="server">
       <ItemTemplate>
       {
       "title": '<%# Eval("Name") %>',
       "lat": '<%# Eval("Latitude") %>',
       "lng": '<%# Eval("Longitude") %>',
       "description": '<%# Eval("Description") %>'
       }
       </ItemTemplate>
       <SeparatorTemplate>
       ,
       </SeparatorTemplate>
       </asp:Repeater>
       ];
       </script>
    2. กำหนดพิกัดที่ต้องการให้แผนที่ค้นหาจุดกึ่งกลางของการแสดงผล ซึ่งโดยปกติจะถือเอาจุดแรกที่ต้องการแสดงเป็นตำแหน่งกึ่งกลางของการแสดงผลตำแหน่งบนแผนที่นั้นๆ เพื่อให้ตำแหน่งดังกล่าวอยู่กึ่งกลางของแผนที่ที่ต้องการแสดงนั่นเอง
      var mapOptions = {
       center: new google.maps.LatLng(markers[0].lat, markers[0].lng),
       zoom: 8,
       mapTypeId: google.maps.MapTypeId.ROADMAP};
    3. กำหนดค่าต่างๆ และวนค่าตัวแปร markers ที่ดึงมาจากฐานข้อมูลและตัว Repeater ที่กล่าวไว้ในข้างต้น และทำการกำหนดค่าต่างๆให้กับแต่ละจุดที่ต้องการ โดยรายละเอียดในการกำหนดค่าจะคล้ายกับวิธีกำหนดแบบจุดเดียว(แบบแรก) แต่จะมีการวนซ้ำให้ครบจำนวนตัวแปรที่ดึงมาจากฐานข้อมูล
      var infoWindow = new google.maps.InfoWindow();
       var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
       for (i = 0; i < markers.length; i++) {
       //เป็นการวนค่าตัวแปร markers ที่ดึงมาจากฐานข้อมูลและตัว Repeater ที่กล่าวไว้ในข้างต้น
      
        var data = markers[i]
       //นำค่าจากตัวแปร markers  ที่ดึงมาทีละรายการตามลำดับมาใส่ไว้ในตัวแปร data เพื่อนำไปใช้ในการกำหนดค่าต่อไป
      var myLatlng = new google.maps.LatLng(data.lat, data.lng);
       //กำหนดค่าละติจูดและลองจิจูด
      var marker = new MarkerWithLabel({
       position: myLatlng, //เป็นการกำหนดตำแหน่งที่ต้องการแสดงผล
      map: map,
       title: data.title, //เป็นการกำหนดชื่อที่ต้องการแสดงผลเมื่อนำเมาส์ไปชี้ยังจุดดังกล่าว
      labelContent: i+1//เป็นการกำหนดหมายเลขลำดับให้กับจุดดังกล่าว
       labelAnchor: new google.maps.Point(7, 30),
       labelClass: "labels", // the CSS class for the label
       labelInBackground: false}
       );(function (marker, data) {google.maps.event.addListener(marker, "click", function (e) {
       infoWindow.setContent(data.description);
       //เป็นการกำหนดข้อความที่ต้องการแสดง เมื่อผู้เยี่ยมชมเว็บไซต์คลิกบนจุดดังกล่าว
      infoWindow.open(map, marker);
       });
       })(marker, data);
       }
    4. Code ทั้งหมดในฝั่งไฟล์ Default.aspx
      <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
      
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
       <html xmlns="http://www.w3.org/1999/xhtml">
       <head runat="server">
       <title></title>
       </head>
       <body>
       <form id="form1" runat="server">
       <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script><script src="js/mapwithmarker.js" type="text/javascript"></script>
      <script type="text/javascript">
       var markers = [
       <asp:Repeater ID="rptMarkers" runat="server">
       <ItemTemplate>
       {
       "title": '<%# Eval("Name") %>',
       "lat": '<%# Eval("Latitude") %>',
       "lng": '<%# Eval("Longitude") %>',
       "description": '<%# Eval("Description") %>'
       }
       </ItemTemplate>
       <SeparatorTemplate>
       ,
       </SeparatorTemplate>
       </asp:Repeater>
       ];
       </script>
       <script type="text/javascript">
       window.onload = function() {
       var mapOptions = {
       center: new google.maps.LatLng(markers[0].lat, markers[0].lng),
       zoom: 8,
       mapTypeId: google.maps.MapTypeId.ROADMAP
       };
       var infoWindow = new google.maps.InfoWindow();
       var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions);
       var j = 1;
       for (i = 0 ; i <= markers.length-1; i++) {
       var data = markers[i]
       var myLatlng = new google.maps.LatLng(data.lat, data.lng);
       // var marker = new google.maps.Marker({
       var marker = new MarkerWithLabel({
       position: myLatlng,
       map: map,
       title: data.title,
       labelContent: i+1,
       labelAnchor: new google.maps.Point(7, 30),
       labelClass: "labels", // the CSS class for the label
       labelInBackground: false
       });
       (function(marker, data) {
       google.maps.event.addListener(marker, "click", function(e) {
       infoWindow.setContent(data.description);
       infoWindow.open(map, marker);
       });
       })(marker, data);
       }
       }
       </script>
       <div id="dvMap" style="width: 500px; height: 500px">
       </div>
       </form>
       </body>
       </html>

     

    ไฟล์ default.cs ในฝั่ง server

    using System;
     using System.Collections.Generic;
     using System.Web;
     using System.Web.UI;
     using System.Web.UI.WebControls;
     using System.Data;public partial class _Default : System.Web.UI.Page
     {
     protected void Page_Load(object sender, EventArgs e)
     {
     if (!this.IsPostBack)
     {
     rptMarkers.DataSource = GetData();
    //กำหนดแหล่งข้อมูลให้กับตัว Repeater ที่เราต้องการใข้ในการแสดงผล
     rptMarkers.DataBind();
     }
     }
      private DataTable GetData()
    //เมธอดที่ใช้ในการดึงค่าข้อมูลจากฐานข้อมูล ซึ่งในที่นี้มีการส่งค่ากลับเป็น datatable และมีการสมมุติค่าข้อมูลใน datatable เพื่อให้เห็นภาพการดึงจากฐานข้อมูลเท่านั้น แต่สามารถนำไปประยุกต์ใช้กับการติดต่อฐานข้อมูลจริงได้  {
     DataTable table = new DataTable();
     table.Columns.Add("Name", typeof(string));
     table.Columns.Add("Latitude", typeof(decimal));
     table.Columns.Add("Longitude", typeof(decimal));
     table.Columns.Add("Description", typeof(string));
     table.Rows.Add("มหาวิทยาลัยสงขลานครินทร์", 7.006923, 100.500238, "Hatyai");
     table.Rows.Add("มหาวิทยาลัยราชภัฏ สงขลา", 7.172661, 100.613726, "Songkla");
     return table;
     }
     }
    1. ดึงข้อมูลจากแหล่งข้อมูลที่ต้องการ ซึ่งประกอบด้วยข้อมูลตามที่ต้องการให้แสดงผล เช่น ค่าละติจูด ลองจิจูด และชื่อสถานที่ โดยในกรณีนี้ผู้เขียนขอสมมติค่าและสร้าง datatable ขึ้นมา เพื่อใช้ในการทดสอบ โดยแสดงในเมธอด GetData() หากเป็นข้อมูลที่ใช้จริงผู้พัฒนาสามารถนำไปประยุกต์กับการดึงข้อมูลของท่านได้
    2. กำหนดค่าแหล่งข้อมูลให้กับ repeater เพื่อสร้างตัวแปร marker ตามที่กล่าวไว้ในข้างต้น
    3. ผลลัพธ์ที่ได้ :

    Mapmultipoint

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

    แหล่งข้อมูลอ้างอิง :
    http://www.dotnetbull.com/2013/06/multiple-marker-with-labels-in-google.html
    http://www.codeproject.com/Articles/36151/Show-Your-Data-on-Google-Map-using-C-and-JavaScrip

  • มาติดตั้ง ADLdap เพื่อใช้ยืนยันตัวตนให้กับ Owncloud 8.0 กันดีกว่า

    การติดตั้ง ADLdap เพื่อใช้ยืนยันตัวตนกับ Owncloud 8.0 นี้จะไม่อธิบายการติดตั้ง owncloud ครับ ซึ่งสามารถเข้าไปดูการติดตั้งได้ที่นี้
    How to: install Owncloud (Easy method) – เกรียงไกร หนูทองคำ
    หรือ
    ติดตั้ง ownCloud เลือกใช้ user PSU Passport หรือ ftp server หรือ RADIUS Server – วิบูลย์ วราสิทธิชัย
    การแบ่งปันความรู้ครั้งนี้จะแนะนำวิธีการนำเอา ADLdap (PHP Library) มาใช้งานร่วมกับ Owncloud เท่านั้นครับ ว่ามีวิธีการและขั้นตอนอย่างไร ซึ่งไม่ยากเกินไปครับ ^^


    ภาพรวมเมื่อทำการติดตั้ง/ตั้งค่า ADLdap สำเร็จ

    1. ลดขั้นตอนการยืนยันตัวตน ซึ่งขั้นตอนนั้นจากเริ่มจาก เซิร์ฟเวอร์ที่ติดตั้ง Owncloud ไปยัง เซิร์ฟเวอร์ Ldap โดยตรง ทำให้ไม่จำเป็นต้องติดตั้งระบบ Authen ในเซิร์ฟเวอร์ Owncloud นี้
    2. สามารถจำกัดบุคลากรที่จะเข้ามาใช้งานได้ ตามคณะ/หน่วยนั้นๆ


    ซอร์ฟแวร์ที่นำมาใช้งาน

    – ubuntu server 14.04.02 (x64)
    – Owncloud 8.0.2
    – PHP 5.5
    – ADLdap 4.0.4 – ดาวน์โหลดได้ที่นี้

    คำแนะนำ หากมีการอัพเดทจาก เวอร์ชั่น 8.0.2 ไปเป็น 8.0.3 หรือเวอร์ชั่นที่สูงกว่า เรียบร้อยแล้ว
    จะต้องทำตามขั้นตอนที่ 4 อีกครั้งครับ 

    ขั้นตอนการติดตั้ง
    1. เข้าสู่ระบบด้วย username ของ admin จากนั้นให้ไปเปิดใช้งาน App โดยคลิ๊กที่เมนู Apps จากนั้นเลือก +Apps ให้คลิ๊กเมนู not enable ดูที่ชื่อ App External user support ให้คลิ๊ก Enable

    12
    ** ขั้นตอนที่ 2-5 จะต้องทำการ ssh ไปยังเซิร์ฟเวอร์ที่ติดตั้ง owncloud ครับ
    ** path ที่ใช้ติดตั้ง owncloud ของบทความนี้ /var/www/owncloud/ ครับ

    2. ให้แตกไฟล์  adLDAP_4.0.4r2.zip จากนั้นให้ Rename ชื่อแฟ้มเป็น adldap (จะ Rename หรือไม่ก็ได้ แต่ต้องอ้างเส้นทางและชื่อแฟ้มให้ถูกต้อง) แล้วไปวางไว้ตำแหน่งนี้ /var/www/owncloud/3rdparty/

    3. เพิ่ม code โดยเข้าไปที่ไฟล์ /var/www/owncloud/config/config.php

    /// AD LDAP ///
    ‘user_backends’ => array(
    0 => array(
    ‘class’ => ‘OC_User_ADLDAP’,
    ‘arguments’ => array(
    0 => ‘dc7.psu.ac.th’,
    ),
    ),
    ),
    /// END /// 

    เมื่อเพิ่มแล้วจะได้ตามนี้

    <?php
    $CONFIG = array (
    ‘instanceid’ => ”,
    ‘passwordsalt’ => ”,
    ‘secret’ => ”,
    ‘trusted_domains’ =>
    array (
    0 => ‘xxx.xxx.psu.ac.th’,
    ),
    ‘datadirectory’ => ‘/var/www/owncloud/data’,
    ‘overwrite.cli.url’ => ‘https://xxx.xxx.psu.ac.th/owncloud’,
    ‘dbtype’ => ‘mysql’,
    ‘version’ => ‘8.0.2.0’,
    ‘dbname’ => ‘owncloud_db’,
    ‘dbhost’ => ‘localhost’,
    ‘dbtableprefix’ => ‘oc_’,
    ‘dbuser’ => ‘xxxxx’,
    ‘dbpassword’ => ‘xxxxx’,
    ‘installed’ => true,
    ‘ldapIgnoreNamingRules’ => false,
    ‘theme’ => ”,
    ‘maintenance’ => false,
    ‘mail_smtpmode’ => ‘smtp’,

    /// AD LDAP ///
    ‘user_backends’ => array(
    0 => array(
    ‘class’ => ‘OC_User_ADLDAP’,
    ‘arguments’ => array(
    0 => ‘dc7.psu.ac.th’,
    ),
    ),
    ),
    /// END ///
    );

     

    4.เพิ่ม code โดยเข้าไปที่ไฟล์ /var/www/owncloud/apps/user_external/appinfo/app.php ไว้บรรทัดล่างสุด

    OC::$CLASSPATH[‘OC_User_ADLDAP’]=’user_external/lib/adldap.php’;

    เมื่อเพิ่มแล้วจะได้ตามนี้

    <?php
    OC::$CLASSPATH[‘OC_User_IMAP’]=’user_external/lib/imap.php’;
    OC::$CLASSPATH[‘OC_User_SMB’]=’user_external/lib/smb.php’;
    OC::$CLASSPATH[‘OC_User_FTP’]=’user_external/lib/ftp.php’;
    OC::$CLASSPATH[‘OC_User_ADLDAP’]=’user_external/lib/adldap.php’;

     

    5. สร้างไฟล์ชื่อ adldap.php นำไปไว้ที่ /var/www/owncloud/apps/user_external/lib/
    จากนั้นให้ทำการเพิ่ม code เข้าไปดังนี้

    หมายเหตุ สำหรับท่านใดที่นำไปใช้งาน ก่อนวันที่ 10-04-58 ขอให้ copy php code ทั้งหมดไปแทนของเดิมด้วยนะครับ

    <?php
    /*
    | ——————————————————————————
    | Owncloud 8 Authentication With ADLDAP 
    | ——————————————————————————
    |
    | Create Date : 08-04-2015
    | Update : 18-10-2016

    | Developer : Aussadayut Ubonkan
    | e-mail : aussadayut.u@psu.ac.th
    | Khunying Long Athakravisunthorn Learning Resources Center, Prince of Songkla University
    |
    */
    require_once(‘3rdparty/adldap/src/adLDAP.php’);

    class OC_User_ADLDAP extends \OCA\user_external\Base{

    private $ad_ldap;

    public $authUser = NULL;
    public $user_authen = NULL;
    public $port = NULL;
    public $basedn = NULL;
    public $ssl = NULL;
    public $account_suffix = NULL;
    public $attribute_mapping = NULL;
    public $addgroup = NULL;
    public $departid = NULL;
    public $set_quota = NULL;


    public function checkPassword($uid,$password){

    $this->set_quota = ‘5 GB’; // Ex. ’12 GB’ , ‘100 MB’
    $this->addgroup = ‘Staffs’; // Group Name
    $this->departid = ‘294’; // Department id , 294 = clib psu

    $this->port = ‘636’; // ldap = 389 , ldaps = 636
    $this->basedn = ‘dc=psu,dc=ac,dc=th’;
    $this->ssl = true;
    $this->account_suffix = ‘@psu.ac.th’;
    $this->attribute_mapping = array(
    “cn”, “dn”, “samaccountname”, “employeeid”, “citizenid”, “company”,
    “campusid”, “department”, “departmentid”, “physicaldeliveryofficename”, “positionid”,
    “description”, “displayname”, “title”, “personaltitle”, “personaltitleid”, “givenname”,
    “sn”, “sex”, “userprincipalname”,”mail”);

    $this->ad_ldap = new adLDAP(
    array(
    ‘domain_controllers’ => array(‘dc7.psu.ac.th’,’dc5.psu.ac.th’),
    ‘ad_port’ => $this->port,
    ‘base_dn’ => $this->basedn,
    ‘use_ssl’ => $this->ssl,
    ‘account_suffix’ => $this->account_suffix,
    )
    );

     

    $this->authUser = $this->ad_ldap->user()->authenticate($uid , $password);
    $this->user_authen = $this->ad_ldap->user()->info($uid,$this->attribute_mapping);

    $check_departid = $this->user_authen[0][“departmentid”][0]; // department id
    $uid = $this->user_authen[0][“samaccountname”][0]; // username
    $uemail = $this->user_authen[0][“userprincipalname”][0]; // e-mail

    /*
    | Print User information
    */
    //echo “<pre>”;
    //print_r($this->user_authen);
    //exit;
    if($check_departid == $this->departid){

    if(!$this->user_Exists($uid)){

    $this->storeUser($uid);

    OC_Group::addToGroup($uid, $this->addgroup); // Add User to Group
    OC_DB::executeAudited(
    ‘INSERT INTO `*PREFIX*preferences` ( `userid`, `appid` , `configkey` ,`configvalue`)’
    . ‘ VALUES( ?, ? , ? ,?)’,
    array($uid, ‘files’, ‘quota’ , $this->set_quota)
    );
    OC_DB::executeAudited(
    ‘INSERT INTO `*PREFIX*preferences` ( `userid`, `appid` , `configkey` ,`configvalue`)’
    . ‘ VALUES( ?, ? , ? ,?)’,
    //array($uid, ‘files’, ‘quota’ , $this->set_quota),
    array($uid, ‘settings’, ’email’ , $uemail)
    ); // Set E-Mail

    return $uid;

    }else{

    $this->storeUser($uid);
    return $uid;
    }
    }else{

    return false;

    }

    } // End checkPassword
    public function user_Exists($uid) {
    $result = OC_DB::executeAudited(
    ‘SELECT COUNT(*) FROM `*PREFIX*users_external`’
    . ‘ WHERE LOWER(`uid`) = LOWER(?)’,
    array($uid)
    );
    return $result->fetchOne() > 0;
    } // user_Exists
    } // End extends \OCA\user_external\Base

    ในที่นี้ขออธิบายเพิ่มเติมสำหรับ adldap.php แค่ 4 ส่วน คือ

    สวนที่ 1. require_once(‘3rdparty/adldap/src/adLDAP.php’);  เส้นทางที่ใช้อ้างอิงไฟล์ ADLdap (PHP Libray) เพื่อนำมาใช้ร่วมกับ owncloud

    สวนที่ 2. $this->set_quota = ‘5 GB’;  กำหนดเนื้อที่เริ่มต้นของของผู้ใช้งาน

    ส่วนที่ 3. $this->addgroup = ‘Staffs’;  ใส่ชื่อกลุ่มที่ต้องการ เพื่อใช้สำหรับเพิ่มกลุ่มให้กับผู้ใช้งานโดยอัตโนมัติ
    เงื่อนไข จะต้องการสร้างชื่อกลุ่มไว้ก่อนจึงจะใช้งานได้ โดยเข้าไปที่เมนู Admin จากนั้นกดเมนูด้านซ้าย Add Group แล้วใส่ชื่อกลุ่มที่ต้องการ จากนั้นกด ปุ่ม +

    ส่วนที่ 4. $this->departid = ‘294’; กำหนดหมายเลขของหน่วยงาน เพื่อใช้กรองว่าต้องการให้หน่วนงานใด สามารถเข้าสู่ระบบมาใช้งานได้ ในที่นี้ 294 คือ หมายเลขของห้องสมุดครับ

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

    ขอขอบคุณทุกท่าน ที่อ่านมาถึงตรงนี้ครับ 

    ^_^

  • วิธีการติดตั้ง CA Certificate PSU Passport บน Ubuntu Server

    ทดสอบบน : Ubuntu 14.04

    ขั้นตอนการติดตั้ง Certificate บนเครื่อง Ubuntu Server

    1. เข้า Web Site : https://ca.psu.ac.th ทำการ Login ด้วย Account PSU Passport

    2. ให้เลือกหัวข้อ Download a CA Certificate….. ดังรูป1

    3. ในหน้าต่าง Download a CA Certificate ให้ทำการ Download ไฟล์ดังรูป (เลือกดี ๆ นะครับ เลือก Base 64 ไม่ใช่ DER)2015-01-27_153731

    4. เมื่อโหลดแล้วจะได้ไฟล์ดังรูป3

    5. เมื่อดูข้อความในไฟล์จะได้ประมาณรูปนี้ (ที่เป็นแถบขาว คือ Sensor ครับ :P)2015-01-27_153759

    6. ทำการ copy ข้อความในไฟล์ certificate ไปยัง folder เก็บ certificate ดังนี้

    sudo sh -c 'cat certnew.cer > /etc/ssl/certs/psucer.crt'
    

    7. ทำการแก้ไขไฟล์ /etc/ldap/ldap.conf ดังนี้

    #TLS_CACERT /etc/ssl/certs/ca-certificates.crt
    TLS_CACERT /etc/ssl/certs/psucer.crt
    

    *หมายเหตุ จริง ๆ แล้วมีอีกวิธีคือข้ามการ Check Certificate ไปเลย โดยจะเพิ่มข้อความ TLS_REQCERT never ท้ายไฟล์ /etc/ldap/ldap.conf ก็ได้ดังนี้

    sudo sh -c 'echo "TLS_REQCERT never" >> /etc/ldap/ldap.conf'

    แต่วิธีนี้อาจจะไม่ปลอดภัยครับ เพราะอาจเจอ phising site ที่ปลอม server เข้ามาสวมรอยได้ครับ เพราะไม่ได้มีการ verify certificate ว่า server เป็นตัวจริง