φυβλαςのβλογ
บล็อกของ phyblas



pyqt เบื้องต้น บทที่ ๒๖: การสร้างข้อมูลสำหรับกดลากวางขึ้นมา
เขียนเมื่อ 2021/08/28 11:02
แก้ไขล่าสุด 2021/09/28 16:42

ต่อจาก บทที่ ๒๕

หลังจากที่บทที่แล้วได้แนะนำเรื่องการลากเอาข้อมูลเข้ามาใช้ สำหรับในบทนี้จะว่าด้วยเรื่องของการสร้างข้อมูลสำหรับลากขึ้นมาบ้าง




การทำให้ลากข้อความในกล่องข้อความได้ {.setDragEnabled}

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

หากต้องการให้ข้อมูลในกล่องแก้ข้อความนั้นสามารถลากออกได้ ก็ทำได้ง่ายโดยใช้ .setDragEnabled ตั้งเป็น True
ตัวอย่างเช่น ลองสร้างกล่องข้อความขึ้นมา ๒ อัน อันบนตั้ง .setDragEnabled(True) ให้สามารถลากได้
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QLineEdit,QVBoxLayout

class Natang(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Tahoma')
        self.vbl = QVBoxLayout()
        self.setLayout(self.vbl)
        # ช่องบน ทำให้ลากได้
        self.chongkhian1 = QLineEdit('ลองคลุมแล้วลากไปปล่อยดู')
        self.vbl.addWidget(self.chongkhian1)
        self.chongkhian1.setDragEnabled(True) # ทำให้ลากเอาข้อมูลได้
        # ช่องล่าง ไม่ได้ตั้งให้ลากได้
        self.chongkhian2 = QLineEdit('')
        self.vbl.addWidget(self.chongkhian2)
        self.show()

qAp = QApplication(sys.argv)
natang = Natang()
qAp.exec_()

ลองลากคลุมข้อความในกล่องด้านบน จากนั้นไปวางไว้ที่ช่องล่างดู



ข้อความจะถูกคัดลอกลงในช่องล่าง



แต่ว่าข้อความจากด้านล่างจะไม่สามารถลากขึ้นด้านบนได้ เพราะกล่องข้อความด้านล่างไม่ได้ตั้งไว้ว่าให้ลากได้




การทำให้พอลากไปแล้วข้อความในที่เดิมถูกลบไปด้วย {.source}

ในตัวอย่างที่แล้วจะเห็นว่าข้อมูลที่ถูกลากไปนั้นถูกคัดลอกไปยังที่ใหม่ที่ถูกลากไป แต่ข้อความเดิมก็ยังคงอยู่ที่เดิม

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

การเขียนคลาส QLineEdit ให้สามารถลากเพื่อย้ายข้อความไปมาระหว่างช่องได้ อาจเขียนได้ดังนี้
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QLineEdit,QVBoxLayout

class Chongkhian(QLineEdit):
    def __init__(self,*arg,**kwarg):
        super().__init__(*arg,**kwarg)
        self.setDragEnabled(True) # ทำให้สามารถลากได้

    def dragMoveEvent(self,e): # เมื่อมีการเคลื่อนเมาส์ขณะกดลากอยู่
        if(e.source()!=self): # หากพื้นที่ที่ลากนั้นไม่ได้อยู่ในที่เดียวกับแหล่งของข้อมูลที่ลาก
            QLineEdit.dragMoveEvent(self,e) # ให้ทำคำสั่งที่ปกติควรทำเวลาลากเมาส์ไปภายในกล่อง

    def dragEnterEvent(self,e):
        e.accept() # เมื่อลากข้อมูลเข้ามาให้ยอมรับ

    def dropEvent(self,e): # เมื่อลากมาวางในอีกกล่อง
        e.source().del_() # ลบข้อความในกล่องเดิมส่วนที่คลุมแล้วลากมา
        self.insert(e.mimeData().text()) # นำข้อความที่ลบจากกล่องเดิมมาเติมให้กล่องที่ลากไป

class Natang(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Courier New; font-size: 18px')
        self.vbl = QVBoxLayout()
        self.setLayout(self.vbl)
        # สร้างช่องเขียนข้อความโดยใช้คลาสใหม่ที่สร้างขึ้นมา
        self.chongkhian1 = Chongkhian('กล่องหนึ่ง')
        self.vbl.addWidget(self.chongkhian1)
        self.chongkhian2 = Chongkhian('กล่องสอง')
        self.vbl.addWidget(self.chongkhian2)
        self.show()

qAp = QApplication(sys.argv)
natang = Natang()
qAp.exec_()

จะได้ ๒ กล่องแบบนี้ออกมา ลองลากข้อความจากกล่องหนึ่งไปยังอีกกล่องเพื่อดูความเปลี่ยนแปลงได้



เช่นลองคลุมส่วนหนึ่งแล้วลากไป ส่วนนั้นก็จะย้ายไป



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




การสร้างข้อมูลที่ลากได้ขึ้นมาเอง {QDrag QMimeData}

นอกจากกล่องแก้ข้อความอย่าง QLineEdit หรือ QTextEdit แล้ว widget อื่นๆส่วนใหญ่นั้นไม่ได้มีข้อมูลที่สามารถลากได้

เช่น QLabel ซึ่งเป็นแผ่นเขียนข้อความธรรมดา โดยปกติต่อให้เอาเมาส์คลิกลากก็ไม่เกิดอะไรขึ้น

หากต้องการให้ข้อมูลในนี้ถูกกดลากได้ ต้องทำการการเขียนโค้ดในส่วนสร้างข้อมูลตรงนี้ขึ้นมาเอง

ยกตัวอย่างเช่น สร้างหน้าต่างที่ประกอบด้วยข้อความที่ใช้เป็นแม่พิมพ์ให้ลากแล้วเอาตัวหนังสือนั้นไปวางที่อื่น ได้ พร้อมทั้งสร้างกระดานสำหรับรับตัวหนังสือจากแม่พิมพ์ที่ลากเข้ามาได้
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QLabel,QHBoxLayout,QVBoxLayout,QFrame
from PyQt5.QtCore import Qt,QMimeData
from PyQt5.QtGui import QDrag

class Maephim(QLabel): # ส่วนข้อความที่จะเป็นแม่พิมพ์ คือให้ลากตัวหนังสือจากตรงนี้ไปวางที่อื่นได้
    def __init__(self,*arg,**kwarg):
        super().__init__(*arg,**kwarg)
        self.setStyleSheet('color: #b24; font-size: 60px;')
        self.setFrameShape(QFrame.Box)
        self.setAlignment(Qt.AlignCenter)
        self.setFixedSize(50,50)

    def mousePressEvent(self,e): # เมื่อกดเมาส์ที่ widget นั้น
        mida = QMimeData() # ก็เริ่มสร้างออบเจ็กต์เก็บ mime data ขึ้นมา
        mida.setText(self.text()) # แล้วเอาข้อความตัวหนังสือยัดใส่ให้เป็น mime data ในนั้น
        drag = QDrag(self) # สร้างออบเจ็กต์แทน event ของการลาก
        drag.setMimeData(mida) # นำข้อมูล mimedata ยัดใส่เข้าไป
        drag.exec_() # สั่ง exec_ เพื่อให้เริ่ม event การลากขึ้น

class Kradan(QFrame): # กระดานที่จะเอาข้อมูลที่ลากจากแม่พิมพ์เข้ามาวาง
    def __init__(self):
        super().__init__()
        self.setFixedSize(400,300)
        self.setStyleSheet('background-color: #6a4;')
        self.setFrameShape(QFrame.Panel)
        self.setFrameShadow(QFrame.Raised)
        self.setLineWidth(5)
        self.setAcceptDrops(True) # ตั้งให้รับข้อมูลที่ลากเข้าได้

    def dragEnterEvent(self,e):
        if(type(e.source())==Maephim): # ให้รับเฉพาะเมื่อเป็นข้อมูลที่ลากมาจากออบเจ็กต์แม่พิมพ์เท่านั้น
            e.accept()

    def dropEvent(self,e): # เมื่อปล่อยเมาส์หลังลากตัวหนังสือมาวาง
        label = QLabel(e.mimeData().text(),self) # สร้าง QLabel แล้วนำตัวหนังสือจาก mime data มาใส่
        label.setStyleSheet('background-color: None; color: #fff; font-size: 24px;')
        label.resize(40,50)
        label.setAlignment(Qt.AlignCenter)
        label.move(e.pos().x()-20,e.pos().y()-25)
        label.show()

class Natang(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Tahoma;')

        hbl = QHBoxLayout()
        self.setLayout(hbl)
        vbl = QVBoxLayout()
        hbl.addLayout(vbl)
        for s in ['ก','ข','ค','ง','จ']: # วางตัวอักษรที่เป็นแม่พิมพ์ไว้ทางซ้าย
            vbl.addWidget(Maephim(s))
        hbl.addWidget(Kradan()) # วางกระดานไว้ทางขวา
        self.show()

qAp = QApplication(sys.argv)
natang = Natang()
qAp.exec_()

ก็จะได้หน้าต่างที่มีตัวหนังสือแม่พิมพ์อยู่ทางซ้าย และกระดานอยู่ทางขวาแบบนี้



ลองคลิกที่แม่พิมพ์แล้วลากมายังกระดาน ก็จะถูกวางลอกมาลงในตำแหน่งที่ต้องการได้






การทำ widget ที่จับลากวางเพื่อเลื่อนตำแหน่งได้

เมื่อเข้าใจเรื่องการกดลากวางได้ดีพอแล้ว เราอาจสามารถสร้าง widget ที่สามารถคลิกลากเพื่อย้ายตำแหน่งไปมาตามที่ต้องการได้

ตัวอย่างเช่นสร้าง QLabel ที่วางรูปนี้ใส่ แล้วลากให้มันเคลื่อนไปมาได้



import sys
from PyQt5.QtWidgets import QApplication,QWidget,QLabel
from PyQt5.QtCore import Qt,QMimeData
from PyQt5.QtGui import QDrag,QPixmap

# สร้างคลาสของ widget ใส่รูปที่ลากเคลื่อนได้
class Rupkhlueandai(QLabel):
    def mousePressEvent(self,e): # เมื่อเมาส์ถูกกด
        if(e.button()==Qt.LeftButton): # ถ้าที่คลิกนั้นเป็นปุ่มเมาส์ซ้าย
            self.xykot = e.pos() # บันทึกตำแหน่งที่ถูกคลิกไว้
            drag = QDrag(self) # สร้างออบเจ็กต์ของ event การลาก
            drag.setMimeData(QMimeData()) # ใส่ mime data ลงไป
            drag.setPixmap(QPixmap('qbicon.png').scaledToWidth(25)) # ใส่รูปที่ให้แสดงเวลาที่ลาก
            drag.exec_() # เริ่ม event การลาก

class Natang(QWidget):
    def __init__(self):
        super().__init__()
        self.setFixedSize(450,350)
        self.setStyleSheet('font-family: Tahoma;')
        self.setAcceptDrops(True) # ตั้งให้หน้าต่างนี้รับการลากได้
        self.rup = Rupkhlueandai('',self) # สร้างตัวรูปเลื่อนที่เลื่อนได้ขึ้นมา
        self.rup.setPixmap(QPixmap('qbicon.png')) # ใส่รูป
        self.rup.setGeometry(50,50,100,100)
        self.show()

    def dragEnterEvent(self,e):
        if(e.source()==self.rup): # ให้ยอมรับการลากเฉพาะเมื่อเป็นตัวออบเจ็กต์รูปที่เราสร้างขึ้นเองนั้น
            e.accept()

    def dropEvent(self,e):
        so = e.source() # ออบเจ็กต์ตัวรูป
        so.move(e.pos()-so.xykot) # ย้ายไปยังตำแหน่งที่วางเมาส์

qAp = QApplication(sys.argv)
natang = Natang()
qAp.exec_()

จะได้หน้าต่างที่มีภาพวางอยู่ ลองกดคลิกที่ภาพแล้วลากดู



ภาพก็จะย้ายไปยังตำแหน่งที่ต้องการ






การลากย้ายข้อความไปมาระหว่างกลุ่มในพื้นที่แสดงกลุ่มข้อความ

จากความเข้าใจเรื่องการกดลากวาง เราสามารถสร้าง QListWidget ที่สามารถโยกย้ายข้อความข้างในไปมาได้
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QListWidget
from PyQt5.QtGui import QDrag
from PyQt5.QtCore import QMimeData

# เขียนคลาสของ QListWidget ใหม่ให้สามารถลากข้อความไปมาได้
class Chongraikan(QListWidget):
    def __init__(self,*arg,**kwarg):
        super().__init__(*arg,**kwarg)
        self.setStyleSheet('font-family: Courier New; font-size: 29px; background-color: #fdd; color: #056;')
        self.setAcceptDrops(True) # ตั้งให้รับการลากเข้าได้

    def mouseMoveEvent(self,e):
        mida = QMimeData() # สร้าง mime data ขึ้นมา
        mida.setText(self.currentItem().text()) #นำข้อความจากตัวที่เลือกอยู่ใส่เข้าไป
        drag = QDrag(self) # สร้าง event การลาก
        drag.setMimeData(mida) # ใส่ mime data ให้
        drag.exec_() # เริ่ม event การลาก

    def dragMoveEvent(self,e):
        if(e.source()==self):
            QListWidget.dragMoveEvent(self,e)

    def dragEnterEvent(self,e):
        e.accept()

    def dropEvent(self,e):
        mida = e.mimeData()
        so = e.source() # ตัวพื้นที่แสดงกลุ่มข้อความที่เป็นที่มาของข้อความที่ลากมา
        so.takeItem(so.row(so.currentItem())) # ลบข้อความตัวนั้นออกจากที่เดิม
        self.addItem(mida.text()) # ใส่ข้อความลงไปให้พื้นที่ส่วนที่ถูกปล่อยเมาส์

class Natang(QWidget):
    def __init__(self):
        super().__init__()
        self.hbl = QHBoxLayout()
        self.setLayout(self.hbl)
        # สร้างพื้นที่แสดงกลุ่มข้อความด้านซ้าย
        self.chongraikan1 = Chongraikan()
        self.hbl.addWidget(self.chongraikan1)
        self.chongraikan1.setFixedSize(200,300)
        self.chongraikan1.addItems(['ส้ม','ละมุด','ลำไย','กล้วย']) # ป้อนข้อความเริ่มต้น
        # สร้างพื้นที่แสดงกลุ่มข้อความด้านขวา
        self.chongraikan2 = Chongraikan()
        self.hbl.addWidget(self.chongraikan2)
        self.chongraikan2.setFixedSize(200,300)
        self.chongraikan2.addItems(['องุ่น','เงาะ','ลิ้นจี่','มะม่วง','แตงโม'])
        
        self.show()

qAp = QApplication(sys.argv)
natang = Natang()
qAp.exec_()

จะได้หน้าต่างที่มีกล่องข้อความสองกล่อง ลองกดลากข้อความจากฝั่งหนึ่งไปยังอีกฝั่ง



ข้อความที่ถูกลากก็จะย้ายไปยังอีกกล่องได้แบบนี้






สรุปท้ายบท

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

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






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

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

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

หมวดหมู่

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



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

  ค้นหาบทความ

  บทความแนะนำ

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

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文