Tkinter's mainloop() Calling Button Function Automatically - python

I'm pretty new to Tkinter but starting to try to put more complex GUI's in my scripts. So this must be pretty basic but I can't figure out what's going wrong.
What I want is pretty simple, a bunch of data entry options and at the bottom an exit and submit buttons. It seems though that mainloop() or something else keeps running the button's command without any user input. Thus because of the exit button, the applet is destroyed before it even shows up. If I put the buttons outside of the mainloop(), there is no problem, but of course it doesn't make sense and the buttons don't show up.
button_exit = Tkinter.Button(root, text = 'Exit', command = root.destroy())
button_exit.grid(row=3, column=0, pady=10, sticky='E')
button_query = Tkinter.Button(root, text = 'Query', command = intQuery())
button_query.grid(row=3, column=1, padx=10, sticky='E')
root.mainloop()
That's basically the problem area of the code. The rest is just that data entry fields, most of the script hasn't even been written yet.
Thanks in advance.

Change
command = root.destroy()
to
command = root.destroy
The reason is this: The parentheses call the method, and method arguments are evaluated before being passed to the method. This is why your program is exiting too early.
Without the parentheses, you are referencing the method as an object which can be passed to the Tkinter.Button, stored, and called later when the button is pressed.
Then do the same with command = intQuery().

Related

Disable mouse press on Tkinter Text Widget

Background
I have created a tk.Text widget within a frame and used grid to place successfully within my tool. I use methods to run read commands and display the results within the tk.Text widget. I only want the tk.Text to display the results nothing else and this works as expected.
resultsbox = tk.Text(centerframe)
resultsbox.config(state='disabled', bd='10', height=38, width=92, bg='black', fg='green', font='fixedsys')
resultsbox.grid(row=0, column=1, sticky="e")
I use the following commands on each method to enable my write results and then disable the user from typing into the tk.Text widget:
Example
resultsbox.config(state='normal')
resultsbox.insert(tk.INSERT, RESULT1, 'standard', "\n\n")
resultsbox.config(state='disabled')
This again works as expected.
Issue
The problem i have is if a user clicks within the tk.Text widget even though it is disabled the next result that the tool writes to the widget will place it at the point where the user last clicked causing a very unreadable result.
Example
This Screenshot shows if a user has clicked within the widget and then the next result is written.
Summary
Is it possible to stop the user pressing within the tk.Text widget?
You don't have to disable your mouse keys. It is very simple. You just replace tk.INSERT with tk.END. This tells where to insert. Either at the marked position or at the end. Check out my example. I created 2 buttons, one with tk.INSERT and one with tk.END.
import tkinter as tk
def insert_END():
RESULT1 = '---OUTPUT---'
resultsbox.config(state='normal')
resultsbox.insert(tk.END, RESULT1, 'standard', "\n\n")
resultsbox.config(state='disabled')
def insert_INSERT():
RESULT1 = '---OUTPUT---'
resultsbox.config(state='normal')
resultsbox.insert(tk.INSERT, RESULT1, 'standard', "\n\n")
resultsbox.config(state='disabled')
root = tk.Tk()
root.geometry("1000x1000")
frame1 = tk.Frame(root).grid(row=1, column=1)
resultsbox = tk.Text(frame1)
resultsbox.config(state='disabled', bd='10', height=38, width=92, bg='black', fg='green', font='fixedsys')
resultsbox.grid(row=0, rowspan=2, column=1, sticky="e")
btn1 = tk.Button(master=frame1, text='END', command=insert_END)
btn1.grid(row=0, column=0, padx=30)
btn2 = tk.Button(master=frame1, text='INSERT', command=insert_INSERT)
btn2.grid(row=1, column=0, padx=30)
root.mainloop()
The state 'disampled' is an editing property and means that the text cannot be modified. I do not know if that is linked with the cursor position issue you describe (as I get it).
If I understand it correctly the issue is that even when the textbox is disabled and you click in it, the position of the click is remembered and the next time you enable the textbox again it inserts the new text at the last position. If this is the issue, I would try to place the cursor at the end of the actual text (e.g. Text.insert(tk.END,...) before the disable command. When you change the status to 'enable' you can then add the new text to the end of the previous string. Alternatively I would try each time I enable the Text widget to check where the cursor is and move it to the end of the previous string.

How to refresh a dictionary in a tkinter mainloop() in Python?

I am new to programming and I have been trying to understand when python/tkinter refresh values of dictionaries.
I've got the following code which basically creates a dictionary called states in which the item is linked to a value of 0.
I then launch the GUI.
from Tkinter import *
import Tkinter, tkFileDialog
states = {'open_thermo':0}
def is_true(state): # in quotes
states[state] = 1
def Station():
Station = Tk()
if states['open_thermo'] == True:
Label(master=Station, text='Thermo has been opened').grid(row=0)
Button(master=Station, text='press to end', command= lambda: combine_funcs(Station.destroy())).grid(row=4, columnspan=1, column=1, pady=4)
else:
Label(master=Station, text='Do you wish to open Thermo ?').grid(row=0)
Button(master=Station, text='press to open Thermo', command= lambda: is_true('open_thermo')).grid(row=4, columnspan=1, column=1, pady=4)
print states['open_thermo'] == True #1st print statement
mainloop()
start = Tk()
Label(master=start, text='start').grid(row=0)
Button(master=start, text='press to start', command= lambda: combine_funcs(start.destroy(),Station())).grid(row=4, columnspan=1, column=1, pady=4)
mainloop()
print states['open_thermo'] == True #2nd print statement
I do not understand two things:
why the print statement commented as #1st print statement doesn't
print over and over again before I click the button. I though
mainloop() repeated the code above it over and over again.
why, when I click the button, the value of states['open_thermo']
doesn't change to 1, and then the if states['open_thermo'] == True: statement becomes true because of mainloop(). I would expect
the label to change to:
Label(master=Station, text='Thermo has been opened').grid(row=0)
Button(master=Station, text='press to end', command= lambda: combine_funcs(Station.destroy())).grid(row=4, columnspan=1, column=1, pady=4)
Thank you in advance for your help !
I am new to programming and I have been trying to understand when python/tkinter refresh values of dictionaries.
python/tkinter never "refreshes values of dictionaries". The dictionaries change immediately when you tell them to change.
why the print statement commented as #1st print statement doesn't print over and over again before I click the button. I though mainloop() repeated the code above it over and over again.
No, mainloop does not repeat code that is above it. The code above it runs exactly once. mainloop() is a simple loop that waits for events and responds to them based on bindings. No code will run after mainloop until the window is destroyed.
why, when I click the button, the value of states['open_thermo'] doesn't change to 1, and then the if states['open_thermo'] == True: statement becomes true because of mainloop().
It's unclear which button you're talking about. However, it looks like both buttons that call is_true will actually change the value. I don't see any evidence that it doesn't. Since is_true doesn't do anything except change the value, I don't see how you think it's not changing.
Again, the code in Station runs exactly once when it is called. It doesn't run over and over and over. You click the button once, it is called once.

Python Tkinter one callback function for two buttons

I have been looking around for a long time for answers to this question but still hasn't find anything. I am creating a GUI using Tkinter, and I have two buttons that do mostly the same thing except they receive information from different widgets. One button is for an Entry widget and the other is for a Listbox widget.
The callback function for these two buttons is long (about 200 lines), so I don't want to have separate functions for each button. I have if-statements in the beginning of this callback function to check which button is clicked, and then the codes will take the corresponding value. But I am not sure if the following code shows the right way to do this because apparently it doesn't work perfectly in my program. The callback function would only work for the first time, and if I click the other button I will receive an error. Here is a sample code that I created to illustrate the idea. Note that I want to check if the button is clicked, I do not want to check if the 'value' exists. Please help.
from Tkinter import *
root = Tk()
def DoSomething():
# is this the right way to check which button is clicked?
if button1:
value = user_input.get()
elif button2:
value = choice.get(choice.curselection()[0])
# then more codes that take 'value' as input.
button1 = Button(master,text='Search',command=DoSomething)
button1.pack()
button2 = Button(master,text='Search',command=DoSomething)
button2.pack()
user_input = Entry(master)
user_input.pack()
choice = Listbox(master,selectmode=SINGLE)
choice.pack()
#assume there are items in the listbox, I skipped this portion
root.mainloop()
If you want to pass the actual widget into the callback, you can do it like this:
button1 = Button(master, text='Search')
button1.configure(command=lambda widget=button1: DoSomething(widget))
button2 = Button(master, text='Search')
button2.configure(command=lambda widget=button2: DoSomething(widget))
Another choice is to simply pass in a literal string if you don't really need a reference to the widget:
button1 = Button(..., command=lambda widget="button1": DoSomething(widget))
button2 = Button(..., command=lambda widget="button2": DoSomething(widget))
Another choice is to give each button a unique callback, and have that callback do only the thing that is unique to that button:
button1 = Button(..., command=ButtonOneCallback)
button2 = Button(..., command=ButtonTwoCallback)
def ButtonOneCallback():
value = user_input.get()
DoSomething(value)
def ButtonTwoCallback():
value=choice.get(choice.curselection()[0])
DoSomething(value)
def DoSomething(value):
...
There are other ways to solve the same problem, but hopefully this will give you the general idea of how to pass values to a button callback, or how you can avoid needing to do that in the first place.

Tkinter unexpected behavior

I have a set of methods in my program the use Tkinter that don't behave like I thought they would. I want to be able to push a button in the window and have more text fields appear, and be able to return a list of the results in the text fields. Here is what I have:
def expandChoice(self):
root = Tk()
choices = []
plusButton = Button (root, text='+', command=self.addChoice(root, choices))
plusButton.pack()
quitButton = Button (root, text='Ok', command=root.destroy )
quitButton.pack()
root.mainloop()
return choices
def addChoice(self, parent, variables):
variables.append(StringVar())
text = Entry(parent, textvariable=variables[len(variables)-1])
text.pack()
What happens is that one text field appears when the window loads (above the buttons), and the plus button does nothing. What am I doing wrong? It seems like the addChoice method get called automatically when the first button's constructor is called and then doesn't work after that.
The command option takes a reference to a callable. You, however, are calling addChoice immediately, then assigning what that retuns (None) to the command option.
You need to do something like Button(...command=self.addChoice)
If you need to pass arguments you will need to either use a lambda or functools.partial. Search for either of those on this site -- variations of this question has been asked and answered many times on SO.

Button command being called automatically

For some reason, this Button is automatically calling bot_analysis_frame without the button being pressed. I'm guessing it's because the command is a function with arguments.
Is there a way to have the button only call this function and pass the required variables only upon being pressed?
Button(topAnalysisFrame, text='OK', command=bot_analysis_frame(eventConditionL, eventBreakL)).pack(side=LEFT)
Read the section here on passing callbacks.
You are storing the result of that function to the command argument and not the function itself.
I believe this:
command = lambda: bot_analysis_frame(eventConditionL,eventBreakL)
might work for you.
I'm pretty sure this has been answered before. Instead of this:
Button(topAnalysisFrame,
text='OK',
command=bot_analysis_frame(eventConditionL,eventBreakL)).pack(side=LEFT)
You could use lambda like so:
Button(topAnalysisFrame,
text="OK",
command=lambda: bot_analysis_frame(eventConditionL, eventBreakL)).pack(side=LEFT)

Categories