PROJET AUTOBLOG


Planet-Libre

source: Planet-Libre

⇐ retour index

Thuban : Un GUI en python : TP 4, l'apparence du GUI

mercredi 1 février 2017 à 09:08

Jusqu'à 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)

Gravatar de Thuban
Original post of Thuban.Votez pour ce billet sur Planet Libre.