So I am trying to do a program in python using customtkinter and I have to start a command using a button from the GUI I have created and once it is started i would like to terminate it using the stop button also from GUI. I have divided my my work into 2 different files app.py where I keep the design and a method.py where I keep the methods that are run when the buttons are pressed. The command i ran it is a terminal command similar to the ping command that I have done here below. The files below if someone wants to try or get a better view on my code
In app.py:
import customtkinter as ctk
import methods as mtd
# System Defaults
ctk.set_appearance_mode('Dark')
ctk.set_default_color_theme('dark-blue')
app = ctk.CTk()
app.title('APP')
my_font = ctk.CTkFont(family="Sans Serif", size=15)
# Set up the grid layout
app.grid_columnconfigure((0, 1, 2), weight=1)
app.grid_rowconfigure(1, weight=1)
app.grid_rowconfigure(0, weight=15)
ipAdd = ctk.CTkEntry(app, font=my_font, placeholder_text='Enter IP address...')
ipAdd.grid(row=1, column=0, sticky='we', padx=(5, 10), pady=(5, 5))
# Set Up Display
display = ctk.CTkTextbox(app)
display.grid(row=0, column=0, columnspan=3, sticky='nswe', padx=5, pady=(5, 0))
# Set up the buttons
startBtn = ctk.CTkButton(app, font=my_font, text='START', fg_color='#006600', border_width=2, corner_radius=50,
hover_color='#009900', border_color='#006600', command=lambda: mtd.ping_host(display, ipAdd))
startBtn.grid(row=1, column=1, padx=(0, 5))
stopBtn = ctk.CTkButton(app, font=my_font, text='STOP', fg_color='#FF0000', border_width=2, corner_radius=50,
hover_color='#CC0000', border_color='#FF0000', command=lambda: mtd.stop_ping(display))
stopBtn.grid(row=1, column=2, padx=(5, 5))
app.mainloop()
In method.py I have:
import customtkinter as ctk
import subprocess
def clear_display(display: ctk.CTkTextbox, ipadd: ctk.CTkEntry):
display.delete(0.0, 'end')
ipadd.delete(0, 'end')
def ping_host(display: ctk.CTkTextbox, ipadd: ctk.CTkEntry):
host = ipadd.get()
display.delete(1.0, ctk.END) # Clear previous results
try:
# Open a process with the ping command
process = subprocess.Popen(['ping', '-n', '5', host], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Read and display the output line by line
while True:
line = process.stdout.readline()
if not line:
break # Break the loop when there's no more output
display.insert(ctk.END, f"{line}")
display.update_idletasks() # Force update of the display
# Wait for the process to complete
process.wait()
# Display any remaining output after the process completes
remaining_output = process.stdout.read()
if remaining_output:
display.insert(ctk.END, f"remain out: {remaining_output}\n")
# Display any errors
errors = process.stderr.read()
if errors:
display.insert(ctk.END, f"Error: {errors}\n")
except Exception as e:
# Handle any exceptions that may occur
display.insert(ctk.END, f"Exception: {str(e)}\n")
I need the stop_ping() method to stop the ping once it has started. Tried with ChatGPT but no luck it wasn't giving any valuable info. Appreciate any help!
I would actually recommend that you put this into a class so that it's a little cleaner to keep track of these variables, but here is a minimally invasive solution. It relies on threading
, and should meet your needs as described.
The core of it is, the ping_host
holds the execution thread till it finishes, so the stop button can't do anything till it finishes. By putting the ping_host
call in a separate threading.Thread
, we give the stop button an opportunity to act.
I created a class to hold the process generated in your methods
module, and slightly modified that module to store the process handle.
And lastly the stop button function call was rerouted to a function which uses python's Popen.kill
function, details here.
app.py
import customtkinter as ctk
import methods as mtd
import threading
class ProcHandleHolder:
def __init__(self):
self.procHandle = None
@property
def procHandle(self):
return self._procHandle
@procHandle.setter
def procHandle(self, invalue):
self._procHandle = invalue
def set(self, procHandle):
self.procHandle = procHandle
def reset(self):
self.procHandle = None
def stop_proc():
if phh.procHandle:
phh.procHandle.kill()
phh.reset()
print('proc killed')
else:
print('proc not running?')
def start_proc():
t = threading.Thread(target=mtd.ping_host, args=(display, ipAdd, phh))
t.start()
phh = ProcHandleHolder()
# System Defaults
ctk.set_appearance_mode('Dark')
ctk.set_default_color_theme('dark-blue')
app = ctk.CTk()
app.title('APP')
my_font = ctk.CTkFont(family="Sans Serif", size=15)
# Set up the grid layout
app.grid_columnconfigure((0, 1, 2), weight=1)
app.grid_rowconfigure(1, weight=1)
app.grid_rowconfigure(0, weight=15)
ipAdd = ctk.CTkEntry(app, font=my_font, placeholder_text='Enter IP address...')
ipAdd.grid(row=1, column=0, sticky='we', padx=(5, 10), pady=(5, 5))
# Set Up Display
display = ctk.CTkTextbox(app)
display.grid(row=0, column=0, columnspan=3, sticky='nswe', padx=5, pady=(5, 0))
# Set up the buttons
startBtn = ctk.CTkButton(app, font=my_font, text='START', fg_color='#006600',
border_width=2, corner_radius=50,
hover_color='#009900', border_color='#006600',
command=lambda: start_proc())
startBtn.grid(row=1, column=1, padx=(0, 5))
stopBtn = ctk.CTkButton(app, font=my_font, text='STOP', fg_color='#FF0000',
border_width=2, corner_radius=50,
hover_color='#CC0000', border_color='#FF0000',
command=lambda: stop_proc())
stopBtn.grid(row=1, column=2, padx=(5, 5))
app.mainloop()
methods.py
import customtkinter as ctk
import subprocess
def clear_display(display: ctk.CTkTextbox, ipadd: ctk.CTkEntry):
display.delete(0.0, 'end')
ipadd.delete(0, 'end')
def ping_host(display: ctk.CTkTextbox, ipadd: ctk.CTkEntry, phh: object):
host = ipadd.get()
display.delete(1.0, ctk.END) # Clear previous results
try:
# Open a process with the ping command
process = subprocess.Popen(['ping', '-n', '5', host],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
phh.set(process)
# Read and display the output line by line
while True:
line = process.stdout.readline()
if not line:
break # Break the loop when there's no more output
display.insert(ctk.END, f"{line}")
display.update_idletasks() # Force update of the display
# Wait for the process to complete
process.wait()
# Display any remaining output after the process completes
remaining_output = process.stdout.read()
if remaining_output:
display.insert(ctk.END, f"remain out: {remaining_output}\n")
# Display any errors
errors = process.stderr.read()
if errors:
display.insert(ctk.END, f"Error: {errors}\n")
except Exception as e:
# Handle any exceptions that may occur
display.insert(ctk.END, f"Exception: {str(e)}\n")
Hope this helps! Feel free to comment with any questions.