การพัฒนาซอฟต์แวร์ด้วย PyQt4 บน Raspberry Pi (Jessie) ตอนที่ 2

ในตอนที่แล้วได้กล่าวถึงการสร้าง User Interface ด้วย Qt4-Designer ซึ่งได้ผลลัพธ์ออกมาเป็นเอกสาร XML  ที่มีนามสกุลเป็น .ui  เราสามารถนำไปใช้กับ Application ของเราโดยตรงหรือนำไปแปลงรูปให้เป็น Python script ไฟล์ก่อนก็ได้ ซึ่งได้ทำ Application ตัวอย่างไว้คือ Calculator

ในการพัฒนาซอฟต์แวร์ที่เป็น Graphic User Interface (GUI) เกือบทั้่งหมดจะใช้สถาปัตยกรรมแบบ Event Driven คือการตรวจจับเหตุการณ์ที่เกิดขึ้นใน Application หรืออาจเรียกว่า State Change แล้วก็ตอบสนองต่อเหตุการณ์ตามที่ผู้พัฒนาออกแบบไว้ และโปรแกรม Calculator ในตัวอย่างก็เช่นเดียวกัน

การสร้าง Event Driven Application โดยทั่วไปเราต้องคำนึงถึง 3 เรื่องด้วยกันคือ

  1. ผู้สร้าง Event 
  2. ตัว Event เอง
  3. ผู้ตอบสนองต่อ Event
แต่ใน PyQt จะมีการเรียกชื่อเหล่านี้ต่างออกไป คือ
  1. เมื่อมีเหตุการณ์เกิดขึ้น ผู้สร้างเหตุการณ์จะสร้าง Signal ขึ้นมา
  2. ผู้ตอบสนองต่อเหตุการณ์จะเรียกว่า Slot  
  3. ผู้สร้างเหตุการณ์จะต้องทำการ Emit ก่อน ผู้ตอบสนองจึงจะรับรู้ได้



การผูก Signal เข้ากับ Slot ใน PyQt


PyQt รุ่นที่เก่ากว่า 4.5 มีการใช้รูปแบบคำสั่งแบบข้างล่างนี้





ต่อมามีการเปลี่ยนแปลงรูปแบบไปให้ง่ายขึ้น ดังนี้





Calculator Application

ในตอนนี้ผมได้เปลี่ยนหน้าตาของ Calculator ไปนิดหน่อย ดังภาพ




ตัวที่ทำหน้าที่เป็นผู้สร้าง Signal หรือ Event ก็คือปุ่มต่าง ๆ บนหน้าจอ ส่วน Slot นั้นผมสร้างเป็น Python Script ดังนี้


def append_text(self):
 sender = self.sender()
 val = sender.text()
 if val in ['/','+','-','(',')','x'] :
  val = " "+val+" "
 disp = self.disp_text.text()
 disp += val
 self.disp_text.clear()
 self.disp_text.setText(disp)
  
def backspace(self):
 self.disp_text.backspace() 
  
def clear_text(self):
 self.disp_text.clear()  
  
def calculate(self):
 # need to change to pythonic string
 txt = str(self.disp_text.text())  
 if len(txt) > 0 :
  result = self.calculator.calculate(txt)
  self.disp_text.clear()
  self.disp_text.setText(str(result))
   


คำอธิบาย

ใน def append_text()

sender = self.sender()

เป็นการตรวจหาผู้ส่ง Signal  ว่ามาจากปุ่มไหน เพราะมีหลายปุ่มที่สามารถส่ง Signal ได้

val  = sender.text()
if val in ['/','+','-','(',')','x'] :
    val = " "+val+"

รับค่า text ของ sender  หากค่านั้นเป็นหนึ่งใน operator  ให้ทำการเพิ่มช่องว่างไว้ข้างหน้าและข้างหลัง (เพื่อประโยชน์ในการตัดข้อความต่อไป)

self.disp_text.clear()
self.disp_text.setText(disp)

นำข้อความจาก sender ไปแสดง

ใน def calculate() จะมีการอ้างถึง self.calculator ซึ่งเป็น class ที่ถูกสร้างขึ้นมาเพื่อใช้ประมวลผล และจะกล่าวถึงต่อไป

ต่อไปเป็น script ในการผูก  Signal เข้ากับ Slot  หรือ การเชื่อมระหว่างปุ่มต่างเข้ากับ slot หรือ Python script ที่สร้างข้างบน

self.num_btn_1.clicked.connect(self.append_text)
self.num_btn_2.clicked.connect(self.append_text)
self.num_btn_3.clicked.connect(self.append_text)
self.num_btn_4.clicked.connect(self.append_text)
self.num_btn_5.clicked.connect(self.append_text)
self.num_btn_6.clicked.connect(self.append_text)
self.num_btn_7.clicked.connect(self.append_text)
self.num_btn_8.clicked.connect(self.append_text)
self.num_btn_9.clicked.connect(self.append_text)
self.num_btn_0.clicked.connect(self.append_text)
self.left_paren_btn.clicked.connect(self.append_text)
self.right_paren_btn.clicked.connect(self.append_text)
self.plus_btn.clicked.connect(self.append_text)
self.minus_btn.clicked.connect(self.append_text)
self.mult_btn.clicked.connect(self.append_text)
self.div_btn.clicked.connect(self.append_text)  
self.dot_btn.clicked.connect(self.append_text)
self.bsp_btn.clicked.connect(self.backspace)
self.clear_btn.clicked.connect(self.clear_text)
self.exe_btn.clicked.connect(self.calculate)

ถึงตอนนี้เราก็มีองค์ประกอบของ Application เกือบครบละครับ ตอนนี้เรามี User Interface, Signal และ Slot แล้วก็เอาทั้งหมดมารวมกันจะได้ั PyQt script ตามนี้


import sys
from PyQt4 import QtCore, QtGui, uic

Ui_MainWindow, QtBaseClass = uic.loadUiType("calculator_demo.ui")

class MyApp(QtGui.QMainWindow, Ui_MainWindow):
 #on_click = pyqtSignal(str)
 
 def __init__(self):
  QtGui.QMainWindow.__init__(self)
  Ui_MainWindow.__init__(self)
  self.setupUi(self)
  self.init_ui()
  self.calculator = Calculator()

 def init_ui(self):
  self.num_btn_1.clicked.connect(self.append_text)
  self.num_btn_2.clicked.connect(self.append_text)
  self.num_btn_3.clicked.connect(self.append_text)
  self.num_btn_4.clicked.connect(self.append_text)
  self.num_btn_5.clicked.connect(self.append_text)
  self.num_btn_6.clicked.connect(self.append_text)
  self.num_btn_7.clicked.connect(self.append_text)
  self.num_btn_8.clicked.connect(self.append_text)
  self.num_btn_9.clicked.connect(self.append_text)
  self.num_btn_0.clicked.connect(self.append_text)
  self.left_paren_btn.clicked.connect(self.append_text)
  self.right_paren_btn.clicked.connect(self.append_text)
  self.plus_btn.clicked.connect(self.append_text)
  self.minus_btn.clicked.connect(self.append_text)
  self.mult_btn.clicked.connect(self.append_text)
  self.div_btn.clicked.connect(self.append_text)  
  self.dot_btn.clicked.connect(self.append_text)
  self.bsp_btn.clicked.connect(self.backspace)
  self.clear_btn.clicked.connect(self.clear_text)
  self.exe_btn.clicked.connect(self.calculate)
 
 def append_text(self):
  sender = self.sender()
  val = sender.text()
  if val in ['/','+','-','(',')','x'] :
   val = " "+val+" "
  disp = self.disp_text.text()
  disp += val
  self.disp_text.clear()
  self.disp_text.setText(disp)
  
 def backspace(self):
  self.disp_text.backspace() 
  
 def clear_text(self):
  self.disp_text.clear()  
  
 def calculate(self):
  # need to change to pythonic string
  txt = str(self.disp_text.text())  
  if len(txt) > 0 :
   result = self.calculator.calculate(txt)
   self.disp_text.clear()
   self.disp_text.setText(str(result))
   
  if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

ปล. ตอนนี้หากนำ script นี้ไปเรียกใช้งานอาจจะมีปัญหาอยู่ เพราะ class Calculator ยังไม่ได้สร้างครับ แล้วจะเอาเล่าต่อตอน 3
Previous
Next Post »