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

  • วิธีการติดตั้ง SDK Sentry บน Platforms .NET (Asp.Net)

    Step 1 ลง Sentry.AspNet

    ผ่าน NuGet Package Manager โดยใช้คำสั่ง

    Install-Package Sentry.AspNet -Version 3.20.1

    Step 2 Create project ผ่าน Sentry

    เลือก Server เป็น ASP.NET และตั้งชื่อ aspnet-e-admission จากนั้น ก็กดปุ่ม Create Project

    เมื่อกดสร้างเรียบร้อยแล้ว จะเห็น Project ชื่อ aspnet-e-admission ตามรูป

    เมื่อคลิกดูรายละเอียด จะแสดงดังภาพ

    Step 3 Project Settings>Client Keys

    นำค่า DSN ที่อยู่ในส่วน Client Keys ไปใช้งานต่อในส่วนของตั้งค่า Web.config

    ศึกษาวิธีการตั้งค่า SDK Sentry บน Platforms .NET

    Step 1 ตั้งค่า Web.config

    เพิ่ม key “SentryDsn” และค่า value ได้มาจาก Project Settings>Client Keys

    Step 2 ตั้งค่า Global.asax.cs

    2.1 เรียก library sentry ที่ต้องใช้งาน

    2.2 ตั้งค่า Application_Start

    2.3 ตั้งค่า Application_BeginRequest

    2.4 ตั้งค่า Application_EndRequest

    2.5 ตั้งค่า Application_Error

    2.6 ตั้งค่า Application_End

    Step 3 ทดสอบรันเวบ และเปิด projects ใน Sentry

    เมื่อทดสอบรันเวบ ระบบรับสมัครนักศึกษาออนไลน์ (PSU Admission) จะเห็น Latest Releases ถ้าเลขเวอร์ชันตรงกับที่รัน แสดงว่าติดตั้งสำเร็จ

    หวังว่า km จะมีประโยชน์ไม่มากก็น้อยนะคะ

    ที่มา https://docs.sentry.io/platforms/dotnet/guides/aspnet/

  • เทคนิคการขยับแถวขึ้นลงใน Table ด้วยวิธี Drag & Drop ใน Blazor

    สำหรับ blog นี้ของผู้เขียน ถือว่าเป็นซีรี่ส์ที่ต่อเนื่องมาจาก https://sysadmin.psu.ac.th/2021/05/25/ขยับแถว-row-ขึ้น-ลง-ใน-asp-net-gridview-ด้วย-j/ และ https://sysadmin.psu.ac.th/2021/05/27/ขยับแถว-row-ขึ้น-ลง-ใน-asp-net-gridview-ด้วย-c/ ก็คือผู้เขียนจะนำเสนอวิธีการขยับแถวอีกวิธีหนึ่ง ที่น่าจะเฟรนด์ลี่ต่อผู้ใช้มากกว่า นั่นก็การ Drag & Drop ก็คือผู้ใช้งานสามารถเลือกคลิก ลาก และวาง เพื่อจัดลำดับได้ตามความต้องการ ซึ่งเมื่อผู้เขียนมาทำงานบน Blazor พบว่าการ implement เรื่องนี้สามารถทำได้ง่ายมาก โดยไม่ต้องพี่งพา javascript แต่อย่างใด เรามาดูกันเลยครับ

    1. เพิ่มโค้ด HTML ในส่วนของการแสดงผลข้อมูลในรูปแบบ Table

    @if (provinces != null)
    {
        <table class="table table-striped" >
            <thead>
                <tr >
                    <th>ลำดับ</th>
                    <th>จังหวัด</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var pr in provinces)
                {
                    <tr role="button" class="cursor-pointer"  >
                        <td>@pr.ID</td>
                        <td>@pr.Name</td>
                    </tr>
                }
            </tbody>
        </table>
    }

    2. เพิ่มโค้ด C# ในส่วนที่ควบคุมการทำงานและจำลองข้อมูล

    private List<Province> provinces = new List<Province>();
    
    protected override void OnInitialized()
    {
    
    	provinces.AddRange(new List<Province> {
    		new Province(1,"สงขลา"),
    		new Province(2,"ปัตตานี"),
    		new Province(3,"ยะลา"),
    		new Province(4,"นราธิวาส"),
    		new Province(5,"สตูล")
    	});
    
    }
    
    public class Province
    {
    	public Int32 ID;
    	public string Name;
    	public Province(int id, string name)
    	{
    		ID = id;
    		Name = name;
    	}
    }

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

    3. จากนั้นเพิ่มโค้ดส่วนที่ควบคุมการ Drag และ Drop ทั้งใน HTML และ C#

    @if (provinces != null)
    {
        <table class="table table-striped" ondragover="event.preventDefault();">
            <thead>
                <tr >
                    <th>ลำดับ</th>
                    <th>จังหวัด</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var pr in provinces)
                {
                    <tr role="button" class="cursor-pointer" draggable="true"
                @ondrop="@(()=> Drop(pr))" @ondrag="@(e => StartDrag(pr))" >
                        <td>@pr.ID</td>
                        <td>@pr.Name</td>
                    </tr>
                }
            </tbody>
        </table>
    }
    • ondragover=”event.preventDefault();” เป็นการขัดขวางการทำงานปกติของการ drag บน object เพื่อให้สามารถ drop ได้
    • @ondrop=”@(()=> Drop(pr))” เรียกฟังก์ชัน Drop พร้อมกับส่ง pr (จังหวัด) เมื่อมีเหตุการณ์ drop เกิดขึ้น
    • @ondrag=”@(e => StartDrag(pr))” เรียกฟังก์ชัน StartDrage พร้อมกับส่ง pr เมื่อมีเหตุการณ์ drag เกิดขึ้น
    /// <summary>
    /// เก็บตำแหน่งของจังหวัดที่คลิกเลือก เมื่อเริ่ม drag
    /// </summary>
    /// <param name="province"></param>
    private void StartDrag(Province province)
    {
    	currentIndex = GetIndex(province);
    }
    
    /// <summary>
    /// หาตำแหน่งของจังหวัดที่อยู่จาก list
    /// </summary>
    /// <param name="province"></param>
    /// <returns></returns>
    int GetIndex(Province province)
    {
    	return provinces.FindIndex(p => p.ID == province.ID);
    }
    
    /// <summary>
    /// เมื่อ Drop จังหวัดที่ drag มาลงในตำแหน่งที่ต้องการ
    /// </summary>
    /// <param name="province"></param>
    void Drop(Province province)
    {
    	if (province != null)
    	{
    		// หาตำแหน่งของจังหวัดที่ถูก drop
    		var index = GetIndex(province);
    
    		// หาจังหวัดที่ถูก drag มา จาก index ที่เก็บไว้ตั้งแต่เริ่ม drag
    		var current = provinces[currentIndex];
    
    		// ลบจังหวัดที่ถูก drag มา ออกจาก list
    		provinces.RemoveAt(currentIndex);
    
    		// แทรกจังหวัดที่ถูก drag มา ลงในตำแหน่งที่ drop
    		provinces.Insert(index, current);
    
    		StateHasChanged();
    	}
    }

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

    5. และเมื่อ drop ลงไปยังตำแหน่งที่ต้องการ จังหวัดที่ลากมาก็จะแทรกเข้าไปยังตำแหน่งที่ drop

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

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


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

  • เทคนิคการดึงข้อมูล Youtube Video ผ่าน Youtube API

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

    โจทย์ที่ว่าก็คือ จะต้องดึงข้อมูลรายละเอียดต่างๆ ของ Youtube จาก URL ที่ระบุ ไม่ว่าจะเป็น Thumbmail, Title, Description ซึ่งจากการไปศึกษา API ที่ Youtube ได้จัดเตรียมไว้ให้ ก็พบว่าสามารถใช้งานได้อย่างไม่ยุ่งยากแต่อย่างใด เพียงแต่เราจะต้องมี API Key ที่ต้องใช้ในการเข้าถึงข้อมูลต่างๆ ของ Youtube ดังนั้นเริ่มต้นเราจะไปดูวิธีการให้ได้มาซึ่ง API Key ก่อน

    1. เข้าไปที่ https://console.cloud.google.com/cloud-resource-manager และ login ด้วย google account ให้เรียบร้อย จากนั้นทำการสร้าง Project ใหม่

    2. ป้อนชื่อโปรเจ็ค ในที่นี้สมมติเป็น Sample Project กด CREATE

    3. จากนั้นให้ทำการ Enable API เพื่อให้โปรเจ็คที่เราสร้างสามารถใช้งานได้ โดยให้ไปที่เมนูแฮมเบอร์เกอร์ -> APIs & Services -> Enabled APIs & services

    4. กด + ENABLE APIS AND SERVISES

    5. ค้น Youtube Data API

    6. เลือก YouTube Data API v3 และกด Enable

    7. กด Enable

    8. กลับไปที่ Enable APIS And Services และเลือก YouTube Data API v3

    9. เลือก CREATE CREDENTIALS

    10. เลือก Public data และกด NEXT

    11. จะได้ API Key ที่จะนำไปใช้ในการเขียนโปรแกรมเพื่อดึงข้อมูลจาก Youtube API ให้สำเนาเก็บไว้ใช้งานในขั้นตอนต่อไป

    เมื่อเราได้ API Key มาแล้ว เราก็จะมาถึงขั้นตอนในการเขียนโปรแกรมกันต่อ เนื่องจากในตอนนี้ผู้เขียนพัฒนาโปรเจ็คต่างๆ ด้วย Blazor ซึ่งเป็นเฟรมเวิร์คปัจจุบันของ ASP.NET ตัวอย่างโค้ดผู้เขียนก็จะขอเขียนด้วย Blazor ซึ่งในส่วนของการติดต่อ API ก็น่าจะพอเป็นแนวทางให้กับการพัฒนาด้วยเฟรมเวิร์คอื่นหรือภาษาอื่นได้ และเพื่อไม่ให้บล็อกนี้ยาวเกินไป ผู้เขียนขอข้ามขั้นตอนการสร้างโปรเจ็คไปเลย

    1. เริ่มต้นเพื่อให้โปรเจ็คของเราสามารถใช้งาน Youtube API ได้ เราจะต้องติดตั้ง Library ที่จำเป็นก่อน โดยเราจะติดตั้งผ่านเครื่องมือที่เรียกว่า Nuget โดยคลิกขวาที่โปรเจ็ค เลือก Manage Nuget Packages…

    2. ค้น Youtube เลือก Google.Apis.Youtube.v3 และ Install

    3. และเพื่อให้ page ที่เราจะเขียนโปรแกรมติดต่อกับ Youtube API สามารถใช้งาน Library ดังกล่าวได้ เราจะต้องเพิ่ม using Library นั้นๆ เข้ามาก่อน

    @using Google.Apis.Services
    @using Google.Apis.YouTube.v3
    @using Google.Apis.YouTube.v3.Data
    

    4. สร้าง YoutubeService เพื่อใช้ในการติดต่อ API และเราจะกำหนด API Key ใน object นี้ ดังโค้ดตัวอย่าง

    var youtubeService = new YouTubeService(new BaseClientService.Initializer()
    {
        ApiKey = "XXxxXxXxXXX0xxxxXXxxxXxXXxxxxXXxX0XX0x0",
        ApplicationName = this.GetType().ToString()
    });

    5. สร้าง object ListRequest และกำหนดข้อมูลที่ต้องการเป็น snippet ผ่านเมทธอด List ซึ่ง snippet จะเป็น object ที่บรรจุข้อมูลต่างๆ ของวิดีโอยูทูปนั้นๆ

    VideosResource.ListRequest req = youtubeService.Videos.List("snippet");

    6. กำหนดค่าแปรวิดีโอที่เราต้องการดึงข้อมูล ซึ่งจะเป็น parameter ที่อยู่หลังตัวแปร v เช่น https://www.youtube.com/watch?v=4VTx7oIzv_8 ค่าที่เราต้องการคือ 4VTx7oIzv_8

    req.Id = "4VTx7oIzv_8";

    7. จากนั้นทำการดึงข้อมูลจาก Youtube โดยเรียกเมทธอด Execute และดึงข้อมูลที่ต้องการจาก property ต่างๆ ที่อยู่ใน object Snippet

    VideoListResponse resp = req.Execute();
    if (resp.Items.Count > 0)
    {
    	title = resp.Items.ElementAt(0).Snippet.Title;
    	description = resp.Items.ElementAt(0).Snippet.Description;
        ThumbnailDetails tmb = resp.Items.ElementAt(0).Snippet.Thumbnails;
        thumbnail = tmb.Default__.Url;
    }

    8. และโด้ด HTML ส่วนแสดงผล

    <p><b>URL : </b> @url</p>
    <p><b>Title : </b> @title</p>
    <p><b>Description : </b> @title</p>
    <p><b>Thumbnail : </b> </p>
    <img src="@thumbnail" />

    9. จะได้ผลลัพธ์ดังตัวอย่าง

    10. นอกจากนี้ผู้อ่านสามารถดึงข้อมูลอื่นๆ ที่เกี่ยวข้องกับ video นั้นๆ ได้ผ่าน object Snippet รวมทั้งมี object ย่อยๆ อีกมากมายอย่างเช่น Thumbnails เป็นต้น หรือผู้อ่านจะทดสอบการทำงานได้ง่ายๆ ผ่าน query string โดยให้แทนที่ API Key ด้วยคีย์ของท่านเอง และ Video ID ด้วย ID ของ Video ที่ต้องการได้เลย ดังตัวอย่าง

    https://www.googleapis.com/youtube/v3/videos?part=snippet&id={video IDs}&key={API key}

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


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

  • เรียนรู้เบื้องต้นกับการใส่ลายน้ำ(Watermark)ให้กับเอกสาร PDF ของเราด้วย iTextSharp

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

    แบบที่ 1 : การสร้างลายน้ำแบบข้อความ ซึ่งวิธีการนี้ จะมีการทำ Template ต้นฉบับของลายน้ำแบบข้อความไว้ก่อน และเมื่อต้องการทำไฟล์ลายน้ำ จะต้องทำการอ่านไฟล์ลายน้ำจากต้นแบบมาจัดทำลายน้ำบนไฟล์ที่ต้องการได้ ดังนี้ค่ะ

    1.1 ระบุ Library เพิ่มเติม

    using iTextSharp.text;
    using iTextSharp.text.pdf;
    using System.IO;

    1.2 สร้างเมธอดในการสร้าง Template ลายน้ำต้นฉบับ เพื่อใช้ในการทำลายน้ำให้กับไฟล์ PDF ที่ต้องการ

            ////กรณียังไม่เคยสร้างหรือมี Template มาก่อน ให้เรียกใช้งาน CreateTemplate("ข้อความที่ต้องการให้แสดงในเทมเพลต",พาธที่จะสร้างไฟล์เทมเพลตดังกล่าว) 
            public void CreateTemplate(string watermarkText, string targetFileName)
            {
                var document = new Document();
    
                ////ระบุพาธที่ต้องการสร้างไฟล์เทมเพลต
                var pdfWriter = PdfWriter.GetInstance(document, new FileStream(targetFileName, FileMode.Create));
                ///ระบุค่าต่างๆเกี่ยวกับตัวอักษรที่จะแสดงผลในเทมเพลตลายน้ำที่สร้างขึ้น
                var font = new Font(Font.FontFamily.HELVETICA, 60, Font.NORMAL, BaseColor.LIGHT_GRAY);
                document.Open();
    
                ////ระบุค่าข้อความ และค่าต่างๆให้กับลายน้ำที่ต้องการ
                ColumnText.ShowTextAligned(pdfWriter.DirectContent, Element.ALIGN_CENTER, new Phrase(watermarkText, font), 300, 400, 45);
                document.Close();
            }

    ตัวอย่าง ไฟล์ PDF ของ Template ลายน้ำที่ได้จากการเรียกใช้งานเมธอด CreateTemplate ข้างต้น

    1.3 สร้างเมธอดในการจัดทำลายน้ำข้อความให้กับไฟล์ PDF จาก template ลายน้ำต้นฉบับ

    public void AddTextWatermark(string sourceFilePath, string watermarkTemplatePath, string targetFilePath)
            {
                ///ระบุพาธไฟล์ต้นทางที่ต้องการทำข้อความลายน้ำ
                var pdfReaderSource = new PdfReader(sourceFilePath);
                ///ระบุพาธไฟล์ปลายทางที่ต้องการบันทึกไฟล์แบบมีลายน้ำ
                var pdfStamper = new PdfStamper(pdfReaderSource, new FileStream(targetFilePath, FileMode.Create));
    
                ///ระบุพาธของไฟล์ต้นแบบลายน้ำที่จัดทำไว้
                var pdfReaderTemplate = new PdfReader(watermarkTemplatePath);
                var page = pdfStamper.GetImportedPage(pdfReaderTemplate, 1);
    
                ///ทำการวนลูปเพื่อทำลายน้ำให้กับไฟล์ PDF ทีละหน้า
                for (var i = 0; i < pdfReaderSource.NumberOfPages; i++)
                {
                    ///ระบุตำแหน่งในการแสดงผลลายน้ำกับเนื้อหาในไฟล์ PDF กรณีนี้คือวางไว้ใต้เนื้อหา แต่หากต้องการให้อยู่บนเนื้อหาให้เปลี่ยนเป็น GetOverContent แทน 
                    var content = pdfStamper.GetUnderContent(i + 1);
                    content.AddTemplate(page, 0, 0);
                }
    
                pdfStamper.Close();
                pdfReaderTemplate.Close();
            }

    1.4 เรียกใช้งานเมธอดเพื่อทำลายน้ำให้กับไฟล์ PDF ที่ต้องการ(กรณีนี้สมมุติให้เป็นการกดปุ่มเพื่อเรียกใช้งานเมธอดดังกล่าว)

     protected void btnGenWatermark_Click(object sender, EventArgs e)
            {
                 ////กำหนด Path ของไฟล์ PDF ต้นทางที่ต้องการทำลายน้ำ กรณีที่ gen จาก iTextSharp สามารถนำมาประยุกต์ใช้โดยเอาพาธดังกล่าวมาใช้เป็น strSourcePath ได้เลย
                string strSourcePath = HttpContext.Current.Server.MapPath("~/PDF/File/PDFDocument.pdf");
    
                 ////สร้างตัวแปรชื่อไฟล์ PDF ที่จะบันทึกใหม่ในชื่อตัวแปร genName
                string genName = Guid.NewGuid().ToString() + ".pdf";
    
                 ////กรณียังไม่เคยสร้างหรือมี Template มาก่อนให้เรียกใช้งาน CreateTemplate("ข้อความที่ต้องการให้แสดงในเทมเพลต",templatePath)  ทั้งชื่อไฟล์เทมเพลตใหม่ที่จะบันทึก
                string templatePath = HttpContext.Current.Server.MapPath("~/PDF/TemplateWatermarkPDF/" + genName);
    
                 ////ระบุพาธปลายทางที่ต้องการบันทึกไฟล์แบบมีลายน้ำพร้อมทั้งชื่อไฟล์ใหม่ที่จะบันทึก
                string targetPath = HttpContext.Current.Server.MapPath("~/PDF/WatermarkPDF/" + genName);
    
                 ////ตรวจสอบว่ามีไฟล์ดังกล่าวอยู่จริงตามที่อยู่ที่ระบุไว้หรือไม่
                if( File.Exists(strSourcePath))
                { 
                     ////กรณียังไม่เคยสร้างหรือมี Template มาก่อน ให้เรียกใช้งาน CreateTemplate("ข้อความที่ต้องการให้แสดงในเทมเพลต",พาธที่จะสร้างไฟล์เทมเพลตดังกล่าว) 
                    CreateTemplate("Copyrights (c) 2023", templatePath);
    
                     ////เรียกใช้งานเมธอดในการสร้างข้อความลายน้ำตามเทมเพลตที่มี(ตามพาธที่ระบุ) โดยส่งค่าที่อยู่ของไฟล์ต้นทาง พาธเทมเพลตที่ต้องการทำลายน้ำ และพาธปลายทางที่ต้องการบันทึกไฟล์แบบมีลายน้ำ
                    AddTextWatermark(strSourcePath, templatePath, targetPath);
                }
            }

    ตัวอย่างผลลัพธ์

    หมายเหตุ : 

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

    แต่สำหรับเนื้อหาในไฟล์ PDF โดยทั่วไปแล้ว สามารถใช้งาน GetUnderContent ได้ตามปกติ เพื่อไม่ให้ข้อความลายน้ำบดบังเนื้อหาในเอกสาร

    แบบที่ 2 การสร้างลายน้ำแบบรูปภาพให้กับเอกสาร PDF โดยจะขอยกตัวอย่างให้ดู 2 แบบนะคะ คือแบบที่สร้างลายน้ำบนไฟล์ PDF ใหม่ และแบบที่สร้างลายน้ำบนไฟล์ต้นทางเดิมค่ะ 

    2.1 แบบสร้างลายน้ำบนไฟล์ใหม่ โดยการสร้างเมธอด AddImageWatermark

        public void AddImageWatermark(string sourceFilePath, string watermarkImagePath, string targetFilePath)
            {
                ///ระบุพาธไฟล์ต้นทางที่ต้องการทำภาพลายน้ำ
                var pdfReader = new PdfReader(sourceFilePath);
    
                ///ระบุพาธไฟล์ปลายทางที่ต้องการบันทึกไฟล์แบบมีภาพลายน้ำ
                var pdfStamper = new PdfStamper(pdfReader, new FileStream(targetFilePath, FileMode.Create));
    
                ///ระบุพาธของไฟล์ภาพที่ต้องการนำมาทำภาพลายน้ำ
                var image = iTextSharp.text.Image.GetInstance(watermarkImagePath);
                image.SetAbsolutePosition(200, 400);
    
                ///ทำการวนลูปเพื่อทำภาพลายน้ำให้กับไฟล์ PDF ทีละหน้า
                for (var i = 0; i < pdfReader.NumberOfPages; i++)
                {
                    var content = pdfStamper.GetUnderContent(i + 1);
                    content.AddImage(image);
                }
    
                pdfStamper.Close();
            }

    วิธีเรียกใช้งาน (กรณีนี้สมมุติให้เป็นการกดปุ่มเพื่อเรียกใช้งานเมธอดดังกล่าว)

            protected void btnGenWatermark_Click(object sender, EventArgs e)
            {
                ////กำหนด Path ของไฟล์ภาพที่ต้องการทำลายน้ำ
                string strWaterMark = HttpContext.Current.Server.MapPath("~/images/clientpreview.png");
                
    ////ระบุ Path ของไฟล์ PDF ต้นทางที่ต้องการทำลายน้ำ string strSourcePath = HttpContext.Current.Server.MapPath("~/PDF/File/job_apply_form.pdf");
    ////สร้างตัวแปรชื่อไฟล์ PDF ที่จะบันทึกใหม่ในชื่อตัวแปร genName string genName = Guid.NewGuid().ToString() + ".pdf";
    ////ระบุพาธปลายทางที่ต้องการบันทึกไฟล์แบบมีลายน้ำพร้อมทั้งชื่อไฟล์ใหม่ที่จะบันทึก string targetPath = HttpContext.Current.Server.MapPath("~/PDF/WatermarkPDF/" + genName);
    ////ตรวจสอบว่ามีไฟล์ภาพลายน้ำ และไฟล์ PDF ต้นทางดังกล่าวอยู่จริงตามที่อยู่ที่ระบุไว้หรือไม่ if (File.Exists(strWaterMark) && File.Exists(strSourcePath)) { AddImageWatermark(strSourcePath, strWaterMark, targetPath); } }

    2.2 แบบสร้างลายน้ำบนไฟล์เดิม โดยการสร้างเมธอด AddImageWatermarkInExistingFile

     private void AddImageWatermarkInExistingFile(string sourceFilePath,string watermarkImagePath )
            {
                 ///อ่านค่าจากพาธไฟล์ต้นทางที่ต้องการทำภาพลายน้ำ
                byte[] bytes = File.ReadAllBytes(sourceFilePath);
    
                 ///ระบุพาธของไฟล์ภาพที่ต้องการนำมาทำภาพลายน้ำ
                var img = iTextSharp.text.Image.GetInstance(watermarkImagePath);
    
                img.SetAbsolutePosition(200, 400);
                PdfContentByte waterMark;
    
                using (MemoryStream stream = new MemoryStream())
                {
                     ///ทำภาพลายน้ำในไฟล์ต้นทางที่ต้องการทำภาพลายน้ำ
                    PdfReader reader = new PdfReader(bytes);
                    using (PdfStamper stamper = new PdfStamper(reader, stream))
                    {
                        ///ทำการวนลูปเพื่อทำภาพลายน้ำให้กับไฟล์ PDF ทีละหน้า
                        int pages = reader.NumberOfPages;
                        for (int i = 1; i <= pages; i++)
                        {
                            waterMark = stamper.GetUnderContent(i);
                            waterMark.AddImage(img);
                        }
                    }
                    bytes = stream.ToArray();
                }
                File.WriteAllBytes(sourceFilePath, bytes);
            }

    วิธีเรียกใช้งาน (กรณีนี้สมมุติให้เป็นการกดปุ่มเพื่อเรียกใช้งานเมธอดดังกล่าว)

            protected void btnGenWatermark_Click(object sender, EventArgs e)
            {
                ////กำหนด Path ของไฟล์ภาพที่ต้องการทำลายน้ำ
                string strWaterMark = HttpContext.Current.Server.MapPath("~/images/clientpreview.png");
    
                ////ระบุ Path ของไฟล์ PDF ต้นทางที่ต้องการทำลายน้ำ
                string strSourcePath = HttpContext.Current.Server.MapPath("~/PDF/File/job_apply_form.pdf");
    
                ////สร้างตัวแปรชื่อไฟล์ PDF ที่จะบันทึกใหม่ในชื่อตัวแปร genName
                string genName = Guid.NewGuid().ToString() + ".pdf";
    
                 ////ตรวจสอบว่ามีไฟล์ภาพลายน้ำ และไฟล์ PDF ต้นทางดังกล่าวอยู่จริงตามที่อยู่ที่ระบุไว้หรือไม่
                if (File.Exists(strWaterMark) && File.Exists(strSourcePath))
                {
                    AddImageWatermarkInExistingFile(strSourcePath,strWaterMark);
                 }
            }
    

    ตัวอย่างผลลัพธ์

    หมายเหตุ :
    1. ทั้งสองวิธีข้างต้นจะให้ผลลัพธ์ของภาพลายน้ำแบบเดียวกัน แต่ต่างกันตรงที่ไฟล์ที่บันทึกภาพลายน้ำ โดยแบบแรกจะสร้างเป็นไฟล์ใหม่ แต่แบบที่สองจะบันทึกลงบนไฟล์เดิม

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

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

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

  • มาลองใส่ข้อความ/ภาพลายน้ำ(Watermark) บนรูปภาพที่เราอัพโหลดด้วย C# กันเถอะ

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

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

    1.การใส่ลายน้ำแบบข้อความ โดยการทำลายน้ำด้วยวิธีนี้ เราจะต้องมีการระบุข้อความที่เราต้องการให้แสดงลายน้ำในภาพที่ทำการอัพโหลด ซึ่งขั้นตอนการทำลายน้ำ มีดังนี้

    1.1 เพิ่ม Library ที่เกี่ยวข้องเพิ่มเติมเข้ามา

    using System.IO;
    using System.Drawing;

    1.2 สร้างหน้าจอที่มี File upload และปุ่มเพื่อทำการอัพโหลดไฟล์

    .aspx page

       <html >
            <head runat="server">
            <title></title>
            </head>
            <body>
                    <form id="form1" runat="server">
                            <div><asp:FileUpload ID="FileUpload" runat="server" />
                            <asp:Button runat="server" Text="Upload" ID="btnSave" OnClick="btnSave_Click" />
                            </div>
                    </form>
            </body>
        </html>

    1.3 สร้างเมธอดที่ใช้ในการสร้างภาพลายน้ำ ที่ชื่อว่า AddTextWatermark() โดยมีพารามิเตอร์เป็นข้อความที่ต้องการให้แสดงเป็นลายน้ำ ดังนี้

    Code C#

    เมธอด AddTextWatermark

     protected void AddTextWatermark(string watermarkText)
            {
                ////รับพารามิเตอร์เป็นข้อความที่ต้องการแสดงผล ในชื่อตัวแปร watermarkText 
                ////สร้างตัวแปรรูปภาพที่ได้จากการอัพโหลดไฟล์โดยผู้ใช้งาน ในชื่อตัวแปร image
                System.Drawing.Image image = System.Drawing.Image.FromStream(FileUpload.PostedFile.InputStream);
    
                ////หาค่าความกว้างและความสูงของภาพที่อัพโหลดใส่ตัวแปร w และ h
                int w = image.Width;
                int h = image.Height;
    
                ////สร้างตัวแปรชื่อรูปภาพที่จะบันทึกใหม่จากการรอัพโหลดไฟล์โดยผู้ใช้งาน ในชื่อตัวแปร genName
                string genName = Guid.NewGuid().ToString() + Path.GetExtension(FileUpload.PostedFile.FileName);
    
                ////สร้างตัวแปร Bitmap ในชื่อ bmp และระบุค่าความสูงและความกว้างตามภาพที่ได้ทำการอัพโหลด 
                System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(w, h);
    
                ////กำหนดค่าต่างๆให้กับ Graphics ในตัวแปรที่ชื่อว่า g จากตัวแปร Bitmap ที่สร้างไว้
                System.Drawing.Graphics g = System.Drawing.Graphics.FromImage((System.Drawing.Image)bmp);
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.Clear(System.Drawing.Color.Transparent);
                g.DrawImage(image, 0, 0, w, h);
    
                ////กำหนดขนาดของตัวอักษร และค่าต่างๆในการแสดงผลของตัวอักษรของข้อความ
                System.Drawing.Font drawFont = new System.Drawing.Font("Arial", 30, FontStyle.Bold, GraphicsUnit.Pixel);
    
                ////หาขนาดของข้อความ ตามค่าตัวอักษร(font)ที่ระบุข้างต้น
                SizeF textSize = new SizeF();
                textSize = g.MeasureString(watermarkText, drawFont);
    
                /////กำหนดสีของข้อความที่ต้องการทำลายน้ำและการกำหนดสีแบบ transparency
                System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(Color.FromArgb(200, 211, 211, 211));
    
                /////กำหนดจุดหมุนจากจุดกึ่งกลางภาพ และหมุนให้ข้อความเอียง 45 องศา
                g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
                g.RotateTransform(45);
    
                ////ระบุข้อความ ลักษณะตัวอักษร สีและตำแหน่งในการวางข้อความลงบนรูปภาพที่กำหนด โดยจะกำหนดให้อยู่กึ่งกลางภาพแบบทะแยงมุม 45 องศา
                g.DrawString(watermarkText, drawFont, drawBrush, -(textSize.Width / 2), -(textSize.Height / 2));
                System.Drawing.Image newImage = (System.Drawing.Image)bmp;
    
                ////บันทึกภาพไปยังที่อยู่ไฟล์ที่ระบุ
                newImage.Save(Server.MapPath("~/Uploads/" + genName));
                g.Dispose();
            }

    เรียกใช้เมธอดเมื่อกดปุ่ม Upload โดยส่งพารามิเตอร์เป็นข้อความที่ต้องการให้แสดงลายน้ำบนภาพไป

    protected void btnSave_Click(object sender, EventArgs e) {       AddTextWatermark("Copyrights (c) 2023"); }

    ผลลัพธ์ตัวอย่าง

    เพิ่มเติม

    1. หากต้องการเปลี่ยนตำแหน่งของภาพลายน้ำ สามารถทำได้หลายแบบ โดยผู้เขียนจะขอยกตัวอย่างกรณีที่ต้องการให้ภาพลายน้ำอยู่ตำแหน่งขวาล่าง ซึ่งปรับแก้เพิ่มเติม ดังนี้
      เปลี่ยนจาก
      /////กำหนดจุดหมุนจากจุดกึ่งกลางภาพ และหมุนให้ข้อความเอียง 45 องศา
      g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
      g.RotateTransform(45);

      ////ระบุข้อความ ลักษณะตัวอักษร สีและตำแหน่งในการวางข้อความลงบนรูปภาพที่กำหนด โดยจะกำหนดให้อยู่กึ่งกลางภาพแบบทะแยงมุม 45 องศา
      g.DrawString(watermarkText, drawFont, drawBrush, -(textSize.Width / 2), -(textSize.Height / 2));
      System.Drawing.Image newImage = (System.Drawing.Image)bmp;
      เป็น 
      Point position = new Point((bmp.Width – ((int)textSize.Width + 10)), (bmp.Height – ((int)textSize.Height + 10)));

      ////ระบุข้อความ ลักษณะตัวอักษร สีและตำแหน่งในการวางข้อความลงบนรูปภาพที่กำหนด โดยจะกำหนดให้อยู่ขวาล่างแทน
      g.DrawString(watermarkText, drawFont, drawBrush, position);
      แทน
    2. ในการนำไปใช้งานจริง ในส่วนของตำแหน่ง สีและขนาดตัวอักษร ท่านสามารถระบุได้ตามความเหมาะสมเพื่อให้รองรับกับการใช้งานจริง ในตัวอย่างเป็นเพียงกรณีศึกษาเท่านั้นค่ะ

    ตัวอย่างผลลัพธ์หลังมีการปรับตำแหน่งการแสดงผลลายน้ำ

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

    Code C#

    เมธอด AddImageWatermark

       protected void AddImageWatermark(string watermarkImagePath)
            {
                try {
                    ////สร้างตัวแปรชื่อรูปภาพที่จะบันทึกใหม่จากการรอัพโหลดไฟล์โดยผู้ใช้งาน ในชื่อตัวแปร genName
                    string genName = Guid.NewGuid().ToString() + Path.GetExtension(FileUpload.PostedFile.FileName);
    
                    ////ประกาศตัวแปรของไฟล์รูปภาพที่ทำการอัพโหลด
                    using (Bitmap image = (Bitmap)System.Drawing.Image.FromStream(FileUpload.PostedFile.InputStream))
    
                    ////ประกาศตัวแปรไฟล์ภาพที่ต้องการนำมาทำเป็นภาพลายน้ำ
                    using (System.Drawing.Image watermarkImage = System.Drawing.Image.FromFile(watermarkImagePath))
    
                    using (Graphics imageGraphics = Graphics.FromImage(image))
                    using (TextureBrush watermarkBrush = new TextureBrush(watermarkImage))
                    {
                        ////หาจุดตำแหน่งกึ่งกลางภาพที่ต้องการวางภาพลายน้ำ
                        int x = (image.Width / 2 - watermarkImage.Width / 2);
                        int y = (image.Height / 2 - watermarkImage.Height / 2);
                        watermarkBrush.TranslateTransform(x, y);
                        imageGraphics.FillRectangle(watermarkBrush, new Rectangle(new Point(x, y), new Size(watermarkImage.Width + 1, watermarkImage.Height)));
    
                        ////บันทึกรูปภาพตามที่อยู่(Path)ที่ระบุ
                        image.Save(Server.MapPath("~/Uploads/" + genName));
                    }
                }
                catch (Exception ex)
                {
                    ////แสดงข้อความแจ้งเตือนกรณีที่พบข้อผิดพลาดระหว่างดำเนินการ
                    Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), "alert", "alert('ไม่สามารถอัพโหลดไฟล์และทำลายน้ำภาพดังกล่าวได้" + ex.Message + "')", true);
                }
               
            }

    เรียกใช้เมธอดเมื่อกดปุ่ม Upload โดยส่งพารามิเตอร์เป็นที่อยู่ของภาพที่ต้องการให้แสดงลายน้ำบนภาพไป

    protected void btnSave_Click(object sender, EventArgs e) { string strWaterMark = HttpContext.Current.Server.MapPath("~/images/preview.png");       AddImageWatermark(strWaterMark); }

    ผลลัพธ์ตัวอย่าง

    หมายเหตุ : หน้า aspx ที่ใช้จะเป็นแบบเดียวกับวิธีที่ 1 (การทำลายน้ำแบบข้อความ) แต่จะแตกต่างกันเฉพาะในส่วนของการทำงานใน btnSave_Click ที่มีการเรียกใช้เมธอดและส่งค่าพารามิเตอร์ต่างกันค่ะ

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

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

  • แก้ปัญหาการเรียกใช้ font ผ่าน css แล้วเกิด error Access to font at has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

    เนื่องด้วยทางทีมงานต้องการใช้ font ผ่าน css ที่อยู่คนละที่ โดยต้องการให้ไปเรียกที่เดียวเพื่อการจัดการง่าย แต่ปัญหาก็เกิดขึ้น เพราะทางต้นทางมีการ block ไม่ให้เข้าถึง วันนี้จึงมีแนวทางการแก้ปัญหาดังนี้

    ขั้นตอนที่ 1 ไปตั้งค่าไฟล์ font ที่ต้องการ โดยในที่นี้ เป็น folder fonts โดยไปที่ IIS เลือก folder ที่ต้องการ จากนั้นเลือกเมนู “HTTP Response Headers” จากนั้น กด “Add” ดังรูป

    ขั้นตอนที่ 2 เพิ่ม Name: Access-Control-Allow-Origin และ Value: * ดังรูป

    ขั้นตอนที่ 3 ผลลัพธ์จากการเพิ่ม Name: Access-Control-Allow-Origin และ Value: * ดังรูป

    ขั้นตอนที่ 4 เพิ่ม Name: Access-Control-Allow-Origin และ Value: * ที่ไฟล์ font.css ด้วยเหมือนกัน

    ขั้นตอนที่ 5 ปรับแก้การเรียกใช้ css โดยใช้ SRI Hash Generator ผ่าน SRI Hash Generator

    ขั้นตอนที่ 6 เวบไซต์ปลายทางก็จะสามารถเรียกใช้ font จาก เวบไซต์ต้นทาง ดังรูป

    หวังว่าจะเป็นประโยชน์ให้กับคนที่มีปัญหาแบบเดียวกันนี้นะคะ

    ที่มา

  • upgrade crystal report version 10 ไปเป็น version 13

    เนื่องด้วยตอนนี้ crystal report version 10 มันเก่ามากแล้วทางผู้เขียนเลยอยากจะ upgrade crystal report version 10 ไปเป็น version 13 ดังนั้นจึงสรุปขั้นตอนไว้เพื่อเป็นแนวทางการทำงานของคนที่มีปัญหาเดียวกัน ดังนี้

    ขั้นตอนที่ 1 แก้ web.config ใน project web application โดยเปลี่ยนเลข version จาก 10.2.3600.0 เป็น 13.0.2000.0

    ขั้นตอนที่ 2 copy folder crystalreportviewers13 ใส่ไว้ที่ project web application

    ขั้นตอนที่ 3 แก้ page ที่เรียกใช้ crystal report โดยเปลี่ยนเลข version จาก 10.2.3600.0 เป็น 13.0.2000.0

    ขั้นตอนที่ 4 แก้ page ที่เรียกใช้ crystal report โดยเพิ่มการเรียก JavaScript ในส่วนของ tag head

    ขั้นตอนที่ 5 ลองเรียกใช้ page ที่มี crystal report ได้ผลลัพธ์ดังรูป

    ทางผู้เขียนหวังว่าจะมีประโยชน์ในการ upgrade crystal report version 10 ไปเป็น version 13 ใน source code ที่เรียกใช้ version เก่านะคะ

  • UX, everything related!

    เรามักได้ยินคำว่า UI เป็นประจำเมื่อเราพัฒนาระบบแต่ รู้หรือไม่ว่านอกจาก UI แล้วมันมีอีกหนึ่งอย่างที่ควรรู้และสำคัญยิ่งกว่าแต่ถูกมองข้ามไปคือ UX (ย่อมาจาก User experience) หลายๆคนมักจะสับสนว่า UI และ UX มันคือสิ่งเดียวกัน จริงๆแล้วมันคือคนละอย่างกันเลย วันนี้เราจะมาเล่าให้ฟัง

    รูปภาพจาก UX vs. UI Design: What’s the Difference? – louelledesignstudio

    UI :: User Interface

    User Interface คือหน้าตาของระบบที่ผู้ใช้ได้เห็น ได้ตอบสนอง ไม่ใช่ระบบในทางคอมพิวเตอร์อย่างเดียวที่มี UI ถ้าเทียบกับขวดซอสมะเขือเทศ ขวดก็คือหนึ่งใน UI เช่นกันหรืออาหาร 1 จาน หน้าตาของอาหารก็ถือว่าเป็น UI ด้วย

    UI เป็นสิ่งที่สามารถมองเห็นได้หรือจับต้องได้


    UX :: User Experience

    User Experience คือ Experience หรือประสบการณ์ของผู้ใช้ที่เราได้ส่งมอบให้ มากกว่า Interface ที่ผู้ใช้งานได้ตอบสนอง เราจะไป focus ที่ผู้ใช้ใช้สินค้าเราแล้วมีความรู้สึกอย่างไร ผู้ใช้ใช้สินค้าเราแล้วได้บรรลุวัตถุประสงค์ของเราหรือไม่

    “UX คือสิ่งที่อยู่กับความรู้สึก จับต้องไม่ได้ มองไม่เห็น แต่วัดประเมินผลได้”

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

    จริงๆแล้ว นอกจาก website หรือ application ที่ต้องมี UX ที่ดีเป็น 1 ในองค์ประกอบแล้ว ทุกๆอย่างรอบตัวในชีวิตประจำวันก็ต้องมี UX ที่ดีเช่นกัน

    รูปภาพจาก Why people need to stop obsessing over UI design | by Zameel Kainikkara | Zartek | Medium

    Why should we have to care on UX?

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


    แล้วเราจะไปหาข้อมูลอะไรมาวิเคราะห์หละ แน่นอน

    UX = Research

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

    การมี user experience ที่ดีมาจากการทำ Research หรือการค้นความหาข้อมูล ถามว่าการตามหาข้อมูลจะทำได้อย่างไรหละ

    User Research

    การ research ข้อมูลของผู้ใช้งาน/กลุ่มผู้ใช้งาน จะได้ออกแบบได้ตรงจุด

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

    ข้อมูลเหล่านี้เป็นตัวแปรตั้งต้นที่เราจะต้องมาออกแบบระบบอย่างไรให้ตอบโจทย์กลุ่มผู้ใช้ที่เราเก็บข้อมูลมา จะเห็นได้ว่า ยิ่งเราทำ research มาเท่าไหร โอกาสของการสร้างสินค้ามาให้ตอบโจทย์คนส่วนใหญ่ได้ จะทำให้สินค้าเราขายออกได้ง่ายกว่าเช่น การออกแบบระบบสารสนเทศที่กลุ่มผู้ใช้ระบบ 90%เป็นผู้มีอายุ การทำระบบให้เขาใช้งานก็ควรมีตัวอักษรที่ใหญ่กว่าทั่วไป มีการทำ Shortcut เมนูที่ง่าย ไม่สับซ้อน

    Brand Research

    คนที่ว่าจ้างหรือว่าง่ายๆคือเจ้าของระบบคือใคร Brand หรืออัตลักษณ์ของเขาเป็นอย่างไร วัตถุประสงค์ขององค์กร สีขององค์กร design token ขององค์กร ก็เป็นอีก 1 อย่างที่ต้องมีการศึกษาข้อมูลด้วยเช่นกัน

    Problem Research

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


    What’s good UX?

    (ยกตัวอย่าง) ในการสร้างสินค้าขึ้นมาสักชิ้นที่จะทำให้ผู้ใช้รู้สึก win แล้วนั้น อีก 2 สิ่งที่ต้อง win ด้วยคือ Brand/Business win ด้วยไหม เสียผลประโยชน์รึเปล่าและ Team หรือคนสร้างสินค้า win ด้วยหรือไม่ ถ้าทั้ง 3 อย่างนี้ win ถือว่าเป็น UX ที่ดี

    ทั้งนี้การมี UX ที่ดีข้างต้นอาจเป็นเพียงสมมติฐานเบื้องต้น อาจจะมีปัจจัยอื่นๆเข้ามาเกี่ยวข้องอีก หรืออาจจะใช้ปัจจัยอื่นมาเป็นตัววัดก็ได้เช่น Happiness, Usability เป็นต้น


    Summary

    จะเห็นได้ว่า UX หรือ User experience ได้เข้าไปอยู่ในทุกๆส่วนในชีวิตประจำวันในหลายๆอย่าง นอกจากการออกแบบระบบสารสนเทศหรือ website เพียงอย่างเดียว ตั้งแต่การออกแบบสินค้าต่างๆที่เป็นมิตรต่อการใช้งาน การออกแบบบริการที่ตอบโจทย์คน การทำ marketing ที่สามารถส่งเสริมการขายได้ดี และอื่นๆอีกมากมาย

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

  • สร้างเงาให้กับวัตถุด้วย SmoothShadow

    ปัจจุบันเทรนการออกแบบเว็ปไซต์ต่างๆมักจะมีการใช้เงามาเป็นส่วนประกอบ ทำให้วัตถุชิ้นนั้นมีความโดดเด่น สร้างจุดสนใจของเว็ปไซต์ได้ หรือเพื่อความสวยงาม ดูมีมิติ แต่การที่เราใส่เงาให้กับวัตถุนั้นๆ เราก็จะต้องมาตั้งค่า Box-shadow หลาย parameter กว่าจะได้เงาสวยๆออกมาสักแบบหนึ่ง นั่งสุ่มตัวเลขความเบลอ ความเข้มของเงา หรือระยะห่างของเงา เว้นแค่ไหนดี ตัวเลขแค่นี้เพียงพอแล้วหรือยัง วันนี้ผมจะมาแนะนำ SmoothShadow เครื่องมือหนึ่งตัวที่มีประโยชน์และช่วยลดเวลาในการเขียน code ของเราได้มาก Smooth Shadow เป็นเว็ปไซต์ที่เราสามารถเข้าไปปรับแต่งเงาผ่าน UI บนเว็ปไซต์และสามารถ copy ออกมาใช้งานได้เลย
    หน้าตาของ SmoothShadow จะประกอบไปด้วยกล่องสี่เหลี่ยมอันหนึ่งกลางหน้าจอ ส่วนนี้จะแสดงรูปแบบเงาที่จะแสดงจริงๆบนเว็ปไซต์ โดยจะแสดงเงาตามการตั้งค่าจากแถบข้างขวา และมี CSS box-shadow แสดงไว้สำหรับการ copy ไปใช้งาน และส่วนของแถบข้างขวาจะแสดงแถบ customize เงา เราสามารถปรับแต่งเงาได้โดยการลาก adjustment bar ตามค่าต่างๆได้หรือลากปรับเส้นกราฟการไล่แสงเงาได้ ซึ่งประกอบไปด้วย 5 ส่วนหลักๆ

    Layer of shadows คือจำนวนชั้นของเงา ปกติโดยทั่วไปเราจะใช้งานกันประมาณ 1-2 layer ซึ่งจำนวนของชั้นเงานี้จะสัมพันธ์กับตัวปรับแต่งด้านล่าง เช่นในรูปตั้งไว้ที่ 4 layers หรือเงา 4 ชั้น เราก็จะสามารถปรับกราฟการไล่เงาซึ่งจะแบ่งไว้ 4 ช่วงของแสงไล่ตั้งแต่ขาวไปดำ 4 ระดับ ถ้าปรับเป็น 7 layers ระบบก็จะซอยช่วงเทาเพิ่มมามากขึ้น ก็จะไล่จากขาวไปดำ 7 ระดับ

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

    Final vertical distance คือการไล่แสงในแนวตั้ง ซึ่งจะสามารถไล่แสงได้แค่ทิศทางเดียวคือ แสงจากบนลงล่าง เงาด้านล่างของวัตถุ ยิ่งเพิ่มค่ามากเงาก็จะเพิ่มมาด้านล่างมากขึ้น ค่าลดลงเงาในแนวตั้งก็จะน้อยลง ส่วนกราฟจะแสดงปริมาณของเงาในแต่ละ layer สามารถดึงวงกลมสีชมพูเพื่อปรับแต่งได้

    Final blur strength คือระดับความเบลอของเงา ยิ่งเพิ่มค่าเบลอ เงาของเราก็จะเบลอ ดูนวลตา กลมกลืนมากขึ้น ถ้าลดค่าความเบลอ เงาก็จะยิ่งชัดขึ้นจะเป็นสี่เหลี่ยม แสงแข็ง เห็นชัดเจน ส่วนกราฟจะความเบลอในแต่ละ layer สามารถดึงวงกลมสีชมพูเพื่อปรับแต่งได้เช่นกัน

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

    ทั้งนี้ทั้งนั้น เราปรับแสงเงาที่กล่าวมาสามารถนำไปใช้ได้ในหลายสถานการณ์ อยากได้เงาสไตล์แบบเข้มๆ หรือแบบจางๆอ่อนๆ ก็สามารถปรับได้ในเบื้องต้น การปรับเอียงเงาไปซ้าย-ขวา หรือขึ้นข้างบน หรือการปรับสีของเงาคงต้องปรับผ่าน CSS ครับ

    อ้างอิง Smoother & sharper shadows with layered box-shadows | Tobias Ahlin , @brumm (@funkensturm) / Twitter