φυβλαςのβλογ
บล็อกของ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文