φυβλαςのβλογ
phyblasのブログ



pyqt เบื้องต้น บทที่ ๑: ทำความเข้าใจภาพรวมการใช้งาน
เขียนเมื่อ 2021/08/03 19:14
แก้ไขล่าสุด 2021/08/07 16:17



ในบทความนี้จะอธิบายวิธีการเขียนโค้ดใช้งานมอดูล pyqt ซึ่งเป็นมอดูลหนึ่งในไพธอนที่เอาไว้ใช้สร้าง GUI (Graphical User Interface = ส่วนต่อประสานกราฟิกกับผู้ใช้)




pyqt คืออะไร

ภาษาไพธอนสามารถสร้าง GUI ได้อย่างไม่ยากนัก มีมอดูลอยู่หลายตัวที่ใช้ทำแบบนี้ได้ เช่น tkinter, kivy, wxpython เป็นต้น

ในจำนวนนั้นตัวหนึ่งที่นิยมใช้กันอย่างกว้างขวางก็คือ pyqt ซึ่งเป็นมอดูลสำหรับเขียนเฟรมเวิร์ก qt ด้วยภาษาไพธอน

ภาพตัวอย่าง GUI ที่ถูกสร้างโดยใช้ pyqt (รายละเอียดอ่านได้ใน https://phyblas.hinaboshi.com/20180301)



qt คือเฟรมเวิร์กสร้าง GUI ที่ได้รับความนิยมสูงและถูกใช้สร้างโปรแกรมต่างๆมามากมายแล้ว โดยเดิมมีพื้นฐานมาจากภาษา C++ แต่ก็ถูกพัฒนาขึ้นมาให้ใช้ในภาษาต่างๆเช่น java, php, python, ruby, ฯลฯ

รายละเอียดเพิ่มเติมอ่านได้ใน wikipedia https://th.wikipedia.org/wiki/Qt

ดังนั้นถ้าใช้ pyqt เป็นแล้วหากจะเปลี่ยนไปเขียน GUI โดยใช้ qt ในภาษาอื่นก็ทำได้ไม่ยาก เพราะใช้พื้นฐานร่วมกัน

qt นั้นได้ถูกพัฒนาขึ้นมาเรื่อยๆ ปัจจุบันเวอร์ชัน qt6 เพิ่งจะออกมา โดยมอดูลของ qt6 ในไพธอนนั้นมีชื่อว่า pyqt6

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

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

มอดูลของ qt5 ในไพธอนมีชื่อว่า pyqt5 ซึ่งเป็นตัวที่จะใช้สอนในนี้เป็นหลัก ส่วนของ qt6 ใช้ชื่อว่า pyqt6

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

และนอกจากนี้ยังสามารถนำไปใช้กับ pyside ได้เช่นกัน โดยสำหรับมอดูลฝั่ง pyside ที่ใช้กับ qt5 มีชื่อว่า pyside2 ส่วนของ qt6 ใช้ชื่อว่า pyside6

pyside2 จะคล้ายกับ pyqt5 ในขณะที่ pyside6 จะคล้ายกับ pyqt6

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

เรื่องเวอร์ชันของ qt และมอดูลในไพธอนอาจสรุปได้ดังนี้ กล่าวโดยรวมคือ

เวอร์ชันของ qtมอดูลในไพธอน
qt4pyqt4 ≈ pyside
qt5pyqt5 ≈ pyside2
qt6pyqt6 ≈ pyside6


โดยแม้ว่าในที่นี้จะอธิบาย pyqt5 เป็นหลัก แต่ก็สามารถนำไปใช้กับ pyqt4, pyqt6, pyside, pyside2, pyside6 ได้โดยแค่อาจต้องเปลี่ยนแปลงโค้ดบางส่วน แต่หลักการโดยรวมแล้วไม่ต่างกันนัก




การติดตั้ง pyqt5

pyqt5 ก็เช่นเดียวกับมอดูลอื่นๆในไพธอน สามารถติดตั้งได้ง่ายโดยใช้ pip
pip install pyqt5

หรืออาจใช้ conda ก็ได้
conda install -c anaconda pyqt

เพียงแต่หากใครใช้ anaconda ก็จะมี pyqt5 ติดมาให้อยู่แล้ว ไม่จำเป็นต้องทำอะไรเพิ่ม

หากใครอยากลอง pyqt6 ก็ติดตั้งได้โดย pip เช่นกัน
pip install pyqt6




การเริ่มลองใช้งาน

เพื่อทดสอบว่า pyqt ถูกติดตั้งลงเครื่องอย่างถูกต้องพร้อมใช้งานได้หรือยัง ลองรันโค้ดตามนี้
import sys
from PyQt5.QtWidgets import QApplication,QWidget

qAp = QApplication(sys.argv)
natang = QWidget()
natang.show()
qAp.exec_()

อนึ่ง ให้ระวังว่าคำว่า pyqt5 ในโค้ดจะต้องเขียนตัวพิมพ์เล็กพิมพ์ใหญ่ให้ตรงกันเป็น PyQt5 ทั้งหมด ไม่เช่นนั้นจะเกิดข้อผิดพลาด

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



แต่ถ้าหากใช้ระบบปฏิบัติการอื่นๆก็จะได้ผลต่างกันออกไป

เช่น ภาพนี้ลองใน mac



ส่วนภาพนี้ลองใน linux



จะเห็นว่าลักษณะหน้าต่างจะมีรายละเอียดปลีกย่อยต่างกันออกไป ขึ้นอยู่กับระบบปฏิบัติการที่ใช้ แต่โดยรวมแล้วจะมีลักษณะเหมือนกัน

สำหรับในบทความนี้จะแสดงผลที่ได้จากการใช้ pyqt 5.9.7 รันใน mac โดยไพธอน 3.9 ใน spyder แต่ไม่ว่าจะใช้ระบบปฏิบัติการไหนหรือใช้ไพธอนเวอร์ชันไหนหรือ pyqt เวอร์ชันไหน โดยรวมแล้ววิธีการใช้งานก็จะไม่ต่างกันมาก

อนึ่ง หากต้องการลองตรวจดูว่า pyqt ที่ใช้อยู่เป็นเวอร์ชันเท่าไหร่ได้ก็อาจทำได้โดยโค้ดนี้
import PyQt5
print(PyQt5.QtCore.QT_VERSION_STR)




widget คืออะไร

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

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

อย่างไรก็ตาม ความหมายของ widget ที่ใช้ในทางด้านคอมพิวเตอร์นั้นมาจากคำว่า window (หน้าต่าง) +‎ gadget (อุปกรณ์ขนาดเล็กๆสำหรับทำอะไรบางอย่าง)

ดังนั้นในที่นี้ widget ก็คือ "อะไรบางอย่างที่มีลักษณะเป็นหน้าต่าง ซึ่งเอาไว้ใช้เป็นอุปกรณ์ทำอะไรบางอย่าง"

ในภาษาจีนเองก็แปลเป็น 小工具xiǎo gōng jù ซึ่งแปลตรงๆก็คือ "อุปกรณ์เล็ก"

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

สำหรับใน qt นั้น การสร้าง GUI จะทำโดยการเอา widget ต่างๆมาประกอบเข้าด้วยกัน

นั่นคือใน qt จะเรียกทุกสิ่งทุกอย่างที่เป็นส่วนประกอบว่า widget ดังนั้นให้เข้าใจว่า widget คือส่วนประกอบที่จะใช้ ซึ่งมีอยู่หลากหลายชนิด

สำหรับใน pyqt นั้น widget ชนิดต่างๆนั้นอยู่ในรูปของคลาส ของไพธอน ซึ่งทั้งหมดถูกบรรจุอยู่ในมอดูลย่อย PyQt5.QtWidgets เรียกใช้ได้โดย
from PyQt5.QtWidgets import ชื่อ_widget

ภาพแสดงส่วนประกอบของ GUI ที่สร้างจาก pyqt ซึ่งประกอบด้วย widget ชนิดต่างๆ



ในที่นี้ widget ที่ชื่อ QWidget คือตัวหลักที่คลุมทั้งหมด ซึ่งทำหน้าที่เป็นหน้าต่างใหญ่

ส่วน QLabel, QLineEdit, QDateEdit, QButtonGroup, QRadioButton, QCheckBox, QComboBox ต่างก็เป็น widget ซึ่งถูกนำมาใช้ประกอบเป็นส่วนย่อยของ widget ที่เป็นหน้าต่างใหญ่อีกที

จากภาพจะเห็นได้ว่า GUI ของ qt นั้นได้จากการเอา widget มาประกอบกันในลักษณะเช่นนี้

ใน pyqt นั้น QWidget คือคลาสของ widget พื้นฐาน และคลาสอื่นๆเล่น QLabel, QLineEdit, ฯลฯ นั้นล้วนเป็นซับคลาสของ QWidget ซึ่งถูกใส่หน้าที่ให้ทำงานเฉพาะในแบบต่างๆ

ชื่อคลาสของ widget ต่างๆของ qt นั้นจะขึ้นต้นด้วย Q ทั้งหมด เป็นเอกลักษณ์อย่างหนึ่งที่ช่วยให้จำง่าย




ส่วนประกอบของมอดูล pyqt5 และการเรียกใช้งาน

ต่อไปจะเริ่มอธิบายส่วนประกอบของ pyqt5 ในเบื้องต้น

pyqt5 จะประกอบไปด้วยมอดูลย่อยที่งานเป็นหลักอยู่ ๓ ตัวคือ QtCore, QtGui, QtWidgets และนอกจากนี้ก็ยังมีตัวอื่นๆอีกที่เอาไว้ใช้ทำงานในด้านต่างๆที่เจาะจงลงไปอีกเช่น QtOpenGL, QtWebEngine, QtSql, QtXml, ฯลฯ

QtWidgets จะเก็บพวกคลาสของ widget ชนิดต่างๆซึ่งจำเป็นต้องใช้เป็นส่วนประกอบในการสร้าง GUI

QtGui เก็บพวกคลาสของส่วนประกอบอื่นๆที่จำเป็นนอกเหนือจากพวก widget

ซึ่งที่จริงแล้วแค่ import PyQt5 ทีเดียวก็สามารถใช้คำสั่งต่างๆทั้งใน QtCore, QtGui, QtWidgets ได้
import PyQt5

print(PyQt5.QtWidgets.QApplication) # ได้ <class 'spydercustomize.SpyderQApplication'>
print(PyQt5.QtCore.Qt) # ได้ <class 'PyQt5.QtCore.Qt'>
print(PyQt5.QtGui.QFont()) # ได้ <PyQt5.QtGui.QFont object at 0x7feefb01c660>

เพียงแต่เนื่องจากต้องพิมพ์ยาว วิธีการเช่นนี้จึงไม่นิยมใช้นัก

วิธีการที่แนะนำให้ใช้คือการใช้ from import เอาเฉพาะส่วนประกอบที่ต้องการมาทีละอย่างแบบนี้แทน
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont

print(QApplication) # ได้ <class 'spydercustomize.SpyderQApplication'>
print(Qt) # ได้ <class 'PyQt5.QtCore.Qt'>
print(QFont()) # ได้ <PyQt5.QtGui.QFont object at 0x7feefb0714a0>

แต่ก็มีคนจำนวนมากที่ใช้ from import * เพื่อ import ทุกสิ่งทุกอย่างมาเตรียมไว้ทีเดียวแบบนี้
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

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




ภาพรวมโดยทั่วไปในการใช้งาน

โดยทั่วไปแล้วเวลาเขียนโค้ด pyqt5 เพื่อสร้าง GUI นั้น โดยภาพรวมแล้วจะออกมาในลักษณะแบบนี้
import sys
from PyQt5.QtWidgets import QApplication,QWidget # เรียกใช้คลาสต่างๆที่ต้องการ

qAp = QApplication(sys.argv) # สร้างอินสแตนซ์ของ QApplication
natang = QWidget() # สร้างอินสแตนซ์ของ QWidget ขึ้นมาเป็นหน้าต่าง

'''
ส่วนประกอบและรายละเอียดต่างๆของ GUI
'''

natang.show() # สั่งให้แสดงหน้าต่างที่สร้างขึ้นมา
qAp.exec_() # ใช้เมธอด .exec_ ที่ตัวอินสแตนซ์ของ QApplication ที่สร้างขึ้นมาเพื่อเริ่มทำงาน

โดยเริ่มจากเรียกใช้โดย import คลาสหรือส่วนประกอบต่างๆที่ต้องการใช้ใน pyqt5 เตรียมไว้

ในตัวอย่างนี้แสดงแค่ ๒ ตัวคือ QApplication กับ QWidget แต่ในการใช้งานจริงหากต้องการใช้อะไรก็ให้ import ตัวนั้นมาด้วย

(หรือถ้าขี้เกียจก็อาจใช้ from PyQt5.QtWidgets import * เรียกใช้ทั้งหมดทีเดียวไปเลย แม้ว่านี่อาจจะไม่ใช่วิธีที่น่าแนะนำนัก)

นอกจากนี้ยังรวมถึงมอดูลอื่นๆที่จะเอามาใช้ด้วย ซึ่งโดยปกติจะต้องมี sys เพราะจำเป็นต้องใช้ sys.argv

จากนั้นก็เริ่มทำการสร้างอินสแตนซ์ของ QApplication ซึ่งเป็นส่วนประกอบหลักสำหรับตัวโปรแกรม โดยเมื่อสร้าง ให้ใส่ sys.argv ไปเป็นอาร์กิวเมนต์

เกี่ยวกับเรื่องของ sys.argv นั้นอ่านรายละเอียดได้ใน https://phyblas.hinaboshi.com/20190705

ในที่นี้อาจทำความเข้าใจแบบง่ายๆแค่ว่า qAp = QApplication(sys.argv) คือโค้ดเริ่มต้นสร้าง GUI ของ qt ในไพธอน ซึ่งมักจะต้องเขียนเสมอเวลาที่ใช้ pyqt

จากนั้นในตอนส่วนท้ายที่สุดของโค้ดคือ qAp._exec() นั้นเป็นการสั่งให้เริ่มต้นการทำงาน

โค้ดที่อยู่ในบรรทัดระหว่าง qAp = QApplication(sys.argv) กับ qAp._exec() นี้คือส่วนของการสร้างหน้าต่าง

โดยมักจะเริ่มต้นด้วย natang = QWidget() เพื่อสร้างอินสแตนซ์ของ widget ที่เป็นหน้าต่างหลักขึ้นมา แล้วก็จบลงด้วย natang.show() เพื่อสั่งให้แสดงหน้าต่างนั้น

ทีนี้โค้ดส่วนที่อยู่ระหว่าง natang = QWidget() กับ natang.show() ก็คือรายละเอียดต่างๆของ GUI ที่เราต้องการสร้างขึ้น เช่นการสร้างและจัดวาง widget ต่างๆและกำหนดการทำงาน

อนึ่ง ทั้ง qAp และ natang นั้นเป็นชื่อตัวแปรที่กำหนดขึ้นเองในที่นี้ ซึ่งตลอดบทเรียนนี้ก็จะใช้แบบนี้ทั้งหมด



ภาพรวมในการเขียนนั้นก็เป็นดังที่กล่าวมานี้ อย่างไรก็ตามในทางปฏิบัติแล้วถ้าไปดูในตัวอย่างการใช้ส่วนใหญ่แล้วจะใช้วิธีการสร้างเป็นคลาสขึ้นโดยทำเป็นซับคลาสของ QWidget ดังนี้
import sys
from PyQt5.QtWidgets import QApplication,QWidget

# สร้างคลาสของหน้าต่างที่เราต้องการขึ้นมาเป็นซับคลาสของ QWidget
class Natang(QWidget):
    def __init__(self):
        super().__init__()
        
        '''
        ส่วนประกอบและรายละเอียดต่างๆของ GUI
        '''
        
        self.show() # สั่งให้แสดงหน้าต่างที่สร้างขึ้นมา

qAp = QApplication(sys.argv)
natang = Natang()  # สร้างอินสแตนซ์ของคลาสหน้าต่างที่เราสร้างขึ้นมา
qAp.exec_()

ซึ่งเมื่อเขียนแบบนี้ก็จะทำให้ดูเป็นระเบียบขึ้นมา เพราะโค้ดทั้งหมดที่กำหนดลักษณะและการทำงานของ GUI จะอยู่ภายในคลาสที่สร้างขึ้นทั้งหมดเลย

อย่างไรก็ตาม ถึงจะไม่สร้างเป็นคลาสก็ทำงานได้เช่นเดียวกัน จึงอาจแล้วแต่ความสะดวกและความต้องการในการใช้ในแต่ละงาน

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




สรุปท้ายบท

ในบทนี้ได้แนะนำให้รู้จักกับ pyqt และภาพรวมของการเขียนโค้ดในเบื้องต้นแล้ว

สำหรับในบทถัดๆไปจะค่อยเริ่มอธิบายการสร้างและปรับแต่ง widget ต่างๆเพื่อประกอบกันเป็น GUI ขึ้นมา



อ่านบทถัดไป >> บทที่ ๒





-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> pyqt

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

目次

日本による名言集
モジュール
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
機械学習
-- ニューラル
     ネットワーク
maya
javascript
確率論
日本での日記
中国での日記
-- 北京での日記
-- 香港での日記
-- 澳門での日記
台灣での日記
北欧での日記
他の国での日記
qiita
その他の記事

記事の類別



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  記事を検索

  おすすめの記事

รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

ไทย

日本語

中文