ต่อจาก
บทที่ ๒๕
หลังจากที่บทที่แล้วได้แนะนำเรื่องการลากเอาข้อมูลเข้ามาใช้ สำหรับในบทนี้จะว่าด้วยเรื่องของการสร้างข้อมูลสำหรับลากขึ้นมาบ้าง
การทำให้ลากข้อความในกล่องข้อความได้ {.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 ต่างๆที่สามารถลากข้อมูลเพื่อทำอะไรต่างๆได้