Ad

Tkinter Window Not Updating Till After The Loop Is Done

- 1 answer

I've been wanting to make a sorting algorithm visualizer using python and settled to use the Tkinter library as my way of visualizing the data (if anyone has better libraries to use I'm open to suggestions, I looked into matplotlib but became discouraged). My problem is that as I'm sorting the array, I want to make a swap, show the updated array after the swap, then continue the sorting; but what ends up happening is that the array sorts and then the entire sorted array is updated.

import tkinter as tk
from tkinter import ttk
import random
import time

class SortingVisualizer(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        tk.Tk.wm_title(self, "Sorting Visualizer")
        tk.Tk.wm_minsize(self, width=600, height=500)
        tk.Tk.wm_resizable(self, width=False, height=False)


        self.topFrame = tk.Frame(self)
        self.topFrame.grid(row=0, sticky='w')

        self.sortOptions = ['Select Algorithm','Bubble sort','Quicksort', 'Merge sort']
        self.optionVar = tk.StringVar()
        self.optionDrop = ttk.OptionMenu(self.topFrame, self.optionVar, *self.sortOptions)
        self.optionDrop.config(width=15)
        self.optionDrop.grid(row=0, column=1, sticky='ew')

        self.sortButton = ttk.Button(self.topFrame, text = "Sort", command = lambda: bubbleSort(self))
        self.sortButton.grid(row=0, column=2, sticky='w')
        self.genButton = ttk.Button(self.topFrame, text = "Generate New Array", command = self.newArray)
        self.genButton.grid(row=0, column=0)

        self.generateArray()

    def newArray(self):
        self.sortCanvas.destroy()
        self.generateArray()

    def generateArray(self):
        self.array = []
        self.numOperations = 0
        i = 0
        while i < 15:
            height = random.randint(15, 200)
            self.array.append(height)
            i = i + 1
        self.drawCanvas()

    def drawCanvas(self):
        self.sortCanvas = tk.Canvas(self, width=600, height=450)
        self.sortCanvas.grid(row=1)
        self.sortCanvas.create_line(15, 15, 585, 15)
        label = "Number of Operations: " + str(self.numOperations)
        self.numLabel = tk.Label(self.topFrame, text = label)
        self.numLabel.grid(row=1)

        bar_width = 20
        bar_gap = bar_width + 10
        start_x = 30
        start_y = 15
        for bar_height in self.array:
            x1 = start_x + bar_width
            y1 = start_y + bar_height
            self.sortCanvas.create_rectangle(start_x, start_y, x1, y1*2, fill='green')
            start_x = start_x + bar_gap

    def redrawCanvas(self):
        self.sortCanvas.destroy()
        self.drawCanvas()

def bubbleSort(self):
    n = len(self.array)
    for i in range(n):
        for j in range(0, n-i-1):
            if self.array[j]>self.array[j+1]:
                temp = self.array[j]
                self.array[j] = self.array[j+1]
                self.array[j+1] = temp
                self.numOperations += 1
                self.after(300, self.redrawCanvas)

app = SortingVisualizer()
app.mainloop()

I have also tried app.after(300, self.redrawCanvas) and get the same result

Ad

Answer

You made a very good first attempt and you were almost there. I have made some changes in your code, most importantly introduced a root object (tk.Tk()) so I can do root.update() in order to redraw to sort_canvas in a new method blip_canvas. To avoid some 'flickering' rather to destroy the canvas each time it is better to delete the 'bar' elements only. Further I took the liberty to change some of the variable names to make it a bit more Pythonic (should make use of underscores rather than capitals) and added the if _name__ == '__main__' statement.

Have a look at the code below.

import tkinter as tk
from tkinter import ttk
import random

class SortingVisualizer:

    def __init__(self):
        self.root = tk.Tk()
        self.root.wm_title("Sorting Visualizer")
        self.root.wm_minsize(width=600, height=500)
        self.root.wm_resizable(width=False, height=False)

        self.top_frame = tk.Frame(self.root)
        self.top_frame.grid(row=0, sticky='w')

        self.sort_options = ['Select Algorithm', 'Bubble sort', 'Quicksort', 'Merge sort']
        self.option_var = tk.StringVar()
        self.option_drop = ttk.OptionMenu(
            self.top_frame, self.option_var, *self.sort_options)
        self.option_drop.config(width=15)
        self.option_drop.grid(row=0, column=1, sticky='ew')

        self.sort_button = ttk.Button(
            self.top_frame, text="Sort", command=self.bubble_sort)
        self.sort_button.grid(row=0, column=2, sticky='w')

        self.gen_button = ttk.Button(
            self.top_frame, text="Generate New Array", command=self.new_array)
        self.gen_button.grid(row=0, column=0)

        self.sort_canvas = tk.Canvas(self.root)
        self.bars = []

    def new_array(self):
        self.generate_array()
        self.blip_canvas()

    def generate_array(self):
        self.array = []
        self.num_operations = 0
        i = 0
        while i < 15:
            height = random.randint(15, 200)
            self.array.append(height)
            i = i + 1

    def draw_canvas(self):
        label = "Number of Operations: " + str(self.num_operations)
        self.num_label = tk.Label(self.top_frame, text=label)
        self.num_label.grid(row=1)

        self.sort_canvas = tk.Canvas(self.root, width=600, height=450)
        self.sort_canvas.grid(row=1)
        self.sort_canvas.create_line(15, 15, 585, 15)

        bar_width = 20
        bar_gap = bar_width + 10
        start_x = 30
        start_y = 15
        self.bars = []
        for bar_height in self.array:
            x1 = start_x + bar_width
            y1 = start_y + bar_height
            self.bars.append(self.sort_canvas.create_rectangle(
                start_x, start_y, x1, y1*2, fill='green'))
            start_x = start_x + bar_gap

    def blip_canvas(self):
        self.sort_canvas.delete(self.bars)
        self.draw_canvas()
        self.root.update()
        self.root.after(200)

    def bubble_sort(self):
        n = len(self.array)
        for i in range(n):
            for j in range(n-i-1):
                if self.array[j] > self.array[j+1]:
                    self.array[j], self.array[j+1] = self.array[j+1], self.array[j]
                    self.num_operations += 1
                    self.blip_canvas()

    def start(self):
        tk.mainloop()


if __name__ == '__main__':
    app = SortingVisualizer()
    app.start()

Note in bubble_sort you do not need the temp variable to swap the values of array[j] and array[j+1]

Rather than to use time.sleep(0.2) to set a delay I have used:

self.root.update()
self.root.after(200)

as suggested in Update button after delay

You can also stick to your original code and just make a few changes.
1) Change the sortButton

self.sortButton = ttk.Button(self.topFrame, text = "Sort", command=self.bubbleSort)

2) Indent the bubbleSort method to be aligned with SortingVisualizer

3) Change method redrawCanvas to:

    def redrawCanvas(self):
        self.sortCanvas.destroy()
        self.drawCanvas()
        self.update()
        self.after(300)

and

4) in bubbleSort make the call to redrawCanvas:

        for j in range(0, n-i-1):
            if self.array[j]>self.array[j+1]:
                temp = self.array[j]
                self.array[j] = self.array[j+1]
                self.array[j+1] = temp
                self.numOperations += 1
                self.redrawCanvas()

et voila, it will work!

Ad
source: stackoverflow.com
Ad