I'm having what seems like a basic problem but I'm not sure how to fix it.
I'm making a program which lets the user draw lines over a canvas. I previously had the entire code in one file but since I'm going to add many new tools it seemed a sensible decision to isolate all functions into separate files.
However doing this has resulted in problems. Apologies that there's a lot of code to press through. Here's my code:
Root program: root.py (The one I'm actually running)
#Import TKINTER toolset:
from tkinter import *
import starting_variables
#Starting variables:
starting_variables.start_vars()
#Tool width control:
global tool_width
tool_width = Scale(control_panel,from_=1,to=32)
canvas.bind("<Button-1>",line_start_xy)
control_panel.pack(side=LEFT,fill=Y)
tool_width.pack()
wrkspace.pack()
canvas.pack()
#Runs window:
window.mainloop()
This file defines all the variables needed from the start: (starting_variables.py)
from tkinter import *
def start_vars():
#Starting variables:
line_startx = 0
line_starty = 0
line_endx = 0
line_endy = 0
mouse_x = 0
mouse_y = 0
#Main window:
window = Tk()
#Workspace and Canvas:
wrkspace = Frame(window, bg="blue",width=640,height=480)
canvas = Canvas(wrkspace,bg="white",width=640,height=480)
control_panel = Frame(wrkspace,bg="white",width=32,relief=SUNKEN,bd=5)
For some reason, when I run the root program is tells me that in line 10 of root.py control_panel has not been defined, but I ran the function which defines it. What am I doing wrong here?
You need to understand scopes in python. Everything you did in the start_vars function is useless to the rest of the program. The control_panel only exists in the body of that function. When the function is done, you have NO control_panel
Although this is bad programming to begin with, you could try doing something like this:
def start_vars():
#Starting variables:
line_startx = 0
line_starty = 0
line_endx = 0
line_endy = 0
mouse_x = 0
mouse_y = 0
#Main window:
window = Tk()
#Workspace and Canvas:
wrkspace = Frame(window, bg="blue",width=640,height=480)
canvas = Canvas(wrkspace,bg="white",width=640,height=480)
control_panel = Frame(wrkspace,bg="white",width=32,relief=SUNKEN,bd=5)
return locals() # this gives a dictionary containing the local names... you don't have acces to them otherwise.
and then, in your root.py do this at some point
my_vars = starting_variables.start_vars()
Then you can get your the control_panel in root.py like this
control_panel = my_vars['control_panel']
and ONLY AFTER THIS you can use the control_panel variable.
Additionally you can do hacks like this
my_vars = starting_variables.start_vars()
globals().update(my_vars)
But this is a bad advice, and you program is structured badly.
[EDIT]
Since you seem a little confused, here's how I would have done this thing:
class Application:
# simple variables (ints, strings, etc) can be initiated here
line_startx = 0
line_starty = 0
...
# you should have some arguments here, so as not to hard-code everything, so you can reuse this application class. For example, the widths, heights, colors maybe
def __init__(self):
# In the init, put things that take a little more customization, that need parameterization, or that take longer to execute
self.window = TK()
self.workspace = Frame(self.window, bg="blue",width=640,height=480)
self.canvas = Canvas(self.wrkspace, bg="white", width=640, height=480)
self.control_panel = Frame(self.wrkspace, bg="white", width=32, relief=SUNKEN, bd=5)
This can be in your starting_variables.py module
Then, in your root.py, you can put
from tkinter import *
import starting_variables
#Starting variables:
app = starting_variables.Application() # here's where you'd pass the heights, widths, and the rest of the parameters.
#Tool width control:
tool_width = Scale(app.control_panel,from_=1,to=32)
app.canvas.bind("<Button-1>",app.line_start_xy)
app.control_panel.pack(side=LEFT,fill=Y)
tool_width.pack()
app.wrkspace.pack()
app.canvas.pack()
#Runs window:
app.window.mainloop()
I'm not sure if you're familiar with object-orientation, but this is just the way I'd do it. Your way would totally work in C, but Python just doesn't have global variables as you expected them to be.... everything's an attribute on a module, an object...something.
Why I think the way I wrote the code is better, is because touching functions like locals, globals, or touching module attributes from other modules is considered bad in Python because it's unexpected. People expect functions to return values, object to hold attributes and methods, classes to describe objects, etc... Sorry If I'm boring you with too much theory man, just hope the explanations help :)
Related
I am making a rock paper scissors program and I need to change whose turn it is when they click a button, but I do not want to use the global keyword because the program is inside of a function.
Here is an example of what I am trying to do without using the global keyword:
from tkinter import *
root = Tk()
var = 1
def buttonClick():
global var
var += 1
print(var)
button = Button(root, text="button", command=buttonClick).pack()
root.mainloop()
I have tried to write command=(var += 1) but that did not work.
If the whole script is inside a function (including the buttonClick() function) then use the nonlocal keyword:
def buttonClick():
nonlocal var
var += 1
print(var)
If the function is not nested, the only way is to create a global variable and the global keyword in both functions.
No, you indeed can't. It would be possible to change the contents of a global varif it were a list, for example. And then you could write your command as a lambda expression with no full function body.
But this is not the best design at all.
Tkinter event model couples nicely with Python object model - in a way that instead of just dropping your UI components at the toplevel (everything global), coordinated by sparse functions, you can contain everything UI related in a class - even if it will ever have just one instance - that way your program can access the var as "self.var" and the command as "self.button_click" with little danger of things messing up were they should not.
It is just that most documentation and tutorial you find out will have OOP examples of inheriting tkinter objects themselves, and adding your elements on top of the existing classes. I am strongly opposed to that approach: tkinter classes are complex enough, with hundreds of methods and attributes -wereas even a sophisticated program will only need a few dozens of internal states for you to worry about.
Best thing is association: everything you will ever care to access should eb a member of your class. In the start of your program, you instantiate your class, that will create the UI elements and keep references to them:
import tkinter as tk # avoid wildcard imports: it is hard to track what is available on the global namespace
class App:
def __init__(self):
self.root = tk.Tk()
self.var = 1
# keep a refernce to the button (not actually needed, but you might)
self.button = tk.Button(self.root, text="button", command=self.buttonClick)
self.button.pack()
def buttonClick(self):
# the button command is bound to a class instance, so
# we get "self" as the object which has the "var" we want to change
self.var += 1
print(self.var)
def run(self):
self.root.mainloop()
if __name__ == "__main__": # <- guard condition which allows claases and functions defined here to be imported by larger programs
app = App()
app.run()
Yes, you can. Here's a hacky way of doing it that illustrates that it can be done, although it's certainly not a recommended way of doing such things. Disclaimer: I got the idea from an answer to a related question.
from tkinter import *
root = Tk()
var = 1
button = Button(root, text="button",
command=lambda: (globals().update(var=var+1), print(var)))
button.pack()
root.mainloop()
The goal is to achieve different "screens" in TkInter and change between them. The easiest to imagine this is to think of a mobile app, where one clicks on the icon, for example "Add new", and new screen opens. The application has total 7 screens and it should be able to change screens according to user actions.
Setup is on Raspberry Pi with LCD+touchscreen attached. I am using tkinter in Python3. Canvas is used to show elements on the screen.
Since I am coming from embedded hardware world and have very little experience in Python, and generally high-level languages, I approached this with switch-case logic. In Python this is if-elif-elif...
I have tried various things:
Making global canvas object. Having a variable programState which determines which screen is currently shown. This obviously didn't work because it would just run once and get stuck at the mainloop below.
from tkinter import *
import time
root = Tk()
programState = 0
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
time.sleep(2)
canvas.delete(ALL) #delete all objects from canvas
programState = 1
elif(programState == 1):
....
....
....
root.mainloop()
Using root.after function but this failed and wouldn't show anything on the screen, it would only create canvas. I probably didn't use it at the right place.
Trying making another thread for changing screens, just to test threading option. It gets stuck at first image and never moves to second one.
from tkinter import *
from threading import Thread
from time import sleep
def threadFun():
while True:
backgroundImage = PhotoImage(file="image1.gif")
backgroundImage2 = PhotoImage(file="image2.gif")
canvas.create_image(0,0,image=backgroundImage, anchor=NW)
sleep(2)
canvas.delete(ALL)
canvas.create_image(0,0,image=backgroundImage2, anchor=NW)
root = Tk()
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
# daemon=True kills the thread when you close the GUI, otherwise it would continue to run and raise an error.
Thread(target=threadFun, daemon=True).start()
root.mainloop()
I expect this app could change screens using a special thread which would call a function which redraws elements on the canvas, but this has been failing so far. As much as I understand now, threads might be the best option. They are closest to my way of thinking with infinite loop (while True) and closest to my logic.
What are options here? How deleting whole screen and redrawing it (what I call making a new "screen") can be achieved?
Tkinter, like most GUI toolkits, is event driven. You simply need to create a function that deletes the old screen and creates the new, and then does this in response to an event (button click, timer, whatever).
Using your first canvas example
In your first example you want to automatically switch pages after two seconds. That can be done by using after to schedule a function to run after the timeout. Then it's just a matter of moving your redraw logic into a function.
For example:
def set_programState(new_state):
global programState
programState = new_state
refresh()
def refresh():
canvas.delete("all")
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
canvas.after(2000, set_programState, 1)
elif(programState == 1):
...
Using python objects
Arguably a better solution is to make each page be a class based off of a widget. Doing so makes it easy to add or remove everything at once by adding or removing that one widget (because destroying a widget also destroys all of its children)
Then it's just a matter of deleting the old object and instantiating the new. You can create a mapping of state number to class name if you like the state-driven concept, and use that mapping to determine which class to instantiate.
For example:
class ThisPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
class ThatPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
page_map = {0: ThisPage, 1: ThatPage}
current_page = None
...
def refresh():
global current_page
if current_page:
current_page.destroy()
new_page_class = page_map[programstate]
current_page = new_page_class()
current_page.pack(fill="both", expand=True)
The above code is somewhat ham-fisted, but hopefully it illustrates the basic technique.
Just like with the first example, you can call update() from any sort of event: a button click, a timer, or any other sort of event supported by tkinter. For example, to bind the escape key to always take you to the initial state you could do something like this:
def reset_state(event):
global programState
programState = 0
refresh()
root.bind("<Escape>", reset_state)
I'm trying to create a menu for a school project and wanted to make it so that it reads the files in a given directory and creates buttons on the screen based on what files are there. My main problem at the moment is that I can't get the Button to send the information i need.
I've tried to use the functions in a different way but this is the closest I can get to what I want.
class Menu(Frame):
def __init__(self, master=None):
#working out how big to make the window
global files_in_area
files_in_area = []
files_in_area = os.listdir(menu_folder_loc)
print files_in_area
a = len(files_in_area)
b = 3
rows = a / b
rows = rows + 1
window_height = rows * 56
root.geometry('%dx%d+%d+%d' % (450, window_height, 0, 0))
Frame.__init__(self, master)
self.master = master
self.init_menu()
def init_menu(self):
self.master.title("Menu")
self.pack(fill=BOTH, expand=1)
for i in range(0, len(files_in_area)):
#button placment
a = i
b = 3
row_no = a / b
row_no = row_no + 1
column_no = a % b
global file_name
file_name = str(files_in_area[i])
b1 = Button(self, text=file_name, bg= "red", height=3, width=20, command=self.client_print_file).grid(row=row_no, column=column_no, sticky=W)
def client_print_file(self):
print file_name
I've got the code to work when finding the files and putting them in a tkinter window, and when I click on the button, it should open the file (I'm just printing the file name at the moment to make sure it works). However it just prints the last file in the list.
Sorry if this question is a bit confusing, and thanks in advance.
This is not a problem about tkinter but about scopes and oop in python. I assume you got started with python by reading online stuff or got a fast introduction. My first advice is: do not use/copy from someone else's code something if you do not understand it. If you copy, try to dig in yourself and MAKE it your own code. One good way of reading up stuff is the very good online documentation of python.
Some exercises to get you started: 1) Research about python's scoping of variables.
2) More importantly: read up on classes, i.e. object oriented programming.
To the code: 1) Ditch the use of global alltogether. No need here. If you want to use the variable files_in_area in all methods of the class simply make it an attribute of the class, just like you did with master which you then go on to use in another method where it was not defined! Now to the variable filename: Again, ditch global. Then filename lives in the scope of the for loop which is exactly what you want here.
See it like this: if you had not used the variable filename at all but simply put text=str(files_in_area[i]) inside the Button-constructor as argument, you wouldnt have run into this problem here - at its core this really is a problem about thinking for yourself and not copying too much code from someone else - I know it because this was a big issue for me for long time.
The hardest thing here is how to pass a command depending on the i of the for loop to each button. You can find a lot here on stackoverflow. This again is about variables and their scope but also about their binding. In each loop pass, we want to generate a value of command depending on i. One way to use this is using the (at first mystical) lambda: command=lambda i=i: self.client_print_file(i). And define def client_print_file(self, i): ... with an additional argument i. The critical thing here is the binding. What would not work is command=lambda i: self.client_print_file(i).
(Another way instead of using lambda would be using partial from functools...)
Assignment: read up on lambda and what it is about and about name binding and variables in python :)
Background: I'm trying to write a graphics library for python built on top of Tkinter. I therefore would like to abstract all of Tkinter's functionality from the user and have method calls that modify the root window sequentially in a Processing-like manner. e.g my library (let's call it mylib) would enable you to write code like this:
from mylib import * #this would be my library
window(400, 400) #open a new window to add objects to
color('red') #set the color of all subsequent items to red
circle(radius=5, centerx = 0, centery=0) #make a red circle
line(10,10, 20, 20) #red line going from (10, 10) to (20, 20)
color('blue')
line(50, 50, 100, 100) #blue line
I thought of implementing it like this:
try:
from tkinter import *
except:
from Tkinter import *
_root = Tk()
class MyCanvas(Canvas):
def __init__(self, width=400, height=400):
master = Toplevel(_root)
Canvas.__init__(self, master, width=width, height=height)
self.pack()
self.items = []
self.width = width
self.height = height
_root.mainloop()
global _c
_c = None
def window():
_c = MyCanvas()
_c.pack()
def line(a,b,c,d):
#config code goes here
_c.create_line(a,b,c,d)
#test
window()
line(10, 10, 50, 50)
That didn't work because Python wouldn't get out of mainloop() so I tried this instead:
try:
from tkinter import *
except:
from Tkinter import *
_root = Tk()
class MyCanvas(Canvas):
def __init__(self, width=400, height=400):
master = Toplevel(_root)
Canvas.__init__(self, master, width=width, height=height)
self.pack()
self.items = []
self.width = width
self.height = height
global _c
_c = None
def window():
_c = MyCanvas()
_c.pack()
def line(a,b,c,d):
#config code goes here
_c.create_line(a,b,c,d)
_root.update_idletasks()
#test
window()
line(10, 10, 50, 50)
But that didn't work too - it just froze. And I tried replacing update_idletasks with update. The window froze again. How do I use update properly?
Is there a way to accomplish this using mainloop() without having the user explicitly call it?
Or is there a way to edit widgets in mainloop? I thought about using after but I didn't see how that would solve the problem.
If no answer exits given these constraints would writing the package in PyOpenGL be useful or portable ? Should I write the module using C from scratch? Does anything else exist? Help me!!!
Sorry for the long question. I've been at this for hours now to no avail.
It looks to me like you might be better off writing this as a tcl implementation and using wish as the executable. WHat you seem to be doing is defining a domain specific language (DSL) that you will interpret into commands that drive Tk. There doesn't seem to be anything that particularly requires python here.
You need a mainloop. Any windowing application has to have an event pump that obtains messages from the system and dispatches them to your windows as necessary. All such applications have to end up at a point where the main UI thread just pumps this message queue. In the case of the Tk wish executable, this is built into the interpreter. Wish sources your script then runs the event loop. You could emulate that in a python library by loading the client code and calling the application main function as an after event. You would probably have to ensure that the client code does not call mainloop to avoid a conflict.
If you've never looked at Tcl/Tk before - you might want to. What you are defining looks not very distant from a Tcl/Tk application really.
We have a functioning program that uses Tkinter as its GUI. Everything works fine however different branches of the code are now using different hardware which realistically need different buttons. Hence we'd like to have the main GUI import modules representing the buttons depending on what hardware is being used.
I've cut out some of the code below, I'm interested in removing the makemenu() function to a separate module, hence when it is called in the Application __init__ (self.makemenu(master)) I would like to make that a reference to a separate module. I've tried doing this and am having trouble. Is this even possible?
I'm a little confused on the parent structure, what needs to be passed to my button module, etc.? I know this is a poorly constructed question but if anyone is able to advise if this is possible and put my on the right track that would be great. For example if someone could show how to modify this code to have the buttons defined in a separate module I could figure out how to do the same in my module.
# Import necessary libraries
import sys
import os
import Tkinter as tk
class Application(tk.Frame):
##################################################################
## Final functions are designed to initialize the GUI and
## connect various mouse movements to useful functions.
##################################################################
def definevars(self):
'''Original definition of all of the key variables that
we need to keep track of while running the GUI
'''
self.disable = True
self.savimgstatus = 'off'
self.mode = 'Standby'
self.status = 'Not Ready'
def makemenu(self,master):
''' Function to create the main menu bar across
the top of the GUI.
'''
self.menubar = tk.Menu(master)
## Motor Submenu
motormenu = tk.Menu(self.menubar,tearoff=1)
motormenu.add_command(label='ALT',state='disabled')
motormenu.add_command(label='error check',
command=lambda: self.geterror('alt'))
motormenu.add_separator()
motormenu.add_command(label='AZ',state='disabled')
motormenu.add_command(label='error check',
command=lambda: self.geterror('az'))
self.menubar.add_cascade(label='Tracker Motors',menu=motormenu)
## Set the big menu as the main menu bar.
master.config(menu=self.menubar)
def __init__(self,tcpconn,DOME,TRACKERSTAGE, master=None):
'''Main function to initialize the GUI. Will scale
the size of the GUI to fit any size screen... to a
point. It will not allow it to be smaller than
600x800.
'''
self.buf = 1024
## Check resolution of screen. Make GUI 2/3rds of size
## unless that means under 600x800.
fh = round(master.winfo_screenheight()*2./3.)
fw = round(master.winfo_screenwidth()*2./3.)
if fh < 600: fh = 600
if fw < 800: fw = 800
print 'GUI resolution set to {0} x {1}'.format(fw,fh)
self.fw = fw
self.fh = fh
self.imwidth = int(0.45*self.fw)
self.imheight = int(0.45*self.fh)
self.imcentx = self.imwidth/2
self.imcenty = self.imheight/2this
## Initialize Frame
tk.Frame.__init__(self, master, height=fh,width=fw)
self.grid()
self.grid_propagate(0)
## Initialize Various variables.
self.definevars()
## Create buttons, etc.
self.createWidgets()
self.makemenu(master)
self.disableall()
## Main Loop function
self.checkoutput()
###################################################################
# Initialize GUI window.
root = tk.Tk()
root.title('Hardware') # window title
app = Application(master=root)
app.mainloop() # go into the main program loop
sys.exit()
If you want to move makemenu to a separate module, that should be pretty simple. However, you'll need to change a few things.
Since makemenu no longer has a reference to self (or has a different reference, if you implement it as a separate class), you need to replace calls like command=lambda: self.geterror('alt')) to be command=lambda: master.geterror('alt')).
The other thing I recommend is to remove the call to add the menu to the root. I believe that modules shouldn't have side effects like this -- the function should make a menu and return it, and let the caller decide how to use it, ie:
self.menubar=makemenu(master)
master.configure(menu=self.menubar)
Roughly speaking, this is a variation of the MVC (model/view/controller) architectural pattern where the Application instance is your controller (and also part of the view unless you make modules of all your UI code). The menu is part of the view, and forwards UI functions to the controller for execution.
Your application then looks something like this:
from makemenu import makemenu
class Application(...):
def __init__(...):
...
self.menubar = makemenu(master)
master.config(menu=self.menubar)
...