Python / tkinter: Binding right button click in an array of widgets - python

enter code hereI am rendering an array of buttons onto the screen and want to implement a right-click function. I have left click working with the default "command=" option on the widget, but for some reason, I can't seem to get the button bind to kick in. My code looks like this:
for key, value in sorted_widget_properties:
if key not in self._filter_list:
continue
colour = value[appearance_mode_index]
if row > 18:
offset = 4
row = 1
# Light mode colours
if row == 1:
pad_y = (10, 0)
else:
pad_y = 5
lbl_property = ctk.CTkLabel(master=widget_frame, text=' ' + key)
lbl_property.grid(row=row, column=1 + offset, sticky='w', pady=pad_y)
btn_property = ctk.CTkButton(master=widget_frame,
border_width=1,
fg_color=colour,
width=button_width,
height=button_height,
text='',
command=lambda widget_property=key: self.colour_picker(widget_property),
corner_radius=3)
btn_property.grid(row=row, column=0 + offset, padx=5, pady=pad_y)
self.widgets[key] = {"widget": btn_property, "button": btn_property, "colour": colour,
'label': lbl_property}
# Set a binding so that we can paste a colour, previously copied into our clipboard
self.widgets[key]['widget'].bind("<Button-3>",
lambda widget_property=key: self._paste_colour(widget_property))
row += 1
I have a print statement in the _paste_colour class method, and it appears that the function is never called and nothing is ever printed:
def _paste_colour(self, widget_property):
print('PASTE COLOUR!"')
new_colour = pyperclip.paste()
if len(new_colour) != 7:
self._status_bar.set_status_text(status_text='Attempt to paste a bad colour code - ignored.')
self._set_widget_colour(widget_property=widget_property, new_colour=new_colour)
self._status_bar.set_status_text(
status_text=f'Colour {new_colour} assigned to widget property {widget_property}.')
Any suggestions appreciated.
Thanks,
Clive

Next time please provide a minimal reproducible example. You have a lot of variables that are not defined in the code you show and the tkinter class is not provided either. Also we do not know if you get any error messages. This makes it difficult to troubleshoot. It might be that you are just missing an event parameter, but not sure if I get your issue correctly.
import tkinter as tk
root = tk.Tk()
root.geometry("200x200")
widget_frame = tk.Frame(root).grid(row=1, column=1)
def right_click(x):
print('right clicked')
print(x)
def left_click():
print('left clicked')
lbl_property = tk.Label(master=widget_frame, text='Label')
lbl_property.grid(row=0, column=0, sticky='w')
btn_property = tk.Button(master=widget_frame,
text='button',
command=left_click
)
btn_property.grid(row=0, column=1, padx=5, pady=5)
param='some parameter'
btn_property.bind("<Button-3>", lambda event, x=param: right_click(x))
root.mainloop()

OK, apologies, I really should have done a bit more prep.
Anyway, taking the above example and replacing with a customtkinter widget. I have reproduced the problem:
import tkinter as tk
import customtkinter as ctk
root = tk.Tk()
root.geometry("200x200")
widget_frame = tk.Frame(root).grid(row=1, column=1)
def right_click(x):
print('right clicked')
print(x)
def left_click():
print('left clicked')
lbl_property = tk.Label(master=widget_frame, text='Label')
lbl_property.grid(row=0, column=0, sticky='w')
btn_property = ctk.CTkButton(master=widget_frame,
text='button',
command=left_click
)
btn_property.grid(row=0, column=1, padx=5, pady=5)
param='some parameter'
btn_property.bind("<Button-3>", lambda event, x=param: right_click(x))
root.mainloop()
Running the above, masks out the right click binding. No errors, just doesn't work.
I'll raise the issue on GitHub on the customtkinter forum.

Related

How to use GUI Tkinter button to increment a number and display it

I'm new to trying out python GUI's and tried tkinter and pyglet, but only through tutorials, in-order-to understand the basic classes and functions. But what I'm currently trying to do is to get a button to increase a number whilst displaying that number at the same time. Somehow, even though the variable number was stated globally as 0, the function to increase it doesn't do anything, it actually produces an error: 'UnboundLocalError: local variable 'number' referenced before assignment'. I have no idea how to correct this.
The tutorials I've seen on both YouTube and as an article, don't talk about how to do this exactly. The article does mention how to change a certain text though, but not a previously created variable (which in my case would be 'number').
from tkinter import *
number = 0
window = Tk()
window.title("Programme")
window.geometry('350x250')
label = Label(window, text=number)
label.grid(column=0,row=0)
def clicked():
number += 1
button = Button(window, text="Push Me", command=clicked)
button.grid(column=1, row=2)
window.mainloop()
Is there any way to do this?
Also I've been looking for how to add time, to handle events and such, through ticks. But everything I find on the internet is about literally displaying a clock on the GUI, which is useless, or at least I don't know how to use it to have a ticking function.
You need to increment the number, like you do, but also update the Label to display the new number:
from tkinter import *
number = 0
window = Tk()
window.title("Programme")
window.geometry('350x250')
label = Label(window, text=number)
label.grid(column=0,row=0)
def clicked():
global number
number += 1
label.config(text=number)
button = Button(window, text="Push Me", command=clicked)
button.grid(column=1, row=2)
window.mainloop()
An easier way to do this is to use tkinter's version of an integer: IntVar. It takes care of the Label updates automatically, but it requires you use get() and set() to work with it.
from tkinter import *
def clicked():
number.set(number.get()+1)
window = Tk()
window.title("Programme")
window.geometry('350x250')
number = IntVar()
label = Label(window, textvariable=number)
label.grid(column=0,row=0)
button = Button(window, text="Push Me", command=clicked)
button.grid(column=1, row=2)
window.mainloop()
Here is my entire code:
from tkinter import *
def up():
number.set(number.get()+1)
def down():
number.set(number.get()-1)
window = Tk()
window.title("Programme")
window.geometry('350x250')
number = IntVar()
frame = Frame(window)
frame.pack()
entry = Entry(frame, textvariable=number, justify='center')
entry.pack(side=LEFT, ipadx=15)
buttonframe = Frame(entry)
buttonframe.pack(side=RIGHT)
buttonup = Button(buttonframe, text="▲", font="none 5", command=up)
buttonup.pack(side=TOP)
buttondown = Button(buttonframe, text="▼", font="none 5", command=down)
buttondown.pack(side=BOTTOM)
window.mainloop()
It looks better for me when the buttons are inside of the entry widget directly.

Python Tkinter changing label text

I know that there are a lot of questions dealing with tkinter but I have looked at a bunch of them and none of them seem to help me.
import tkinter
class Calculator:
def __init__(self):
window = tkinter.Tk()
window.geometry("200x300")
window.title("Calculator")
lbl = tkinter.Label(window, text="placeholder", bg="blue", textvariable="labelText")
lbl.grid(row=0, column=0, columnspan=3)
self.firstNumArray = []
self.secondNumArray = []
self.operation = ""
self.currentNum = "first"
def appendNumber(self, number):
print("Appending Number")
if self.currentNum == "first":
self.firstNumArray.append(number)
print("".join(str(x) for x in self.firstNumArray))
lbl.config(text="".join(str(x) for x in self.firstNumArray))
window.update()
else:
self.secondNumArray.append(number)
for i in range(1,4):
string = "Creating button at ({0},{1})".format(0,i)
print(string)
button = tkinter.Button(text=i, command=lambda: appendNumber(self, i))
button.grid(row=1, column=i-1)
for i in range(1,4):
string = "Creating button at ({0},{1})".format(1,i)
print(string)
button = tkinter.Button(text=i+3, command=lambda: appendNumber(self, i+3))
button.grid(row=2, column=i-1)
for i in range(1,4):
string = "Creating button at ({0},{1})".format(2,i)
print(string)
button = tkinter.Button(text=i+6, command=lambda: appendNumber(self, i+6))
button.grid(row=3, column=i-1)
div = tkinter.Button(text="/")
mult = tkinter.Button(text="*")
add = tkinter.Button(text="+")
sub = tkinter.Button(text="-")
add.grid(row=1, column=3)
sub.grid(row=2, column=3)
mult.grid(row=3, column=3)
div.grid(row=4, column=3)
button = tkinter.Button(text="0")
button.grid(row=4, column=1)
window.mainloop()
calc = Calculator()
When I launch the program the window opens. When I click on a button the text in the label does not change. I have tried using a StringVar as the textvariable and then calling the set() function, but that did not work either. I think it has to do with the scope of the function. I had to place the appendNumber() function inside the __init__() because for some reason self.lbl = tkinter.Label() makes nothing pop up at all.
There are a few problems with your code.
labelText should, of course, be a StringVar and not a string...
labelText = tkinter.StringVar()
lbl = tkinter.Label(window, bg="blue", textvariable=labelText)
lbl.grid(row=0, column=0, columnspan=3)
Now you can use labelText.set to update the text. Also, no need for self parameter or window.update
def appendNumber(number):
if self.currentNum == "first":
self.firstNumArray.append(number)
labelText.set("".join(str(x) for x in self.firstNumArray))
else:
self.secondNumArray.append(number)
You can put all the buttons in one loop using // (integer (!) division) and % (modulo) operations. Also, be aware that the variable in the lambda is evaluated when the function is called, not when it is declared, i.e. all the lambdas will use the last value of i (9 in this case) -- see e.g. here. As a remedy, use lambda n=i+1: appendNumber(n).
for i in range(9):
btn = tkinter.Button(text=i+1, command=lambda n=i+1: appendNumber(n))
btn.grid(row=i//3+1, column=i%3)
Not really a problem, but since you don't need a reference to those buttons, you can make your code a bit more compact (same for the others):
tkinter.Button(text="/").grid(row=1, column=3)

Dynamically changing Label variable

What I am trying to do is build a window with a number(default value=1) and
3 buttons underneath it named: "UP","DOWN" and "QUIT". "Up" button is going to increment the number by 1 and the rest of the buttons are clear what they do.
from Tkinter import *
root=Tk()
number=1
Label(root,text=number,height=10,width=7).grid(row=0,pady=10,padx=10,column=10,columnspan=2,sticky=W+E)
def auksisi(number):
number+=1
return number
def meiosi(number):
number = number -1
return number
Label(root, text=auksisi(number),height=10,width=7).grid(row=0,pady=10,padx=10,column=10,columnspan=2,sticky=W+E)
Button(root, text="up",command=lambda: auksisi(number)).grid(row=3,column=2,columnspan=2)
Button(root, text="down",command=lambda: meiosi(number)).grid(row=3,column=3,columnspan=2)
Button(root, text="quit",command=root.destroy).grid(row=3,column=4,columnspan=2)
root.update()
root.mainloop()
What is happening is when I press the buttons nothing changes.Don't worry about the layout I will fix it, I just want the buttons to work.
The grid method returns None, and calling it directly after the creation of your objects, would make your eventual references to be None as well. To change the values of your label, you need a reference to it:
label_reference = Label(root, text=auksisi(number), height=10, width=7)
label_reference.grid(row=0, pady=10, padx=10, column=10, columnspan=2, sticky=W+E)
Now, through label_reference you can change the text using for example the config() method. You can do this in the method that is called when you click your buttons:
def auksisi(number):
number += 1
label_reference.config(text=number)
return number

How to create a tkinter toggle button?

I've been working on a text editor using Tkinter in Python 2.7.
A feature that I'm trying to implement is the Night Mode, where the user can toggle between a black background and a light one, that switches from light to dark with a click of the toggle button.
from Tkinter import *
from tkSimpleDialog import askstring
from tkFileDialog import asksaveasfilename
from tkFileDialog import askopenfilename
from tkMessageBox import askokcancel
Window = Tk()
Window.title("TekstEDIT")
index = 0
class Editor(ScrolledText):
Button(frm, text='Night-Mode', command=self.onNightMode).pack(side=LEFT)
def onNightMode(self):
if index:
self.text.config(font=('courier', 12, 'normal'), background='black', fg='green')
else:
self.text.config(font=('courier', 12, 'normal'))
index = not index
However, on running the code, it is always in the night mode and the toggle doesn't work. Help.
Source Code: http://ideone.com/IVJuxX
You can import tkinter library (Use capital letter for python 2.7):
import Tkinter
Create tkinter objects...
root = tk.Tk()
...and tkinter button
toggle_btn = tk.Button(text="Toggle", width=12, relief="raised")
toggle_btn.pack(pady=5)
root.mainloop()
Now create a new command button called "toggle" in order to create the effect of "toggle" when you press playing on the relief property (sunken or raised) :
def toggle():
if toggle_btn.config('relief')[-1] == 'sunken':
toggle_btn.config(relief="raised")
else:
toggle_btn.config(relief="sunken")
At the end apply this behaviour on your button:
toggle_btn = tk.Button(text="Toggle", width=12, relief="raised", command=toggle)
The background and fg are set only in the if-clause. You need to set them also in the else clause:
def onNightMode(self):
if index:
self.text.config(font=('courier', 12, 'normal'), background='black', fg='green')
else:
self.text.config(font=('courier', 12, 'normal'))
index = not index
i.e.,
else:
self.text.config(font=('courier', 12, 'normal'), background='green', fg='black')
Here's a code snippet that will help you with the toggle button animation if you would like to. You only need to add the functions that you want to execute when clicking of course, that's up to you.
'''
import tkinter as tk
# --- functions ---
def move(steps=10, distance=0.1):
if steps > 0:
# get current position
relx = float(frame.place_info()['relx'])
# set new position
frame.place_configure(relx=relx+distance)
# repeate it after 10ms
root.after(10, move, steps-1, distance)
def toggle(event):
if button["text"] == "Yes":
move(25, 0.02) # 50*0.02 = 1
button["text"] = "No"
print("Clicked on yes")
elif button["text"] == "No":
move(25, -0.02)
button["text"] = "Yes"
print("Clicked on no")
# --- main --
root = tk.Tk()
frame = tk.Frame(root, background='red')
frame.place(relx=0, rely=0, relwidth=0.5, relheight=1)
# to center label and button
#frame.grid_columnconfigure(0, weight=1)
#frame.grid_rowconfigure(0, weight=1)
#frame.grid_rowconfigure(3, weight=1)
button = tk.Button(frame, text='Yes',width=5,height=1)
button.place(relx=0.25,rely=0.5,relwidth=0.5, relheight=0.1)
button.bind("<Button-1>",toggle)
root.mainloop()
Albe's answer is good but it has some bad coding practices.
Following the same steps:
Import Tkinter as tk
top = tk.TK()
Define your function here and make it work for any button, not hard coded to the specific button you might use.
def toggle(button: tk.Button):
if button.config('relief')[-1] == 'sunken':
button.config(relief="raised")
else:
button.config(relief="sunken")
Then create and pack all the toggle buttons you want.
toggleButton = tk.Button(text="Toggle", width=12, relief="sunken",
command =lambda:toggle(toggleButton))
toggleButton.pack(pady=5)
top.mainloop()
This is better for two reasons. Creating the button object twice is redundant and will lead to buggy code. Hard coding the button to a specific toggle function is unscalable. This solution makes the code reusable and simple to add to. For example, replace that last block with:
for _ in range(4):
b = tk.Button(text="Toggle", width=12, relief="sunken")
b['command']= lambda a=b:toggle(a)
b.pack(pady=5)
And now you get 4 toggling buttons without any additional functions or copy/paste

Tkinter radio button initialization bug

If I put a radio button in a function and draw them; the first time they are drawn you cannot hover over them without making them look like they are all selected.
The same code out of a function does not exhibit this behaviour.
from Tkinter import *
def App(master):
v = StringVar()
v.set('python') # initialize
lable1 = Label(master, text=' hovering over below radio buttons will cause them to look like they are selected')
lable1.pack()
runtimeFrame = Frame(master, relief=GROOVE, borderwidth = 3)
runtimeFrame.pack(fill = X, pady = 5, padx = 5)
for mode in ['java', 'python', 'jython']:
b = Radiobutton(runtimeFrame, text=mode, variable=v, value=mode, indicatoron = 1 )
b.pack(side = LEFT)
if __name__ == '__main__':
master = Tk()
App(master)
#The following code chunk is the same as that in App()
#------------------------
v = StringVar()
v.set('python') # initialize
lable1 = Label(master, text=' hovering over below radio buttons will cause them to Not look selected as expected')
lable1.pack()
runtimeFrame = Frame(master, relief=GROOVE, borderwidth = 3)
runtimeFrame.pack(fill = X, pady = 5, padx = 5)
for mode in ['java', 'python', 'jython']:
b = Radiobutton(runtimeFrame, text=mode, variable=v, value=mode, indicatoron = 1 )
b.pack(side = LEFT)
#------------------------
mainloop()
Once you have made a selection this does not happen again.
Am I doing something wrong? Is there a workaround, because my code has to be in a function!
This is the second elementary bug I have found in Tkinter. Is there something better for Python GUI development?
ps: I'm using python 2.7
The place where you store the variable object (StringVar, v, in your case) must persist so that this odd behavior wont show up. My guess is we're seeing this behavior because v, goes out of scope, something is going wrong. Aside from using a global, I can't think of a way to do this from a function.
Broken code:
from Tkinter import *
def App(master):
v = StringVar()
v.set('python')
lable1 = Label(master, text=' hovering over below radio buttons will cause them to look like they are selected')
lable1.pack()
runtimeFrame = Frame(master, relief=GROOVE, borderwidth=3)
runtimeFrame.pack(fill=X, pady=5, padx=5)
for mode in ['java', 'python', 'jython']:
b = Radiobutton(runtimeFrame, text=mode, variable=v, value=mode, indicatoron=1)
b.pack(side=LEFT)
if __name__ == '__main__':
master = Tk()
App(master)
mainloop()
Example Fix:
from Tkinter import *
def App(master, radio_var):
radio_var.set('python')
lable1 = Label(master, text=' hovering over below radio buttons will cause them to look like they are selected')
lable1.pack()
runtimeFrame = Frame(master, relief=GROOVE, borderwidth=3)
runtimeFrame.pack(fill=X, pady=5, padx=5)
for mode in ['java', 'python', 'jython']:
b = Radiobutton(runtimeFrame, text=mode, variable=radio_var, value=mode, indicatoron=1)
b.pack(side=LEFT)
if __name__ == '__main__':
master = Tk()
radio_var = StringVar()
App(master, radio_var)
mainloop()
Consider that if you have more than one variable that needs to persist you can pass in a list or dictionary of variables.
Also, just in case "has to be in a function" is a homework assignment requirement, consider wrapping the code in a class. I'm not a tk expert, but that would seem the preferred manner of organizing your code.
Example fix 2:
from Tkinter import *
class App(object):
def __init__(self, master):
self.radio_var = StringVar()
self.radio_var.set('python')
lable1 = Label(master, text=' hovering over below radio buttons will cause them to look like they are selected')
lable1.pack()
runtimeFrame = Frame(master, relief=GROOVE, borderwidth=3)
runtimeFrame.pack(fill=X, pady=5, padx=5)
for mode in ['java', 'python', 'jython']:
b = Radiobutton(runtimeFrame, text=mode, variable=self.radio_var, value=mode, indicatoron=1)
b.pack(side=LEFT)
if __name__ == '__main__':
master = Tk()
app = App(master)
mainloop()
Notice a minor change in
app = App(master)
This is required so that the App instance is not garbage collected prematurely. This would effectively pull the self.radio_var out of scope and we're back at square one.
Try this, it works for me.
v = 0 # this is global variable
def some_function():
global v
v = IntVar()
v.set(0)
rb1 = Radiobutton (parent, variable = v, value = 0)
rb1.pack()
rb2 = Radiobutton (parent, variable = v, value = 1)
rb2.pack()
Make your radio buttons and then you get your radio buttons as they should.
I know that passed a lot of time, but I've tried all the strategies showed here and none of them work with me. What worked for me was simple "rewrite" the event handler for mouse motion event. It's not a perfect solution because I print some kind of garbage to the terminal, but well, this is not a problem in my particular case.
server_name = IntVar()
server_name.set(1)
server_name_rb_1 = Radiobutton(container_3, text="Server", variable=server_name, value=1)
server_name_rb_1.select()
server_name_rb_1.pack()
server_name_rb_2 = Radiobutton(container_3, text="Local", variable=server_name, value=2)
server_name_rb_2.deselect()
server_name_rb_2.pack()
server_name_rb_2.bind('<Motion>',lambda e: print(str(server_name.get())) )
P.S.: You don't need to rewrite all functions just rewrite one of them must be enough.
i was able to fix it by simply adding state=NORMAL, to all the radio buttons. i appreciate that this is the default behavior but it looks like that tkinter kind of doesn't remember this.
I found a solution that it actually works fine so maybe it can be helpful for other desperate people like me.
First thing, I created separate radioButtons so that I can rapidly access to the variable names; finally, I binded the mouse "Leave" event, of the unselected widget, to "break" (doing this I undid the normal behaviour of the widget aka when the mouse is over the radio button now it does nothing).
As to now, I'm not having any problems and it works just fine.
Below, an example code:
...
self.__var__ = IntVar()
self.__var__.set(2)
unselected_btn = Radiobutton(self.__frame__,
text="One", padx=20,
variable=self.__var__,
value=1
)
unselected_btn.grid(row=0, column=1, sticky=E)
selected_btn = Radiobutton(self.__frame__,
text="Two",
padx=20,
variable=self.__var__,
value=2
)
selected_btn.grid(row=0, column=0, sticky=W)
unselected_btn.bind("<Leave>", lambda e: "break")
...
NOTE: here I used grid, but you can simply change it to pack or place
I will admit you found quite an odd "glitch". I'm not entirely prepared to call it a glitch seeing as I'm not sure what is causing it. It seemed to be stemming from the variable, argument. A work around would be instead of getting the variable, get the actual text of the widget via the .cget('text') method.
My changes to your App function:
def App(master):
v = StringVar()
v.set('python') # initialize
lable1 = Label(master, text=' hovering over below radio buttons will cause them to look like they are selected')
lable1.pack()
runtimeFrame = Frame(master, relief=GROOVE, borderwidth = 3)
runtimeFrame.pack(fill = X, pady = 5, padx = 5)
for mode in ['java', 'python', 'jython']:
b = Radiobutton(runtimeFrame, text=mode, value=mode, indicatoron = 1 ) # I omitted the variable argument, which seemed to be the root of your troubles.
b.pack(side = LEFT)
b.deselect() # manually deselects each instance of the Radiobutton.
b.select() # manually selects a default instance of the Radiobutton.

Categories

Resources