Category: Developer

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

  • Firebase และ Arduino ตอนที่ 1

    บทความนี้ ตอนที่ 1 จะพูดถึง (แจกโค๊ด) Firebase Realtime Database และ ตอนที่ 2 จะพูดถึง Arduino โดยที่ในตอนที่ 1 จะเป็นโปรแกรมให้เห็นว่า นำข้อมูลใส่เข้าใน Firebase Realtime Database ได้อย่างไร แล้วในตอนที่ 2 เราจะโปรแกรม Arduino ให้เชื่อมต่อเน็ตผ่าน Wi-Fi 802.1x แล้วหลอด LED จะถูกเปิด/ปิด หรือ เปลี่ยนสี เมื่อมีการสั่งจากโปรแกรม JavaScript ในตอนที่ 1

    ในขั้นตอนการติดตั้ง Firebase สมัครใช้งาน สร้าง Database ลองหาอ่านกันเอาเองนะครับ มันง่าย ๆ ครับ เรามาเริ่มต้นที่มี Database ชื่อว่า Firstapp-IoT ที่ผมสร้างไว้แล้ว ขั้นตอนมาเราจะโปรแกรม JavaScript โดยการเขียนโปรแกรมไว้ใน local folder เครื่องของผมใช้ Windows 10 ผมก็สร้างไว้ที่ C:\myFirebase และก็ไปดาวน์โหลด firebase-tools-instant-win.exe เพื่อใช้ในการทดสอบแบบ localhost ด้วยคำสั่ง firebase serve แล้วทำการทดสอบโปรแกรมผ่านเว็บเบราว์เซอร์ที่ localhost:5000 และเมื่อโปรแกรมพร้อมใช้ก็นำขึ้นไปไว้ใน Firebase hosting ด้วยคำสั่ง firebase deploy –only hosting แล้วก็สามารถใช้โปรแกรมผ่านทางเว็บเบราว์เซอร์ที่ firstapp-iot.web.app

    ภาพที่ 1 สร้างไดเรกทอรีขึ้นมาเองชื่อ myFirebase

    รัน firebase-tools-instant-win.exe

    ในขั้นตอน setup ครั้งแรก เราสร้างไดเรกทอรีชื่อ nodemcu

    จะมีคำถามว่าจะสร้าง project ใหม่ หรือ ใช้ที่มีอยู่ ผมมีอยู่แล้ว ดังรูป

    ภาพที่ 2 Firebase setup

    และถัดไป

    ภาพที่ 3 Firebase setup เสร็จจะได้ C:\myFirebase\nodemcu\public ไว้เขียนโปรแกรม

    เราจะเขียนโปรแกรมภาษา JavaScript ไว้ที่เรา setup ไว้ ไฟล์แรกที่เราจะเขียนคือ index.html และเมื่อเสร็จเราก็รัน firebase-tools-instant-win.exe กลับเข้ามา และทดสอบโดยคำสั่ง firebase serve

    ภาพที่ 4 ย้ายเข้าไปในที่ที่เรากำหนดเป็น project ของเรา และรันคำสั่งเพื่อทดสอบ

    และเมื่อโปรแกรมเริ่มใช้งานได้แล้ว ก็นำไปไว้บน Firebase hosting ด้วยคำสั่ง firebase deploy ดังรูป

    ภาพที่ 5 คำสั่ง firebase deploy –only hosting คือเอาเฉพาะที่อยู่ในไดเรกทอรี public

    ผลลัพธ์ของโปรแกรม

    ภาพที่ 6 เว็บเพจ firstapp-iot.web.app หน้านี้ใช้ index.html

    เมื่อเราใส่ email ที่อนุญาต ก็จะเข้าสู่หน้าต่อไปที่จะทดสอบการอัปเดตค่าข้อมูลใน Firebase Realtime Database ของ project ชื่อ Firstapp-IoT ดังรูป

    ภาพที่ 7 เว็บเพจหน้านี้ใช้ setled.html

    เมื่อคลิกที่ SET LED ON ได้ผลลัพธ์แบบนี้

    ภาพที่ 8 ที่ Firebase มีการอัปเดตข้อมูล

    สำหรับเว็บเพจ setled.html เป็นการทดสอบให้เห็นว่า เราสามารถตั้งค่า Firebase authentication ด้วย email แล้วอนุญาตให้แก้ไขข้อมูลใน Firebase Realtime Database ได้ โดยต้องไปตั้งค่า Rules ให้ดี

    ถัดไป คลิกที่ Goto NodeMCU ESP8266 Project ผมจะสาธิตการควบคุม Arduino NodeMCU ESP8266 ให้เปิด/ปิด LED ตรงนี้เชื่อมต่อผ่านเน็ต Wi-Fi 802.1x

    ภาพที่ 9 เว็บเพจ setledcolor.html

    ทดสอบคลิก Set LED On และเลือก GREEN

    ภาพที่ 10 ที่ Firebase จะมีการอัปเดตข้อมูลตามที่เราเลือก

    และดูผลลัพธ์ที่อุปกรณ์ ดังรูป

    ภาพที่ 11 Arduino เปิด LED และเปลี่ยนสีเขียว

    ถัดไปลองอีกตัวอย่าง คลิก Set LED Off และเลือก BLUE

    ภาพที่ 12 ที่ Firebase จะมีการอัปเดตข้อมูลตามที่เราเลือก

    และดูผลลัพธ์ที่อุปกรณ์ ดังรูป

    ภาพที่ 13 Arduino ปิด LED และเปลี่ยนสีฟ้า

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

    ภาพที่ 14 ใช้สายชาร์ตแบตมือถือนำไปเสียบปลั๊กไฟเพื่อจ่ายไฟแทนก้อนแบตก็ได้

    ต่อไปมาดูส่วนโปรแกรมกันบ้าง

    ภาพที่ 15 แสดงไดเรกทอรีที่เราทำขั้นตอน firebase setup

    คลิกเข้าไปในไดเรกทอรี public จะเห็นดังรูป

    ภาพที่ 16 แสดงไดเรกทอรีที่เราเขียนโปรแกรมเอาไว้

    แจกโค๊ดให้ครับ เข้าไปที่ github ตาม link นี้ https://github.com/woonpsu/firstapp-iot

    ถัดไปเรามาดูส่วนที่เราต้องมีใน Firebase

    ภาพที่ 17 ส่วนของ Web App ที่เราจะต้อง set up แล้วเราจะได้ firebaseConfig นำไปใส่ในโค๊ดที่แจก

    ในการทดสอบโปรแกรม เราจะต้องเปิด Developer tools ดังรูป

    ภาพที่ 18 เว็บเบราว์เซอร์ Google Chrome เปิด Developer tools

    เราใช้คำสั่ง console.log() เพื่อ debug ดูว่าโปรแกรม error มั้ย

    ภาพที่ 19 แสดงผลการ debug ของคำสั่ง console.log(‘Success!’);

    ถัดไปมาดูส่วนที่เราจะต้องมีใน Firebase

    ภาพที่ 20 แสดงการ set up Firebase Authentication ให้ใช้ Sign-in method เฉพาะ Email/Password

    สร้าง User ได้ในแท็บ Users และ Firebase Authentication Built-In Sign-In อนุญาตให้สร้างเพิ่มจากหน้า Sign-In หากยังไม่มีชื่อ User ที่ป้อน ตรงนี้เราต้องจัดการลบทิ้ง หรือ setup Firebase API ควบคุม หรือ เขียนส่วน Sign-In ใหม่เองครับ

    ภาพที่ 21 แสดงตัวอย่าง User ที่จะได้ Unique User UID ใช้ในการอนุญาต

    ถัดไปมาดู Rules ที่เราใช้ใน Project นี้

    ภาพที่ 22 แสดง Rules ที่กำหนดว่า อนุญาตให้ Read และ อนุญาตให้ Write ได้อย่างไรบ้าง

    ในตัวอย่าง setled.html แสดงตัวอย่างการใช้ rules แบบ “.read”: true และ “.write”: “$uid === auth.uid” โดยมี path เริ่มต้นที่ “$uid”

    ในตัวอย่าง setledcolor.html แสดงตัวอย่างการใช้ rules แบบ “.read”: true และ “.write”: true เนื่องจาก FirebaseArduino Library ไม่รองรับคำสั่ง Firebase Authentication ด้วย User

    และเมื่อเราใช้ Firebase cli ในการ deploy เราก็จะได้ Firebase Hosting ดังรูป

    ภาพที่ 23 แสดง Hosting ของ Project firstapp-iot และเห็น Current release และ Deployed ซึ่งเป็นอันก่อนหน้า

    ในบทความต่อไปจะเป็น Arduino เชื่อมต่อเน็ต Wi-Fi 802.1x ตอนที่ 2

  • สร้างเงาให้กับวัตถุด้วย 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

  • Flutter : ดึงข้อมูลจาก RESTFul API

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

    ขั้นตอนการติดตั้งบน Windows และเตรียมเครื่องมือสามารถอ่านได้ที่ https://flutter.dev/docs/get-started/install/windows

    ในบทความนี้ผมใช้ Visual Studio Code นะครับโดยติดตั้ง Extention เพิ่มเติมดังนี้

    1. เปิด Visual Studio Code
    2. คลิกเมนู View > Command Palette
    3. พิมพ์ “install”, จากนั้นเลือก Extensions: Install Extensions.
    4. พิมพ์ “flutter” เลือก install
    5. พิมพ์ “dart” เลือก install
    6. เมื่อติดตั้งเสร็จเรียบร้อยจะมีรายการ Extention ดังรูปครับ

    จากนั้นทำการสร้างโปรเจค ดังนี้ครับ

    1. คลิกเมนู View > Command Palette
    2. พิมพ์ “flutter”, จากนั้นเลือก Flutter:New Application Project
    3. จะได้โปรเจคที่มีโครงสร้างไฟล์ดังรูปครับ

    สำหรับท่านใดที่อยากเห็นหน้าตาแอปพลิเคชันเริ่มต้น ให้เปิด USB Debuging ที่มือถือให้เรียบร้อย ต่อเข้ากับคอมพิวเตอร์ จากนั้นที่มุมขวามือล่างของ Visual Studio Code ให้เลือกชื่อมือถือของท่าน จากนั้นกด F5 ได้เลยครับ

    ในการเชื่อมต่อกับ API เราจะต้อง import package เพิ่มโดยพิมพ์คำสั่งที่ terminal > cmd ดังรูปครับ

    จากนั้นแก้ไขในไฟล์ main.dart โดยเริ่มจากการ import ดังนี้ครับ

    import 'dart:async';
    import 'dart:convert';
    
    import 'package:flutter/material.dart';
    import 'package:http/http.dart' as http;
    

    ด้านล่างเป็น Code ที่เชื่อมต่อ API โดยใช้ http.get เพื่อดึงข้อมูลแบบ Get จากนั้นก็ return ค่า response โดยใช้ประเภท Future เทียบกับภาษาอื่นๆ ประเภทข้อมูลนี้ก็คือ CallBack นั้นเองครับ เอาไว้อ่านค่าที่ได้จาก api เมื่อได้ข้อมูลมาแล้ว (ในอนาคต ไม่รู้ตอนไหน เมื่อเสร็จจะบอก ประมาณนั้น) ซึ่งข้างในเป็นค่าประเภท dynamic ที่ได้จาก jsonDecode

    Future<dynamic> fetchAlbum() async {
      final response =
          await http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums'));
    
      if (response.statusCode == 200) {
        return jsonDecode(response.body);
      } else {
        throw Exception('Failed to load album');
      }
    }

    ตรงนี้เป็นการสร้าง Widget สำหรับแสดงข้อมูลเป็นแถวๆ

    Widget _buildRow(String dataRow) {
      return ListTile(
        title: Text(
          dataRow,
          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
      );
    }

    ต่อไปก็จะเป็น Main Function ที่มีการประกาศ state เพื่อเก็บข้อมูลไว้แสดงผลในรูปแบบ ListView เมื่อได้ข้อมูลจาก API มาแล้วนะครับ

    void main() => runApp(MyApp());
    
    class MyApp extends StatefulWidget {
      MyApp({Key? key}) : super(key: key);
    
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      late Future<dynamic> futureAlbum;
    
      @override
      void initState() {
        super.initState();
        futureAlbum = fetchAlbum();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Fetch Data Example',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Scaffold(
            appBar: AppBar(
              title: Text('Fetch Data Example'),
            ),
            body: Center(
              child: FutureBuilder<dynamic>(
                future: futureAlbum,
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return ListView.builder(//สร้าง Widget ListView
                        padding: EdgeInsets.all(16.0),
                        itemBuilder: (context, i) {
                           //หากไม่สร้าง Object สามารถเรียกใช้งานแบบนี้ได้เลย
                          return _buildRow(snapshot.data[i]["title"].toString()); 
                        });
                  } else if (snapshot.hasError) {
                    return Text("${snapshot.error}");
                  }
    
                  // รูป Spiner ขณะรอโหลดข้อมูล
                  return CircularProgressIndicator();
                },
              ),
            ),
          ),
        );
      }
    }

    เมื่อ Run ดูก็จะได้หน้าตาประมาณนี้ครับ

    เรียกได้ว่าสัมผัสแรกกับ Flutter รู้สึกประทับใจทั้งในด้าน Extension ที่มีใน Visual Studio Code ให้ความรู้สึกไม่ต่างจากการพัฒนาด้วย .Net Framwork เพราะสามารถ Debug ได้ มี Intellisense ครบถ้วน ในด้าน Syntax เนื่องจากเป็นภาษาใหม่ยังต้องทำความเข้าใจอีกพอสมควร ในด้านการออกแบบ UI สำหรับท่านใดที่เคยใช้ React Native มาน่าจะพอเข้าใจ Concept ของ View Widget (เทียบเท่า Component) ได้ไม่ยากนัก ถ้าหากได้นำมาใช้ในงาน Production จริงๆ แล้วจะนำประเด็นที่น่าสนใจอื่นๆมาแชร์กันเพิ่มเติมครับ

    อ้างอิง

    https://flutter.dev/docs/get-started/codelab

    https://flutter.dev/docs/cookbook/networking/fetch-data

  • การสร้าง Dashboard บน Azure DevOps

    เวลานี้ไม่ว่าจะไปที่ไหน เรามักจะได้ยินคำว่า “Dashboard” บ่อยมาก แล้ว Dashboard มีประโยชน์และสร้างอย่างไร  วันนี้เรามีวิธีการสร้าง Dashboard ง่ายๆ เพื่อใช้ในการติดตามโครงการมานำเสนอค่ะ โดยจะใช้เครื่องมือที่เรียกว่า Azure DevOps โดยเราต้องสร้างโครงการบน Azure DevOps ก่อนนะคะ (ครั้งหน้าจะมาแนะนำการสร้างโครงการบน Azure DevOps รอติดตามกันนะคะ)

    หลังจากสร้างโครงการบน Azure DevOps แล้ว ในการบริหารโครงการจะมีการแบ่งงานออกเป็น Work Item ในแต่ละ Work Item ก็จะมีการมอบหมายงานหรือ Assigned ให้กับผู้ร่วมโครงการแต่ละคน จากนั้นจะมีการติดตามว่าได้ดังเนินการไปถึงไหน ใช้เวลาเท่าไหร่ ตัวอย่างการสร้าง Work Item ตามภาพ ข้างล่าง

    เมื่อมีการมอบหมายงานเรียบร้อย เราก็สามารถติดตามสถานะการดำเนินงานต่างๆ ได้ โดยการสร้าง Dashboard ซึ่งให้เลือกโครงการที่ต้องการติดตาม จากนั้นไปที่ “Dashboards” และกดปุ่ม “New Dashboard” ซึ่งจะมี Template หลากหลายรูปแบบให้เลือก ตามภาพข้างล่าง

    ตัวอย่าง เช่น หากต้องการทราบว่า มีงานใดที่มอบหมายให้ตัวเองและยังไม่ได้ทำให้เสร็จสิ้นบ้าง ให้เลือก Template Assigned to Me แล้วกดปุ่ม Add ผลลัพท์จะได้ตามภาพข้างล่าง

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

    หากต้องการดู Dashboard ผลการดำเนินงานของโครงการ ไม่ว่าจะเป็น การแบ่งงานในแต่ละ Work Item หรือระยะเวลาผู้ที่ได้รับมอบหมายดำเนินการในแต่ละ Work Item ของโครงการ เราสามาถสร้าง Dashboard โดยเลือก Template เป็น “Chart for Work Items” และกำหนด รายละเอียดที่เราต้องการ เช่น ประเภทของ Chart  เป็นต้น ตามภาพข้างล่าง

    นอกจากนี้ยังสามารถสร้าง Dashboard จาก Query ที่เราได้สร้างไว้เพื่อให้ได้ Dashboard ตามที่เราต้องการ ตัวอย่างเช่นการส้ราง Dashboard จาก Query ที่ชื่อ Completed Task และ All Work Item ที่ได้สร้างไว้ ตามภาพข้างล่าง

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

    Dashboard จำนวน Work Item ที่แต่ละคนได้รับมอบหมายและจำนวน Work Item ทั้งหมดในโครงการ ไม่ว่าจะเป็นระดับ Task, User Story หรือ Feature

    ตัวอย่างการสร้าง Query เพื่อดูรายละเอียดของการดำเนินการทุกขั้นตอน (Work Item Type) และทุกสถานะ (State) เมื่อ Run query จะได้ผลลัพท์ตามภาพข้างล่าง

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

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

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

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

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

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

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

    string lastCatName = "";
    string AllName = "";
    
     
    protected void GroupGv_DataBound(object sender, EventArgs e)
            {
                lastCatName = "";
                Table table = (Table)GroupGv.Controls[0];
    
                foreach (GridViewRow row in GroupGv.Rows)
                {
                  
                    HiddenField HidCatName = (HiddenField)row.FindControl("HidCatName");
                    HiddenField HidCatID = (HiddenField)row.FindControl("hdCatID");
                    HiddenField HidID = (HiddenField)row.FindControl("hdID");
    
                    ////ตั้งชื่อ id ให้กับแถวย่อย(child) แต่ละแถว
                    row.Attributes.Add("id", "Name" + HidID.Value);
    
                   ////กำนดสไตล์ชีทของตัวแถวย่อยและระบุเพื่อให้ย่อยุบได้ ด้วย accordian-body collapse และระบุพื้นที่เพื่อให้อ้างถึงจากแถวหลัก(parent)ได้ 
                  /////ซึ่งเป็นในส่วนของ multi-collapse จะเป็น multi-collapse ตามด้วยชื่อรหัสประเภทนั่นเอง 
                  /////โดยแถวย่อย(child) ที่มีแถวหลัก(parent)เดียวกัน ค่าของ multi-collapse จะตรงกันและตรงกับค่า target ที่ระบุไว้ในแถวหลัก(parent)ด้วย
    
                    if (row.RowState == DataControlRowState.Alternate)  ////กรณีที่ต้องการให้แต่ละแถวสลับสีกันเพื่อให้ง่ายต่อการดูข้อมูล 
                       row.CssClass = "Blue accordian-body collapse multi-collapse" + HidCatID.Value;
                    else               
                      row.CssClass = "accordian-body collapse multi-collapse" + HidCatID.Value;
    
                    if (HidCatName.Value != lastCatName)
                    {
    
                       ////////วนเพื่อหาจำนวน child ในแต่ละประเภทเพื่อใช้ในการกำหนด aria-controls
    
                        var strList = GroupData.Select("CategoryID='" + HidCatID.Value + "'");
                        foreach (DataRow dr in strList)
                        {
                            AllName += " Name" + dr["ID"];
                        }
    
                       //// เป็นการหาผลรวมค่าของฟิลด์ Amt ซึ่งหมายถึงจำนวน โดยเป็นการรวมค่าฟิลด์แยกตามแต่ละ CategoryID นั่นเอง
    
                        var sumOfValuesInCategory = GroupData.AsEnumerable().Where(x => x.Field<string>("CategoryID") == HidCatID.Value).Sum(x => x.Field<int>("Amt")).ToString();
                       
                        int realIndex = table.Rows.GetRowIndex(row);
                        string text = HidCatName.Value;
                        GridViewRow newHeaderRow = new GridViewRow(realIndex, 0, DataControlRowType.Header, DataControlRowState.Normal);
    
                        /////กำหนดค่าต่างๆ (data-toggle  data-target  aria-controls )ให้กับแถว เพื่อให้สามารถย่อ-ยุบได้ 
    
                        newHeaderRow.Attributes.Add("data-toggle", "collapse");
                        newHeaderRow.Attributes.Add("data-target", ".multi-collapse" + HidCatID.Value);
                        newHeaderRow.Attributes.Add("aria-expanded", "true");
                        newHeaderRow.Attributes.Add("aria-controls", AllName);
    
                        TableCell newCell = new TableCell();
                        newHeaderRow.Cells.AddAt(0, newCell);
    
                       /////ปรับแก้การระบุค่า ColumnSpan จากเดิมที่รวมกันทุกคอลัมน์(GroupGv.Columns.Count) 
                       //// แต่กรณีนี้ต้องเว้นคอลัมน์ไว้แสดงผลจำนวนรวมแต่ละประเภทด้วย
    
                        newCell.ColumnSpan =1;
                        newCell.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                        newCell.ForeColor = System.Drawing.Color.White;
                        newCell.Font.Bold = true;
                        newCell.Attributes.Add("class", "collapseToggle");
    
                        /////ใส่ไอคอน + หน้าข้อความชื่อประเภท เพื่อบอกให้ทราบว่าสามารถกดย่อ-ยุบได้
                        newCell.Text = "<i class='fas fa-plus mr-2'></i>" + string.Format(HidCatName.Value, "&nbsp;{0}", text);
    
                        ///สร้าง TableCell หรือคอลัมน์ใหม่ เพื่อแสดงผลข้อมูลจำนวนรวมของแต่ละประเภท 
    
                        TableCell newCellTotal = new TableCell();
    
                        /////เพิ่ม TableCell ในแถวที่กำลังสร้างและระบุค่าต่างๆ
                        newHeaderRow.Cells.AddAt(1, newCellTotal);
                        newCellTotal.ColumnSpan = 1;
                        newCellTotal.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                        newCellTotal.ForeColor = System.Drawing.Color.White;
                        newCellTotal.Font.Bold = true;
                        newCellTotal.HorizontalAlign = HorizontalAlign.Right;
    
                        ////นำค่าผลรวมในตัวแปร sumOfValuesInCategory ที่คำนวณได้ข้างต้นมาจัดรูปแบบก่อนแสดงผลใน TableCell สร้าง
    
                        newCellTotal.Text = string.Format("{0:#,##0}",int.Parse(sumOfValuesInCategory));
                        newCellTotal.Attributes.Add("class", "collapseToggle");
                        table.Controls.AddAt(realIndex, newHeaderRow);
                    
                          AllName = "";
    
                    }
                    lastCatName = HidCatName.Value;
                }
            }

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

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

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

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

    ผลลัพธ์

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

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

    แบบเดิม

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

    แบบใหม่

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

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

    แบบเดิม

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

    แบบใหม่

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

    ผลลัพธ์

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

    เพิ่มเติม

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

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

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

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

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

    ผลลัพธ์

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

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

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

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

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

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

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

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

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

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

    1. เตรียมข้อมูลในการแสดงผล โดยจากตัวอย่างนี้ จะทำการสมมุติข้อมูลของดอกไม้ ผลไม้ และต้นไม้ และมีข้อมูลเพื่อใช้ในการแยกประเภทไว้ด้วย เพื่อให้เห็นภาพมากขึ้นค่ะ
    //// ประกาศตัวแปร GroupData
     เป็น ViewState เพื่อใช้ในงานข้อมูลในส่วนการแทรกแถวประเภทกลุ่มใน Event อื่นด้วย 
    
    public DataTable  GroupData
    
            {
                get
                {
                    if (ViewState["GroupData"] == null)
                    {
                        ViewState["GroupData"] = new DataTable();
                    }
                    return (DataTable)ViewState["GroupData"];
                }
                set { ViewState["GroupData"] = value; }
            }
    
    //// ผู้อ่านสามารถเรียกใช้ฟังก์ชั่น Getdata() การดึงข้อมูลนี้ในตอน Page_Load เพื่อดูเป็นตัวอย่างได้ค่ะ
    protected void Getdata() 
            {
                
                GroupData.Columns.AddRange(new DataColumn[5] {
                             new DataColumn("CategoryName", typeof(string)),
                             new DataColumn("Name", typeof(string)),
                             new DataColumn("ID", typeof(string)),
                              new DataColumn("Amt", typeof(int)),
                            new DataColumn("CategoryID",typeof(string))});
    
                GroupData.Rows.Add("Flower", "Rose", "1",2500, "01");
                GroupData.Rows.Add("Flower", "Lotus", "3",150, "01");
                GroupData.Rows.Add("Fruit", "Grape", "2",350, "02");
                GroupData.Rows.Add("Fruit", "Mango", "4",1750, "02");
                GroupData.Rows.Add("Fruit", "Orange", "5",2240, "02");
                GroupData.Rows.Add("Tree", "Cactus", "6",370, "03");
                GroupData.Rows.Add("Tree", "Hazelnut Tree", "6",2250, "03");
                ////นำข้อมูลใน Datatable ชื่อ GroupData แสดงผลใน GridView
                GroupGv.DataSource = GroupData;
                GroupGv.DataBind();
            }
    

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

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

        <asp:GridView ID="GroupGv" runat="server" AutoGenerateColumns="False"
            DataKeyNames="ID"  CssClass="table table-sm table-hover Blue" Width="100%">
            <AlternatingRowStyle CssClass="Blue" />
            <EmptyDataRowStyle CssClass="Blue" />
            <EmptyDataTemplate>
                <br />
                <div style="text-align: center">
                    <i class="fas fa-exclamation-circle"></i>&nbsp;&nbsp; ไม่พบข้อมูล<br />
                    &nbsp;
                </div>
    
            </EmptyDataTemplate>
            <EmptyDataRowStyle HorizontalAlign="Center" />
            <HeaderStyle CssClass="Blue" />
    
            <Columns>
                <asp:TemplateField HeaderText="Name">
                    <ItemTemplate>
                        <asp:HiddenField ID="hdCatID" runat="server" Value='<%# Eval("CategoryID") %>' />
                        <asp:HiddenField ID="hdID" runat="server" Value='<%# Eval("ID") %>' />
                        <asp:HiddenField ID="HidCatName" runat="server" Value='<%# Eval("CategoryName") %>' />
                        <asp:Label ID="lblName" runat="server" Text='<%# Eval("Name") %>'></asp:Label>
                    </ItemTemplate>
                     <ItemStyle  Width="50%"  />
                </asp:TemplateField> <asp:BoundField HeaderText="Category Name" DataField="CategoryName">
                <ItemStyle  Width="30%"/>
                 </asp:BoundField>
                  <asp:BoundField HeaderText="Amount" DataField="Amt"  DataFormatString="{0:#,##0}" >
                <ItemStyle HorizontalAlign="Right"  Width="20%"/>
                 </asp:BoundField>
               
            </Columns>
        </asp:GridView>
    

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

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

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

        <asp:GridView ID="GroupGv" runat="server" AutoGenerateColumns="False"
            DataKeyNames="ID"  CssClass="table table-sm table-hover Blue" Width="100%" OnDataBound="GroupGv_DataBound">
            <AlternatingRowStyle CssClass="Blue"  />
            <EmptyDataRowStyle CssClass="Blue" />
            <EmptyDataTemplate>
                <br />
                <div style="text-align: center">
                    <i class="fas fa-exclamation-circle"></i>&nbsp;&nbsp; ไม่พบข้อมูล<br />
                    &nbsp;
                </div>
    
            </EmptyDataTemplate>
            <EmptyDataRowStyle HorizontalAlign="Center" />
            <HeaderStyle CssClass="Blue" />
    
            <Columns>
                <asp:TemplateField HeaderText="Name">
                    <ItemTemplate>
                         <%--นำค่าไปใช้ตอนแทรกแถวหมวดหมู่ที่ต้องการจัดกลุ่ม--%>
                        <asp:HiddenField ID="hdCatID" runat="server" Value='<%# Eval("CategoryID") %>' />
                        <asp:HiddenField ID="hdID" runat="server" Value='<%# Eval("ID") %>' />
                        <asp:HiddenField ID="HidCatName" runat="server" Value='<%# Eval("CategoryName") %>' />
                        <asp:Label ID="lblName" runat="server" Text='<%# Eval("Name") %>'></asp:Label>
                    </ItemTemplate>
                     <ItemStyle  Width="70%"  />
                </asp:TemplateField> 
                  <asp:BoundField HeaderText="Amount" DataField="Amt"  DataFormatString="{0:#,##0}">
                <ItemStyle HorizontalAlign="Right"  Width="30%"/>
                 </asp:BoundField>
               
            </Columns>
        </asp:GridView>
    

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

            string lastCatName = "";
            string AllName = "";
    
            protected void GroupGv_DataBound(object sender, EventArgs e)
            {
                lastCatName = "";
                Table table = (Table)GroupGv.Controls[0];
    
               ////////วนเพื่อแทรกแถวชื่อแต่ละประเภทของข้อมูล เช่น ดอกไม้ ต้นไม้ หรือผลไม้ 
                foreach (GridViewRow row in GroupGv.Rows)
                {
                  
                  
                    HiddenField HidCatName = (HiddenField)row.FindControl("HidCatName");
                    HiddenField HidCatID = (HiddenField)row.FindControl("hdCatID");
                    HiddenField HidID = (HiddenField)row.FindControl("hdID");
                    
            
               ////////หากพบว่าเป็นประเภทใหม่จะทำการสร้างแถวและเพิ่มแทรกเข้าไป โดยมีการกำหนดค่าต่างๆ เช่น ข้อความที่จะแสดง สีพื้นหลัง สีตัวอักษร และค่า ColumnSpan เป็นต้น
    
     
                    if (HidCatName.Value != lastCatName)
                    {
                       
                        int realIndex = table.Rows.GetRowIndex(row);
                        string text = HidCatName.Value;
                        GridViewRow newHeaderRow = new GridViewRow(realIndex, 0, DataControlRowType.Header, DataControlRowState.Normal);
    
    
                        /////สร้าง TableCell และระบุค่าต่างๆ ก่อนนำไปเพิ่มในแถว newHeaderRow ที่เพิ่งสร้าง
                       TableCell newCell = new TableCell();
                        newHeaderRow.Cells.AddAt(0, newCell);
    
                        ///กำหนด ColumnSpan เท่ากับจำนวนคอลัมน์ทั้งหมดใน GridView (GroupGv.Columns.Count)เพื่อให้คอลัมน์ที่ต้องการเพิ่มยาวครอบคลุมทั้งแถว
                        newCell.ColumnSpan = GroupGv.Columns.Count;
                        newCell.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                        newCell.ForeColor = System.Drawing.Color.White;
                        newCell.Font.Bold = true;
                        newCell.Text = string.Format(HidCatName.Value, "&nbsp;{0}", text);
    
                        ////เพิ่มแถวที่ต้องการแทรกเข้าไปในตารางหรือ GridView ที่เรากำลังจัดการอยู่นั่นเอง
                        table.Controls.AddAt(realIndex, newHeaderRow);
                       
                    }
                    lastCatName = HidCatName.Value;
                }
            }         
    

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

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

     protected void GroupGv_DataBound(object sender, EventArgs e)
            {
                lastCatName = "";
                Table table = (Table)GroupGv.Controls[0];
    
                foreach (GridViewRow row in GroupGv.Rows)
                {
                  
                    HiddenField HidCatName = (HiddenField)row.FindControl("HidCatName");
                    HiddenField HidCatID = (HiddenField)row.FindControl("hdCatID");
                    HiddenField HidID = (HiddenField)row.FindControl("hdID");
    
    
                    if (HidCatName.Value != lastCatName)
                    {
    
                       //// เป็นการหาผลรวมค่าของฟิลด์ Amt ซึ่งหมายถึงจำนวน โดยเป็นการรวมค่าฟิลด์แยกตามแต่ละ CategoryID นั่นเอง
    
                        var sumOfValuesInCategory = GroupData.AsEnumerable().Where(x => x.Field<string>("CategoryID") == HidCatID.Value).Sum(x => x.Field<int>("Amt")).ToString();
                       
    
                        int realIndex = table.Rows.GetRowIndex(row);
                        string text = HidCatName.Value;
                        GridViewRow newHeaderRow = new GridViewRow(realIndex, 0, DataControlRowType.Header, DataControlRowState.Normal);
                        TableCell newCell = new TableCell();
                        newHeaderRow.Cells.AddAt(0, newCell);
    
                       /////ปรับแก้การระบุค่า ColumnSpan จากเดิมที่รวมกันทุกคอลัมน์(GroupGv.Columns.Count) 
                       //// แต่กรณีนี้ต้องเว้นคอลัมน์ไว้แสดงผลจำนวนรวมแต่ละประเภทด้วย
    
                        newCell.ColumnSpan =1;
                        newCell.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                        newCell.ForeColor = System.Drawing.Color.White;
                        newCell.Font.Bold = true;
                        newCell.Text = string.Format(HidCatName.Value, "&nbsp;{0}", text);
    
                        ///สร้าง TableCell หรือคอลัมน์ใหม่ เพื่อแสดงผลข้อมูลจำนวนรวมของแต่ละประเภท 
    
                        TableCell newCellTotal = new TableCell();
    
    
                        /////เพิ่ม TableCell ในแถวที่กำลังสร้างและระบุค่าต่างๆ
                        newHeaderRow.Cells.AddAt(1, newCellTotal);
                        newCellTotal.ColumnSpan = 1;
                        newCellTotal.BackColor = System.Drawing.Color.FromName("#399ea9"); ;
                        newCellTotal.ForeColor = System.Drawing.Color.White;
                        newCellTotal.Font.Bold = true;
                        newCellTotal.HorizontalAlign = HorizontalAlign.Right;
    
    
                        ////นำค่าผลรวมในตัวแปร sumOfValuesInCategory ที่คำนวณได้ข้างต้นมาจัดรูปแบบก่อนแสดงผลใน TableCell สร้าง
    
                        newCellTotal.Text = string.Format("{0:#,##0}",int.Parse(sumOfValuesInCategory));
                       
                        table.Controls.AddAt(realIndex, newHeaderRow);
                    
                    }
                    lastCatName = HidCatName.Value;
                }
            }

    ผลลัพธ์

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

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

  • Crystal Report : Report หลายตัวหัวท้ายต่าง แต่ตรงกลาง Code เหมือนกัน จัดการอย่างไร (WinApp)

    หลาย ๆ ครั้งที่ท่านมีรายงานมากกว่า 1 ฉบับที่มีรูปร่างหน้าตาส่วนหัวกระดาษหรือท้ายกระดาษที่ต่างกัน แต่ส่วนของ Detail นั้นเหมือนกันอย่างกับแกะ และเจ้าส่วน Detail นี้ ดั๊นนนน เป็นเจ้า Detail ที่มีเงื่อนไขเยอะแยะ ยุบยับซับซ้อนซ่อนเงื่อน ถ้าจะให้สร้าง File เพื่อวาง Report Viewer เป็น 2 File หรือตามจำนวนรายงานก็ใช่ที่

    จากการที่ประสบพบเจอมากับตัว ส่วนหัว ส่วนท้าย ดั๊นนน ไม่คล้ายไม่เหมือนเลย แต่ข้อมูลตรงกลางนั้น ต้องใช้ข้อมูลชุดเดียวกัน เหมือนกัน ทีนี้ถึงกับกุมขมับเลยว่าจะทำยังไงดี ได้คำปรึกษาแนะนำจากพี่ชายที่แสนดี @wachirawit-j มาช่วยชี้แจงแถลงไข ถึงต้องรีบจัดการเก็บข้อมูลไว้ เผื่อครั้งหน้าเจออีกแล้วลืมจะได้กลับมาอ่านซ้ำ ไปดูกันเล้ยยยยยย

    ReportClass cr = new ReportClass();

    ตัวแปร cr ที่ประกาศนี้ จะเป็นตัวแปรที่ไว้ใช้ในอนาคตทั้งไฟล์ เป็นตัวแปรที่เป็นตัวเก็บค่าว่า จะเป็น Crystal Report File ตัวไหนและใช้สำหรับกำหนดค่า FormulaField เพื่อใช้กับ Report File นั้น ๆ ตัวอย่าง

    cr.DataDefinition.FormulaFields["ffType"].Text = "'Normal'";

    Code ข้างต้นเป็นการกำหนดค่าให้กับ Formula Field ที่ชื่อว่า ffType ในฝั่ง Crystal Report โดยกำหนดให้มีค่าเท่ากับข้อความว่า “Normal” ซึ่งจะเห็นว่ามีการใช้ตัวแปร cr ในการดำเนินการ

    Code ส่วนอื่น ๆ ที่มีการกำหนดค่าก็จะมีการเรียกตัวแปร cr นี้เหมือนกัน ซึ่ง cr จะเป็นตัวที่บอกว่าคือ Crystal Report File ไหน

    private class CRModel
    {
       public RptNormal crNormal {get; set;}
       public RptSpecial crSpecial {get; set;}
    }

    Code ข้างต้นเป็นการสร้าง Class ชื่อว่า CRModel มีสมาชิกภายใน Class 2 Report นั่นคือ RptNormal.rpt กำหนดให้ใช้ตัวแปร crNormal และ RptSpecial.rpt กำหนดให้ใช้ตัวแปร crSpecial

    จากนั้นในส่วนของการตรวจสอบว่าจะใช้ Report ตัวไหนหรือการกำหนด report ให้กับ Reportsource เป็นดังนี้

    if(rptType == "normal") //ตรวจสอบว่าเป็นรายงานประเภทปกติ?
    {
       crModel.crNormal = new RptNormal(); //กำหนด crNormal ให้เป็นรายงาน RptNormal
       SetValue(crModel, rptType); //เรียก Method กำหนดค่าต่าง ๆ ในรายงาน
       crystalReportViewer1.ReportSource = crModel.crNormal; //กำหนด Reportsource ให้มีค่าเป็น crNormal
    }
    else
    {
       crModel.crSpecial = new RptSpecial(); //กำหนด crNormal ให้เป็นรายงาน RptSpecial
       SetValue(crModel, rptType); //เรียก Method กำหนดค่าต่าง ๆ ในรายงาน
       crystalReportViewer1.ReportSource = crModel.crSpecial; //กำหนด Reportsource ให้มีค่าเป็น crSpecial
    }
    private void SetValue(CRModel objCR, string rptType)
    {
       if(rptType == "normal")
       {
          cr = objCR.crNormal
       }
       else
       {
          cr = objCR.crSpecial
       }
    
       #Region Code ส่วนกำหนดค่าให้กับ cr 
    
          cr.DataDefinition.FormulaFields["ff1"].Text = "'123'";
          cr.DataDefinition.FormulaFields["ff2"].Text = "'456'";
    
       #endRegion
    }

    เพียงเท่านี้ก็จะสามารถจัดการ Code ที่ File เดียวได้แล้ว เย้!

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

  • Crystal Report : รูปที่ใส่ไป ทำไม size ถึงกลับไป(ใหญ่)เท่าเดิม

    เคยหรือไม่ ที่ใส่รูปลงไปในรายงาน จากนั้นหากต้องการ Config ค่าของรูปนั้น เมื่อจัดการเสร็จ กด OK ออกมา รูปที่ท่านใส่ไปนั้น กลับขยายกลายร่างกลับสู่ขนาดจริง

    ซึ่งบางครั้งหากท่านไม่ได้มีการย่อขนาดรูปให้ตรงตามที่ต้องการก่อนแต่ใช้วิธี Config ในส่วนของ Object Picture แทน ท่านอาจจะเจอปัญหานี้กันบ้าง

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

    เมื่อท่านลาก Object Picture มาแล้ว ให้เลือก Menu Format Object ที่ ๆ ท่านเข้าไป Config รูปปกติค่ะ

    จากนั้นที่ Tab Picture เมื่อท่านตั้งค่าทุกอย่างเสร็จแล้วอย่าเพิ่งกดปุ่ม OK นะคะ เบรกก๊อนนน ><

    ให้สลับกลับไปที่ Tab Common ก่อนค่ะ

    สังเกตดี ๆ นะคะ ที่วงไว้สีแดง ๆ หนา ๆ ตัวนี้เลยค่ะ Can Grow เจ้าปัญหา ให้กลับมาดูก่อนนะคะ ว่ามีเครื่องหมายถูกอยู่ข้างหน้ารึเปล่า ถ้าไม่มี ไม่เป็นไรค่ะ นั่นคือสิ่งที่ถูกต้องแล้ว ให้กด OK ออกได้เล้ยยย แต่ถ้ามีเครื่องหมายถูกอยู่ เอาออกด่วน ๆๆๆๆ เลยค่ะ ไม่งั้น รูปที่ท่านใส่ไว้ ก็จะ Grow กลับสู่ขนาดดั้งเดิม ซึ่งหากท่านใส่ไว้แบบไม่ปรับขนาดมาก่อน แล้วมีขนาดใหญ่มาก ก็จะเป็นปัญหาได้ค่ะ

    หวังว่าจะเป็นประโยชน์ต่อผู้อ่านไม่มากก็น้อยนะคะ 🙂