Python Forum
[Tkinter] tkinter freezes by clicking button
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] tkinter freezes by clicking button
#31
I had commented the original line 8. I saw it was my error. I uncommented the original line, and I commented my following line(import tkcallasync). Now I get no error message, but after 5 minutes, solution does not appear. Is there another mistake, or I did not wait enough ??
Reply
#32
@sylas Use from tkcallasync import tk_call_async. You could perhps start a new thread with your whole code because Zatox11 doesn't seem to have other issues with it.
Reply
#33
I am absent from home all next week. I'll look at it in 9 days.
Reply
#34
Hey, I have recently switched from Tkinter to wxPython, because after reading several comments on that topic I came to the conclusion that this might be a good idea.

In order to keep the GUI responsive during backtracking, I used threading like in this example: Long running tasks

I also succeeded to implement a working "Stop Button" to abort the backtracking thread.

If someone is interested, here is the full code:

# GUI File

import wx
import time
import SudokuSolverV2_Solver as SOLVER
from threading import *

START_ID = wx.NewId()
STOP_ID = wx.NewId()
EVT_RESULT_ID = wx.NewId()


class ResultEvent(wx.PyEvent):
    def __init__(self, data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

class WorkerThread(Thread):
    def __init__(self, notify_window, *args):
        Thread.__init__(self)
        self._notify_window = notify_window
        self.matrix = args[0]
        self.aborted = False
        self.start()

    def run(self):
        if self.aborted:
            wx.PostEvent(self._notify_window, ResultEvent(None))
            return
        SOLVER.solve(self.matrix, 0)
        wx.PostEvent(self._notify_window, ResultEvent(SOLVER.SOLUTION))

    def abort(self):
        self.aborted = True
        SOLVER.DISABLED = True
        self.run()
        

class SudokuSolver(wx.Frame):

    def __init__(self, parent):
        super(SudokuSolver, self).__init__(parent, title='SudokuSolver' ,size=(355,435),
                                           style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
        self.Entries = {}
        self.seconds = 0
        self.minutes = 0
        self.timer_mode = 'stopped'

        self.Connect(-1, -1, EVT_RESULT_ID, self.OnResult)
        self.worker = None
        
        self.InitUI()
        self.Centre()
        self.Show()
        
    def InitUI(self):

        menuBar = wx.MenuBar()

        FileBtn = wx.Menu()
        InitItem = FileBtn.Append(wx.NewId(), 'Init')
        NewItem = FileBtn.Append(wx.NewId(), 'New')
        SaveItem = FileBtn.Append(wx.NewId(), 'Save')
        ExitItem = FileBtn.Append(wx.ID_EXIT, 'Exit')
        self.Bind(wx.EVT_MENU, self.InitSudoku, InitItem)
        self.Bind(wx.EVT_MENU, self.BlankGrid, NewItem)
        self.Bind(wx.EVT_MENU, self.SaveSudoku, SaveItem)
        self.Bind(wx.EVT_MENU, self.Quit, ExitItem)
        menuBar.Append(FileBtn, '&File')

        OptionsBtn = wx.Menu()
        TimerStartItem = OptionsBtn.Append(wx.NewId(), 'Start Timer')
        TimerStopItem = OptionsBtn.Append(wx.NewId(), 'Stop Timer')
        self.Bind(wx.EVT_MENU, self.StartTimer, TimerStartItem)
        self.Bind(wx.EVT_MENU, self.StopTimer, TimerStopItem)
        menuBar.Append(OptionsBtn, '&Options')

        HelpBtn = wx.Menu()
        menuBar.Append(HelpBtn, '&Help')

        self.SetMenuBar(menuBar)
       
        self.panel = wx.Panel(self)
        self.font = wx.Font(wx.FontInfo(15).Bold())
        self.InitGrid()

        self.message_label = wx.StaticText(self.panel, label='', pos=(10,358))
        self.timer_label = wx.StaticText(self.panel, label='00:00', pos=(280,358))

        #self.PrintBtn = wx.Button(self.panel, pos=(0,0), size=(50,28), label='Print')
        #self.PrintBtn.Bind(wx.EVT_BUTTON, self.PrintSudokus)

        self.StartBtn = wx.Button(self.panel, START_ID, label='Solve', size=(50,28), pos=(88,0))
        self.StartBtn.Bind(wx.EVT_BUTTON, self.OnStart, id=START_ID)

        self.CheckBtn = wx.Button(self.panel, pos=(146,0), size=(50,28), label='Check')
        self.CheckBtn.Bind(wx.EVT_BUTTON, self.CheckSudoku)

        #self.TimeBtn = wx.Button(self.panel, pos=(202,0), size=(50,28), label='Start')
        #self.TimeBtn.Bind(wx.EVT_BUTTON, self.StartTimer)

        self.StopBtn = wx.Button(self.panel, STOP_ID, label='Stop', size=(50,28), pos=(202,0))
        self.StopBtn.Bind(wx.EVT_BUTTON, self.OnStop, id=STOP_ID)

    def Quit(self, event):
        self.Close()
 
    def InitGrid(self):
        x, y = 10, 30
        for i in range(1,10):
            for j in range(1,10):
                self.Entries[i,j] = wx.TextCtrl(self.panel, style=wx.TE_CENTRE,
                                                pos=(x, y), size=(30, 30))
                self.Entries[i,j].SetFont(self.font)
                if j%3==0: x += 40
                else: x += 35
                if j==9: x = 10
            if i%3==0: y += 40
            else: y += 35

    def BlankGrid(self, event, del_matrix=True):
        self.message_label.SetLabel('')
        self.ResetTimer()
        if del_matrix: SOLVER.clear()
        for i in range(1,10):
            for j in range(1,10):
                L = self.Entries[i,j].GetLineLength(0)
                self.Entries[i,j].Remove(0,L)

    def InitSudoku(self, event):
        self.BlankGrid(None)
        SOLVER.SUDOKU = SOLVER.sudoku_from_txt()
        for i in range(1,10):
            for j in range(1,10):
                self.Entries[i,j].write(SOLVER.SUDOKU.matrix[i-1][j-1])
        SOLVER.SUDOKU.draw()

    def SaveSudoku(self, event):
        if SOLVER.SOLUTION.valid() and SOLVER.SOLUTION.complete():
            SOLVER.sudoku_to_txt()
            self.message_label.SetLabel('Files saved...')
        else:
            self.message_label.SetLabel('Sudoku must be valid and complete!')

    def CheckSudoku(self, event):
        self.GetEntries(SOLVER.SOLUTION)
        if not SOLVER.SOLUTION.complete():
            self.message_label.SetLabel('Sudoku is not complete!')
        elif not SOLVER.SOLUTION.valid():
            self.message_label.SetLabel('Solution is not correct!')
        else:
            self.message_label.SetLabel('Solution correct :)')

    def GetEntries(self, Sudoku):
        for i in range(1,10):
            for j in range(1,10):
                val = self.Entries[i,j].GetLineText(0)
                if val=='': Sudoku.matrix[i-1][j-1] = ' '
                elif len(val)!=1: Sudoku.matrix[i-1][j-1] = ' '
                elif not val.isdigit(): Sudoku.matrix[i-1][j-1] = ' '
                elif int(val) < 1 or int(val) > 9: Sudoku.matrix[i-1][j-1] = ' '
                else : Sudoku.matrix[i-1][j-1] = val

    def ShowSolution(self, data):
        self.BlankGrid(None, False)
        for i in range(1,10):
            for j in range(1,10):
                self.Entries[i,j].write(data.matrix[i-1][j-1])

    def StartTimer(self, event):
        self.timer_mode = 'started'
        def timer():
            if self.timer_mode == 'stopped': return
            if self.seconds == 60:
                self.seconds = 0
                self.minutes += 1
            if self.seconds > 9 and self.minutes > 9:
                self.timer_label.SetLabel('{}:{}'.format(self.minutes, self.seconds))
            elif self.seconds > 9 and self.minutes <= 9:
                self.timer_label.SetLabel('0{}:{}'.format(self.minutes, self.seconds))
            elif self.seconds <= 9 and self.minutes <= 9:
                self.timer_label.SetLabel('0{}:0{}'.format(self.minutes, self.seconds))
            elif self.seconds <= 9 and self.minutes > 9:
                self.timer_label.SetLabel('{}:0{}'.format(self.minutes, self.seconds))
            self.seconds += 1
            wx.CallLater(1000, timer)

        timer()
        
    def StopTimer(self, event):
        self.timer_mode = 'stopped'

    def ResetTimer(self):
        self.minutes = 0
        self.seconds = 0
        self.timer_mode = 'stopped'
        self.timer_label.SetLabel('0{}:0{}'.format(self.minutes, self.seconds))
        
    def OnStart(self, event):
        clues, valid = SOLVER.SUDOKU.clues(), SOLVER.SUDOKU.valid()
        self.message_label.SetLabel('clues: {}, valid: {}'.format(clues, valid))
        if not clues >= 17 or not valid:
            self.message_label.SetLabel('This sudoku does not have a solution')
        else:
            matrix = SOLVER.SUDOKU.copy(SOLVER.SUDOKU.matrix)
            if not self.worker:
                self.message_label.SetLabel('Backtracking started...')
                self.worker = WorkerThread(self, matrix)
            else:
                self.message_label.SetLabel('WARNING: Still backtracking...')

    def OnStop(self, event):
        if self.worker:
            SOLVER.DISABLED = True
            self.message_label.SetLabel('backtracking aborted')
            self.worker.abort()
        else:
            self.message_label.SetLabel('currently not backtracking')

    def OnResult(self, event):
        if event.data is None:
            self.ShowSolution(SOLVER.SUDOKU)
            self.message_label.SetLabel('Computation aborted')
        else:
            self.ShowSolution(event.data)
            self.message_label.SetLabel('Solution found! {} backtracks needed'.format(SOLVER.BACKTRACKS))
        self.worker = None
        SOLVER.BACKTRACKS = 0
        SOLVER.DISABLED = 0
"""
    def PrintSudokus(self, event):
        print('Worker: ', self.worker)
        print('Sudoku:')
        SOLVER.SUDOKU.draw()
        print('Solution:')
        SOLVER.SOLUTION.draw()
"""
    
if __name__ == '__main__':
  
    app = wx.App()
    SudokuSolver(None)
    app.MainLoop()
# Solver File

import SudokuSolverV2_GUI as SudokuSolver
import SudokuSolverV2_SudokuClass as SUDOKU_
import os
from random import randint

def sudoku_from_txt():
    sudoku_dir = os.path.join(os.getcwd(), 'sudokus')
    if not os.path.exists(sudoku_dir):
        Sudoku = SUDOKU_.Sudoku(EXAMPLE)
        return Sudoku
    Sudoku = SUDOKU_.Sudoku()
    Files = []

    for filename in os.listdir(sudoku_dir):
        if filename.startswith('sudoku') and filename.endswith('.txt'):
            Files.append(filename)

    if len(Files) < 1:
        print('ERROR: No Sudokus saved, yet! Try this one')
        return Sudoku(example)
    else:
        R = randint(0, len(Files)-1)
        sudokuFile = open(os.path.join(sudoku_dir,Files[R]))
        row, col = 0, 0
        for value in sudokuFile.read():
            if value.isdigit() or value==' ':
                if value=='0': value = ' '      # zeros can also be used as blanks
                Sudoku.matrix[row][col] = value
                col += 1
                if col==9:
                    if row==8: break
                    row += 1
                    col = 0
        sudokuFile.close()

    if not Sudoku.valid():
        print('{} does not contain a valid sudoku'.format(Files[R]))
        return SUDOKU.Sudoku()
    else:
        return Sudoku

def sudoku_to_txt():
    sudoku_dir = os.path.join(os.getcwd(), 'sudokus')
    if not os.path.exists(sudoku_dir): os.makedirs(sudoku_dir)
    
    filecount = 1    # variable for how many sudoku files are already existing
    while os.path.isfile(os.path.join(sudoku_dir,("sudoku%s.txt" % filecount))):
        filecount += 1

    # ----- create textfiles for sudoku and the solution ----- #
    sudokuFile = open(os.path.join(sudoku_dir,('sudoku%s.txt' % filecount)), 'w')
    solutionFile = open(os.path.join(sudoku_dir,('solution%s.txt' % filecount)), 'w')
    
    for row in range(9):
        for col in range(9):
            sudokuFile.write(SUDOKU.matrix[row][col])
            solutionFile.write(SOLUTION.matrix[row][col])
        sudokuFile.write('\n')
        solutionFile.write('\n')
    sudokuFile.close()
    solutionFile.close()

def consistent(matrix, row, col, value):
    for i in range(9):
        if matrix[row][i]==value: return False
        if matrix[i][col]==value: return False
    rowStart = row - row%3
    colStart = col - col%3
    for m in range(3):
        for k in range(3):
            if matrix[rowStart+k][colStart+m]==value: return False
    return True

def solve(matrix, num):
    global BACKTRACKS, DISABLED
    if DISABLED: return True
    BACKTRACKS += 1
    if num==81:
        global SOLUTION
        SOLUTION = SUDOKU_.Sudoku(matrix)
        return True
    else:
        row = int(num / 9)
        col = num % 9
        if matrix[row][col]!=' ':
            solve(matrix, num+1)
        else:
            for value in range(1,10):
                if consistent(matrix, row, col, str(value)):
                    matrix[row][col] = str(value)
                    if solve(matrix, num+1): return True
                matrix[row][col]=' '
            return False

def clear():
    SUDOKU.matrix = SUDOKU.emptySudoku()
    SOLUTION.matrix = SOLUTION.emptySudoku()


DISABLED = False
BACKTRACKS = 0
SUDOKU = SUDOKU_.Sudoku()
SOLUTION = SUDOKU_.Sudoku()

EXAMPLE = [[' ',' ',' ','2','1',' ',' ',' ',' '],
           [' ',' ','7','3',' ',' ',' ',' ',' '],
           [' ','5','8',' ',' ',' ',' ',' ',' '],
           ['4','3',' ',' ',' ',' ',' ',' ',' '],
           ['2',' ',' ',' ',' ',' ',' ',' ','8'],
           [' ',' ',' ',' ',' ',' ',' ','7','6'],
           [' ',' ',' ',' ',' ',' ','2','5',' '],
           [' ',' ',' ',' ',' ','7','3',' ',' '],
           [' ',' ',' ',' ','9','8',' ',' ',' ']]

if __name__ == '__main__':
    Sudoku = sudoku_from_txt()
    Sudoku.draw()
# SudokuClass File

class Sudoku():

    def __init__(self, *args):
        if len(args) > 0:
            self.matrix = self.copy(args[0])
        else:
            self.matrix = self.emptySudoku()

    def draw(self):
        for row in range(9):
            print("|-----------------------------------|")
            for col in range(9):
                print("| " + self.matrix[row][col] + " ",end='')
            print("|")
        print("|-----------------------------------|")

    def get(self, row, col):
        return self.matrix[row][col]

    def copy(self, matrix):
        matrix_copy = self.emptySudoku()
        for row in range(9):
            for col in range(9):
                matrix_copy[row][col] = matrix[row][col]
        return matrix_copy

    def emptySudoku(self):
        return [[" " for col in range(9)] for row in range(9)]

    def complete(self):
        for row in range(9):
            for col in range(9):
                if self.matrix[row][col] == ' ':
                    return False
        return True

    def valid(self):
        for row in range(9):
            for col in range(9):
                val = self.matrix[row][col]
                if val != ' ':
                    self.matrix[row][col] = ' '
                    if not self.consistent(row, col, val):
                        self.matrix[row][col] = val
                        return False
                    self.matrix[row][col] = val
        return True

    def clues(self):
        clues = 0
        for row in range(9):
            for col in range(9):
                if self.matrix[row][col] != ' ':
                    clues += 1
        return clues

    def consistent(self, row, col, value):
        for i in range(9):
            if self.matrix[row][i]==value: return False
            if self.matrix[i][col]==value: return False
        rowStart = row - row%3
        colStart = col - col%3
        for m in range(3):
            for k in range(3):
                if self.matrix[rowStart+k][colStart+m]==value: return False
        return True


if __name__ == '__main__':
    matrix = [[' ',' ',' ','2','1',' ',' ',' ',' '],
              [' ',' ','7','3',' ',' ',' ',' ',' '],
              [' ','5','8',' ',' ',' ',' ',' ',' '],
              ['4','3',' ',' ',' ',' ',' ',' ',' '],
              ['2',' ',' ',' ',' ',' ',' ',' ','8'],
              [' ',' ',' ',' ',' ',' ',' ','7','6'],
              [' ',' ',' ',' ',' ',' ','2','5',' '],
              [' ',' ',' ',' ',' ','7','3',' ',' '],
              [' ',' ',' ',' ','9','8',' ',' ',' ']] 
    S = Sudoku(matrix)
    S.draw()
    print('(1,4) = {}, valid = {}, clues = {}, complete = {}'.format(S.get(0,3),
                                                              S.valid(),
                                                              S.clues(),
                                                              S.complete()))
The "project" is still far from being finished. I am considering to add more options and some kind of README file.
I am very thankful for any improvement suggestions on the code or the "project" in general. I am still new to programming and I want to learn how to get better.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] TKinter Remove Button Frame Nu2Python 8 978 Jan-16-2024, 06:44 PM
Last Post: rob101
  tkinter - touchscreen, push the button like click the mouse John64 5 843 Jan-06-2024, 03:45 PM
Last Post: deanhystad
  Centering and adding a push button to a grid window, TKinter Edward_ 15 4,729 May-25-2023, 07:37 PM
Last Post: deanhystad
  [Tkinter] Clicking on the button crashes the TK window ODOshmockenberg 1 2,240 Mar-10-2022, 05:18 PM
Last Post: deanhystad
  Can't get tkinter button to change color based on changes in data dford 4 3,418 Feb-13-2022, 01:57 PM
Last Post: dford
  Creating a function interrupt button tkinter AnotherSam 2 5,525 Oct-07-2021, 02:56 PM
Last Post: AnotherSam
  [Tkinter] Have tkinter button toggle on and off a continuously running function AnotherSam 5 5,006 Oct-01-2021, 05:00 PM
Last Post: Yoriz
  tkinter showing image in button rwahdan 3 5,617 Jun-16-2021, 06:08 AM
Last Post: Yoriz
  tkinter button image Nick_tkinter 4 4,033 Mar-04-2021, 11:33 PM
Last Post: deanhystad
  tkinter python button position problem Nick_tkinter 3 3,558 Jan-31-2021, 05:15 AM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020