jarvisteach / appJar

Simple Tkinter GUIs in Python
http://appJar.info
Other
612 stars 68 forks source link

Loops halt GUI updates, methods setStatusbar and setTextArea not run until loop exit #544

Open tnsolley opened 5 years ago

tnsolley commented 5 years ago

Bug Report


Context


Making a Test Program GUI that sends and receives serial commands to attached HW, and prints useful feedback/status updates as it goes to the Statusbar as well as to ScrolledTextAreas.

Expected Behaviour


The Statusbar and ScrolledTextArea will be updates at each call to the Set function.

Actual Behaviour


Statusbar and ScrolledTextArea do not change until exiting the appropriate Loop; GUI freezes during this loop (as can be seen by the static Time indicator).

Any error messages produced by appJar


N/A.

Sample code, demonstrating the issue


Full edited code; showcase_edit.zip

What steps are needed to reproduce the bug


[Full code in file]

Summary:

Change the default Statusbar to: # add a statusbar to show the time app.addStatusbar(fields=3, side="RIGHT") app.setStatusbarWidth(50, 2) app.setStatusbarBg("grey", 2) app.setStatusbarFg("white", 2) app.registerEvent(showTime) app.stopFunction = logoutFunction

Add the above functions to the showcase.py file, as well as the below Tabs; """ TEST HW SCREEN """ # http://appjar.info/pythonWidgetGrouping/#scroll-pane # http://appjar.info/inputWidgets/#textarea ### <sendSetup()>, <setupQuery()>, <userCheck1()>, <userCheck2()> # Perform HW stuff. Results of each printed to a Dialog screen. with app.tab("HW", bg="slategrey", sticky="news"): with app.labelFrame("HW Feedback"): app.label("foo4", "Feedback:", row=0, column=0, colspan=1, rowspan=1) app._textMaker("lb4", "scroll", row=1, column=0, colspan=2, rowspan=1, sticky="news") app.setTextAreaRelief("lb4", "raised") app.separator(row=31, column=0, colspan=1, sticky='news') app.buttons(["Run","Retest","Quit HW"], s3, row=32, column=0) app.buttons(["To Results"], t4, row=33, column=0) # Token for now # End with-statement # End with-statement

Version Information


Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:22:17) [MSC v.1500 32 bit (Intel)] on win32 Windows 10 AppJar 0.9.3

tnsolley commented 5 years ago

If this is a TKinter-based issue, maybe the TK calls need to be updated in the AppJar files? http://forums.devshed.com/python-programming-11/tkinter-update-object-loop-372610.html

LEWP commented 5 years ago

I believe this may apply to any of the display widgets. I ran into this issue on raspbian/python3 with a clock/dashboard program. Am using a registerEvent to check the time to see if the minute had changed. What did not work per pseudo code :

# in main code: 
registerEvent (checkTime)

def checkTime:
    if minute has changed:
        use setLabel to update time
        get dashboard info  (takes some time)
        use setLabel to update dashboard info
        get other dashboard info  (takes some more time)
        use setLabel to update other dashboard info

The problem I eventually discovered was none of the display widgets were updating until checkTime routine completely finished. I incorrectly assumed that setLabel would update when invoked, but seems to be updating only on passes through the top level code. My work around was to use flags and to put the clock and dashboard info code into separate routines with their own registerEvent.

# in main code: 
registerEvent (checkTime)
registerEvent (updateDashboard1)
registerEvent (updateDashboard2)

def checkTime:
    if minute has changed:
        use setLabel to update time
        set flag for updateDashboard1

def updateDashboard1:
    if dashboardFlag1:
        get dashboard info  (takes some time)
        use setLabel to update dashboard info
        set flag for updateDashboard2

def updateDashboard2:
    if dashboardFlag2:
        get other dashboard info  (takes some time)
        use setLabel to update other dashboard info

Yes it is a hack, but now the clock instantly updates and the others as data becomes available.

tnsolley commented 5 years ago

@LEWP You're absolutely correct.

As a side note, for any other folks new to AppJar or Python in general, I managed to get around my own problem by using the Threading and Queue abilities built-in to the library, as outlined https://github.com/jarvisteach/appJar/blob/45b568f8dc6cbabdae51134c1eb6fe0b6cd15ccb/docs/mkdocs/docs/pythonThreads.md.

While it seems kindof odd, maybe, that this library does NOT auto-queue/auto-update graphics when called, for folks not getting into this level of complexity it may be a non-issue.

jarvisteach commented 5 years ago

Hi @tnsolley & @LEWP - I'd like to reopen this...

I've tried to document best practices on this, see:

The issue I think in both cases was that the GUI didn't update until your code finished. This is not so much an appJar issue, but rather a tkinter feature.

I've added the update queue to deal with this problem, and it works well. However, it seems like people just aren't finding that feature.

I think something @tnsolley has mentioned is that he expected this to be done automatically - ie. every function call in appJar gets queued and processed separately.

I like this idea - it would mean that the whole library is 'thread-safe', but it would be a massive job to rewrite every function, and it might also add unnecessary overhead.

Before I explore that idea more, I was wondering if either of you could suggest improvements for how the docs are presented, to make the feature easier to find?

tnsolley commented 5 years ago

@jarvisteach You make a good point. I suppose it depends on how familiar the reader is with Python or event-driven terminology. When reading the AppJar wiki, the required info IS there... if you know what Threading is. For folks new to AppJar, Python, and/or TKinter, it might be useful to add a small note to the Main GitHub page? This does get into the "how explicit can we make the instructions without overloading people" trade-off, i.e. "if you know it you know it, if you don't you don't".

Or perhaps adding a note under the "Help->Limitations" page?

LEWP commented 5 years ago

What you have written is good. Given your target audience however, it is probably best to not assume they are that familiar with threading and event-driven code per @tnsolley. Maybe it is a side of the pond thing, but would suggest "freezes" or "temporarily hangs" instead of "hangs" the sentence "But, if an event takes a long time to process, the GUI loop won't be checking for other events - this is when the GUI hangs (stops processing events). " Hang has a more fatal/permanent connotation to me.

I think it would be worthwhile to either repeat or link to that paragraph more prominently/earlier in the documentation. Thought I had read all the relevant documentation, but alas I overlooked it.

Will have to defer on whether to rewrite the code to always update. My first thought was absolutely yes. But on additional reflection, I'm not sure given the potential overhead. Could see it going either way. At this point, if one knows how this works, the "work around" is not a big deal at all. It may not be worth your time/effort for a major rewrite.

FYI - I experimented with the update queue, and initially it seemed to work, but in less than 12 hours of running, it crashed with a "put raise Full queue.Full" error. (in Python 3.6 in queue.py) Probably something on my end, but needing very high dependability, it doesn't seem to be the right solution for my program. Clearly YMMV.

For the record, I did modify/simplify my code to be less of a kludge and only have one event. I feel obligated to save face with a nicer solution.

# in main code: 
turn = 1
registerEvent (checkTime)

def checkTime:
    if hhmm == currentHHMM:
        return
    if turn ==1:
        call routine to get data and set labels
        turn += 1
    elif turn == 2:
        call routine to get more data and set more labels
        turn += 1
    else:
         turn = 1
         reset hhmm to currentHHMM

Please let me conclude with a hearty thank you for this code. It has been extremely helpful.

tnsolley commented 5 years ago

... Something that's come up for me, on attempting to execute threads/queued functions in a looping fashion (i.e. at end of "main", do everything again). @LEWP , I'm also getting issues with the Queue (e.g. "Full") and running out of threads. Been trying to find the original thread and queueFunction methods you use (via GitHub search), @jarvisteach , but so far I've not been able to. I of course want to look into these as a means of clearing, or re-setting, the Queue and Threads at the end of the "main" function, i.e. right before looping, to allay those error messages. Would you be willing to add to your documentation on these methods?

Edit: If you are using the thread module, I would be looking for something similar to app.fn.exit()here. If you are using the Queue module, I would be looking for something similar to app.Queue.join()here. .... I think. Difficult to say for sure if those functions will allay those issues, without knowing how the app.thread and app.queueFunction objects are made in the first place...

tnsolley commented 5 years ago

After a bit of digging I found the instantiation of the thread and queueFunction functions in appjar.py. See lines 2152 and 2118, respectively.

1) Based on the use of self.eventQueue.put() in line 2131, which refers to the Queue class in Queue.py, put method line 107, the queueFunction method tries to add the function-call to the queue... and if no free slot is available, the Full exception gets raised. This may explain the issues @LEWP and I have been seeing. Might not hurt to let folks know about this, and possibly give them the option of NOT setting block=False. The other way I see of handling this error is for the programmer to add every queueFunction call inside of a try-catch within a while-loop... E.g.

while(1):
  try:
    app.queueFunction(app.setStatusbar, "message")
  except Exception as e:
    if e == "Full":
      continue
    # End if-case
  else:
    break
  # End try-catch
# End while-loop

2) t = Thread() line 2161 of appjar.py, creates an instance of the Thread class (see threading.py, line 631). The creation of this object is, for all I can tell, internal (not as a global or return-ed object). I'm not familiar with threading as a concept (didn't attempt to use it until now), so I'm not sure if the __stop or __delete method(s) are required to perform garbage-collection at the end of a 'main' loop. My suggestion here might be to do the above as well; either maybe add a function that lets AppJar users do their own garbage-collection of their instantiated thread objects, or let users know that they have to ref each call to its own global object, and call the appropriate Thread function when they wish to perform manual garbage-collection.

LEWP commented 5 years ago

@tnsolley as you alluded to, and what is not clear to me, is why would the queue be full in the first place? In my case, we were making 2 calls per minute...granted the second one may have only been a fraction of a second behind the first one in limited cases, but it wasn't like we were queuing up dozens of items at a time. At most it was 1440 total items queued over the 12 hr period.

(Just to be completely clear, per traceback, my full queue error was in the python package queue.py, not appjar code proper, so I was not sure if this is ultimately an appjar issue or queue.py issue. )

tnsolley commented 5 years ago

@LEWP My code is running much faster; in some cases the delay between queueFunction calls was in hundreds of milliseconds, not minutes. Since posting, I've not had the queue full issue arise often enough to be repeatable (or predictable), unfortunately---I've not been able to snag the traceback in the Terminal window.

EDIT: And of course, now that I try to re-capture the problem (by performing my code in a looping fashion), I've yet to get a Thread issue...

tnsolley commented 5 years ago

Modified my code to run in an infinite loop, to see how long before/what-kind-of errors accrue.

So far, here's what's happened;

11-02-2018, 1203, test 144
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "\\COTOFS01\tsolley$\Coto\Vmax\appJar_pyserial_tests\vmax_appJar_test2_inf_loops.py", line 2361, in importSetup
    app.setEntryValid("numLoops")
  File "C:\Python27\lib\site-packages\appJar\appjar.py", line 9375, in setEntryValid
    self.setValidationEntry(title, "valid")
  File "C:\Python27\lib\site-packages\appJar\appjar.py", line 9411, in setValidationEntry
    entry.config(fg=col)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1330, in configure
    return self._configure('configure', cnf, kw)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1321, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
TclError: invalid command name "-fg"

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1541, in __call__
    return self.func(*args)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 592, in callit
    func(*args)
  File "C:\Python27\lib\site-packages\appJar\appjar.py", line 2150, in _processEventQueue
    self.processQueueId = self.after(self.EVENT_SPEED, self._processEventQueue)
  File "C:\Python27\lib\site-packages\appJar\appjar.py", line 2094, in after
    return self.topLevel.after(delay_ms, callback, *args)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 600, in after
    return self.tk.call('after', ms, name)
SystemError: Negative size passed to PyString_FromStringAndSize

-----------------------------------------------------------------------------------------

11-02-2018, 1501, test 383
Exception in thread Thread-2687:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "\\COTOFS01\tsolley$\Coto\Vmax\appJar_pyserial_tests\vmax_appJar_test2_inf_loops.py", line 5330, in userCheck1
    blah4.see("end")
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 3160, in see
    self.tk.call(self._w, 'see', index)
TclError: bad text index "63254000_flash"

-----------------------------------------------------------------------------------------

11-02-2018, 1505, test 1
Tcl_AppendLimitedToObj called with shared object

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

-> "Debug"
"An unhandled win32 exception occured in python.exe [17236]"

-----------------------------------------------------------------------------------------

11-02-2018, 1509, test 2
Exception in thread Thread-20:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "\\COTOFS01\tsolley$\Coto\Vmax\appJar_pyserial_tests\vmax_appJar_test2_inf_loops.py", line 5170, in userCheck1
    blah4.see("end")
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 3160, in see
    self.tk.call(self._w, 'see', index)
TclError: bad option ".49845200.49845232.52505296.52505520.57015432.57390224.57421096.57374800": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview

-----------------------------------------------------------------------------------------

@LEWP do any of these seem familiar or similar to what you've been getting?

tnsolley commented 5 years ago

New additions:

11-02-2018, seen @ 1606, test 173
"python.exe has stopped working"

-> "Debug"
"Unhandled exception at 0x1007697F (tcl85.dll) in python.exe:
0xC0000005: Access violation reading location 0x000000C."

-----------------------------------------------------------------------------------------

11-02-2018, seen @ 1625, test 23
"python.exe has stopped working"

-> "Debug"
"An unhandled win32 exception occured in python.exe [4812]."

->
"Unhandled exception at 0x1005557D (tcl85.dll) in python.exe:
0xC0000005: Access violation reading location 0x000000C."

-----------------------------------------------------------------------------------------

11-02-2018, seen @ 1641, test 28
"TCLobject...."
[LOST]

"python.exe has stopped working"

-> "Debug"
"An unhandled win32 exception occured in python.exe [1728]."

->
[LOST]

-----------------------------------------------------------------------------------------

11-02-2018, seen @ 1721, test 81
Exception in thread Thread-573:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "\\COTOFS01\tsolley$\Coto\Vmax\appJar_pyserial_tests\vmax_appJar_test2_inf_loops.py", line 5332, in userCheck1
    blah4.see("end")
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 3160, in see
    self.tk.call(self._w, 'see', index)
TclError: invalid command name "end"

"python.exe has stopped working"

-> "Debug"
"An unhandled win32 exception occured in python.exe [16072]."

->
"Unhandled exception at 0x1000CC9D (tcl85.dll) in python.exe:
0xC0000005: Access violation reading location 0x000000C."

-----------------------------------------------------------------------------------------
LEWP commented 5 years ago

@tnsolley your errors look a bit different. I only made the one attempt with the queue function and got the fatal error below at around the 12 hr mark. The error was:

Exception in Tkinter callback
[T]raceback (most recent call last):
File "..../anaconda3/lib/python3.6/tkinter/__init__.py", line 1699, in __call__
    return self.func(*args)
File "..../anaconda3/lib/python3.6/tkinter/__init__.py", line 745, in call [?]t
    func(*args)
File ".../anaconda3/lib/python3.6/site-packages/appJar/appjar.py, line 2[?]94, in _poll
    e()
File "./piClock21t.py", line 267, in getAtom
    app.queueFunction(app.clearLabel("wwa_"+str(i+1)))
File ".../anaconda3/lib/python3.6/site-packages/appJar/appjar.py, line 2[?]31, in
queueFunction
    self.eventQueue.put((5, func, args/kwargs), block=False)
File "..../anaconda3/lib/pyton3.6/queue.py", line 130, in put 
    raise Full
queue.Full

Unfortunately I just did a simple printout of the error and missed one character at the end of each line that word wrapped....hence the [?]. My time is very limited over the next week, so responses to any additional questions posted next week may be delayed.

tnsolley commented 5 years ago

@LEWP I'm still finding the

"python.exe has stopped working"

-> "Debug"
"An unhandled win32 exception occured in python.exe [16072]."

->
"Unhandled exception at 0x1000CC9D (tcl85.dll) in python.exe:
0xC0000005: Access violation reading location 0x000000C."

These continue to happen, around the same chunk of code---but not always on the same # of iterations. Since it's a Win32 exception, I'm inclined to think that there is something going on behind the scenes with AppJar, and thus Tkinter, and thus Tcl/Tk---but I've already confirmed that Tkinter is running properly on my PC, and even started the trouble of manually installing ActiveTcl and Ruby as the Tkinter webpage (outdated, 2011) suggests.

tnsolley commented 5 years ago

Actual code where the crashing seems to happen (general area): Primary area:

""" INF """
# Queued by bivStuff() || p6()
# If Admin, calls s1_0()
# Intermediary between BIV and Setup tabs, move cursor to numSensors, enable buttons.
def t1():
  global accessLevel
  global setupPath
  global newBatch

  try:
    # Need for posterity
    app.setEntryWaitingValidation("numSensors")
    # Do for all items, ~ 15...

    if(newBatch==False):
      # Check permissions
      if accessLevel=="Tester":
        app.setTabbedFrameDisabledTab("Tabs", "Setup", False)
        changeTab("Setup")
        app.setTabbedFrameDisabledTab("Tabs", "BIVs", True)
        app.setLabel("setupPath", 'D:\Setup.csv')
        app.setFocus("numSensors")
        app.enableButton("Import Setup")

        app.disableEntry("numSensors")
        # Do for all items, ~ 15...

      elif accessLevel != "Tester":
        changeTab("Setup")

        app.enableButton("Choose Setup File")
        app.enableButton("Import Setup")
        app.enableButton("Clear Setup Fields")
        app.enableButton("Quit Setup")
        app.enableEntry("numSensors")
        # Do for all items, ~ 15...

        s1_0()
      # End if-elif
    elif(newBatch==True):
      # Clear stuff
      app.clearEntry("numSensors")
      # Do for all items, ~ 15...

      # Reset fields
      app.setEntryWaitingValidation("numSensors")
      # Do for all items, ~ 15...

      # Check permissions
      if accessLevel=="Tester":
        app.setTabbedFrameDisabledTab("Tabs", "Setup", False)
        changeTab("Setup")
        app.setTabbedFrameDisabledTab("Tabs", "BIVs", True)
        app.setLabel("setupPath", 'D:\Setup.csv')
        app.setFocus("numSensors")
        app.enableButton("Import Setup")

        app.disableEntry("numSensors")
        # Do for all items, ~ 15...

      elif accessLevel != "Tester":
        changeTab("Setup")

        app.enableButton("Choose Setup File")
        app.enableButton("Import Setup")
        app.enableButton("Clear Setup Fields")
        app.enableButton("Quit Setup")
        app.enableEntry("numSensors")
        # Do for all items, ~ 15...

        s1_0()
      # End if-elif
    # End if-elif
    app.setStatusbar("Setup Tab", 1)
    app.setStatusbar("Setup Tab", 2)
    """
    INF
    """
    print("-----------------------------------------------------------------------------------------0")
  except Exception as e:
    print("\n")
    print(e)
    print("\n")
  else:  
    # Tied to the "Tester" permissions
    s1("Import Setup")
  # end try-catch  
# End <t1()>  

# Button-press for Setup tab.
# Conditionally thread importSetup()
# Provide Additional functionality, AFTER t1() has been run. 
def s1(name):
  global accessLevel

  while(1):
    try:
      app.clearTextArea("lb2")

      if name=="Import Setup":
        if accessLevel=="Tester":
          app.disableButton("Import Setup")
          app.disableButton("Clear Setup Fields")
          app.disableButton("Quit Setup")
          # This will import and update all the parameters automatically.
          app.thread(importSetup, chain=True)
          break
        # End if-case
        elif accessLevel != "Tester":
          app.enableButton("Clear Setup Fields")
          app.queueFunction(app.infoBox, "Careful1", "Pressing 'Okay' will run the importSetup() function!")
          # This will import and update all the parameters automatically.
          app.thread(importSetup, chain=True)
          break
        # End if-case
      elif name=="Clear Setup Fields":
        app.disableButton("Clear Setup Fields")
        app.queueFunction(app.setTextArea, "lb2", "Clearing Form entries...")
        app.clearEntry("numSensors")
        # Do for all items, ~ 15...

        # Make fields static, permissions-based
        if accessLevel=="Tester":
          app.disableEntry("numSensors")
          # Do for all items, ~ 15...

        else:
          app.enableEntry("numSensors")
          # Do for all items, ~ 15...

        # End if-else  
      elif name=="Quit Setup":
        quitGame() 
      # End if-elif  
    except Exception as e:
      print("\n")
      print(e)
      print("\n")
    else:
      pass
    # End try-catch  
  # End while-loop  
# End <s1()>

# Threaded from s1()
# thread initCom()
# Import Setup File; if Import fails, give error message and option/time to change for Accepted Path.
def importSetup(chain=False):
  # Search-for known Setup File name, location.
  global accessLevel
  global userCheck_s
  global setupPath
  global setupVar
  global numSensors
  global numLoops
  global numSteps
  global numReads
  global maxField_v
  global rampTimePerStep
  global vddMin
  global vddMax
  global testType
  global maxvVal
  global minvVal
  global maxiVal
  global miniVal
  global vdd0min
  global vdd0max
  global vin0min
  global vin0max
  global iin0min
  global iin0max
  global voqmin
  global voqmax

  global configVar

  # Internal data to ARD; not blitted to screen.
  global vSupply0
  global vSupply1
  global iCoil05
  global iCoil16
  global iCoil27
  global iCoil38
  global iCoil49

  app.enableEntry("numSensors")        # Want to be sure the fields are not tampered-with, after validation!
  # Do for all items, ~ 15...

  # Give the user the chance to unplug & fix the file without quitting and restarting the program
  setupConfirm = False
  message = "Importing file..."
  saveLog(message+"\n", "t,a+")
  print(message)   

  while setupConfirm <> True:
    try:
      time.sleep(1)
      app.queueFunction(app.setStatusbar, "importSetup", 1)
      setupVar = list(csv.reader(open(setupPath,'r')))

      # Assign variables, check validity
      """
      https://stackoverflow.com/questions/43986126/how-to-search-for-a-text-or-number-in-a-csv-file-with-python-and-if-exists-p?rq=1
      https://docs.python.org/2/library/csv.html
      https://stackoverflow.com/questions/24606650/reading-csv-file-and-inserting-it-into-2d-list-in-python
      """

      for i in range(0,len(setupVar)):
        if setupVar[i][0] == "numSensors":
          app.setFocus("numSensors")
          numSensors = setupVar[i][1]
          if not allNumbers(numSensors):
            message = ("05 Error, sensors # {%s} must be an integer!" % str(numSensors))
            app.queueFunction(app.setStatusbar, message, 2)
            saveLog(message+"\n", "t,a+")
            print(message)   
            app.setEntry("numSensors", str(numSensors))
            app.setEntryInvalid("numSensors")
            raise IOError
          # End if-case
          if int(numSensors) < 1:
            message = "04.5 Error, can't have less than 1 sensors!"
            app.queueFunction(app.setStatusbar, message, 2)
            saveLog(message+"\n", "t,a+")
            print(message)   
            app.setEntry("numSensors", str(numSensors))
            app.setEntryInvalid("numSensors")
            raise IOError
          # End if-case 
          elif int(numSensors)>10 and int(numSensors)<>10:
            message = "04.6 Error, can't have more than 10 sensors!"
            app.queueFunction(app.setStatusbar, message, 2)
            saveLog(message+"\n", "t,a+")
            print(message)   
            app.setEntry("numSensors", str(numSensors))
            app.setEntryInvalid("numSensors")
            raise IOError
          # End elif-case
          else:
            numSensors = int(numSensors)
            app.setEntry("numSensors", numSensors)
            app.setEntryWaitingValidation("numSensors")
          # End if-else  
        # End if-case   

        # Do for all items, ~ 15...
      # End for-loop

      """ Consider this a Sanity Check, to make SURE the globals have been changed from their default values"""
      ### Consider adding in future? [TBD]
      # http://appjar.info/pythonDialogs/#message-boxes
      # Check to make sure that data has been changed
      if numSensors==0:
        message = "10 Parameter {numSensors} has not been updated properly, please check code"
        app.queueFunction(app.setStatusbar, message, 2)
        saveLog(message+"\n", "t,a+")
        print(message)   
        raise IOError
      # End if-case
      # Do for all items, ~ 15...               

      # If reached this far, then the values were successfully imported and their global value has been updated.
      app.setEntryValid("numSensors")
      # Do for all items, ~ 15...       # _tkinter.TclError ?

      app.disableEntry("numSensors")        # Want to be sure the fields are not tampered-with, after validation!
      # Do for all items, ~ 15...

      print("Setup Success!")
      app.queueFunction(app.setStatusbar, "Setup Success!", 2)
      saveLog("Setup Success!\n", "t,a+")
    # End try-statement
    except IOError: 
      message = "18 Error accessing Setup File"
      app.queueFunction(app.setStatusbar, message, 2)
      saveLog(message+"\n", "t,a+")
      print(message)   
      message = "19 Please make sure the file and its contents are formatted correctly"
      app.queueFunction(app.setStatusbar, message, 2)
      saveLog(message+"\n", "t,a+")
      print(message)   
      message = "20 Search location is: %s" % setupPath
      app.queueFunction(app.setStatusbar, message, 2)
      saveLog(message+"\n", "t,a+")
      print(message)   

      ### Change to PopupWindow
      s = ("21 Please unplug the USB and make whatever changes to the Setup File, then click 'Confirm' when you are ready to try again, or 'Deny' to quit")
      app.queueFunction(app.setStatusbar, s, 2)
      saveLog(s+"\n", "t,a+")
      print(s)
      # Force the program to Wait and Loop until we have the desired input
      app.queueFunction(app.showSubWindow, "Warning!")
      while userCheck_s <> "Confirm":
        if userCheck_s == "Deny":
          logoutFunction()
          break
        # End if-case
        elif userCheck_s!="Confirm" and userCheck_s!="Deny" and userCheck_s!="default":
          #time.sleep(1)
          message = "23 Error, improper input"
          app.queueFunction(app.setStatusbar, message, 2)
          saveLog(message+"\n", "t,a+")
          print(message)   
        """
        else:
          app.queueFunction(app.setStatusbar, "24 Invalid input", 2)
          message = "24 Invalid input\n"
        """
        # End if-else   
      # End while-loop  
      app.queueFunction(app.hideSubWindow, "Warning!")
    # End except-statement
    except SystemError:
      # Start back from the beginning?
      print("Improper variable-size passed to Tkinter.py")
      # Enable access
      app.enableEntry("numSensors")        
      # Do for all items, ~ 15...

      # Clear contents
      app.clearEntry("numSensors")        
      # Do for all items, ~ 15...

      # Waiting
      app.setEntryWaitingValidation("numSensors")        
      # Do for all items, ~ 15...

    # End except-statement 
    except ((_tkinter.TclError) or (TclError)):
      print("\n")
      print("TclError\n")
      app.clearEntry("numSensors")        
      # Do for all items, ~ 15...

      # Waiting
      app.setEntryWaitingValidation("numSensors")        
      # Do for all items, ~ 15...

      app.setFocus("snGroup")
      continue
    # End except-statement
    except NameError:
      print("\n")
      print("NameError\n")
      app.clearEntry("numSensors")        
     # Do for all items, ~ 15...

      # Waiting
      app.setEntryWaitingValidation("numSensors")        
      # Do for all items, ~ 15...

      app.setFocus("snGroup")
      continue
    else:
      message = "25 Setup File read and imported successfully"
      app.queueFunction(app.setStatusbar, message, 2)
      saveLog(message+"\n", "t,a+")
      print(message)   
      app.disableButton("Quit Setup")
      setupConfirm = True
    # End try-catch
  # End while-loop
  if chain==True:
    app.thread(initCom, True)
  # End if-case  
# End <importSetup()>

I see the most number of fails somewhere is this set, e.g. -> GUI stopped between changeTab("Setup") and actual blit of Fields. -> statusBar stopped at "Setup Tab" -> Terminal stopped at "-----------------------------------------------------------------------------------------0"