Dessin avec Python Tkinter et PySide

Dessin avec Python Tkinter et PySide #

Cette page présente le même programme, qui affiche une fenêtre contenant deux rectangles, une image et un segment, en utilisant tkinter ou PySide. Voici ce qu’affichent les programmes :

Cet avis n’engage que moi : PySide (Qt) est plus moderne que Tk, il contient des Widgets plus complexes et plus complets. Cependant, il est moins standard en Python que tkinter, et il est plus complexe (pour faire des choses simples, c’est a priori aussi simple, mais si on entre dans les détails…). On peut toutefois réaliser avec PySide des choses qui seraient compliquées à faire avec Tkinter… Il y a aussi d’autres outils pour réaliser des interfaces graphiques, dont Kivy, qui vaut probablement la peine qu’on s’y attarde.

Pour tester les programmes suivants, il faut aussi avoir l’image utilisée. Merci de la télécharger :

Dessiner avec Tkinter, sans POO #

# Documentation : http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
import tkinter
from PIL import ImageTk

# Variable globale pour le logo (voir plus loin)
image = None

# Si on clique dans la fenêtre, on verra les coordonnées s'afficher dans le terminal
def click(event):
    print("clic souris : ", event.x, event.y)


# L'application sera contenue dans une /Frame/ (un conteneur).
# Cette frame contiendra un /Canvas/ qui contiendra des objets graphique.
# On peut faire ce programme *sans* utiliser de /Frame/, mais directement le 
# canvas. Cependant, si par la suite on souhaite ajouter d'autres éléments graphiques
# ce sera tout prêt...

def gui(parent):
    # Titre de l'application :
    parent.title("Example graphique Tkinter sans classe")
    # Création de la Frame
    frame = tkinter.Frame(parent)
    # Disposition de la Frame (dans la fenêtre application). La frame va prendre toute la place 
    # disponible dans la fenêtre, même si on change la taille de la fenêtre
    frame.pack(fill='both', expand=1)
    # On crée un canevas  de taille 400x300 dans la Frame 
    canvas = tkinter.Canvas(frame, width=300, height=400, background="grey")
    # Le canevas est "packé" dans sa fenêtre en laissant une petite bordure de taille 8
    canvas.pack(padx=8, pady=8)#  Ajouter  fill='both', expand=1 pour voir...
    draw_board(canvas)
    # On demande à associer la callback /click/ à l'événement /<Button-1>/
    canvas.bind("<Button-1>", click)

def draw_board(canvas):
        global image
        # On trace un rectangle rouge : coin hg 10,10, coin bd 100,20
        canvas.create_rectangle(10, 10, 100, 30, fill="#ff0000")
        # Puis un bleu
        canvas.create_rectangle(80, 20, 200, 60, fill="#0000ff", outline="#00ff00")
        # On trace une ligne violette
        canvas.create_line(0, 0, 200, 300, fill="#ff00ff")
        # On charge l'image 
        image = ImageTk.PhotoImage(file="python.png")
        # Et on l'affichge, les coordonnées sont celles du centre (anchor="c")
        canvas.create_image(128,128, image=image, anchor="c")

        # Attention, on ne peut pas utiliser de variable locale pour l'image car après avoir
        # donné l'ordre d'affichage /create_image/ Tk doit pouvoir continuer à disposer des données.
        # Or les variables locales sont libérées (et l'image effacée) à la sortie de la fonction 
        # qui les contient. C'est pourquoi on utilise ici une variable globale (il y a d'aurtes solutions
        # toutefois...

# Création de l'application
root = tkinter.Tk()
# Ajout des éléments graphiques 
gui(root)
# Boucle des événements
root.mainloop()

Dessiner avec Tkinter, avec POO #

# Documentation : http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
import tkinter
from PIL import ImageTk

# Si on clique dans la fenêtre, on verra les coordonnées s'afficher dans le terminal
def click(event):
    print("clic souris : ", event.x, event.y)


# L'application sera contenue dans un objet qui hérite de /Frame/ (un conteneur).
# Cette frame contiendra un /Canvas/ qui contiendra des objets graphique.
# On peut faire ce programme *sans* utiliser de /Frame/, mais directement le 
# canvas. Cependant, si par la suite on souhaite ajouter d'autres éléments graphiques
# ce sera tout prêt...

class Fenetre(tkinter.Frame):
    def __init__(self, parent):
        # Initialiseur de la classe parente :
        super().__init__(parent)
        # Titre de l'application :
        parent.title("Example graphique Tkinter avec classe")
        # Disposition de la Frame (dans la fenêtre application). La frame va prendre toute la place 
        # disponible dans la fenêtre, même si on change la taille de la fenêtre
        self.pack(fill='both', expand=1)
        # On crée un canevas  de taille 400x300 dans la Frame /self/
        self.canvas = tkinter.Canvas(self, width=300, height=400, background="grey")
        # Le canevas est "packé" dans sa fenêtre en laissant une petite bordure de taille 8
        self.canvas.pack(padx=8, pady=8)#  Ajouter  fill='both', expand=1 pour voir...
        self.image = None # initialisation propre d'un attribut utilisé plus loin
        # On dessine
        self.draw_board()
        # On demande à associer la callback /click/ à l'événement /<Button-1>/
        self.canvas.bind("<Button-1>", click)


    def draw_board(self):

        # On trace un rectangle rouge : coin hg 10,10, coin bd 100,20
        self.canvas.create_rectangle(10, 10, 100, 30, fill="#ff0000")
        # Puis un bleu
        self.canvas.create_rectangle(80, 20, 200, 60, fill="#0000ff", outline="#00ff00")
        # On trace une ligne violette
        self.canvas.create_line(0, 0, 200, 300, fill="#ff00ff")
        # On charge l'image 
        self.image = ImageTk.PhotoImage(file="python.png")
        # Et on l'affichge, les coordonnées sont celles du centre (anchor="c")
        self.canvas.create_image(128,128, image=self.image, anchor="c")

        # Attention, on ne peut pas utiliser de variable locale pour l'image car après avoir
        # donné l'ordre d'affichage /create_image/ Tk doit pouvoir continuer à disposer des données.
        # Or les variables locales sont libérées (et l'image effacée) à la sortie de la fonction 
        # qui les contient. C'est pourquoi on utilise ici un attribut de classe. self.image a ainsi 
        # la même durée de vie que l'objet global /gui/ (voir plus bas)

def main():
    # Création de l'application
    root = tkinter.Tk()
    # Ajout des éléments graphiques
    gui = Fenetre(root)
    # Boucle des événements
    root.mainloop()

if __name__ == "__main__":
     main()

Dessiner avec PySide, avec POO #

# Documentations :
# https://deptinfo-ensip.univ-poitiers.fr/ENS/doku/doku.php/stu:python_gui:pyqt
# https://deptinfo-ensip.univ-poitiers.fr/ENS/pyside-docs/index.html
from PySide import QtGui,QtCore
import sys

# Fenêtre principale (c'est une Frame), qui contient : une Frame et une 
# zone de dessin (dans la frame intérieure)  
class Fenetre(QtGui.QFrame):
    def __init__(self,parent=None) :
        super().__init__(parent)

        # Titre de l'application :
        self.setWindowTitle("Example graphique PySide (avec classes...)")
        # On place une première /Frame/ (conteneur), un peu plus petit.
        frame = QtGui.QFrame(self)
        frame.setGeometry(10, 10, 300, 400)
        frame.setStyleSheet("background:grey")
        # Puis une zone de dessin (définie dans la classe /ZoneDessin/ plus loin,
        # qui occupe toute la frame (la plus petite)
        dessin = ZoneDessin(frame)
        dessin.setGeometry(0, 0, 300, 400)

# Zone principale (dessin)
class ZoneDessin(QtGui.QWidget) :
    def __init__(self,parent=None) :
        super().__init__(parent)

    def paintEvent(self,e) :
        # On demande un pbjet Painter et on dessine dessus
        p = QtGui.QPainter(self)
        # On trace un rectangle rouge à bord noir (x,y du coin puis largeur hauteur)
        p.setPen(QtGui.QColor(0, 0, 0))
        p.setBrush(QtGui.QColor(255, 0, 0))
        p.drawRect(10, 10, 90, 20)
        # puis un bleu à bord vert
        p.setPen(QtGui.QColor(0, 255, 0))
        p.setBrush(QtGui.QColor(0, 0, 255))
        p.drawRect(80, 20, 120, 40)
        # On trace une ligne violette
        p.setPen(QtGui.QColor(255, 0, 255))
        p.drawLine(0, 0, 200, 300)
        # On affiche l'image
        pixmap = QtGui.QPixmap("python.png")
        p.drawPixmap(78, 78, pixmap)

    # Si on clique dans la fenêtre, on verra les coordonnées s'afficher dans le terminal
    def mousePressEvent(self, e):
        print("clic souris : ",e.x(), e.y())

app = QtGui.QApplication(sys.argv)
frame = Fenetre()
frame.show()
sys.exit(app.exec_())