Thuban : Un GUI en python : TP 4, l'apparence du GUI
mercredi 1 février 2017 à 09:08Jusqu'à présent, nous ne nous sommes pas occupés de l'apparence de notre application. Et vous l'avez sans doute remarqué, ce n'est pas très joli.
On peut heureusement améliorer légèrement les choses avec la bibliothèque ttk
.
On ajoute alors ceci au début de notre code :
from tkinter import ttk
Nous allons ainsi pouvoir utiliser des widgets améliorés. Pour cela, rien de compliqué, on a juste à ajouter ttk
devant. Ainsi, Button
devient ttk.Button
.
Pour activer un autre thème, il faut insérer ce bout de code pour choisir entre "clam", "alt", "default" et "classic" :
#('clam', 'alt', 'default', 'classic')
style = ttk.Style()
style.theme_use("clam")
Vous pouvez voir ci-dessous ce que ça change avec les interfaces avant/après côte à côte.
Notez que l'on peut aller encore plus loin en modifiant les couleurs, polices et bordures. Cela devient vite compliqué, mais c'est faisable. Par exemple :
theme = {
'disabledfg':"#eeeeee",
'dark': "#777777",
'darker': "#333333",
'darkest': "#777777",
'lighter': "#777777",
'lightest': "#ffffff",
'selectbg': "#41B1FF",
'selectfg': "#ffffff",
'foreground': "#111111",
'background': "#dddddd",
'borderwidth': 1,
'font': ("Droid Sans", 10)
}
style.configure(".", padding=5, relief="flat",
background=theme['background'],
foreground=theme['foreground'],
bordercolor=theme['darker'],
indicatorcolor=theme['selectbg'],
focuscolor=theme['selectbg'],
darkcolor=theme['dark'],
lightcolor=theme['lighter'],
troughcolor=theme['darker'],
selectbackground=theme['selectbg'],
selectforeground=theme['selectfg'],
selectborderwidth=theme['borderwidth'],
font=theme['font']
)
style.map(".",
foreground=[('pressed', theme['darkest']), ('active', theme['selectfg'])],
background=[('pressed', '!disabled', 'black'), ('active', theme['lighter'])]
)
style.configure("TButton", relief="flat")
style.map("TButton",
background=[('disabled', theme['disabledfg']), ('pressed', theme['selectbg']), ('active', theme['selectbg'])],
foreground=[('disabled', theme['disabledfg']), ('pressed', theme['selectfg']), ('active', theme['selectfg'])],
bordercolor=[('alternate', theme['selectbg'])],
)
Ça ressemble maintenant à ça :
J'ai tenté de rassembler les couleurs ensemble au cas où vous voudriez bricoler. Afin d'aller plus loin, une bonne idée est d'aller fouiller dans le fichier de thème par défaut. Pour moi, il s'agit de /usr/local/lib/tcl/tk8.5/ttk/clamTheme.tcl
.
Même si ce n'est pas notre priorité, on voit qu'il est possible de faire un peu tout ce que l'on veut au niveau de l'apparence.
Ça sera tout pour aujourd'hui. La prochaine fois, on parlera des évènements afin de contrôler la visionneuse au clavier.
Et voici le code final (avec un petit bug pour le défilement d'images corrigé).
#!/usr/bin/env python3.4
# -*- coding:Utf-8 -*-
import os
import sys
import mimetypes
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
from PIL import Image, ImageTk
img_extensions = ('.png', '.PNG', '.jpg', '.JPG', '.gif', '.GIF')
### Fonctions ###
def pick_img():
img_path = filedialog.askopenfilename(\\
initialdir=(os.path.expanduser("~")),\\
filetypes=[('Images', img_extensions), ('Tout', '.*')],\\
title="Image à ouvrir",\\
parent=w)
return img_path
def open_img(img_container, img_path):
# Ouverture de l'image
image = Image.open(img_path)
# Dimensions de l'écran :
gap = 100 # marge par rapport aux bords de l'écran
screen_width = w.winfo_screenwidth() - gap
screen_height = w.winfo_screenheight() - gap
if image.width > screen_width :
image = image.resize((screen_width, int(image.height * screen_width / image.width)), Image.ANTIALIAS)
if image.height > screen_height :
image = image.resize((int(image.width * screen_height / image.height), screen_height), Image.ANTIALIAS)
# Chargement de l'image en mémoire
img = ImageTk.PhotoImage(image)
# On met l'image dans le conteneur
img_container.configure(image = img)
# On s'assure que l'image sera bien gardée en mémoire
img_container.image = img
# Ainsi que son emplacement
img_container.path = img_path
def chg_img(img_container):
# change l'image affichée
i = pick_img()
if i: # On a bien choisi une image
open_img(img_container,i)
def defile_img(img_container, sens):
"""
On fait défiler les images dans un sens ou dans l'autre
sens == "prev" : précédent,
sens == "next" : suivant,
On a besoin de passer le conteneur de l'image
en argument pour retrouver l'emplacement de l'image courante.
"""
# Emplacement de l'image actuelle :
cur_path = img_container.path
# Dossier de l'image actuelle :
d = os.path.dirname(cur_path)
# Liste des images
l = os.listdir(d)
# on ne garde que les images
# "{}/{}".format(d,i) : on met le chemin complet vers l'image
# for i in l :pour toutes les images dans la liste l
# if os.path.splitext(i)[1] in img_extensions :
# si l'extension de l'image est dans la liste des extensions
img_list = [ "{}/{}".format(d,i) for i in l if os.path.splitext(i)[1] in img_extensions ]
# On met dans l'ordre
img_list = sorted(img_list)
# On ne fait tourner que si il y a plusieurs images
if len(img_list) > 1:
# on retrouve la position de l'image actuelle
pos = img_list.index(cur_path)
if sens == "next":
newpos = pos + 1
if newpos > len(img_list) -1: # fin de liste
newpos = 0
elif sens == "prev":
newpos = pos - 1 # début de liste
if newpos < 0:
newpos = len(img_list) - 1
open_img(img_container, img_list[newpos])
def change_colors(style):
theme = {
'disabledfg':"#eeeeee",
'dark': "#777777",
'darker': "#333333",
'darkest': "#777777",
'lighter': "#777777",
'lightest': "#ffffff",
'selectbg': "#41B1FF",
'selectfg': "#ffffff",
'foreground': "#111111",
'background': "#dddddd",
'borderwidth': 1,
'font': ("Droid Sans", 10)
}
style.configure(".", padding=5, relief="flat",
background=theme['background'],
foreground=theme['foreground'],
bordercolor=theme['darker'],
indicatorcolor=theme['selectbg'],
focuscolor=theme['selectbg'],
darkcolor=theme['dark'],
lightcolor=theme['lighter'],
troughcolor=theme['darker'],
selectbackground=theme['selectbg'],
selectforeground=theme['selectfg'],
selectborderwidth=theme['borderwidth'],
font=theme['font']
)
style.map(".",
foreground=[('pressed', theme['darkest']), ('active', theme['selectfg'])],
background=[('pressed', '!disabled', 'black'), ('active', theme['lighter'])]
)
style.configure("TButton", relief="flat")
style.map("TButton",
background=[('disabled', theme['disabledfg']), ('pressed', theme['selectbg']), ('active', theme['selectbg'])],
foreground=[('disabled', theme['disabledfg']), ('pressed', theme['selectfg']), ('active', theme['selectfg'])],
bordercolor=[('alternate', theme['selectbg'])],
)
### tkv ###
# Notre fenêtre principale
w = Tk()
w.title("tkv : visionneuse d'images") # Un titre
w.configure(background='#000000') # Fond noir
#('clam', 'alt', 'default', 'classic')
style = ttk.Style()
style.theme_use("clam")
change_colors(style)
# Un conteneur dans la fenêtre
mainframe = ttk.Frame(w)
mainframe.pack(fill=BOTH,expand=True, padx=15, pady=15)
# Ouverture de l'image
img_path=""
if len(sys.argv) == 2:
# On a une image en agument
img_path = sys.argv[1]
if not os.path.isfile(img_path):
# On va chercher une image sur le disque
img_path = pick_img()
if not img_path: # L'utilisateur n'a choisi aucune image, on quitte
sys.exit(0)
# Est-ce un fichier valide ?
mimtyp = mimetypes.guess_type(img_path)[0] # i.e 'image/jpeg'
if not mimtyp or "image" not in mimtyp :
# Il n'y a pas le mot "image" dans le mimetype
from tkinter import messagebox
messagebox.showerror("Fichier invalide", "Le fichier demandé n'est pas une image.")
sys.exit(1)
# Conteneur de l'image
img_widget = ttk.Label(mainframe)
img_widget.pack()
# Insertion de l'image dans le conteneur.
open_img(img_widget, img_path)
# Frame contenant les boutons en bas de la fenêtre :
btnbox = ttk.Frame(mainframe)
btnbox.pack()
# Bouton ouvrir
b_open = ttk.Button(btnbox, text="Ouvrir", width=12, command=lambda: chg_img(img_widget))
# Boutons suivant et précédent
b_next = ttk.Button(btnbox, text="Suivant →", width=12, command=lambda: defile_img(img_widget, "next"))
b_prev = ttk.Button(btnbox, text="← Précédent", width=12, command=lambda: defile_img(img_widget, "prev"))
# On affiche les boutons dans la boîte les uns à côté des autres
b_open.pack(side=LEFT)
b_prev.pack(side=LEFT)
b_next.pack(side=LEFT)
# Démarrage du programme
w.mainloop()
sys.exit(0)
Original post of Thuban.Votez pour ce billet sur Planet Libre.
Articles similaires
- Thuban : Programmer un GUI en python -- Pourquoi TkInter ? (04/01/2017)
- Thuban : Un GUI en python : TP 1, une visionneuse d'images (11/01/2017)
- Thuban : Un GUI en python : TP 2, des dialogues (18/01/2017)
- Thuban : Un GUI en python : TP 3, la guerre des boutons (25/01/2017)