Looping through tkinter buttons not working well - python

I'm writing a new code to display sqlite search results on buttons using loop. The code works correctly except for 1 issue. I wrote a function to change the background color and play a sound when hovering over the buttons. The problem is that when I hover over any button of the result, the color changes over the last button only although the sound plays without problem. Here's the part of the code involved:
SearchSelection = SearchVar.get()
SearchParameter = txtSearch.get()
conn = sqlite3.connect("EasyClinic.db")
cursor = conn.cursor()
cursor.execute ("SELECT * FROM patients WHERE (%s) = (?)" %(SearchSelection),
(SearchParameter,))
results = cursor.fetchall()
conn.commit()
conn.close()
if len(results) == 0:
print ("No result found")
else:
for result in results:
ResultsButtons = tk.Button(canvasResult, text=result, bg=BackGroundColor, fg=ForeGroundColor, relief='flat', font=MyFont2, width=65, height=2)
ResultsButtons.pack(pady=5)
def on_enter_results(result):
ResultsButtons['background'] = Hover
winsound.PlaySound ('Hover.wav', winsound.SND_ASYNC)
def on_leave_results(result):
ResultsButtons['background'] = BackGroundColor
ResultsButtons.bind("<Enter>", on_enter_results)
ResultsButtons.bind("<Leave>", on_leave_results)
Please for you assistance
Thanks

You can change the two functions, on_enter_results() and on_leave_results() as below:
def on_enter_results(event):
# event.widget is the widget that triggers this event
event.widget['background'] = Hover
winsound.PlaySound ('Hover.wav', winsound.SND_ASYNC)
def on_leave_results(event):
event.widget['background'] = BackGroundColor
Actually you can move these two functions outside of for loop (i.e. before the for loop).

The problem is that python always overwrites the ResultsButtons variable with the latest button, and this gives a problem when calling the funciton on_enter_results or on_leave_results, because it uses the overwritten ResultsButtons variable. The solution is to more directly specify the button and pass it to your function with a lambda function. I can't check if this code works because I don't have the full example, but something like this should work:
if len(results) == 0:
print ("No result found")
else:
def on_enter_results(event_info, resbutton):
resbutton['background'] = Hover
winsound.PlaySound ('Hover.wav', winsound.SND_ASYNC)
def on_leave_results(event_info, resbutton):
resbutton['background'] = BackGroundColor
for result in results:
ResultsButton = tk.Button(canvasResult, text=result, bg=BackGroundColor, fg=ForeGroundColor, relief='flat', font=MyFont2, width=65, height=2)
ResultsButton.pack(pady=5)
ResultsButton.bind("<Enter>", lambda event_info, btn=ResultsButton: on_enter_results(btn))
ResultsButton.bind("<Leave>", lambda event_info, btn=ResultsButton: on_leave_results(btn))
For a better understanding of what acually is going on: Python FAQ

It was solved by acw1668. Thank you all for your support

Related

How can I pass the 2nd item of sqlite 3 table of every item in a label in tkinter python module?

I have a problem, the problem is i want to create a post-it tkinter gui app and I want to store all the posts the user create so they can open it when they rerun the app, so i used sqlite 3 module to achieve this, but im stuck at the moment when the user opens the existing post-its bcs it opens the last content of the for loop
In case u dont get it here is the code:
"""
from tkinter import *
import sqlite3
conn = sqlite3.connect("post-it.db")
row = 1
cursor = conn.cursor()
posts = cursor.execute("SELECT rowid, * FROM postits")
postsFetch = posts.fetchall()
print(f"{postsFetch}")
def createPost():
pass
def openPost(name):
print(name)
post = Tk()
text = Label(post,text=name)
text.pack()
post.mainloop()
window = Tk()
window.geometry("400x400")
window.config(bg="blue")
createNew = Button(text="Create new Post-it",command=createPost)
createNew.grid(column=1,row=1)
createName = Entry()
createName.grid(column=1,row=2)
frame = Frame()
frame.grid(column=2)
#the problem is at this for loop it opens the last item text
for postit in postsFetch:
postitBtn = Button(frame,text=postit[1],command=lambda: openPost(postit[2]))
postitBtn.grid(column=8,row=row)
row += 1
conn.commit()
window.mainloop()
conn.close()
"""
if u know the answer please help
Firstly, don't use Tk more than once in a program - it can cause problems later on. For all other windows, use Toplevel. Replace post = Tk() with post = Toplevel().The reason your loop doesn't work is explained here. To fix it, change your lambda function to lambda postit = postit: openPost(postit[2]))

.set in function is not being found in other function so it's creating an error tkinter python

I'm trying to create a GUI, in the nav menu you can click a cascade option to open another window where you can click roll to generate a set of numbers. It comes up with error. I think it's because the function is called from another function I just don't know how to get that function to call it/ if there is any other ways to fix this. I've tried global functions and looking it up but haven't found anything other than using classes so far, which I don't know how to do.
line 147, in totalRolls
txtresultsOut.set(totalRollResults)
NameError: name 'txtresultsOut' is not defined
Here is the code that is relevant to it. I've called the function to skip having to input all the other code for the main gui window.
def rollSix():
s = 0
numbers = [0,0,0,0]
for i in range(1,5):
numbers[s] = randrange(1,7)
s += 1
numbers.remove(min(numbers))
Result = sum(numbers)
totalRollResults.append(Result)
def totalRolls():
rollOne()
rollTwo()
rollThree()
rollFour()
rollFive()
rollSix()
txtresultsOut.set(totalRollResults)
def rollw():
rollWindow = tix.Tk()
rollWindow.title("Dice Rolls")
diceLabel = Label(rollWindow, text = "Click Roll for your Stats")
diceLabel.grid(row = 0, column = 0)
rollBtn = Button(rollWindow, text = "Roll Stats", command = totalRolls)
rollBtn.grid(row = 1, column = 0)
txtresultsOut = StringVar()
resultsOut = Entry(rollWindow, state = "readonly", textvariable = txtresultsOut)
resultsOut.grid(row = 2, column = 0)
rollw()
first of all I would NOT recommend using StringVar(). You can use the .get() method of Entry to obtain the value inside the same. Try this way and make a global declaration of the Entry whose values you want to get in other functions.
EDIT------------
#you can use the following code to make your entry active to be edited.
entry.configure(state='normal')
# insert new values after deleting old ones (down below)
entry.delete(0,END)
entry.insert(0, text_should_be_here)
# and finally make its state readonly to not let the user mess with the entry
entry.configure(state='readonly')

How do I edit specific shelf buttons at runtime in Maya with Python?

I'm trying to create a script in Python in Maya that will allow me to dynamically alter the image of a specific button and nothing else about that button. I'm crashing into some serious issues that I'll detail below:
import maya.cmds as cmds
import maya.mel as mel
cmds.refresh(cv=1, f=1)
gShelfTopLevel = mel.eval("global string $gShelfTopLevel; $temp = $gShelfTopLevel;")
currentShelf = cmds.tabLayout(gShelfTopLevel,q=1,st=1)
buttons = cmds.shelfLayout(currentShelf,q=1,ca=1)
buttonName = "Button 1"
for button in buttons:
if cmds.shelfButton(button, q=True, l=True) == buttonName:
cmds.shelfButton(button, h=35, w=35, e=1, i="icons/axis_Object.png", p=currentShelf )
#If this was working I'd have an if statement here for a second image.
break
Toggler()
class Toggler():
if ctx == 'moveSuperContext':
tool = 'Move'
mode = cmds.manipMoveContext(tool, q=1, m=1)
if mode != 2:
cmds.manipMoveContext(tool, e=1, m=2)
else:
cmds.manipMoveContext(tool, e=1, m=0)
if ctx == 'RotateSuperContext':
tool = 'Rotate'
mode = cmds.manipRotateContext(tool, q=1, m=1)
if mode != 0:
cmds.manipRotateContext(tool, e=1, m=0)
else:
cmds.manipRotateContext(tool, e=1, m=1)
if ctx == 'scaleSuperContext':
tool = 'Scale'
mode = cmds.manipScaleContext(tool, q=1, m=1)
if mode != 0:
cmds.manipScaleContext(tool, e=1, m=0)
else:
cmds.manipScaleContext(tool, e=1, m=2)
Firstly this is the script. What the button should do is defined at the bottom and as best as I can tell that is all fine. It was already existing code that I was handed.
My problems are as follows:
The image changes for all buttons on the bar. This is incredibly unhelpful and I'm not sure why this would be the case.
The names of all the buttons change to whatever buttonName is. So in this case, all buttons are renamed to "Button 1" which is also incredibly frustrating for me.
The script on the original button is cloned to all other buttons.
An addendum to 2 is I've tried renaming my buttonName variable on the off chance that buttonName is an intrinsic variable assigned to these button scripts.
In the past I was able to accomplish editing just the image of a button with the following MEL code:
shelfButton -edit -image "icons/axis_World.png" $button;
I cannot figure out what is unique about this code compared to what I've done in Python but clearly there is something going on for me.
Any aid is welcome because at this point I'm completely at a loss. It looks like clicking any button on a shelf will cause it to iterate through all buttons on that shelf.
Thanks!
I can't speak to your Toggler() class, because I'm quite confused by its purpose, but the below script snippet is fully functional in Maya 2018.4:
import maya.cmds as cmds
import maya.mel as mel
gShelfTopLevel = mel.eval("global string $gShelfTopLevel; $temp = $gShelfTopLevel;")
currentShelf = cmds.tabLayout(gShelfTopLevel, q=True, st=True)
buttons = cmds.shelfLayout(currentShelf, q=True, ca=True)
targetButton = 'Button 1' # Button 'name' not 'icon label'
toggleIcons = ['showManip.png', 'globalManip.png']
for b in buttons:
label = cmds.shelfButton(b, q=True, l=True)
if label != targetButton:
continue
print('Found target button: `{}` -> {}'.format(targetButton, b))
currentIcon = cmds.shelfButton(b, q=True, i=True)
newIcon = toggleIcons[0] # default
if currentIcon in toggleIcons:
try:
idx = toggleIcons.index(currentIcon) + 1
newIcon = toggleIcons[idx] if idx < len(toggleIcons) else toggleIcons[0]
except Exception as e:
print('Failed to iterate through list of icons, using default: {}'.format(e))
print('Current image is {} -> swapping to {}'.format(currentIcon, newIcon))
cmds.shelfButton(b, e=True, i=newIcon)
break
I'm not using the width, height and parent flags you were using. Other than that, everything is more or less the same as your own code. Perhaps editing the parent isn't working as expected? Your mel equivalent command does not set this flag either.

Change the color of a single word in a tk option menu?

So I'm grabbing links of events off a website and putting them into a drop down menu to be selected. My code for the menu:
import Tkinter as tk
from Tkinter import StringVar
selectMenu = tk.Tk()
# #-> this is what I have
# Followed by what you can use
#var = Vars()
#events = var.GetVars('Event')
events = " "
options = []
links = []
#forms = (driver.find_elements_by_class_name("with-cats")) #This is what I have
forms = ["Yolo ","Dad? Closed","Anotha One","Normies! Closed"] #This is so you can try it for yourself
for x in forms:
#info = x.text
info = x #Again, this is so you can try it for yourself
if events in info.lower():
links.append(x)
for link in range(0,len(links)):
#options.append(links[link].text)
options.append(links[link])
list(set(options))
selection = []
for link in range(0,len(options)):
selection.append(options[link])
select = StringVar(selectMenu)
select.set("--None Selected--")
menu = tk.OptionMenu(selectMenu, select, *(selection))
msg = "Which one would you like to attend?"
label = tk.Label(selectMenu, text=msg, font="Helvedica 14")
label.pack(side='top', pady=10)
menu.pack(side="top", pady=10)
selectMenu.attributes('-topmost', True)
selectMenu.mainloop()
So this works fine and dandy, but I would like to improve the look to make it more obvious which events are open. To clarify, an event found that is open and put into the menu may look like "This is a cool event", but one that is closed would be read as "This is a cool event Closed". My aim is to be able to make the foreground red of either just the word Closed or the string containing Closed, whichever is possible if any (And I'm not sure if it's possible because menus and buttons on osx are usually defaulted to system settings, maybe there is a way around this?).
Current: Desired:
According to the documentation for OptionMenu here and here I don't think there is a way to set the color of text.
You might be able to get something close to what you want by using a listBox instead. See post here for the listBox example.
Found a solution! Using a Menu inside of a MenuButton the same way Tkinter creates MenuOptions, I was able to create a custom MenuOption. If you want to add more options, you can use the menbutton.configure() option to edit the button, and menbutton.menu to edit the menu items.
import Tkinter as tk
from Tkinter import Menu, Menubutton
class Vars():
global vari
vari = {}
def GetVars(self, var):
return vari.get(str(var))
def SendVars(self, var, val):
vari[str(var)] = val
class App():
def buttselect(self, link, menbutton, selectMenu):
var = Vars()
var.SendVars("Selection", link) # Store selected event
menbutton.configure(text=link) # Set menu text to the selected event
def prnt(self, link):
var = Vars()
print var.GetVars("Selection") # Print event
def __init__(self, selectMenu):
events = " "
options = []
links = []
forms = ["Yolo ","Dad? Closed","Anotha One","Normies! Closed"] #This is so you can try it for yourself
menbutton = Menubutton (selectMenu, text="--None Selected--", relief="raised")
menbutton.grid()
menbutton.menu = Menu (menbutton, tearoff=0)
menbutton["menu"] = menbutton.menu
#Get a list of event names
for x in forms:
info = x #Again, this is so you can try it for yourself
#If desired event keyword is in an event name, add it to the correct links
if events in info.lower():
links.append(x)
#Remove duplicates
for link in range(0,len(links)):
options.append(links[link])
list(set(options))
#Final list of event names turned into menu commands
for link in options:
if "Closed" in link:
menbutton.menu.add_command( label= link, command= lambda link=link: self.buttselect(link, menbutton, selectMenu), foreground='red')
else:
menbutton.menu.add_command( label= link, command= lambda link=link: self.buttselect(link, menbutton, selectMenu))
b = tk.Button(selectMenu, text="Selection", command= lambda link=link: self.prnt(link)) #Print selected event
b.pack()
msg = "Which one would you like to attend?"
label = tk.Label(selectMenu, text=msg, font="Helvedica 14")
label.pack(side='top', pady=10)
menbutton.pack(side="top", pady=10)
selectMenu = tk.Tk()
selectMenu.attributes('-topmost', True)
app = App(selectMenu)
selectMenu.mainloop()
This results in exactly the result desired:
I found a way!
Let's say x is an optionmenu with options:
options=['Red','Blue','Green']
defopt=tk.StringVar(options[0]) #StringVariable to hold the selected option.
x=tk.OptionMenu(self.optmenuframe,defopt,*options)
Now, get the menu object from the optionmenu and use entryconfig method. That's it!
x.children['menu'].entryconfig(0,foreground='red')
x.children['menu'].entryconfig(1,foreground='blue')
x.children['menu'].entryconfig(2,foreground='green')
#0 is the index of the option you want to apply the configurations to.

Selecting/Invoking Menubuttons in python Tkinter

I am trying to create a menu that calls an SQL database for life expectancy data across the world. I'm having trouble with menubutton, as I want to set the first value ("No region selected") as my initial value. There are three things that I want: how to set the value as the default (ideally the equivalent of invoked for normal radiobuttons), is there a better for what I want option than menubutton, and is there a decent web page that can help me (tutorialspoint got me in the initial direction, but unless I'm looking in the wrong places, I can't find how to select and invoke radiobuttons in the menubutton).
Ideally, I'd also like advise on how not to have the menuoptions stretch up everywhere too. A limit of say 20?
Here's a look of a somewhat stripped down piece of my code.
Edit: I've found a solution to my select/invoke issues. mb.menu.invoke(0)
from Tkinter import *
import tkMessageBox
import Tkinter
import sqlite3
def selectStuff():
selectionRegion = str(countryVar.get())
mb.config(text = selectionRegion)
sqlLocation = 'AllData/LifeExpectency.sqlite'
sqlDB = sqlite3.connect(sqlLocation)
cursor = sqlDB.cursor()
root = Tk()
frameCountry = Frame(root)
frameCountry.pack()
stringCountry= StringVar()
labelCountry = Label(frameCountry, textvariable=stringCountry)
stringCountry.set("Select Region")
labelCountry.pack()
'''-------------------------------'''
mb = Menubutton ( frameCountry, relief=RAISED, activebackground="white")
mb.grid()
mb.menu = Menu ( mb, tearoff = 0 )
mb["menu"] = mb.menu
sql1 = "SELECT Country FROM total_lifespan"
countryVar = StringVar()
mb.menu.add_radiobutton ( label="No Region Selected", variable =countryVar,
value = "No Region Selected", command =selectStuff)
try:
cursor.execute(sql1)
results = cursor.fetchall()
for row in results:
mb.menu.add_radiobutton ( label=row, variable =countryVar, value = row,
command =selectStuff)
except:
print "Error: unable to retrieve data."
mb.pack()
'''-------------------------------'''
#Edit:
mb.menu.invoke(0)
#/Edit:
root.mainloop()
sqlDB.close()

Categories