diff --git a/README.md b/README.md index e9a2d97..41947e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -https://code.activestate.com/recipes/579048-python-mandelbrot-fractal-with-tkinter/ -https://stackoverflow.com/questions/9886274/how-can-i-convert-canvas-content-to-an-image -https://tkdocs.com/tutorial/text.html -https://stackoverflow.com/questions/33084097/tkinter-really-all-complete-event-list -https://stackoverflow.com/questions/4172659/master-list-of-all-tkinter-events -https://www.tcl.tk/man/tcl8.5/TkCmd/bind.htm#M7 +* https://code.activestate.com/recipes/579048-python-mandelbrot-fractal-with-tkinter/ +* https://stackoverflow.com/questions/9886274/how-can-i-convert-canvas-content-to-an-image +* https://tkdocs.com/tutorial/text.html +* https://stackoverflow.com/questions/33084097/tkinter-really-all-complete-event-list +* https://stackoverflow.com/questions/4172659/master-list-of-all-tkinter-events +* https://www.tcl.tk/man/tcl8.5/TkCmd/bind.htm#M7 diff --git a/editor.py b/editor.py index 1c7909b..41fdd47 100644 --- a/editor.py +++ b/editor.py @@ -1,32 +1,111 @@ import network import tkinter as tk +from tkinter import ttk, filedialog, colorchooser import sys listen_addr = ("0.0.0.0", int(sys.argv[1])) -send_addr = ("192.168.0.255", int(sys.argv[2])) +send_addr = ("192.168.1.255", int(sys.argv[2])) sock = network.Sock() waiting_for_text = True root = tk.Tk() -area = tk.Text() -area.pack() +def foo(): + print("foo") + +root.option_add('*tearOff', False) +menubar = tk.Menu(root) +root['menu'] = menubar +menu_file = tk.Menu(menubar) +menu_edit = tk.Menu(menubar) +menubar.add_cascade(menu=menu_file, label='File') +menubar.add_cascade(menu=menu_edit, label='Edit') +menu_file.add_command(label='New', command=foo) + +area = tk.Text(root) +area.grid(column=0, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) + +area_scrolly = ttk.Scrollbar(root, orient=tk.VERTICAL, command=area.yview) +area_scrolly.grid(column=1, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) +area.configure(yscrollcommand=area_scrolly.set) + +canvas = tk.Canvas(root, background='white') +canvas.grid(column=2, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) + +canvas_last_x = 0 +canvas_last_y = 0 + +def canvas_save_pos(event): + global canvas_last_x, canvas_last_y + canvas_last_x = event.x + canvas_last_y = event.y + +def canvas_move(event): + canvas.create_line(canvas_last_x, canvas_last_y, event.x, event.y) + sock.send({"type":"create_line", "x1":canvas_last_x, "y1":canvas_last_y, "x2":event.x, "y2":event.y}, send_addr) + canvas_save_pos(event) + +def canvas_click(event): + print(colorchooser.askcolor(initialcolor='black')) + +canvas.bind("", canvas_save_pos) +canvas.bind("", canvas_move) +canvas.bind("", canvas_click) + +root.rowconfigure(1, weight=1) +root.columnconfigure(0, weight=1) +root.columnconfigure(2, weight=1) + +def on_save_bt(): + path = tk.filedialog.asksaveasfilename(filetypes=[("Fichier texte", ".txt")]) + file = open(path, "w") + file.write(area.get("1.0", "end")) + +toolbar = ttk.Frame(root) +toolbar.grid(column=0, row=0, columnspan=2, sticky=(tk.N, tk.S, tk.E, tk.W)) + +save_bt = ttk.Button(toolbar, text="Enregistrer", command=on_save_bt) +save_bt.grid(column=0, row=0, sticky=(tk.W)) + +open_bt = ttk.Button(toolbar, text="Ouvrir") +open_bt.grid(column=1, row=0, sticky=(tk.W)) def diff(old, new): index = 0 - if len(old) > len(new): - for i in range(len()): - pass + line = 1 + char = 0 + for i in range(min(len(old), len(new))): + if old[i] != new[i]: + return { + "type": "replace", + "start": str(line)+"."+str(char), + "end": 0, + "text": new[i:i+abs(len(old)-len(new))] + } + char += 1 + if old[i] == "\n": + line += 1 + char = 0 + + return { + "type": "insert", + "start": "end", + "text": new[i:i+abs(len(old)-len(new))] + } -def area_input(event): +def on_area_input(event): #print(area.get("1.0", "end")) print(event) - sock.send({"type":"insert", "start":"1.0", "text":area.get("1.0", "end")}, send_addr) + #sock.send({"type":"insert", "start":"1.0", "text":area.get("1.0", "end")}, send_addr) + sock.send({"type":"replace", "start":"1.0", "end":"end", "text":area.get("1.0", "end")}, send_addr) #if event.keycode == 25: # area.mark_set("insert", "1.0") +area.bind("", on_area_input) +area.bind("", on_area_input) + def on_request(r): global waiting_for_text print(r) @@ -41,9 +120,8 @@ def on_request(r): area.replace(c["start"], c["end"], c["text"]) elif c["type"] == "get": sock.send({"type":"set", "text":area.get("1.0", "end")}, send_addr) - area.insert("1.0", r[1]) - -area.bind("", area_input) + elif c["type"] == "create_line": + canvas.create_line(c["x1"], c["y1"], c["x2"], c["y2"]) sock.listen(listen_addr, on_request) diff --git a/makefile b/makefile new file mode 100755 index 0000000..2a9df7a --- /dev/null +++ b/makefile @@ -0,0 +1,7 @@ +all: tutorial.pdf + +tutorial.pdf: tutorial.html + wkhtmltopdf -s A4 --footer-center "[page] / [topage]" --footer-font-name "Libertinus Sans" --footer-font-size 11 --header-left "[subsection]" --header-right "[title]" --header-font-name "Libertinus Sans" --header-font-size 11 --margin-top 20mm --footer-spacing 7 --header-spacing 7 --margin-bottom 20mm tutorial.html tutorial.pdf + +tutorial.html: tutorial.md tutorial-head.html + pandoc tutorial.md -o tutorial.html --self-contained -M title="Tutoriel Tkinter" --highlight-style pygments -H tutorial-head.html || exit 1 diff --git a/skeleton.py b/skeleton.py new file mode 100644 index 0000000..bced63b --- /dev/null +++ b/skeleton.py @@ -0,0 +1,21 @@ +import network +import tkinter as tk +from tkinter import ttk +import sys + +listen_addr = ("0.0.0.0", 4444) +send_addr = ("192.168.1.255", 4444) + +sock = network.Sock() + +root = tk.Tk() + +def on_request(r): + c, a = r + print(c, a) + +sock.listen(listen_addr, on_request) + +sock.send({"message":"bonjour"}, send_addr) + +root.mainloop() diff --git a/tutorial-head.html b/tutorial-head.html new file mode 100644 index 0000000..a2e4e43 --- /dev/null +++ b/tutorial-head.html @@ -0,0 +1,33 @@ + diff --git a/tutorial.html b/tutorial.html new file mode 100644 index 0000000..ff4b48e --- /dev/null +++ b/tutorial.html @@ -0,0 +1,452 @@ + + + + + + + Tutoriel Tkinter + + + + +
+

Tutoriel Tkinter

+
+

Tutoriel Tkinter

+

Pour aller plus loin : TkDocs Tutorial

+

Code de base

+

Créer une fenêtre vide :

+
import tkinter as tk
+from tkinter import ttk
+
+root = tk.Tk()
+
+root.mainloop()
+

Ajouter des boutons dans la fenêtre :

+
import tkinter as tk
+from tkinter import ttk
+
+root = tk.Tk()
+
+bt1 = ttk.Button(root, text="Bonjour")
+bt1.grid(column=0, row=0)
+
+bt2 = ttk.Button(root, text="Au revoir")
+bt2.grid(column=1, row=0)
+
+root.mainloop()
+

Positionnement

+

On peut changer le nombre de colonnes ou de lignes occupées par un +widget en ajoutant les paramètres columnspan ou +rowspan en plus de column et +row.

+

Pour coller un widget au bord de la grille, on peut ajouter +sticky :

+
bt2.grid(column=1, row=0, sticky=(tk.W, tk.N))
+

sticky prend une liste de 0 à 4 éléments parmi :

+
    +
  • tk.E : East = droite
  • +
  • tk.N : North = haut
  • +
  • tk.S : South = bas
  • +
  • tk.W : West = gauche
  • +
+

Redimensionner la fenêtre

+

Il faut indiquer quelles lignes et colonnes doivent changer de taille +quand la fenêtre change de taille.

+

Dans cet exemple, ce sont la ligne 1 et la colonne 0.

+
root.rowconfigure(1, weight=1)
+root.columnconfigure(0, weight=1)
+

On peut appeler plusieurs fois ces fonctions pour choisir plusieurs +lignes et colonnes, et changer les weight pour modifier les +proportions.

+

Widgets

+

Tous les widgets peuvent être positionnés avec grid, de +la même manière que ci-dessus.

+

Label

+

Juste du texte.

+
label = ttk.Label(root, text="Je suis un texte. Wow.")
+

On peut aussi modifier le texte plus tard :

+
label_text = tk.StringVar()
+label["textvariable"] = label_text
+label_text.set("Le nouveau texte. Tout ça pour ça !")
+

Bouton

+

Un bouton qu’on peut cliquer.

+
def onclick():
+    print("On a cliqué")
+
+bt = ttk.Button(root, text="Cliquez-moi", command=onclick)
+

Image

+

Un bouton ou un label peut afficher une image :

+
img = tk.PhotoImage(file='image.png')
+label["image"] = img
+

Entrée de texte

+

Boîte dans laquelle on peut écrire une ligne de texte.

+
text = tk.StringVar()
+entry = ttk.Entry(root, textvariable=text)
+

Plus tard, on peut récupérer le texte :

+
print("L'utilisateur a écrit :", text.get())
+

Entrée de texte multiligne

+

Zone dans laquelle on peut écrire du texte en plusieurs lignes.

+
text = tk.Text(root)
+

Il y a plusieurs méthodes pour interagir avec le texte :

+
print(area.get("1.0", "end")) # obtenir tout le texte
+print(area.get("2.0", "8.0")) # le texte de la ligne 2 à la ligne 8
+area.replace("1.0", "end", "le nouveau texte") # remplacer du texte
+area.insert("1.0", "le nouveau texte") # insérer du texte
+

Barre de défilement

+

Certains widgets (Text) peuvent défiler avec la molette, +mais pour afficher la barre de défilement il faut un widget +supplémentaire.

+
scroll = ttk.Scrollbar(root, orient=tk.VERTICAL, command=mon_widget_qui_défile.yview)
+scroll.grid(column=1, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
+mon_widget_qui_défile.configure(yscrollcommand=scroll.set)
+

Cadre

+

Un cadre qui peut avoir une bordure et contenir d’autres widgets.

+
frame = ttk.Frame(root, borderwidth=5, relief="ridge", width=200, height=100)
+

Les options ne sont pas obligatoires.

+

Le cadre contient ses propres lignes et colonnes. Pour placer des +widgets dedans, il suffit de remplacer root par +frame en les créant.

+

Barre de menus

+

La barre de menus en haut de la fenêtre.

+
root.option_add('*tearOff', False)
+menubar = tk.Menu(root)
+root['menu'] = menubar
+
+menu_file = tk.Menu(menubar)
+menu_edit = tk.Menu(menubar)
+
+menubar.add_cascade(menu=menu_file, label='Fichier')
+menubar.add_cascade(menu=menu_edit, label='Édition')
+
+menu_file.add_command(label='Ouvrir', command=on_open)
+

Canevas

+

Le canevas est une zone de dessin.

+
canvas = tk.Canvas(root, background='white')
+

On peut y dessiner des formes :

+
canvas.create_line(x1, y1, x2, y2)
+canvas.create_line(x1, y1, x2, y2, fill="red", width=3, dash=6)
+canvas.create_rectangle(x1, y1, x2, y2, fill="red", outline="blue")
+canvas.create_oval(x1, y1, x2, y2, fill="red", outline="blue")
+

Événements

+

Quand il se passe quelque chose sur un widget, un événement est +généré. On peut écouter les événements de certains types, c’est-à-dire +lancer une fonction quand l’action se produit.

+

Par exemple, on affiche des informations quand une touche du clavier +est relâchée dans la zone de texte :

+
text = tk.Text(root)
+
+def onrelease(event):
+    print("On a frappé le clavier !")
+    print(event)
+
+area.bind("<KeyRelease>", onrelease)
+

On peut aussi utiliser les infos contenues dans l’événement, par +exemple event.keycode.

+

Liste des événements

+
    +
  • Souris +
      +
    • ButtonPress: clic enfoncé d’un bouton de la souris
    • +
    • Button-1: comme ButtonPress mais seulement +pour le clic gauche
    • +
    • Button-2: comme ButtonPress mais seulement +pour le clic molette
    • +
    • Button-3: comme ButtonPress mais seulement +pour le clic droit
    • +
    • ButtonRelease: clic relâché d’un bouton de la +souris
    • +
    • Double-1: double-clic gauche
    • +
    • Double-3: double-clic droit
    • +
    • Enter: le pointeur de la souris entre
    • +
    • Leave: le pointeur de la souris sort
    • +
    • Motion: le pointeur de la souris bouge dedans
    • +
    • B1-Motion: comme Motion mais avec le clic +gauche enfoncé
    • +
    • B2-Motion: comme Motion mais avec le clic +molette enfoncé
    • +
    • B3-Motion: comme Motion mais avec le clic +droit enfoncé
    • +
  • +
  • Clavier +
      +
    • KeyPress: appui d’une touche du clavier
    • +
    • KeyRelease: relâche d’une touche du clavier
    • +
  • +
  • Widget +
      +
    • Configure: le widget est créé
    • +
    • Expose: le widget est affiché ou réaffiché
    • +
    • FocusIn: le widget reçoit le focus
    • +
    • FocusOut: le widget pert le focus
    • +
  • +
+

Dialogues

+

On peut ouvrir des fenêtres pour dire ou demander des choses.

+

Fichiers

+

Demander où enregistrer un fichier, quel fichier ouvrir, choisir un +dossier.

+
from tkinter import filedialog
+
+filetypes = [
+    ("Texte", "*.txt"),
+    ("Image PNG", "*.png"),
+    ("Autre", "*.*")
+]
+chemin = tk.filedialog.asksaveasfilename(filetypes=filetypes)
+print(chemin)
+

On peut remplacer asksaveasfilename par +askopenfilename ou askdirectory.

+

Couleur

+

Demander de choisir une couleur.

+
from tkinter import colorchooser
+
+color = colorchooser.askcolor(initialcolor='black')
+print(color)
+

On peut aussi indiquer la couleur initiale avec sa notation +hexadécimale "#000000".

+ + diff --git a/tutorial.md b/tutorial.md new file mode 100644 index 0000000..e640efb --- /dev/null +++ b/tutorial.md @@ -0,0 +1,272 @@ +# Tutoriel Tkinter + +Pour aller plus loin : [TkDocs Tutorial](https://tkdocs.com/tutorial/index.html) + +## Code de base + +Créer une fenêtre vide : + +```python +import tkinter as tk +from tkinter import ttk + +root = tk.Tk() + +root.mainloop() +``` + +Ajouter des boutons dans la fenêtre : + +```python +import tkinter as tk +from tkinter import ttk + +root = tk.Tk() + +bt1 = ttk.Button(root, text="Bonjour") +bt1.grid(column=0, row=0) + +bt2 = ttk.Button(root, text="Au revoir") +bt2.grid(column=1, row=0) + +root.mainloop() +``` + +### Positionnement + +On peut changer le nombre de colonnes ou de lignes occupées par un widget en ajoutant les paramètres `columnspan` ou `rowspan` en plus de `column` et `row`. + +Pour coller un widget au bord de la grille, on peut ajouter `sticky` : + +```python +bt2.grid(column=1, row=0, sticky=(tk.W, tk.N)) +``` + +`sticky` prend une liste de 0 à 4 éléments parmi : + +* `tk.E` : _East_ = droite +* `tk.N` : _North_ = haut +* `tk.S` : _South_ = bas +* `tk.W` : _West_ = gauche + +### Redimensionner la fenêtre + +Il faut indiquer quelles lignes et colonnes doivent changer de taille quand la fenêtre change de taille. + +Dans cet exemple, ce sont la ligne 1 et la colonne 0. + +```python +root.rowconfigure(1, weight=1) +root.columnconfigure(0, weight=1) +``` + +On peut appeler plusieurs fois ces fonctions pour choisir plusieurs lignes et colonnes, et changer les `weight` pour modifier les proportions. + +## Widgets + +Tous les widgets peuvent être positionnés avec `grid`, de la même manière que ci-dessus. + +### Label + +Juste du texte. + +```python +label = ttk.Label(root, text="Je suis un texte. Wow.") +``` + +On peut aussi modifier le texte plus tard : + +```python +label_text = tk.StringVar() +label["textvariable"] = label_text +label_text.set("Le nouveau texte. Tout ça pour ça !") +``` + +### Bouton + +Un bouton qu'on peut cliquer. + +```python +def onclick(): + print("On a cliqué") + +bt = ttk.Button(root, text="Cliquez-moi", command=onclick) +``` + +### Image + +Un bouton ou un label peut afficher une image : + +```python +img = tk.PhotoImage(file='image.png') +label["image"] = img +``` + +### Entrée de texte + +Boîte dans laquelle on peut écrire une ligne de texte. + +```python +text = tk.StringVar() +entry = ttk.Entry(root, textvariable=text) +``` + +Plus tard, on peut récupérer le texte : + +```python +print("L'utilisateur a écrit :", text.get()) +``` + +### Entrée de texte multiligne + +Zone dans laquelle on peut écrire du texte en plusieurs lignes. + +```python +text = tk.Text(root) +``` + +Il y a plusieurs méthodes pour interagir avec le texte : + +```python +print(area.get("1.0", "end")) # obtenir tout le texte +print(area.get("2.0", "8.0")) # le texte de la ligne 2 à la ligne 8 +area.replace("1.0", "end", "le nouveau texte") # remplacer du texte +area.insert("1.0", "le nouveau texte") # insérer du texte +``` + +### Barre de défilement + +Certains widgets (`Text`) peuvent défiler avec la molette, mais pour afficher la barre de défilement il faut un widget supplémentaire. + +```python +scroll = ttk.Scrollbar(root, orient=tk.VERTICAL, command=mon_widget_qui_défile.yview) +scroll.grid(column=1, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) +mon_widget_qui_défile.configure(yscrollcommand=scroll.set) +``` + +### Cadre + +Un cadre qui peut avoir une bordure et contenir d'autres widgets. + +```python +frame = ttk.Frame(root, borderwidth=5, relief="ridge", width=200, height=100) +``` + +Les options ne sont pas obligatoires. + +Le cadre contient ses propres lignes et colonnes. +Pour placer des widgets dedans, il suffit de remplacer `root` par `frame` en les créant. + +### Barre de menus + +La barre de menus en haut de la fenêtre. + +```python +root.option_add('*tearOff', False) +menubar = tk.Menu(root) +root['menu'] = menubar + +menu_file = tk.Menu(menubar) +menu_edit = tk.Menu(menubar) + +menubar.add_cascade(menu=menu_file, label='Fichier') +menubar.add_cascade(menu=menu_edit, label='Édition') + +menu_file.add_command(label='Ouvrir', command=on_open) +``` + +### Canevas + +Le canevas est une zone de dessin. + +```python +canvas = tk.Canvas(root, background='white') +``` + +On peut y dessiner des formes : + +```python +canvas.create_line(x1, y1, x2, y2) +canvas.create_line(x1, y1, x2, y2, fill="red", width=3, dash=6) +canvas.create_rectangle(x1, y1, x2, y2, fill="red", outline="blue") +canvas.create_oval(x1, y1, x2, y2, fill="red", outline="blue") +``` + +## Événements + +Quand il se passe quelque chose sur un widget, un événement est généré. +On peut écouter les événements de certains types, c'est-à-dire lancer une fonction quand l'action se produit. + +Par exemple, on affiche des informations quand une touche du clavier est relâchée dans la zone de texte : + +```python +text = tk.Text(root) + +def onrelease(event): + print("On a frappé le clavier !") + print(event) + +area.bind("", onrelease) +``` + +On peut aussi utiliser les infos contenues dans l'événement, par exemple `event.keycode`. + +### Liste des événements + +* Souris + * `ButtonPress`: clic enfoncé d'un bouton de la souris + * `Button-1`: comme `ButtonPress` mais seulement pour le clic gauche + * `Button-2`: comme `ButtonPress` mais seulement pour le clic molette + * `Button-3`: comme `ButtonPress` mais seulement pour le clic droit + * `ButtonRelease`: clic relâché d'un bouton de la souris + * `Double-1`: double-clic gauche + * `Double-3`: double-clic droit + * `Enter`: le pointeur de la souris entre + * `Leave`: le pointeur de la souris sort + * `Motion`: le pointeur de la souris bouge dedans + * `B1-Motion`: comme `Motion` mais avec le clic gauche enfoncé + * `B2-Motion`: comme `Motion` mais avec le clic molette enfoncé + * `B3-Motion`: comme `Motion` mais avec le clic droit enfoncé +* Clavier + * `KeyPress`: appui d'une touche du clavier + * `KeyRelease`: relâche d'une touche du clavier +* Widget + * `Configure`: le widget est créé + * `Expose`: le widget est affiché ou réaffiché + * `FocusIn`: le widget reçoit le focus + * `FocusOut`: le widget pert le focus + +## Dialogues + +On peut ouvrir des fenêtres pour dire ou demander des choses. + +### Fichiers + +Demander où enregistrer un fichier, quel fichier ouvrir, choisir un dossier. + +```python +from tkinter import filedialog + +filetypes = [ + ("Texte", "*.txt"), + ("Image PNG", "*.png"), + ("Autre", "*.*") +] +chemin = tk.filedialog.asksaveasfilename(filetypes=filetypes) +print(chemin) +``` + +On peut remplacer `asksaveasfilename` par `askopenfilename` ou `askdirectory`. + +### Couleur + +Demander de choisir une couleur. + +```python +from tkinter import colorchooser + +color = colorchooser.askcolor(initialcolor='black') +print(color) +``` + +On peut aussi indiquer la couleur initiale avec sa notation hexadécimale `"#000000"`. diff --git a/tutorial.pdf b/tutorial.pdf new file mode 100644 index 0000000..0d1e952 Binary files /dev/null and b/tutorial.pdf differ