Elle Nolan
Elle Nolan

Reputation: 399

How would I go about putting StringVar()s within StringVar()?

I wasn't entirely sure how to phrase this, but I'm creating a Tkinter application that includes some ASCII art. Part of it is art, but part is text that the user is intended to read. The raw text art looks like this:

 _______________________________________
|                    .                  |
| +===================================+ |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| |                                   | |
| +===================================+ |
|_______________________________________|
\  +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+  \
|\  \__\__\__\__\__\__\__\__\__\__\_____  \
\ \  \___\__\__\__\__\__\__\__\__\__\____  \
 \ \  \____\__\__\__\__\__\__\__\__\______  \
  \ \  \______\__\__\__\__\__\__\__\_______  \
   \ \  \__\__\__\_________________\__\_____  \
    \ \                                        \
     \ \                                        \
      \ \                                        \
       \ \________________________________________\
        \|________________________________________|

Instead of the 35 spaces in the screen, however, I have 9 %s's in each row, except the first and last, with two spaces on either side, making each one 31 characters long. (It looks like this as a variable):

comp = r"""
 _______________________________________
|                    .                  |
| +===================================+ |
| |                                   | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |  %s  | |
| |                                   | |
| +===================================+ |
|_______________________________________|
\  +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+  \
|\  \__\__\__\__\__\__\__\__\__\__\_____  \
\ \  \___\__\__\__\__\__\__\__\__\__\____  \
 \ \  \____\__\__\__\__\__\__\__\__\______  \
  \ \  \______\__\__\__\__\__\__\__\_______  \
   \ \  \__\__\__\_________________\__\_____  \
    \ \                                        \
     \ \                                        \
      \ \                                        \
       \ \________________________________________\
        \|________________________________________|
"""

I also have a series of "screens" for the computer, which are lists with exactly 9 elements with exactly 31 characters each. Below is an example of what I might do to display something on the screen, assuming the ASCII computer had already been defined as a raw text multiline string as above:

def change_screen():
    comp_art.set(comp % tuple(screen1))

blank = [" "*31, " "*31, " "*31, " "*31, " "*31, " "*31, " "*31, " "*31, " "*31]
screen1 = ["31 characters of text", "31 char..."]  #List with 9 elements


from tkinter import *
root = Tk()
root.option_add("*Label.Font", "courier")

comp_art = StringVar()
comp_art.set(comp % tuple(blank))

comp_label = Label(root, textvariable=comp_art, justify=LEFT)
change_button = Button(root, text="TEST", command=change_screen)
comp_label.pack()
change_button.pack()

root.mainloop()

When run, this code makes a window with a blank computer art and a button under it. When the button is pressed, the text in the list screen1 is displayed on the computer's screen. That works fine, but because this is a game, I'd like to display messages like that one at a time. I've tried something where I define a third list that starts completely blank, and in a for loop it changes each element of the list from blank to text, one at a time. It would look something like this:

import time
def display(x): 
    # x is a list, like screen1 above, that I want to display one line at a time
    show_list = [x[0]]
    for _ in range(8):
        show_list.append(" "*31)
        # Makes the list 9 elements long, and all spaces except for the first
        # one, which is the first element of x, the one I want to display

    for n in range(x):
        comp_art.set(comp % tuple(show_list)
        time.sleep(0.5)
        show_list[n] = x[n]

The latter part of that function, the second for loop, changes the StringVar() called comp_art, which I assigned to a Label before. It always makes the screen display the contents of show_list, but each time I change show_list, I also comp_art.set(), which in turn should change what the computer looks like because that's the StringVar() associated with comp_label. However, when I run that code, it doesn't work. It changes the computer's display, but rather than doing it one line at a time it just waits 4.5 seconds (because of the time.sleep(0.5)'s) while the screen stays blank and then displays what I wanted it to. Does anyone know why, or what I could do differently? Despite my best efforts, I've been unable to think of anything more efficient than this, because if I use multiple labels there's a gap between them and the ASCII art is all off.

I'm also wondering if there's a way to sort of "nest" StringVar()s. For instance, when you define a label in tkinter and give it a textvariable as a StringVar, the label will update automatically as the variable changes. Is there a way to make a StringVar() with StringVar()s in it, so that when you updated one StringVar() in the larger one, the larger one would in turn update, and if it were assigned to a label, that label would update. Is there a way to do that? Would the trace() function in tkinter help me do that, and if so what would I do?

Sorry for the obscene length of this question... Thank you for taking the time to read it, and thank you for any and all answers.

EDIT: I've done several more things, including writing four separate functions that edit the first four lines of the computer, each of which calls time.sleep(0.5) After defining and packing the label, but before the root.mainloop(), I call each of these functions, one at a time. However, what this does is creates a window, just as I wanted, but the window stays completely black until the four functions have run successfully, at which point the label comes onto the screen, with all four of the changed lines. The animation bit that I wanted still isn't working. Now, I have a third list called comp_screen, which is currently equal to blank but which I can change. I also have a function, update_screen(), which updates the computer's StringVar(). What am I doing wrong?

comp_screen = blank

def update_screen():
    computer.set(art.comp % tuple(comp_screen))

def intro_f1():
    wait(0.5)
    comp_screen[0] = intro[0]
    update_comp()

def intro_f2():
    wait(0.5)
    comp_screen[1] = intro[1]
    update_comp()

def intro_f3():
    wait(0.5)
    comp_screen[2] = intro[2]
    update_comp()

def intro_f4():
    wait(0.5)
    comp_screen[3] = intro[3]
    update_comp()

# The Tk() code that makes the window and label (see above - no use typing
# it out again). I have not yet called mainloop().

intro_f1()
intro_f2()
intro_f3()
intro_f4()

root.mainloop()

Upvotes: 2

Views: 93

Answers (1)

Blckknght
Blckknght

Reputation: 104752

The display function you have doesn't do what you want because it's running outside of the tkinter event loop which handles stuff like redrawing the screen. While your loop is running, the rest of the window is stalled, unable to update and show the new text you've set.

So, rather than writing a function with a loop that waits like that, you need to write something that can be called repeatedly (perhaps from a timer) and updates a new line each time.

The same general idea is probably also how you should handle your "nested StringVar" issue. Rather than trying to make one directly contain the other, write a function that extracts the text from the "inner" ones, and puts it into the right place in the "outer" StringVar. Then make that function get called at the right time.

Upvotes: 1

Related Questions