Python Tkinter GUI - Cerca file nella directory per istanze di una stringa

Oct 15 2020

Questo script apre un modulo di base che consente di cercare in tutti i file in una directory istanze di una stringa, prima di inviare i risultati a una casella di testo e CSV (che si trova nella posizione dello script). Ogni riga emessa ha il formato:

Parola {stringa} in {file} sulla riga {numero riga} : {riga completa}

Passaggi:

  1. Specifica directory
  2. Stringa di input che desideri cercare
  3. Attiva o disattiva ignora maiuscole / minuscole
  4. Fare clic su Vai.

Appunti:

Questo non è ancora completamente finito. Lo script cercherà solo i file TXT di base. Sono anche un principiante con Tkinter / GUI, quindi non ho spostato i pulsanti, ecc. Ho appena aggiunto i pulsanti e le etichette uno dopo l'altro. Questo alla fine verrà risolto. C'è anche un problema per cui se cerco una grande directory di file, il modulo sembrerà bloccato fino al termine della ricerca. Infine, potrei aver esagerato con la gestione degli errori. Anch'io sono un novellino.

Sto cercando di essere un programmatore migliore, in particolare con la strutturazione e la leggibilità, quindi qualsiasi critica costruttiva sarebbe molto apprezzata.

from tkinter import filedialog

from tkinter.scrolledtext import ScrolledText

import pandas as pd

import tkinter as tk

import os

import re

import sys

################ FUNCTIONS ################

def save_to_file(wordlist):
    """Save list to CSV format and save CSV to script directory"""
    script_directory = os.path.dirname(sys.argv[0]) # Path where script is being run from
    df = pd.DataFrame(data={"Results": wordlist})
    df.to_csv(script_directory+"/mycsv.csv", sep=",", index=False, line_terminator='\n')
    
def print_to_textbox(wordlist):
    """Print all lines in wordlist to textbox"""
    for lines in wordlist:
        text_box.insert("end", "\n"+lines)
    if len(wordlist) == 0:
        text_box.insert("1.0", "\nNothing To Display")

def browse_button():
    """Button will open a window for directory selection"""
    global folder_path
    selected_directory = filedialog.askdirectory()
    folder_path.set(selected_directory)
    
def search_files():
    """Search all files in specified directory"""
    folderPath = folder_path.get()
    searchString = string_entry.get()
    text_box.delete("1.0", tk.END)
    
    # Set word case option on/off.
    if var1.get() == 1:
        IGNOREWORDCASE = True
    else:
        IGNOREWORDCASE = False
        
    # List to store all lines where string is found.
    wordlist = []  
    
    # Loop through all files and search for string, line by line.
    for (path, directories, files) in os.walk(folderPath, topdown=True):
        for file in files:
            filepath = os.path.join(path, file)
            
            try: 
                
                with open(filepath, 'r') as currentfile:          
                        for lineNum, line in enumerate(currentfile, 1):  
                            line = line.strip()
                            match = re.search(searchString, line, re.IGNORECASE) if IGNOREWORDCASE else re.search(searchString, line)
                            if match:
                                word = f"Word '{searchString}' in '{file}' on line {lineNum}: {line}"
                                wordlist.append(word)
                                
            except IOError as ex:
                words = f"Error; {file}; {ex}"
                wordlist.insert(0, words)
                
            except EnvironmentError as ex:
                words = f"Error; {file}; {ex}"
                wordlist.insert(0, words)
                
            except OSError as ex:
                words = f"Error; {file}; {ex}"
                wordlist.insert(0, words)
                
            except UnicodeDecodeError as ex:
                words = f"Error; {file}; {ex}"
                wordlist.insert(0, words)
                
            except:
                words = f"Error; {file}"
                wordlist.insert(0, words)

    # Print all lines to text box.
    print_to_textbox(wordlist)
    
    # Save to file.
    save_to_file(wordlist)
                
################ TKINTER SCRIPT ################

# Setup Window.
window = tk.Tk()
window.geometry("900x500")
window.title("String Search")

# Button to select directory.
select_directory = tk.Button(window, text = "Select Directory", command=browse_button)
select_directory.pack()

# Label to store chosen directory.
folder_path = tk.StringVar()
directory_label = tk.Label(window, textvariable = folder_path, bg="#D3D3D3", width=70)
directory_label.pack()

# Entry to type search string.
string_entry = tk.Entry(window, bg="#D3D3D3")
string_entry.pack()

# Check button to turn ignore case on/off.
var1 = tk.IntVar()
stringCase_select = tk.Checkbutton(window, text='Ignore Case',variable=var1, onvalue=1, offvalue=0)
stringCase_select.pack()

# Button to run main script.
go_button = tk.Button(window, text="Go", command=search_files)
go_button.pack()

# Button to quit the app.
quit_button = tk.Button(window, text = "Quit", command=window.quit)
quit_button.pack()

# Text box to display output of main text.
text_box = ScrolledText(width=110, borderwidth=2, relief="sunken", padx=20)
text_box.pack()

# Button to clear the text box display.
clear_button = tk.Button(window, text = "Clear", command = lambda: text_box.delete("1.0", tk.END))
clear_button.pack()

# Run an event loop.
window.mainloop()

Risposte

4 AryanParekh Oct 15 2020 at 01:06

PEP-8

Per mantenere la coerenza durante la scrittura del codice, il codice Python segue la convenzione di denominazione PEP-8 . Per esempio

fonte

I nomi delle funzioni devono essere minuscoli, con le parole separate da trattini bassi se necessario per migliorare la leggibilità.

I nomi delle variabili seguono la stessa convenzione dei nomi delle funzioni.

La libreria attualmente in uso, che tkintersegue anche la stessa convenzione di denominazione. Si può vedere che le classi piace Button, Canvase StringVar(). Tutti seguono il CamelCasedato che sono classi.

Posizionamento dei widget nella tua applicazione

Sebbene .pack()funzioni, vedrai presto i suoi limiti quando inizi a provare a posizionare i widget in punti specifici, ad esempio un pulsante di uscita in genere rimane nell'angolo, Un titolo di solito è posizionato in alto. In questi scenari .pack()è solo molto limitato.

Un modo comune è quello di utilizzare .place()per posizionare i widget nella tua applicazione. Ha molti argomenti come bordermodee anchorper personalizzare il tuo compito, i due principali sono xe yche sono fondamentalmente solo i punti orizzontali e verticali in cui verrà posizionato il tuo widget.

Ecco una semplice finestra di Tkinter geometry("500x500")che ho creato per mostrare l'utilizzo di place(). Ha anche Labelun semplice widget.

Esempio: widget.place(x = 300,y = 50)

Puoi decidere dove posizionare accuratamente i tuoi widget.

Strutturare Tkun'applicazione

@ Reinderien ha suggerito il tuo TKINTER_SCRIPTin una funzione. Questo ha senso perché hai già buone funzioni come search_files(), perché dovresti avere la tua applicazione Tkinter nell'ambito principale?

Mentre alcuni potrebbero non essere d'accordo, il mio suggerimento è di optare per un approccio orientato agli oggetti , che aiuterà a mantenere pulito il codice. Nella tua situazione, ha senso avere una sola MainApplication()classe. Ecco un semplice esempio di come sarebbe

class MainApplication:
    def __init__(self,window,size = "500x500"):
        self.window = window
        window.geometry(size)
        self.entry_box = # Entry box widget
        self.title = # Title label 
        # More widgets that are relevant to MainApplication

    def close_application():
        # print any messages on window like "Thank you for using..."
        self.window.close()

root = tk.Tk()
window = MainApplication(root)

root.mainloop()

Strutturare un'applicazione Tkinter

Piccoli suggerimenti

  • Usa root.iconbitmap( #icon image )per impostare 16x16un'icona per la tua applicazione tkinter, questa apparirà al posto della piccola piuma che arriva
  • Tkinterha una grande varietà di widget, è possibile che tu possa trovarne uno nuovo da questo elenco di widget principali che potrebbe essere perfetto per la tua applicazione.
  • le finestre di messaggio in tkinter saranno adatte per quando un utente potrebbe fare qualcosa di sbagliato, o forse vuoi ottenere una conferma.

Esempio di una finestra di messaggio


2 Reinderien Oct 14 2020 at 23:26

Prendi in considerazione l'utilizzo di suggerimenti di tipo PEP-484 per le firme delle funzioni.

Utilizzare un linter simile pyflakes, flake8o blackche vi darà una serie di suggerimenti circa il formato di codice.

Spostati TKINTER SCRIPTin una o più funzioni, invece che nell'ambito globale.

Usa pathlib, in modo che questo:

script_directory+"/mycsv.csv

può essere

Path(script_directory) / 'mycsv.csv'

Se var1.get()restituisce 0/1, puoi semplicemente scrivere

IGNORE_WORD_CASE = bool(var1.get())

anche se dovresti dare var1un nome più significativo.