2. Come Creare un Editor Testuale con Python Pt.1

In questa lezione impareremo come creare un editor testuale utilizzando il modulo Tkinter della Standard Library di Python. Il nostro editor sarà composto da tre parti principali: un pannello nella parte superiore della schermata, una status bar nella parte inferiore e un’area centrale in cui si potrà scrivere il testo. Il programma sarà in grado di aprire, salvare e creare nuovi file di testo.

Potete trovare il codice completo di questa lezione nel repository di GitHub.


Creazione della finestra principale

Cominciamo importando il modulo tkinter e creando la prima delle tre classi che comporranno le parti del nostro editor: PyText. Aggiungiamo anche l’istruzione per avviare la finestra principale del programma come spiegato nella lezione precedente, chiamiamo la finestra master e passiamola al dunder init nella nostra nuova classe.

import tkinter as tk


class PyText:

    def __init__(self, master):
        pass


if __name__ == "__main__":
    master = tk.Tk()
    pt = PyText(master)
    master.mainloop()

Definiamo quindi le proprietà della nostra finestra master cominciando con il titolo e la geometria e inizializziamo anche master in modo che i metodi di PyText possano accedervi.

import tkinter as tk

class PyText:

    def __init__(self, master):
        master.title("Untitled - PyText")
        master.geometry("1200x700")

        self.master = master


Come aggiungere l'area in cui scrivere il testo e una scrollbar

Aggiungiamo alla finestra anche il widget che rappresenta il fulcro del nostro editor: quello di tipo Text. Passiamogli master come parametro e aggiungiamolo anche all’inizializzatore init in modo da poterlo utilizzare tramite tutti i metodi di PyText.

class PyText:

    def __init__(self, master):
	    # ...

        self.textarea = tk.Text(master, font=font_specs)

Il nostro editor dovrà essere in grado di gestire anche testi molto lunghi, perciò abbiamo bisogno che l’area Text sia scrollabile: aggiungiamo un widget di tipo Scrollbar e indichiamo che lo scroll della nostra area di testo deve essere verticale (lo scroll deve avvenire quindi lungo l’asse y) passandogli come parametro command=self.textarea.yview.

self.scroll = tk.Scrollbar(master, command=self.textarea.yview)

Impostiamo anche textarea per ospitare la scrollbar:

self.textarea.configure(yscrollcommand=self.scroll.set)

Nella lezione precedente abbiamo definito le posizioni dei nostri widget nella finestra utilizzando la geometria a griglia tramite grid; per creare il nostro editor introduciamo invece il metodo pack che ci permette di ordinare i widget gli uni rispetto agli altri.

Impostiamo il parametro side passato al metodo pack per il widget di tipo Text su LEFT per posizionare l’area di testo nella parte sinistra della finestra. Mentre per impostare la scrollbar a destra passiamo il parametro RIGHT:

self.textarea.pack(side=tk.LEFT)
self.scroll.pack(side=tk.RIGHT)

Se eseguiamo il nostro codice vediamo che i widget non riempiono tutto lo spazio disponibile.

Aggiungiamo al metodo pack il parametro fill per specificare l’orientamento che i widget devono seguire per riempire lo spazio. Impostiamo fill su tk.BOTH per il widget di testo in modo da indicargli che deve riempire tutto lo spazio disponibile sia lungo l’asse x che lungo l’asse y e assegniamo il valore True al parametro expand. Impostiamo poi fill su tk.Y per il widget della scrollbar per far si che si estenda in verticale:

self.textarea.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scroll.pack(side=tk.RIGHT, fill=tk.Y)

Possiamo anche specificare il font e la dimensione del testo aggiungendo una tupla al costruttore init e passandola come parametro al nostro widget:

font_specs = ("ubuntu", 18)
self.textarea = tk.Text(master, font=font_specs)

Il nostro codice a questo punto sarà il seguente:

import tkinter as tk

class PyText:

    def __init__(self, master):
        master.title("Untitled - PyText")
        master.geometry("1200x700")
        font_specs = ("ubuntu", 18)
        self.master = master
        self.textarea = tk.Text(master, font=font_specs)
        self.scroll = tk.Scrollbar(master, command=self.textarea.yview)
        self.textarea.configure(yscrollcommand=self.scroll.set)
        self.textarea.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.scroll.pack(side=tk.RIGHT, fill=tk.Y)

if __name__ == "__main__":
    master = tk.Tk()
    pt = PyText(master)
    master.mainloop()


Come creare il Menu a tendina

Se eseguiamo lo script possiamo vedere che la nostra area di testo scrollabile è pronta. Iniziamo quindi a creare il Menu aggiungendo una nuova classe e passando al suo costruttore init argomento parent come argomento in modo che sia possibile permettere al Menu di accedere all’area testuale di PyText.

class Menubar:

    def __init__(self, parent):
        pass


class Pytext:

    def __init__(self, master):
        
        # ...

        self.menubar = Menubar(self)

Creiamo quindi un nuovo widget di tipo Menu all’interno della classe omonima e diamole come argomento parent.master per creare il widget e permettergi di controllare PyText. Specifichiamo anche il font assegnandogli una grandezza leggermente inferiore a quella dell’area testuale.

class Menubar:

    def __init__(self, parent):
        font_specs = ("ubuntu", 14)

        menubar = tk.Menu(parent.master, font=font_specs)
        parent.master.config(menu=menubar)


Come creare i sottomenù della voce “File” del menu

Come ogni menù che si rispetti anche il nostro dovrà avere una tendina con un sottomenù. Aggiungiamo innanzitutto il menù “File”, che ci permetterà di creare, aprire e salvare i file. Assegniamo ad una nuova variabile chiamata file_dropdown un widget di tipo Menu e passiamogli come argomento il Menu che lo contiene (ovvero menubar) e lo stile del font che abbiamo specificato tramite font_specs.

class Menubar:

    def __init__(self, parent):
        # ...

        file_dropdown = tk.Menu(menubar, font=font_specs)

Aggiungiamo un comando al nostro sottomenù che ci permetterà di creare un nuovo file assegnando al parametro command il metodo new_file che creeremo tra poco:

file_dropdown.add_command(label="Nuovo File",
                          command=parent.new_file)

Per mostrare il nostro menù dropdown File contenente il comando “Nuovo File”, assegniamo al parametro menu il widget file_dropdown e utilizziamo il metodo add_cascade del widget Menu, che ci consente di creare un sottomenu che si apre quando si seleziona una voce di un altro menu.

menubar.add_cascade(label="File", menu=file_dropdown)

Adesso possiamo cliccare sopra il menu “File” e vedremo il comando “Nuovo File”.

La linea tratteggiata sulla cima del sottomenù permette di staccarlo dalla barra in alto, per evitare questo comportamento dobbiamo aggiungere il parametro tearoff al Menu impostarlo su 0.

file_dropdown = tk.Menu(menubar, font=font_specs, tearoff=0)

Torniamo a PyText e creiamo tutti i metodi che andremo ad implementare nel Menu tramite i comandi del sottomenu, incluso new_file.

class PyText:

    def __init__(self, master):
        # ...

    def set_window_title(self):
        pass

    def new_file(self):
        pass

    def open_file(self):
        pass

    def save(self):
        pass

    def save_as(self):
        pass

Aggiungiamo anche gli altri comandi alla classe Menubar:

class Menubar:

    def __init__(self, parent):

        # ...

        file_dropdown.add_command(label="Nuovo File",
                                  command=parent.new_file)
        file_dropdown.add_command(label="Apri File",
                                  command=parent.open_file)
        file_dropdown.add_command(label="Salva",
                                  command=parent.save)
        file_dropdown.add_command(label="Salva con Nome",
                                  command=parent.save_as)

Aggiungiamo anche una linea di separazione e il comando Esci che chiuderà la finestra tramite il metodo destroy di master:

file_dropdown.add_separator()
file_dropdown.add_command(label="Esci",
                                                 command=parent.master.destroy)

Se eseguiamo il codice dovremmo ritrovarci in questa situazione:

screenshot_editor_tk01

Ed ecco il codice completo:

import tkinter as tk


class Menubar:

    def __init__(self, parent):
        font_specs = ("ubuntu", 14)

        menubar = tk.Menu(parent.master, font=font_specs)
        parent.master.config(menu=menubar)

        file_dropdown = tk.Menu(menubar, font=font_specs, tearoff=0)
        file_dropdown.add_command(label="Nuovo File",
                                  command=parent.new_file)
        file_dropdown.add_command(label="Apri File",
                                  command=parent.open_file)
        file_dropdown.add_command(label="Salva",
                                  command=parent.save)
        file_dropdown.add_command(label="Salva con Nome",
                                  command=parent.save_as)
        file_dropdown.add_separator()
        file_dropdown.add_command(label="Esci",
                                  command=parent.master.destroy)

        menubar.add_cascade(label="File", menu=file_dropdown)


class PyText:

    def __init__(self, master):
        master.title("Untitled - PyText")
        master.geometry("1200x700")

        font_specs = ("ubuntu", 18)

        self.master = master

        self.textarea = tk.Text(master, font=font_specs)
        self.scroll = tk.Scrollbar(master, command=self.textarea.yview)
        self.textarea.configure(yscrollcommand=self.scroll.set)
        self.textarea.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.scroll.pack(side=tk.RIGHT, fill=tk.Y)

        self.menubar = Menubar(self)

    def set_window_title(self):
        pass

    def new_file(self):
        pass

    def open_file(self):
        pass

    def save(self):
        pass

    def save_as(self):
        pass

    

if __name__ == "__main__":
    master = tk.Tk()
    pt = PyText(master)
    master.mainloop()