I have a dashboard that gets node count, etc. from slurm and displays that as a button grid, and is refreshed every n seconds. I want the ability to click on any of the buttons and have something happen, e.g. get additional info, run a script, etc. The problem is that interactivity with the buttons in the grid does not work in a while loop. In the following test code, if event_loop = False, grid button clicks work as expected, but there is no update (no loop). If event_loop = True, the grid is updated, but the grid buttons no longer work. from ipywidgets import GridspecLayout, Button, Layout, Output from IPython.display import display, clear_output import time nodes = [1, 2, 3, 4, 5, 6] rows = 3 cols = 3 event_loop = False button_go = Button( description='Go', disabled=False, ayout={'width': '150px'} ) output_command = Output() output_command.append_stdout('') @output_command.capture() def button_go_clicked(event): print(event) if event_loop: while event_loop: update_grid(rows, cols, nodes) time.sleep(10) else: update_grid(rows, cols, nodes) button_go.on_click(button_go_clicked) def create_dynamic_grid(rows, cols, nodes): grid = GridspecLayout(rows, cols) node = 0 for i in range(rows): for j in range(cols): if node < len(nodes): if nodes[node]: button_grid = Button(description='Node_{}-{}'.format(i, j), button_style='primary', layout=Layout(width='auto', height='auto')) grid[i, j] = button_grid grid[i, j].on_click(button_grid_clicked) node += 1 return grid output_grid = Output() def update_grid(rows, cols, nodes): with output_grid: output_grid.clear_output() new_grid = create_dynamic_grid(rows, cols, nodes) display(new_grid) @output_command.capture() def button_grid_clicked(btn): print(btn) display(button_go, output_grid, output_command) I need some way to 1. immediately interrupt/pause the loop when clicking a grid button, and 2. maintain/resume the loop refresh time of n seconds. After searching around on google, I found two interesting methods and integrated them. The first is from 'Kill a loop with Button Jupyter Notebook?' The second was from a google Generative AI from the prompt 'python immediately break from sleep', which generated the following: import time import threading def sleeper(seconds, event): while seconds > 0 and not event.is_set(): time.sleep(min(seconds, 1)) seconds -= 1 if event.is_set(): print("Sleep interrupted") else: print("Slept for", seconds, "seconds") def main(): sleep_event = threading.Event() sleep_thread = threading.Thread(target=sleeper, args=(5, sleep_event)) sleep_thread.start() input("Press Enter to interrupt sleep...\n") sleep_event.set() sleep_thread.join() print("Continuing main thread") if __name__ == "__main__": main() I have attempted to integrate these into my test code, my apologies if it's a bit of a jumble. The results are that when event_loop = True the grid is updated constantly, and if event_loop = False, no update. from ipywidgets import GridspecLayout, Button, Output, Layout from IPython.display import display, clear_output import asyncio, threading, time nodes = [1, 2, 3, 4, 5, 6] rows = 3 cols = 3 event_loop = False sleep_event = None sleep_thread = None button_go = Button( description='Go', disabled=False, layout={'width': '150px'} ) output_command = Output() output_command.append_stdout('') async def function(): while event_loop: print('function() event_loop: ', event_loop) print('done') def sleeper(seconds, event): while seconds > 0 and not event.is_set(): print('sleeping: ', event) print('sleep_time: ', seconds) time.sleep(seconds) if event.is_set(): print('sleep interrupted: ', event) def set_sleeper(sleep_time): global sleep_event global sleep_thread sleep_event = threading.Event() sleep_thread = threading.Thread(target=sleeper, args=(sleep_time, sleep_event)) sleep_thread.start() return sleep_event @output_command.capture() def button_go_clicked(event): loop = asyncio.get_event_loop() loop.create_task(function()) print('button_go_clicked event_loop: ', event_loop) se = set_sleeper(10) print('set_sleeper: ', se) if event_loop: while event_loop: update_grid(rows, cols, nodes) # grid updates constantly in free run # time.sleep(10) # <-- stop free run, but looking for the correct way to sleep here AND be able to interrupt on_click else: update_grid(rows, cols, nodes) button_go.on_click(button_go_clicked) def create_dynamic_grid(rows, cols, nodes): grid = GridspecLayout(rows, cols) node = 0 for i in range(rows): for j in range(cols): if node < len(nodes): if nodes[node]: button_grid = Button(description='Node_{}-{}'.format(i, j), button_style='primary', layout=Layout(width='auto', height='auto')) grid[i, j] = button_grid grid[i, j].on_click(button_grid_clicked) node += 1 return grid output_grid = Output() def update_grid(rows, cols, nodes): with output_grid: output_grid.clear_output() new_grid = create_dynamic_grid(rows, cols, nodes) display(new_grid) @output_command.capture() def button_grid_clicked(btn): print(btn) if event_loop: global sleep_event global sleep_thread print('interrupting sleep...\n') sleep_event.set() sleep_thread.join() print('continuing thread') display(button_go, output_grid, output_command) My anticipation was that set_sleeper(10) would act as the refresh time (10 seconds). Any suggestions on how I can make this work as desired? Regards Continue reading...