Newbie question: Progress bar not working while my for loop is executing

Hi. I’ve tried both determinate and indeterminate progress bars. Neither work while my main for loop is executing. The determinate progress bar fills up after the for loop finishes and the indeterminate progress bar works as long as I don’t execute my for loop. I’ve tried asking ChatGPT, but I’m not getting anywhere with it. Any suggestions would be greatly appreciated!

Code below:

import tkinter as tk
from tkinter import ttk
import threading # Import threading module

def execute_code():
    import openpyxl
    import re
    from tkinter import Tk
    from tkinter.filedialog import askopenfilename

    # Hide the root window
    Tk().withdraw()

    # Open file dialog and get the file path
    file_path = askopenfilename()

    # Load the workbook and select the worksheet
    wb = openpyxl.load_workbook(file_path)
    ws = wb.active

    max_row = ws.max_row

    # Loop through column N starting from row 2
    for row in range(2, max_row + 1):
        cell_value = ws.cell(row=row, column=14).value
        if not cell_value:
            break

        # Extract text between tags
        if '<cm:compliance-actual-value>' in cell_value and '<\\/cm:compliance-actual-value>' in cell_value:
            actual_value = cell_value.split('<cm:compliance-actual-value>')[1].split('<\\/cm:compliance-actual-value>')[0]
        else:
            actual_value = ''

        if '<cm:compliance-policy-value>' in cell_value and '<\\/cm:compliance-policy-value>' in cell_value:
            policy_value = cell_value.split('<cm:compliance-policy-value>')[1].split('<\\/cm:compliance-policy-value>')[0]
        else:
            policy_value = ''

        if '<cm:compliance-result>' in cell_value and '<\\/cm:compliance-result>' in cell_value:
            result = cell_value.split('<cm:compliance-result>')[1].split('<\\/cm:compliance-result>')[0]
        else:
            result = ''

        if "<cm:compliance-actual-value>'" in cell_value and "'<\\/cm:compliance-actual-value>" in cell_value:
            actual_value = cell_value.split("<cm:compliance-actual-value>'")[1].split("'<\\/cm:compliance-actual-value>")[0]
        else:
            actual_value = ''

        if "<cm:compliance-policy-value>'" in cell_value and "'<\\/cm:compliance-policy-value>" in cell_value:
            policy_value = cell_value.split("<cm:compliance-policy-value>'")[1].split("'<\\/cm:compliance-policy-value>")[0]
        else:
            policy_value = ''

        # Paste values to columns O, P and Q
        ws.cell(row=row, column=15).value = '<cm:compliance-actual-value>' + '\r' + actual_value + '\r' + '<\\/cm:compliance-actual-value>'
        ws.cell(row=row, column=16).value = '<cm:compliance-policy-value>' + '\r' + policy_value + '\r' + '<\\/cm:compliance-policy-value>'
        ws.cell(row=row, column=17).value = '<cm:compliance-result>' + '\r' + result + '\r' + '<\\/cm:compliance-result>'

        # Check conditions and paste text to columns if necessary
        if ((ws.cell(row=row, column=11).value == 'FAILED' and result == 'FAILED' and actual_value == '' and policy_value == '') or + 
            (ws.cell(row=row, column=11).value == 'FAILED' and result == '' and actual_value == '' and policy_value == '')):
            ws.cell(row=row, column=18).value = 'False Positive'
            ws.cell(row=row, column=19).value = 'Needs Further Research: No values to compare'
        elif (ws.cell(row=row, column=11).value == 'FAILED' and actual_value == 'None'):
            ws.cell(row=row, column=18).value = 'Valid Fail'
            ws.cell(row=row, column=19).value = 'Required Policy setting is (' + policy_value + ') ' + 'and' + ' actual system setting is (' + actual_value + ')'
        elif 'false' in actual_value and 'true' in policy_value and 'FAILED' in result and 'FAILED' in ws.cell(row=row, column=11).value:
            ws.cell(row=row, column=18).value = 'Valid Fail'
            ws.cell(row=row, column=19).value = 'Required Policy setting is (' + policy_value + ') ' + 'and' + ' actual system setting is (' + actual_value + ')'
        elif 'true' in actual_value and 'false' in policy_value and 'FAILED' in result and 'FAILED' in ws.cell(row=row, column=11).value:
            ws.cell(row=row, column=18).value = 'Valid Fail'
            ws.cell(row=row, column=19).value = 'Required Policy setting is (' + policy_value + ') ' + 'and' + ' actual system setting is (' + actual_value + ')'
        elif "ERROR ( message:Configuration error" in actual_value:
            ws.cell(row=row, column=18).value = 'Valid Fail'
            ws.cell(row=row, column=19).value = 'Cannot read configuration file'
        elif "Server execution failed" in actual_value:
            ws.cell(row=row, column=18).value = 'Valid Fail'
            ws.cell(row=row, column=19).value = 'Server execution failed'
        # Check if policy_value contains '[' and ']' and result value contains 'FAILED'
        elif '[' in policy_value and ']' in policy_value and 'FAILED' in ws.cell(row=row, column=11).value:
            # Extract regular expression pattern from policy_alue
            pattern = policy_value.split('[')[1].split(']')[0]

            # Check if actual_alue complies with the regular expression pattern
            if re.match(pattern, actual_value):
                ws.cell(row=row, column=18).value = 'False Positive'
                ws.cell(row=row, column=19).value = 'Complies with regular expression pattern'
            else:
                ws.cell(row=row, column=18).value = 'Valid Fail'
                ws.cell(row=row, column=19).value = 'Does not comply with regular expression pattern'
        else:
            ws.cell(row=row, column=18).value = ''
            ws.cell(row=row, column=19).value = ''

        #progress['value'] += 100 / max_row

    # Save the workbook
    wb.save(file_path)

def start_thread():
 # Create a new thread to run execute_code
 thread = threading.Thread(target=execute_code)
 thread.start()

root = tk.Tk()
root.title("Execute Code")
root.geometry("400x200")

frame = tk.Frame(root)
frame.pack()

button_execute_code = tk.Button(frame,
                   text="Execute Code",
                   command=execute_code)
button_execute_code.pack(side=tk.LEFT)

progress_frame = tk.Frame(root)
progress_frame.pack()

progress_label = tk.Label(progress_frame,
                          text="Progress:")
progress_label.pack(side=tk.LEFT)

progress = ttk.Progressbar(progress_frame,
                           orient="horizontal",
                           length=200,
                           mode="indeterminate")
#progress.pack(side=tk.RIGHT)
progress.pack()

# Start the progress bar animation
progress.start()

# Stop the progress bar animation after 5 seconds
root.after(5000, progress.stop)

root.mainloop()

A question: you have two ‘progress related’ lines of code that are commented out:

#progress['value'] += 100 / max_row
#progress.pack(side=tk.RIGHT)

Is that a mistake or deliberate?

deliberate, as the current code reflect the use of an indeterminate progress bar.
to change back to determinate progress bar, I would comment out:

  1. #progress.pack()
  2. #progress.start()
  3. #progress.stop()
  4. #root.after(5000, progress.stop)

and uncomment

  1. #progress.pack(side=tk.RIGHT)
  2. #progress[‘value’] += 100 / max_row

and update mode=“indeterminate” to mode=“determinate”

The GUI is handled in the main thread. While execute_code is running, it doesn’t update the GUI because, well, it’s busy!

You should ensure that event handlers are quick so that the GUI remains responsive. Any slow tasks should be done in a background thread. All GUI stuff should be done in the main thread.

My approach would be that when you click the button, you ask the user for the filename, etc, then disable the button, start the background thread to do the CPU-intensive work, and then return. The main loop will be running, keeping the GUI responsive. You can check periodically whether the background thread has finished in a callback called by root.after. When you find that the background thread has finished, you can then re-enable the button.

Get rid of the Tk import and call within the execute function. Move the askopenfilename import to the top. Add ‘parent=root’ to its call.

I would make the progress bar determinate and call step(n) at the bottom of the for loop. Let n be something like round(row/max_row * 100) if bar max is 100.

MRAB - Many thanks brother! It worked. I had the button misconfigured; I was calling the wrong thread.

button_execute_code = tk.Button(frame,
                   text="Execute Code",
                   command=execute_code)

when it should have been

button_execute_code = tk.Button(frame,
                   text="Execute Code",
                   command=start_thread)