ด้วยสํานึกในพระมหากรุณาธิคุณสมเด็จพระนางเจ้าสิริกิติ์เป็นล้นพ้นอันหาที่สุดมิได้
ด้วยสํานึกในพระมหากรุณาธิคุณสมเด็จพระนางเจ้าสิริกิติ์เป็นล้นพ้นอันหาที่สุดมิได้


การกำหนดและใช้งาน PopupMenuButton ใน Flutter

เขียนเมื่อ 4 ปีก่อน โดย Ninenik Narkdee
popupmenubutton flutter enum type

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ popupmenubutton flutter enum type

ปัจจุบัน นักพัฒนาสามารถ ใช้ ChatGPT | Gemini | Claude | Perplexity | Deepseek ช่วยในการแก้ไขปัญหาต่างๆ ในการเขียนโปรแกรม หรือหาข้อมูลเพิ่มเติมได้ง่ายและสะดวก แนะนำให้ทุกคนใช้งานเพื่อพัฒนาศักยภาพของตัวเอง

ดูแล้ว 6,171 ครั้ง




เนื้อหาตอนต่อไปนี้จะมาดู Widget เล็กๆ ที่มีรูปแบบการใช้งาน
ง่ายๆ ที่เรียกว่า PopupMenuButton เป็นปุ่มเมนูเพิ่มเติมที่แสดงมา
ให้เราเลือกใช้งาน หรือกำหนดการทำคำสั่งที่ต้องการทำงานเพิ่มเติม
จะใช้เนื้อหาจากตอนที่แล้ว จะจัดการเฉพาะในไฟล์ article.dart
ทบทวนตอนที่แล้วได้ที่บทความ
    การใช้งาน WebView แสดงเว็บไซต์ ใน Flutter http://niik.in/1043
 
    *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
 
 
 

การใช้งาน PopupMenuButton

    ตัว PopupMenuButton เมื่อกำหนดหรือเรียกใช้งาน จะแสดงเป็นปุ่มไอคอน จุด 3 จุดใน
แนวตั้งหรือชื่อไอคอนว่า Icons.more_vert เป็นการสื่อว่ามีเพิ่มเติม เมื่อเรากดที่ปุ่มนี้ก็จะแสดง
ลิสราายการปุ่มต่างๆ ให้เราเลือก ถ้าเราเลือกปุ่มรายการใดๆ ก็จะใช้ค่าปุ่มรายการนั้นๆ เป็นตัว
กำหนดเงื่อนไขการทำงานอีกที ถ้าเราไม่ต้องการเลือกรายการที่แสดง ก็สามารถกดไปที่พื้นที่ว่าง
นอกรายการเพื่อปิดปุ่มนั้นๆ ไป  ในการสร้างปุ่ม PopupMenuButton จะต้องมีการกำหนด itemBuilder
เพื่อสร้างรายการของปุ่ม 
 

    รูปแบบการใช้งาน PopupMenuButton

 
PopupMenuButton<T>(
  onSelected: (T result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<T>>[
    const PopupMenuItem<T>(
      value: T.value,
      child: Text('Menu 1'),
    ),
    const PopupMenuItem<T>(
      value: T.value,
      child: Text('Menu 2'),
    ),    
  ],
)
 
    สัญลักษณ์ T คือข้อมูลประเภท Type หรือก็คือ class ดูตัวอย่าง type ในภาษา Dart 
 
// ข้อมูล type ColorOption
enum ColorOption { red, green, blue }
// ข้อมูล type Option
class Option{
  int a = 0;
}
 
    ทั้ง ColorOption และ Option เป็นรูปแบบหนึ่งของ class โดยตัว ColorOption จะใช้คำว่า enum เป็นคำ
keyword เป็น class พิเศษเฉพาะที่กำหนดจำนวนของค่าคงที่ ที่เรียกว่า enum type ข้อมูลที่มีการระบุแจกแจง
ค่าไว้อย่างชัดเจน ค่าของ Enum จะอ้างอิงผ่าน property ที่ชื่อ values
 
print(ColorOption.values); // แสดงข้อมูลของ enum type
 
    ก็จะได้เป็น List<ColorOption> มีค่าเป็น
 
[ColorOption.red, ColorOption.green, ColorOption.blue]
// ColorOption.values[0] = ColorOption.red
// ColorOption.values[1] = ColorOption.green
// ColorOption.values[2] = ColorOption.blue
 
    เราจะใช้ช้อมูล Enum type สำหรับกำหนดรายการให้กับ PopupMenuButton ยกตัวอย่างเช่นข้อมูล
 
enum ColorOption { red, green, blue }
 
    สามารถกำหนดใช้งานใน PopupMenuButton เป็นดังนี้
 
PopupMenuButton<ColorOption>(
  onSelected: (ColorOption result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<ColorOption>>[
    const PopupMenuItem<ColorOption>(
      value: ColorOption.red,
      child: Text('Menu 1 Red'),
    ),
    const PopupMenuItem<ColorOption>(
      value: ColorOption.green,
      child: Text('Menu 2 Green'),
    ),    
    const PopupMenuItem<ColorOption>(
      value: ColorOption.blue,
      child: Text('Menu 3 Blue'),
    ),        
  ],
)
 
    หรือกรณีเราใช้เป็นข้อมูล String type ก็จะเป็นประมาณนี้ 
 
PopupMenuButton<String>(
  onSelected: (String result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
    const PopupMenuItem<String>(
      value: '1',
      child: Text('Menu 1 Red'),
    ),
    const PopupMenuItem<String>(
      value: '2',
      child: Text('Menu 2 Green'),
    ),    
    const PopupMenuItem<String>(
      value: '3',
      child: Text('Menu 3 Blue'),
    ),        
  ],
)
 
    หรือกรณีเราใช้เป็นข้อมูล boolean type ก็จะเป็นประมาณนี้ 
 
PopupMenuButton<bool>(
  onSelected: (bool result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<bool>>[
    const PopupMenuItem<bool>(
      value: true,
      child: Text('Menu 1 Red'),
    ),
    const PopupMenuItem<bool>(
      value: false,
      child: Text('Menu 2 Green'),
    ),    
  ],
)
 
    เราสามารถสร้างลิสรายการจากข้อมูลอาเรย์หรือ List ได้ง่ายเพื่อลดขึ้นตอนการกำหนดแต่ละรายการ
 
// สร้างตัวแปร ลืสรายการเมนูที่เป็น String
var myMenuItems = <String>[
  'Home',
  'Profile',
  'Setting',
];    
 
    จากนั้นทำการวนลูปแสดงใน PopupMenuItem ดังนี้
 
PopupMenuButton<String>(
  onSelected: (String result) { },
  itemBuilder: (BuildContext context) {
    return myMenuItems.map((String choice) {
      return PopupMenuItem<String>(
        child: Text(choice),
        value: choice,
      );
    }).toList();
  }
)
 
    ผลลัพธ์ที่ได้
 


 
 
    กรณีประยุกต์กับ Map Type เพิ่ม FontAwesome ไอคอนเข้าไป
 
 
// สร้างตัวแปร ลืสรายการเมนูที่เป็น Map<dynamic, dynamic>
var myMenuItems = <Map>[
  {'icon':FontAwesomeIcons.home,'value':'home','label':'Home'},
  {'icon':FontAwesomeIcons.userAlt,'value':'profile','label':'Profile'},
  {'icon':FontAwesomeIcons.cog,'value':'setting','label':'Setting'}
];   
 
    จากนั้นทำการวนลูปแสดงใน PopupMenuItem ดังนี้
 
PopupMenuButton<Map>(
  onSelected: (Map result) { },
  itemBuilder: (BuildContext context) {
    return myMenuItems.map((Map choice) {
      return PopupMenuItem<Map>(
        child: ListTile(
          leading: Icon(choice['icon']),
          title: Text(choice['label'], style: Theme.of(context).textTheme.bodyText1),
        ),
        value: choice,
      );
    }).toList();
  }
)
 
    ผลลัพธ์ที่ได้
 


 
 
    ตอนนี้เราได้รู้จักแนวทางการประยุกต์การสร้างลิสรายการในรูปแบบต่างๆ ให้สังเกตให้ค่า value ของ
PopupMenuItem คือเมื่อเราแตะเลือกที่รายการใด ค่า value นี้จะถูกส่งเข้าไปใน callback ฟังก์ชั่นของ
onSelected ดังนั้นในการกำหนดเงื่อนไขการทำงาน ก็จะไปกำหนดในค่าที่เลือกว่าเป็นค่าใด และให้ทำงาน
อย่างเรา ยกตัวอย่างรูปแบบกรณีล่าสุด ก็จะเป็น
 
PopupMenuButton<Map>(
  onSelected: (Map result) {
    result = Map<String, dynamic>.from(result); // แปลงค่ากลับ
    switch (result['value']) { // ตรวจสอบค่าที่จะใช้งานเป็นเงื่อนไข
      case 'home':
        print('Home clicked');
        break;
      case 'profile':
        print('Profile clicked');
        break;
      case 'setting':
        print('Setting clicked');
        break;
    }
  },
  itemBuilder: (BuildContext context) {
    return myMenuItems.map((Map choice) {
      return PopupMenuItem<Map>(
        child: ListTile(
          leading: Icon(choice['icon']),
          title: Text(choice['label'], style: Theme.of(context).textTheme.bodyText1),
        ),
        value: choice,
      );
    }).toList();
  }
)
 
    เนื่องจากค่าจาก Map type เป็นข้อมูลที่มีความซับซ้อนดังนั้น จึงมีการแปลงกลับมาในรูปแบบที่สามารถ
อ้างอิงการใช้งานได้ก่อน แต่ถ้าเป็นค่าอื่นๆ เช่น boolean Sring Int Enum เหล่านี้ สามารถนำค่าไปตรวจ
สอบเป็นเงื่อนไขได้เลย 
    ในตัวอย่างการทำคำสั่งเมื่อเข้าเงื่อนไข จะใช้เป็นการเรียกใช้ฟังก์ชั่นอีกที เพราะถ้าเขียนการทำงานในนี้
ก็จะยาวเกินไป ข้างต้นเราแค่ทดสอบแสดงข้อความเท่านั้น
 

    ไฟล์ article.dart แบบเต็ม

 
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class Articles extends StatefulWidget {
    static const routeName = '/articles';
 
    const Articles({Key? key}) : super(key: key);
  
    @override
    State<StatefulWidget> createState() {
        return _ArticlesState();
    }
}
  
class _ArticlesState extends State<Articles> {

    // แก้ไขตัวแปรสำหรับ contrller ใหม่ ให้เป็นชนิดข้อมูล late 
    late final WebViewController _controller;
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  // กำหนดค่าเริ่มต้นเป็น false    
    final ValueNotifier<bool> _canGoBack = ValueNotifier<bool>(false);
    final ValueNotifier<bool> _canGoForward = ValueNotifier<bool>(false);    
    // ส่วนของตัวแปรจัดการ cookies
    final WebViewCookieManager _cookieManager = WebViewCookieManager();   
    // ส่วนของตัวแปร กำหนดให้ตรวจสอบว่าโหลด url แล้วหรือไม่เพื่อเรียกใช้งานเพียงครั้งเดียวที่เปิดขึ้นมา
    bool _isUrlLoaded = false;


    @override
    void initState() {
      super.initState();

      _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            print("WebView is loading (progress : $progress%)");
            // Update loading bar.
          },
          onPageStarted: (String url) async {
            _canGoBack.value = await _controller.canGoBack();
            _canGoForward.value = await _controller.canGoForward();
          },
          onPageFinished: (String url) async {
            _canGoBack.value = await _controller.canGoBack();
            _canGoForward.value = await _controller.canGoForward();
          },          
          onHttpError: (HttpResponseError error) {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {// กำหนดการทำงานเมื่อคลิกลิ้งค์ในเว็บเพจ
            // เช่นการตรวจ url และ block ไม่ให้ใช้้งาน url ที่กำหนด
            if (request.url.startsWith('https://www.youtube.com/')) {
              print('blocking navigation to $request}');
              return NavigationDecision.prevent; // ถ้าเป็นจากลิ้งค์ youtube ให้ block
            }
            print('allowing navigation to $request');
            return NavigationDecision.navigate; // ถ้าเป็นลิ้งค์อื่นๆ เข้าไปปกติ
          },
        ),
      // เพิ่มส่วนนี้เพื่อ สร้าง JavascriptChannel สำหรับรับค่าข้อมูลที่ส่งผ่านทาง JavaScript  
      )..addJavaScriptChannel(
        'Toaster', // กำหนดชื่อสำหรับเรียกใช้งาน
        onMessageReceived: (JavaScriptMessage message) {
          print(message.message);
          // ในที่นี้เมื่อได้ค่ามาแล้ว จะแสดงข้อความด้วย SnackBar
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );          
        },
      );       

    }


    @override
    Widget build(BuildContext context) {
        // เนื่องจาการใช้งาน PopupMenuButton จะมีการ rebuild widget ทุกครั้งที่กด
        // ดังนั้นเพื่อไม่ให้มีการโหลดหน้าเพจ เมื่อกดที่ปุ่มเมนูนี้ เราต้องกำหนดเงื่อนไขว่า 
        // โหลดหน้าเพจเฉพาะครั้งแรกเท่าานั้น
        if (!_isUrlLoaded) {
        // รับค่า url ที่ส่งมาใน arguments          
          final url = ModalRoute.of(context)!.settings.arguments as String;
          _controller.loadRequest(Uri.parse(url));
          _isUrlLoaded = true;
        }

        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
                actions: <Widget>[
                  NavigationControls( // เมนูส่วนของการใช้งาน NavigationControls
                    controller: _controller, 
                    canGoBack: _canGoBack, 
                    canGoForward: 
                    _canGoForward
                  ),          
                  SampleMenu( // เมนูส่วนของการใชงาน PopupMenuButton
                    controller: _controller,
                    cookieManager: _cookieManager),
                ],                
            ),
            body: WebViewWidget(controller: _controller),
            floatingActionButton: scrollTopButton(), // เรียกใช้ปุ่มจากฟังก์ชั่น
        );
    }


    // สร้างฟังก์ชั่น คืนค่าเป็น widget
    Widget scrollTopButton() {
      return FloatingActionButton( // คืนค่าเป็นปุ่มรูปหัวใจ
          onPressed: () async { // ถ้ากด
              // เรียกคำสั่ง javascript  เลื่อน scroll ไปด้านบนสุด
              await _controller.runJavaScript('window.scrollTo(0, 0);');      
          },
          child: const Icon(Icons.arrow_upward),
        );
    }

}

// สร้าง widget สำหรับทำปุ่มควบคุม เช่น ก่อนหน้า ย้อนหลัง รีเฟรช
class NavigationControls extends StatelessWidget {

  // กำหนด class constructor รับค่าที่จำเป็น
  const NavigationControls({
    required this.controller,
    required this.canGoBack,
    required this.canGoForward,
    Key? key,
  }) : super(key: key);

  // กำหนดตัวแปรที่เกี่ยวข้อง
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  final WebViewController controller;
  final ValueNotifier<bool> canGoBack;
  final ValueNotifier<bool> canGoForward;

  /*
  ValueListenableBuilder เป็น widget ที่ใช้ในการสร้าง UI ที่ฟังการเปลี่ยนแปลงค่าของ 
  ValueNotifier และทำการ rebuild UI เมื่อค่าของ ValueNotifier มีการเปลี่ยนแปลง
  */
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        ValueListenableBuilder<bool>(
          valueListenable: canGoBack,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_back),
              onPressed: value ? () => controller.goBack() : null,
            );
          },
        ),
        ValueListenableBuilder<bool>(
          valueListenable: canGoForward,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_forward),
              onPressed: value ? () => controller.goForward() : null,
            );
          },
        ),
        IconButton(
          icon: const Icon(Icons.refresh),
          onPressed: () => controller.reload(),
        ),
      ],
    );
  }
}


// กำหนด Enum Type สำหรับเป็นลิสรายการของ PopupMenuButton
enum MenuOptions {
  showUserAgent,
  listCookies,
  clearCookies,
  addToCache,
  listCache,
  clearCache,
}
 
// สร้าง widget สำหรับทำปุ่มควบคุม เพิ่มเติมแบบ PopupMenuButton
class SampleMenu extends StatelessWidget {

  // กำหนด class constructor รับค่าที่จำเป็น
  SampleMenu({
    required this.controller,
    required this.cookieManager,
    Key? key,
  }) : super(key: key);

  final WebViewController controller; // ใช้งาน WebViewController
  final WebViewCookieManager cookieManager; // ใช้งาน CookieManager
 
  @override
  Widget build(BuildContext context) {
        return PopupMenuButton<MenuOptions>(
          onSelected: (MenuOptions value) {
            switch (value) { // ใช้เงื่อนไขค่าที่เลือก ทำฟังก์ชั่นที่ต้องการ
              case MenuOptions.showUserAgent:
                _onShowUserAgent(controller, context);
                break;
              case MenuOptions.listCookies:
                _onListCookies(controller, context);
                break;
              case MenuOptions.clearCookies:
                _onClearCookies(context);
                break;
              case MenuOptions.addToCache:
                _onAddToCache(controller, context);
                break;
              case MenuOptions.listCache:
                _onListCache(controller, context);
                break;
              case MenuOptions.clearCache:
                _onClearCache(controller, context);
                break;
            }
          },
          itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
            PopupMenuItem<MenuOptions>(
              value: MenuOptions.showUserAgent,
              child: const Text('Show user agent'),
              // enabled: controller.!,
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.listCookies,
              child: Text('List cookies'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.clearCookies,
              child: Text('Clear cookies'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.addToCache,
              child: Text('Add to cache'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.listCache,
              child: Text('List cache'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.clearCache,
              child: Text('Clear cache'),
            ),
          ],
        );
  }
 
  // ส่วนของฟังก์ชั่นการทำงานต่างๆ 
 
  // ฟังก์ชั่นแสดง UserAgent ของ WebView
  void _onShowUserAgent(
      WebViewController controller, BuildContext context) async {
    await controller.runJavaScript(
        'Toaster.postMessage("User Agent: " + navigator.userAgent);');
  }
 
  // ฟังก์ชั่นแสดงรายการ cookie
  void _onListCookies(
      WebViewController controller, BuildContext context) async {
      final String cookies = await controller
      .runJavaScriptReturningResult('document.cookie')
      .then((value) => value.toString());
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          const Text('Cookies:'),
          _getCookieList(cookies),
        ],
      ),
    ));
  }
 
  // ฟังก์ชั่นเพิ่มรายการ cache
  void _onAddToCache(WebViewController controller, BuildContext context) async {
    await controller.runJavaScript(
        'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      content: Text('Added a test entry to cache.'),
    ));
  }
 
  // ฟังก์ชั่นแสดงรายการ cache
  void _onListCache(WebViewController controller, BuildContext context) async {
    await controller.runJavaScript('caches.keys()'
        '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
        '.then((caches) => Toaster.postMessage(caches))');
  }
 
  // ฟังก์ชั่นล้างค่า cache
  void _onClearCache(WebViewController controller, BuildContext context) async {
    await controller.clearCache();
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      content: Text("Cache cleared."),
    ));
  }
 
  // ฟังก์ชั่นล้างค่า cookie
  void _onClearCookies(BuildContext context) async {
    final bool hadCookies = await cookieManager.clearCookies();
    String message = 'There were cookies. Now, they are gone!';
    if (!hadCookies) {
      message = 'There are no cookies.';
    }
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(message),
    ));
  }
 
  // ฟังก์ชั่นแสดงรายการ cookie
  Widget _getCookieList(String cookies) {
    if (cookies.isEmpty || cookies == '""') {
      return Container();
    }
    final List<String> cookieList = cookies.split(';');
    final Iterable<Text> cookieWidgets =
        cookieList.map((String cookie) => Text(cookie));
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: cookieWidgets.toList(),
    );
  }
}
 
    ผลลัพธ์ที่ได้
 


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


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 28-07-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

http://niik.in/download/flutter/demo_018_28072024_source.rar





อ่านต่อที่บทความ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง








เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ