0

On every Tkinter notebook tab, there is a list of checkbuttons and the variables get saved to their corresponding v[ ] (i.e. cb.append(Checkbuttons(.., variables = v[x],..)).

For now, I am encountering this error:

File "/home/pass/OptionsInterface.py", line 27, in __init__
self.ntbk_render(f = self.f1, ntbkLabel="Options",cb = optsCb, msg = optMsg)
File "/home/pass/OptionsInterface.py", line 59, in ntbk_render
text = msg[x][1], command = self.cb_check(v, opt)))
File "/home/pass/OptionsInterface.py", line 46, in cb_check
opt[ix]=(v[ix].get())
IndexError: list assignment index out of range

And I think the error is coming here. I don't know how to access the values of the checkbutton variables.

def cb_check(self, v = [], cb = [], opt = []):
    for ix in range(len(cb)):
      opt[ix]=(v[ix].get())
    print opt

Here are some snippets:

  def cb_check(self, v = [], cb = [], opt = []):
    for ix in range(len(cb)):
      opt[ix]=(v[ix].get())
    print opt

  def ntbk_render(self, f=None, ntbkLabel="", cb = [], msg = []):
    v = []
    opt = []
    msg = get_thug_args(word = ntbkLabel, argList = msg) #Allows to get the equivalent list (2d array) 
      #to serve as texts for their corresponding checkboxes

    for x in range(len(msg)):
      v.append(IntVar())
      off_value = 0
      on_value = 1
      cb.append(Checkbutton(f, variable = v[x], onvalue = on_value, offvalue = off_value,
        text = msg[x][1], command = self.cb_check(v, opt)))
      cb[x].grid(row=self.rowTracker + x, column=0, sticky='w')
      opt.append(off_value)
      cb[-1].deselect()

After solving the error, I want to get all the values of the checkbutton variables of each tab after pressing the button Ok at the bottom. Any tips on how to do it will help!

sparklights
  • 97
  • 1
  • 1
  • 9
  • Need carry session(save if changed) every tab view section. x.get() not work if x exists. Check button maybe disabled on other tab. Create an event, store all status on every step. – dsgdfg Jun 18 '16 at 08:22
  • I don't know if it's the whoel problem, but this is definitely part of the problem: `command = self.cb_check(v, opt)`. You're calling `self.cb_check` _immediately_, and giving the result to the `command` parameter. See http://stackoverflow.com/q/5767228/7432 for more info. – Bryan Oakley Jun 18 '16 at 11:29
  • @BryanOakley Yeah, a while ago, those were the results it was showing. I'm gonna get back to update this. Thanks for the reminder! – sparklights Jun 18 '16 at 15:05
  • @BryanOakley Hi, I followed your suggestion on putting lambda but still having that error. Do you think there is something wrong with how I access the values of the checkbuttons. I really need help, I am stuck here for two days now. Any tips will do – sparklights Jun 18 '16 at 19:51

1 Answers1

2

Alright, so there’s a bit more (… alright, maybe a little more than a bit…) here than I intended, but I’ll leave it on the assumption that you’ll simply take away from it what you need or find of value.

The short answer is that when your Checkbutton calls cb_check, it’s passing the arguments like this:

cb_check(self = self, v = v, cb = opt, opt = [])

I think it’s pretty obvious why you’re getting an IndexError when we write it out like this: you’re using the length of your opt list for indexes to use on the empty list that the function uses when opt is not supplied; in other words, if you have 5 options, the it will try accessing indices [0…4] on empty list [] (obviously, it stops as soon as it fails to access Index 0). Your function doesn’t know that the thing you’re passing it are called v and opt: it simply takes some random references you give it and places them in the order of the positional arguments, filling in keyword arguments in order after that, and then fills out the rest of the keyword arguments with whatever defaults you told it to use.

Semi-Quick Aside:

When trying to fix an error, if I have no idea what went wrong, I would start by inserting a print statement right before it breaks with all the references that are involved in the broken line, which will often tell you what references do not contain the values you thought they had. If this looks fine, then I would step in further and further, checking any lookups/function returns for errors. For example:

def cb_check(self, v = [], cb = [], opt = []):
    for ix in range(len(cb)):
        print(ix, opt, v)  ## First check, for sanity’s sake

        print(v[ix])       ## Second Check if I still can’t figure it out, but
                           ## this is a lookup, not an assignment, so it
                           ## shouldn’t be the problem

        print(v[ix].get()) ## Third Check, again, not an assignment

        print(opt[ix])     ## “opt[ix]={something}” is an assignment, so this is
                           ## (logically) where it’s breaking. Here we’re only
                           ## doing a lookup, so we’ll get a normal IndexError
                           ## instead (it won’t say “assignment”)

        opt[ix]=(v[ix].get()) ##point in code where IndexError was raised

The simple fix would be to change the Checkbutton command to “lambda: self.cb_check(v,cb,opt)” or more explicitly (so we can do a sanity check) “lambda: self.cb_check(v = v, cb = cb, opt = opt).” (I’ll further mention that you can change “lambda:” to “lambda v = v, cb = cb, opt = opt:” to further ensure that you’ll forever be referring to the same lists, but this should be irrelevant, especially because of changes I’ll suggest below)

[The rest of this is: First Section- an explicit explanation of what your code is doing and a critique of it; second section- an alternative approach to how you have this laid out. As mentioned, the above fixes your problem, so the rest of this is simply an exercise in improvement]

In regards to your reference names-

There’s an old adage “Code is read much more often than it is written,” and part of the Zen of Python says: “Explicit is better than Implicit.[…] Readability counts.” So don’t be afraid to type a little bit more to make it easier to see what’s going on (same logic applies to explicitly passing variables to cb_check in the solution above). v can be varis; cb can be cbuttons; ix would be better (in my opinion) as ind or just plain index; f (in ntkb_render) should probably be parent or master.

Imports-

It looks like you’re either doing star (*) imports for tkinter, or explicitly importing parts of it. I’m going to discourage you from doing either of these things for two reasons. The first is the same reason as above: if only a few extra keystrokes makes it easier to see where everything came from, then it’s worth it in the long run. If you need to go through your code later to find every single tkinter Widget/Var/etc, then simply searching “tk” is a lot easier than searching “Frame” then “Checkbutton” then IntVar and so on. Secondly, imports occasionally clash: so if- for example- you do

import time ## or from time import time, even
from datetime import time

life may get kinda hairy for you. So it would be better to “import tkinter as tk” (for example) than the way you are currently doing it.

cb_check-

There are a few things I’ll point out about this function:

1) v, cb, and opt are all required for the function to work correctly; if the empty list reference is used instead, then it’s going to fail unless you created 0 Checkbuttons (because there wouldn’t be anything to iterate over in the “for loop”; regardless, this doesn’t seem like it should ever happen). What this means is that they’re better off simply being positional arguments (no default value). Had you written them this way, the function would have given you an error stating that you weren’t giving it enough information to work with rather than a semi-arbitrary “IndexError.”

2) Because you supply the function with all the information it needs, there is no practical reason (based on the code supplied, at any rate) as to why the function needs to be a method of some object.

3) This function is being called each time you select a Checkbutton, but reupdates the recorded values (in opt) of all the Checkbuttons (instead of just the one that was selected).

4) The opt list is technically redundant: you already have a reference to a list of all the IntVars (v), which are updated/maintained in real time without you having to do anything; it is basically just as easy to perform v[ix].get() as it is to do opt[ix]: in exchange for the “.get()” call when you eventually need the value you have to include a whole extra function and run it repeatedly to make sure your opt list is up to date. To complicate matters further, there’s an argument for v also being redundant, but we’ll get to that later.

And as an extra note: I’m not sure why you wrapped the integer value of your IntVar (v[ix].get()) with parentheses; they seem extraneous, but I don’t know if you’re trying to cast the value in the same manner as C/Java/etc.

ntbk_render-

Again, notice that this function is given nearly everything it needs to be executed, and therefore feels less like a method than a stand-alone function (at this moment; again, we’ll get to this at the end). The way it’s setup also means that it requires all of that information, so they would better off as positional argument as above.

The cb reference-

Unlike v and opt, the cb reference can be supplied to the function. If we follow cb along its path through the code, we’ll find out that its length must always be equal to v and opt. Assumedly, the reason we may want to pass cb to this method but not v or opt is because we only care about the reference to cb in the rest of our code. However, notice that cb is always an empty iterable with an append method (seems safe to assume it will always be an empty list). So either we should be testing to make sure that it’s empty before we start doing anything with it (because it will break our code if it isn’t), or we should just create it at the same time that we’re creating v and opt. Not knowing how your code is set up, I personally would think it would be easiest to initialize it alongside the other two and then simply return it at the end of the method (putting “return cb” at the end of this function and “cb=[whatever].ntbk_render(f = someframe, ntbklabel = “somethug”, msg = argList)”). Getting back to the redundancy of opt and v (point 4 in cb_check), since we’re keeping all the Checkbuttons around in cb, we can use them to access their IntVars when we need to.

msg-

You pass msg to the function, and then use it to the value of “argList” in get_thug_args and replace it with the result. I think it would make more sense to call the keyword that you pass the ntbk_render “argList” because that’s what it is going to be used for, and then simply let msg be the returned value of get_thug_args. (The same line of thought applies to the keyword “ntbkLabel”, for the record)

Iterating- I’m not sure if using an Index Reference (x) is just a habit picked up from more rigid programing languages like C and Java, but iterating is probably one of my favorite advantages (subjective, I know) that Python has over those types of languages. Instead of using x, to get your option out of msg, you can simply step through each individual option inside of msg. The only place that we run into insurmountable problems is when we use self.rowTracker (which, on the subject, is not updated in your code; we’ll fix that for now, but as before, we’ll be dealing with that later). What we can do to amend this is utilize the enumerate function built into Python; this will create a tuple containing the current index followed by the value at the iterated index. Furthermore, because you’re keeping everything in lists, you have to keep going back to the index of the list to get the reference. Instead, simply create references to the things (datatypes/objects) you are creating and then add the references to the lists afterwards.

Below is an adjustment to the code thus far based on most of the things I noted above:

import tkinter as tk ## tk now refers to the instance of the tkinter module that we imported
def ntbk_render(self, parent, word, argList):
    cbuttons=list() ## The use of “list()” here is purely personal preference; feel free to
                    ## continue with brackets
    msg = get_thug_args(word = word, argList=argList) ## returns a 2d array [ [{some value},
                                                      ## checkbutton text,…], …]

    for x,option in enumerate(msg):
        ## Each iteration automatically does x=current index, option=msg[current_index]
        variable = tk.IntVar()
        ## off and on values for Checkbuttons are 0 and 1 respectively by default, so it’s
        ## redundant at the moment to assign them
        chbutton=tk.Checkbutton(parent, variable=variable, text=option[1])
        chbutton.variable = variable ## rather than carrying the variable references around,
                                     ## I’m just going to tack them onto the checkbutton they
                                     ## belong to
        chbutton.grid(row = self.rowTracker + x, column=0, sticky=’w’) 
        chbutton.deselect()
        cbuttons.append(chbutton)
    self.rowTracker += len(msg) ## Updating the rowTracker
    return cbuttons

def get_options(self, cbuttons):
    ## I’m going to keep this new function fairly simple for clarity’s sake
    values=[]
    for chbutton in cbuttons:
        value=chbutton.variable.get() ## It is for this purpose that we made
                                      ## chbutton.variable=variable above
        values.append(value)
    return values

Yes, parts of this are a bit more verbose, but any mistakes in the code are going to be much easier to spot because everything is explicit.

Further Refinement

The last thing I’ll touch on- without going into too much detail because I can’t be sure how much of this was new information for you- is my earlier complaints about how you were passing references around. Now, we already got rid of a lot of complexity by reducing the important parts down to just the list of Checkbuttons (cbuttons), but there are still a few references being passed that we may not need. Rather than dive into a lot more explanation, consider that each of these Notebook Tabs are their own objects and therefore could do their own work: so instead of having your program add options to each tab and carry around all the values to the options, you could relegate that work to the tab itself and then tell it how or what options to add and ask it for its options and values when you need them (instead of doing all that work in the main program).

Reid Ballard
  • 1,480
  • 14
  • 19
  • Thanks for your answer and suggestions on how to make my code more readable and why. Points are appreciated :). I had to admit that I code with no care about future reading problems, and code just to get the work done. The purpose of `ntbk_render` is to really show the options that each tab offers, just need to make it readable and strip things I will not need. Thanks for the tip, I will try to be a good programmer from now, maybe. :D – sparklights Jun 20 '16 at 04:47
  • Now, I really get it. After using 'ntbk_render' and getting cbuttons, I can get an update of the values of the variables by your 'get_options'. Thank you so much for your clear explanation and help! – sparklights Jun 20 '16 at 05:40
  • I didn't get the logic in the update of rowTracker. Isn't `len(msg)` has a fixed length? – sparklights Jun 20 '16 at 06:04
  • From the code provided, it looks like **self.rowTracker** tracks the last row on the grid for the purpose of appending items later. So if we start at row index 0 and add Checkbuttons **A**,**B**,**C** then the last row index (the index of **C**) is 2. Because Enumerate starts at 0 by default, when we add Checkbuttons **D**,**E**,**F** they will have indexes +0,+1,+2. So if we update **self.rowTracker** with the length of the first set (length 3), **D** will have a row index 3+0, **E**->3+1, and **F**->3+2. The next set of Checkbuttons we add will then start at 6: 3 + len( [**D**,**E**,**F**] ) – Reid Ballard Jun 20 '16 at 15:17
  • Running over the code one more time, I noticed that the indenting on the last two lines of ntbk_render got borked when I copied it over: **self.rowTracker** is just getting updated once after we're done adding all the Checkbuttons and- obviously- the return should only happen after we're done iterating. – Reid Ballard Jun 20 '16 at 15:32
  • Oh, ok. I get it now. Thanks! I can now access the checkbutton values of the notebook from every tab. They get printed after I press an Ok button at the bottom. Sorry for this but is there a way that I can maintain the checked buttons after going back to the main window. Because when I press a button for the Notebook to appear, they all go back to zero values and are unchecked. Is there also a way that I can set a checkbutton checked already. I just assigned it as disabled but I can't show that the option is already chosen in default. Thanks! – sparklights Jun 20 '16 at 17:15
  • I'll assume you're saving the options in a dict. In that case, you can pass the dict to ntbk_render and then check it each time you create a Checkbutton, for example: `name=option[1] if name in values_dict: if values_dict[name]: chbutton.selected() else: chbutton.deselected()` – Reid Ballard Jun 20 '16 at 17:44