• בלוג
  • עמוד 254
  • איך לכתוב ממשק משתמש מדליק לתוכנית Python ב Qt

איך לכתוב ממשק משתמש מדליק לתוכנית Python ב Qt

13/02/2017

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

שפת Python מגיעה עם ספריית ממשק משתמש פשוטה בשם Tk, שאומנם קל מאוד לבנות בה ממשק אבל התוצאה תמיד נראית מיושנת והקוד מסורבל.

ספריית Qt לעומתה מפותחת באופן פעיל כבר מעל עשרים שנה, היא הדרך הסטנדרטית לכתוב ממשקי משתמש ב Linux ומציעה פתרון מעולה גם למערכות ההפעלה האחרות (חלונות, מק ואפילו טלפונים).

שילוב Qt בתוכנית Python שלנו מאפשר פיתוח ממשק משתמש איכותי עם קוד נקי וכלי פיתוח מאוד נוחים, כפי שנראה במדריך זה.

1. התקנה

יש מספר ספריות Python המחברות את השפה ל Qt, המרכזיות שבהן הן PyQt ו PySide. הראשונה (PyQt) היא מוצר מסחרי של חברת Riverbank. בניגוד ל Qt עצמה, ספריית PyQt מופצת רק ברשיון דואלי והבחירה היא בין GPL לרשיון מסחרי. היא קלה יותר להתקנה, מתוחזקת טוב יותר ועדיין יציבה יותר.

השניה (PySide) פותחה בנוקיה בתקופה ש Qt היה בבעלותה במטרה לבנות חיבור איכותי וחופשי בין השפות. הגירסא העדכנית היא PySide2 והיא עובדת מול Qt5.6. התקנת PySide קצת יותר מסורבלת ודורשת שתתקינו בנפרד את Qt מבעוד מועד.

במדריך זה אבחר להציג את PyQt בגירסת פייתון 3. התקינו תחילה את פייתון 3 ולאחר מכן את PyQt באמצעות הפקודה:

pip3 install pyqt5

למי שמעדיף את PySide מוזמנים לעקוב אחר הוראות ההתקנה במדריך כאן: https://wiki.qt.io/PySide2_GettingStarted

2. הממשק הראשון שלי

עכשיו שיש לנו את PyQt מותקן נוכל לכתוב תוכנית Python המציגה ממשק משתמש פשוט. הקלידו את הקוד הבא והפעילו:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

app = QApplication(sys.argv)

label = QLabel("Hello World")
label.setGeometry(100, 100, 200, 200)
label.setAlignment(Qt.AlignCenter)
label.show()

app.exec_()

בתוכנית יצרנו אוביקט מסוג QLabel שהוא חלון המציג טקסט, הגדרנו את הטקסט בחלון והפעלנו את הפקודה app.exec_ כדי להכנס ללולאת ה GUI הראשית. פקודה אחרונה זו היא שאפשרה לקוד להציג חלון, והיא שקולה לפעולת mainloop ב Tk.

את שאר הפקודות אפשר למצוא בקלות בתיעוד של Qt על QLabel בקישור: http://doc.qt.io/qt-5/qlabel.html.

3. הצגת מספר רכיבים באותו חלון

נוסיף לתצוגה גם תיבת טקסט אינטרקטיבית הנקראת QLineEdit ב Qt. תיבה זו תתן למשתמש לכתוב ולמחוק טקסט ותאפשר לנו להגיב לשינויים בטקסט. בשביל למקם מספר רכיבים באותו חלון צריך להגיד ל Qt איך לסדר אותם. אנחנו נבחר בסידור הפשוט שנקרא QVBoxLayout- סידור שאומר ל Qt לסדר את הרכיבים אחד מעל השני. שם הסידור הוא קיצור של המשפט Qt Vertical Box Layout כלומר מיקום בתיבה אנכית.

עדכנו את התוכנית לקוד הבא והריצו:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

app = QApplication(sys.argv)

w = QWidget()
layout = QVBoxLayout(w)

layout.addWidget(QLineEdit())
layout.addWidget(QLabel("Type Something..."))

w.show()

app.exec_()

הפעם יצרנו חלון מסוג QWidget ובתוכו הגדרנו layout מסוג QVBoxLayout. החיבור בין ה layout לחלון התבצע בזכות העברת החלון כפרמטר לפונקציית האתחול QVBoxLayout.

לאחר מכן כל הוספה של רכיב ל layout גרמה לרכיב זה להתווסף לחלון כך שמיקום הרכיב נקבע על ידי ה Layout.

4. תגובה לפעולות משתמש

יש לנו תיבת עריכת טקסט ותיבת הצגה ונרצה לחבר אותן כך שלחיצה על Enter בתיבת עריכת הטקסט תעתיק את הטקסט לתיבת התצוגה. ב Qt חיבור כזה מתבצע דרך מנגנון שנקרא signals/slots. הרכיבים השונים של Qt מדווחים לעולם כשדברים מעניינים קורים בהם, דיווח שנקרא ״שליחת signal״.

אנו יכולים לחבר פונקציות לטיפול בסיגנלים אלו וכך כל פעם שישלח סיגנל תופעל הפונקציה שלנו שמטפלת בסיגנל זה. בשביל שיהיה נוח נהוג להגדיר קלאס עבור כל מסך הכולל את הפקדים שנמצאים במסך ואת פונקציות הטיפול בסיגנלים מפקדים אלו. בעדכון הבא של התוכנית הקלידו את הקוד הבא:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


class MyWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.layout = QVBoxLayout(self)
        self.eSource = QLineEdit()
        self.lResult = QLabel("Type Something...")

        self.layout.addWidget(self.eSource)
        self.layout.addWidget(self.lResult)

        self.eSource.returnPressed.connect(self.copyText)

    def copyText(self):
        self.lResult.setText(self.eSource.text())

app = QApplication(sys.argv)

w = MyWidget()
w.show()

app.exec_()

יש כאן מספר שינויים קטנים ושינוי אחד גדול. השינויים הקטנים הם כמובן במבנה: במקום לכתוב את כל הקוד בגוף התוכנית העברתי אותו לפונקציית init של מחלקה חדשה, ואת כל הרכיבים שמרתי כשדות מידע של אותה מחלקה.

השינוי הגדול הוא בשורה 16 והוא פקודת ה connect. פקודה זו מחברת פונקציה לטיפול בסיגנל, במקרה שלנו בכל פעם שתיבת הטקסט תרצה לדווח שמישהו לחץ על Enter בתיבה תופעל הפונקציה copyText של המחלקה החדשה.

את רשימת הסיגנלים של פקד אפשר למצוא בתיעוד שלו. עבור תיבת טקסט למשל התיעוד הרלוונטי עם רשימת כל הסיגנלים שלה נמצא בקישור: http://doc.qt.io/qt-5/qlineedit.html.

הפעילו את התוכנית, כתבו משהו בתיבה ולחצו Enter. תוכלו לראות שהטקסט הועתק מהתיבה לתווית התצוגה.

5. עיצוב הממשק ב QSS

יתרון גדול של Qt על פני Tk הוא ביכולת לעצב את הממשק. ל Qt יש שפה שלמה לעיצוב המבוססת על CSS ונקראת Qt Style Sheets. המנגנון מאפשר לכתוב קוד עיצוב לכל פקד בקובץ התוכנית או בקובץ נפרד בפורמט שמאוד דומה להגדרת CSS.

לצורך פשטות נשתמש בקוד עיצוב מוטמע בהגדרת המחלקה. עדכון אחרון לתוכנית והרצה:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


class MyWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setStyleSheet('''
MyWidget {
    background: lightblue;
}


QLineEdit#eSource {
    color: gray;
    font-size: 28px;
    padding: 10px;
}

QLabel{
    color: purple;
    font-size: 28px;
    padding-left: 5px;
}
        ''')

        self.layout = QVBoxLayout(self)
        self.eSource = QLineEdit()
        self.eSource.setObjectName('eSource')

        self.lResult = QLabel("Type Something...")
        self.lResult.setObjectName('lResult')

        self.layout.addWidget(self.eSource)
        self.layout.addWidget(self.lResult)

        self.eSource.returnPressed.connect(self.copyText)

    def copyText(self):
        self.lResult.setText(self.eSource.text())

app = QApplication(sys.argv)

w = MyWidget()
w.show()

app.exec_()

הפעם הצבעים והגדלים עודכנו להתאים לקוד העיצוב שכתבנו. התוספת setObjectName עבור שני הרכיבים היא שאפשרה להתייחס אליהם ספציפית בתוך הגדרות העיצוב.

בפוסט זה נגענו בקושי בקצה הקרחון של היכולות של Qt. אולי הדבר הכי מדליק שלא דיברתי עליו כאן הוא ה Qt Designer שנותן לכם בממשק Drag & Drop נוח יכולת לבנות ולעצב ממשקים, ואולי נקדיש לו פוסט בעתיד. כמובן שיש תמיכה מלאה בתרגומים לכל השפות של הממשק, תפריטים, פקדים מכל הסוגים ועיצוב שלהם בכל צורה שתבחרו ובניית פקדים מותאמים אישית שלכם.