I would like to update the label text of checkbuttons within a menubutton using an update function when a checkbutton is clicked.
I have got around it so far by deleting the whole menubutton and recreating but it doesn't work perfectly and adds unnecessary complexity. Here is what I have so far:
from Tkinter import *
INGREDIENTS = ['cheese','ham','pickle','mustard','lettuce']
def print_ingredients(*args):
values = [(ingredient, var.get()) for ingredient, var in data.items()]
print values
results = []
def update():
values = [(ingredient, var.get()) for ingredient, var in data.items()]
for value in values:
if value[1] == 1:
results.append(value[0])
print results
for value in values:
mb.menu.delete(0)
for ingredient in INGREDIENTS:
if ingredient in results:
on_val = 0
off_val = 1
click = "Clicked!"
else:
on_val = 1
off_val = 0
click = ""
var = IntVar()
mb.menu.add_checkbutton(label=ingredient + " " + click, variable=var, onvalue = on_val, offvalue = off_val, command = update)
data[ingredient] = var # add IntVar to the dictionary
data = {} # dictionary to store all the IntVars
top = Tk()
mb= Menubutton ( top, text="Ingredients", relief=RAISED )
mb.menu = Menu ( mb, tearoff = 0 )
mb["menu"] = mb.menu
for ingredient in INGREDIENTS:
var = IntVar()
mb.menu.add_checkbutton(label=ingredient, variable=var, command = update)
data[ingredient] = var # add IntVar to the dictionary
btn = Button(top, text="Print", command=print_ingredients)
btn.pack()
mb.pack()
top.mainloop()
Is there a way to update the label text of a checkbutton within a menubutton?
You could trace the variables you attached to the checkbuttons. If you name the variables after the ingredients and store them in a dict, you can get the ingredient and the variable in the callback of the trace and change the entry at the right index:
from Tkinter import *
INGREDIENTS = ['cheese','ham','pickle','mustard','lettuce']
def update(var_name, *args):
# Beacause we defined names for the BooleanVars, the first argument is the name of the changed Var
# We named the Vars after the ingredients
ingredient = var_name
# Get the actual var from the dict
var = data[var_name]
# Get the index of the clicked ingredient
i = INGREDIENTS.index(ingredient)
# Check wether the checkbutton is clicked on or not
if var.get() == True:
# If checked, change label to include 'Clicked'
mb.menu.entryconfigure(i, label = ingredient + ' Clicked!')
else:
# If unchecked, change label to just ingredient
mb.menu.entryconfigure(i, label = ingredient)
data = {} # dictionary to store all the IntVars
top = Tk()
mb= Menubutton ( top, text="Ingredients", relief=RAISED )
mb.menu = Menu ( mb, tearoff = 0 )
mb["menu"] = mb.menu
for ingredient in INGREDIENTS:
# Create a Boolean variable with the name of the ingredient
var = BooleanVar(name = ingredient)
# Trace changes to the variable
var.trace("w", update)
# Create Checkbutton without command
mb.menu.add_checkbutton(label=ingredient, variable=var)
# Add variable to the dictionary
data[ingredient] = var
mb.pack()
top.mainloop()
Related
I have written the code for Undo & Redo actions for QTableView by using QUndoStack class in PyQt4.
The Undo action works fine; but when I perform Redo action, I am getting "RuntimeError: maximum recursion depth exceeded while calling a Python object."
I have used QTableView's pressed & dataChanged signals to retrieve the texts from model.
Following is the code sample.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtSql import *
import os
import sys
import random
# SQL Query:---------------------------------------------------------------------------------------------
def StudentQuery():
StudentQuery = QSqlQuery()
StudentQuery.exec_("DROP TABLE STUDENTS")
StudentQuery.exec_("""CREATE TABLE STUDENTS (
s1 REAL NULL,
s2 REAL NULL,
s3 REAL NULL)""")
Students = ("STD1", "STD2", "STD3")
StudentQuery.prepare("INSERT INTO STUDENTS (s1, s2, s3) VALUES (?, ?, ?)")
for Student in Students:
s1 = random.randint(0, 25)
s2 = random.randint(0, 25)
s3 = random.randint(0, 25)
StudentQuery.addBindValue(QVariant(s1))
StudentQuery.addBindValue(QVariant(s2))
StudentQuery.addBindValue(QVariant(s3))
StudentQuery.exec_()
QApplication.processEvents()
# Ui Dialog:------------------------------------------------------------------------------------------------
class Ui_Student(QDialog):
def __init__(self, parent=None):
super(Ui_Student, self).__init__(parent)
self.setFixedSize(340, 170)
self.setWindowTitle("STUDENT")
self.model = QSqlTableModel(self)
self.model.setTable("STUDENTS")
self.model.setEditStrategy(QSqlTableModel.OnManualSubmit)
self.model.select()
self.view = QTableView(self)
self.view.setGeometry(QRect(10, 10, 320, 120))
self.view.setSelectionBehavior(QAbstractItemView.SelectItems)
self.view.setFocusPolicy(Qt.StrongFocus)
self.view.setModel(self.model)
self.view.installEventFilter(self)
QSqlDatabase.database().commit()
# Button Box:---------------------------------------------------------------------------------------------------
self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setGeometry(QRect(118, 127, 100, 45))
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
# SIGNAL & SLOT: -----------------------------------------------------------------------------------------------
QObject.connect(self.buttonBox, SIGNAL("accepted()"), self.accept)
QObject.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
# Code For Undo/Redo:---------------------------------------------------------------------------------------------------
self.undoStack = QUndoStack(self)
self.undoStack.setUndoLimit(10)
self.L_Row = []
self.L_Column = []
self.L_Text0 = []
self.L_Text1 = []
self.view.pressed.connect(self.InitialText)
self.view.model().dataChanged.connect(self.FinalText)
def InitialText(self, signal):
self.Row = signal.row()
self.Column = signal.column()
Model = self.view.model()
self.Text0 = Model.data(Model.index(self.Row, self.Column), 0).toString()
self.L_Row.append(self.Row)
self.L_Column.append(self.Column)
self.L_Text0.append(self.Text0)
# print self.L_Text0
def FinalText(self):
View = self.view
Model = self.view.model()
self.Text1 = Model.data(Model.index(self.Row, self.Column), 0).toString()
self.L_Text1.append(self.Text1)
for i in range(len(self.L_Text0)):
if not (self.L_Text0[i] == self.L_Text1[i]):
command = CommandEdit(View, Model, self.L_Row[i], self.L_Column[i],
self.L_Text0[i], self.L_Text1[i], "ABC")
self.undoStack.push(command)
# ContextMenu:---------------------------------------------------------------------------------------------------
def contextMenuEvent(self, event):
menu = QMenu(self)
UndoAction = self.undoStack.createUndoAction(self)
UndoAction.setText("&Undo")
menu.addAction(UndoAction)
UndoAction.setShortcuts(QKeySequence.Undo)
self.connect(UndoAction, SIGNAL("triggered()"), self.undoStack.undo)
RedoAction = self.undoStack.createUndoAction(self)
RedoAction.setText("&Redo")
menu.addAction(RedoAction)
RedoAction.setShortcuts(QKeySequence.Redo)
self.connect(RedoAction, SIGNAL("triggered()"), self.undoStack.redo)
menu.exec_(event.globalPos())
# QUndoCommand Class:---------------------------------------------------------------------------------------------------
class CommandEdit(QUndoCommand):
def __init__(self, View, Model, RowIndex, ColumnIndex, Text0, Text1, description):
super(CommandEdit, self).__init__(description)
self.view = View
self.model = Model
self.rowIndex = RowIndex
self.columnIndex = ColumnIndex
self.text0 = Text0
self.text1 = Text1
def undo(self):
self.model.setData(self.model.index(self.rowIndex, self.columnIndex), QVariant(self.text0))
def redo(self): # Error occurred while executing this function.
self.model.setData(self.model.index(self.rowIndex, self.columnIndex), QVariant(self.text1))
# Code Execute:---------------------------------------------------------------------------------------------------
if __name__ == "__main__":
app = QApplication(sys.argv)
filename = os.path.join(os.path.dirname(__file__), "STD.db")
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(filename)
db.open()
StudentQuery()
form = Ui_Student()
form.show()
sys.exit(app.exec_())
Am I doing something wrong while defining def InitialText & def FinalText?
I am trying to add rows to a table like GUI.
These rows are to be a list of labels.
Each row that is updated has the following methods in that class:
AddRow - To add the list of strings that will be the text in that
label.
AddLabel - Adds a label to that row and appends the Label list
AddLabelRow - Creates the list Row in the actual table and
initializes the text to "empty"
ChangeLableText - Takes an input list of strings and changes the string in the label for that class.
I then have initialized a list of objects ContentList[] for this class and call the methods
But no matter which object's ChangeLabelText is called, only the text for ContentList[0] is updated.
import json
import requests
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.app import runTouchApp
class AddContent(GridLayout):
#response = requests.get("http://localhost:10010/")
# Get the response data as a python object. Verify that it's a dictionary.
#data = response.json()[3]
#Column_keys = ["country", "date", "answered_calls", "total_calls", "asr", "mou", "aou"]
RowList = []
Label_List = []
size = 0
def AddRow(self, InputList):
self.RowList = InputList
self.size = len(InputList)
def AddLabel(self,LayoutObj):
lbl = Label(size_hint_y = None, height = 30)
LayoutObj.add_widget(lbl)
return lbl
def AddLabelRow(self,LayoutObj):
for i in range(self.size):
Lbl = self.AddLabel(LayoutObj)
Lbl.text = "empty"
#self.Label_List[i].text = data[Column_keys[i]]
#Lbl.text = str(self.data[self.Column_keys[i]])
self.Label_List.append(Lbl)
def ChangeLabel_ListText(self, TextList):
for i in range(self.size):
#self.Label_List[i].text = data[Column_keys[i]] #data is fetched from Db
self.Label_List[i].text = TextList[i]
class TableView(GridLayout):
Col_Names = ["Date","Vendor","Country","MOU","ASR","AOU"]
ContentList = [AddContent(),AddContent(),AddContent()]
def __init__(self,**kwargs):
self.layout = GridLayout(cols = len(self.Col_Names), padding =5)
self.layout.bind(minimum_height=self.layout.setter('height'))
for i in range(len(self.Col_Names)):
btn = Button(text=self.Col_Names[i], size_hint_y=None, height=30)
self.layout.add_widget(btn)
self.ContentList[0].AddRow(['1sample1','1sample2','1sample3','1sample4','1sample5','1sample6'])
self.ContentList[1].AddRow(['2sample1','2sample2','2sample3','2sample4','2sample5','2sample6'])
self.ContentList[2].AddRow(['3sample1','3sample2','3sample3','3sample4','3sample5','3sample6'])
for i in range(3):
self.ContentList[i].AddLabelRow(self.layout)
self.ContentList[2].ChangeLabel_ListText(['a','b','c','d','e','f'])
if __name__ == '__main__':
Table = TableView()
runTouchApp(Table.layout)
The line self.ContentList[2].ChangeLabel_ListText(['a','b','c','d','e','f'])
updates only the first row whatever number is given for the index.
I have been breaking my head over this for the past week. I had initially done this with only one class instead of two which gave the same output.
Any help will help greatly. Thanks!
Your first problem is that RowList, Label_List, and size are class attributes in your code. But you want to set them in every instance individually. Solution: initialize these attributes inside the __init__ method like this:
def __init__(self, **kwargs):
super(AddContent, self).__init__(**kwargs)
self.RowList = []
self.Label_List = []
self.size = 0
The second problem is, that the GridLayout (which your class is subclassing) also contains an attribute called size. Solution: pick a different name for this attribute like so:
self.length = 0
If you now do
self.ContentList[i].ChangeLabel_ListText(['a','b','c','d','e','f'])
your i-th ContentList-entry will be changed.
Here is the complete solution:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.app import runTouchApp
class AddContent(GridLayout):
#response = requests.get("http://localhost:10010/")
# Get the response data as a python object. Verify that it's a dictionary.
#data = response.json()[3]
#Column_keys = ["country", "date", "answered_calls", "total_calls", "asr", "mou", "aou"]
def __init__(self, **kwargs):
super(AddContent, self).__init__(**kwargs)
self.RowList = []
self.Label_List = []
self.length = 0
def AddRow(self, InputList):
self.RowList = InputList
self.length = len(InputList)
def AddLabel(self,LayoutObj):
lbl = Label(size_hint_y=None, height=30)
LayoutObj.add_widget(lbl)
return lbl
def AddLabelRow(self,LayoutObj):
for i in range(self.length):
Lbl = self.AddLabel(LayoutObj)
Lbl.text = "empty"
#self.Label_List[i].text = data[Column_keys[i]]
#Lbl.text = str(self.data[self.Column_keys[i]])
self.Label_List.append(Lbl)
def ChangeLabel_ListText(self, TextList):
for i in range(self.length):
#self.Label_List[i].text = data[Column_keys[i]] #data is fetched from Db
self.Label_List[i].text = TextList[i]
class TableView(GridLayout):
Col_Names = ["Date","Vendor","Country","MOU","ASR","AOU"]
ContentList = [AddContent(),AddContent(),AddContent()]
def __init__(self,**kwargs):
self.layout = GridLayout(cols = len(self.Col_Names), padding=5)
self.layout.bind(minimum_height=self.layout.setter('height'))
for i in range(len(self.Col_Names)):
btn = Button(text=self.Col_Names[i], size_hint_y=None, height=30)
self.layout.add_widget(btn)
self.ContentList[0].AddRow(['1sample1','1sample2','1sample3','1sample4','1sample5','1sample6'])
self.ContentList[1].AddRow(['2sample1','2sample2','2sample3','2sample4','2sample5','2sample6'])
self.ContentList[2].AddRow(['3sample1','3sample2','3sample3','3sample4','3sample5','3sample6'])
for i in range(3):
self.ContentList[i].AddLabelRow(self.layout)
self.ContentList[2].ChangeLabel_ListText(['a','b','c','d','e','f'])
if __name__ == '__main__':
Table = TableView()
runTouchApp(Table.layout)
I have an option menu and i bound it to a function but i can't put scrollbar on it so i used ComboBox instead. But now the binding doesn't work
Cat_options = ['Select Category']
self.var = StringVar(self.ViewWM)
conn = sqlite3.connect(self.DatabaseName)
conn.text_factory = str
cursor = conn.cursor()
cursor.execute("SELECT Category FROM Websites_info WHERE LoginName LIKE ?" ,(self.LoginName,))
conn.commit()
a =''
for row in cursor.fetchall():
if a == row:
pass
else:
row = str(row)
row = row.replace("('","")
row = row.replace("',)","")
Cat_options.append(row)
a = row
self.var.set(Cat_options[0]) # initial value
Cat_ComboBox = ttk.Combobox(self.ViewWM, textvariable = self.var , values = Cat_options)
Cat_ComboBox.place(x=10,y =45 , width = 183)
Cat_ComboBox.bind('<<ComboBoxSelected>>', self.Cat_callback)
b1 = Button(self.ViewWM,text="aaaa",command=self.Cat_callback)
b1.place(x = 200,y=200)
self.ViewWM.mainloop()
def Cat_callback(self, event=None):
self.Selcted_Cat = self.var.get()
print self.Selcted_Cat
print 'hello'
My a button works fine but doesn't bind
Problem is upper B in <<ComboBoxSelected>>. It has to be <<ComboboxSelected>>
Full working example
from __future__ import print_function
try:
# Python 2
import Tkinter as tk
import ttk
except ImportError:
# Python 3
import tkinter as tk
from tkinter import ttk
# -------
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var = tk.StringVar()
options = ["Alpha", "Beta", "etc.", "Omega"]
cb = ttk.Combobox(self, textvariable=self.var, values=options)
cb.pack()
cb.bind('<<ComboboxSelected>>', self.callback)
b1 = ttk.Button(self, text="OK", command=self.callback)
b1.pack()
def callback(self, event=None):
print('--- callback ---')
print('var.get():', self.var.get())
if event:
print('event.widget.get():', event.widget.get())
# -------
App().mainloop()
with using
Cat_ComboBox.bind('<<ComboboxSelected>>',
lambda event: self.Cat_callback())
every thing its working now
Cat_options = ['Select Category']
self.var = StringVar(self.ViewWM)
conn = sqlite3.connect(self.DatabaseName)
conn.text_factory = str
cursor = conn.cursor()
cursor.execute("SELECT Category FROM Websites_info WHERE LoginName LIKE ?" ,(self.LoginName,))
conn.commit()
a =''
for row in cursor.fetchall():
if a == row:
pass
else:
row = str(row)
row = row.replace("('","")
row = row.replace("',)","")
Cat_options.append(row)
a = row
self.var.set(Cat_options[0]) # initial value
Cat_ComboBox = ttk.Combobox(self.ViewWM, textvariable = self.var , values = Cat_options)
Cat_ComboBox.place(x=10,y =45 , width = 183)
Cat_ComboBox.bind('<<ComboboxSelected>>',
lambda event: self.Cat_callback()) #changing in code
b1 = Button(self.ViewWM,text="aaaa",command=self.Cat_callback)
b1.place(x = 200,y=200)
self.ViewWM.mainloop()
def Cat_callback(self, event=None):
self.Selcted_Cat = self.var.get()
print self.Selcted_Cat
print 'hello'
So I have been teaching myself Object Oriented Programming for Tkinter projects as I clearly see that they are much more organized for large amounts of coding. However I must admit that I've been coasting by simply copying various bits of coding from online, not fully understanding what its purpose is.
This has lead me to the point that my code does not work at all and I have no idea why not. The first issue is an issue with simply changing an aspect of other widgets.
I have this sample code:
import Tkinter as tk
LARGE_FONT = ("Verdana", 12)
class SeaofBTCapp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0 , weight = 1)
self.frames = {}
frame = StartPage(container, self)
self.frames[StartPage] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Start Page", font = LARGE_FONT)
label.pack(pady = 10, padx = 10)
button = tk.Button(self, text = "Change Label", command = self.change)
button.pack(pady = 10, padx = 10)
def change(self):
label["text"] = "It has changed"
app = SeaofBTCapp()
app.mainloop()
Which SHOULD be a simple enough code that, with a button press, change the label from "Start Page" to "It has changed". But whenever I run it, it says that the global variable "label" is not defined. Additionally, if I then change it to self.label, it states that StartPage instance has no attribute 'label'. I don't know what I'm doing wrong.
Additionally, in a similar vein, I'm working on a project that has a SideBar class and a Main class tied to one MainApplication class. The Main class takes a value and displays it on a Frame in the Main class. Following this, a button in the SideBar increases that value by 1. But the Main display doesn't update and I have no idea how to tie the Main updating with the button in the SideBar.
import Tkinter as tk
something = [0, 6]
class Main():
def __init__(self, root):
mainboard = tk.Frame(root, height = 100, width = 100)
self.maincanvas = tk.Canvas(mainboard, bd = 1, bg = "white")
mainboard.grid(row = 0, column = 0)
self.maincanvas.grid(row = 0, column = 0)
self.maincanvas.create_text(45, 50, anchor = "center", text = str(something[1]))
class SideBar():
def __init__(self, root):
sidebarframe = tk.Frame(root, height = 100, width = 100)
button = tk.Button(sidebarframe, width = 20, text = "Change Value", command = self.add)
sidebarframe.grid(row = 0, column = 1)
button.grid(row = 0, column = 0)
def add(self):
something[1] += 1
print something[1]
class MainApplication():
def __init__(self, parent):
self.parent = parent
self.sidebar = SideBar(self.parent)
self.main = Main(self.parent)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root)
root.mainloop()
All help would be appreciated, but please try and not use a lot of technical terms, as I am still learning.
In the first scenario replace:
label = tk.Label(self, text = "Start Page", font = LARGE_FONT)
label.pack(pady = 10, padx = 10)
With:
self.label = tk.Label(self, text = "Start Page", font = LARGE_FONT)
self.label.pack(pady = 10, padx = 10)
And also in the function change put it like this:
self.label["text"] = "It has changed"
And in your second problem i changed the code a little bit so it works:
import Tkinter as tk
something = [0, 6]
class Main():
def __init__(self, root):
mainboard = tk.Frame(root, height = 100, width = 100)
self.maincanvas = tk.Canvas(mainboard, bd = 1, bg = "white")
mainboard.grid(row = 0, column = 0)
self.maincanvas.grid(row = 0, column = 0)
self.maincanvas.create_text(45, 50, anchor = "center", text = str(something[1]))
class SideBar():
def __init__(self, root, main):
self.main = main # Putting the main object to self.main
sidebarframe = tk.Frame(root, height = 100, width = 100)
button = tk.Button(sidebarframe, width = 20, text = "Change Value", command = self.add)
sidebarframe.grid(row = 0, column = 1)
button.grid(row = 0, column = 0)
def add(self):
something[1] += 1
self.main.maincanvas.delete("all") # Removing everything from canvas
self.main.maincanvas.create_text(45, 50, anchor = "center", text = str(something[1])) # Inserting the new value
print something[1]
class MainApplication():
def __init__(self, parent):
self.parent = parent
self.main = Main(self.parent) # The main needs to run first
self.sidebar = SideBar(self.parent, self.main) # So that SideBar can use its canvas
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root)
root.mainloop()
Is there any way that i can delete the user input in an entry widget when it's state is disabled and re-enabled? The user input stays as is, I would like to it without having to add a button event.
from Tkinter import *
class Interface():
def __init__(self, window):
frame = Frame(window)
frame.pack()
self.hopLabel = Label(frame, text="Number:", anchor=E)
self.hopLabel.grid(row=0, column=0, sticky=EW)
options = range(0,6)
options.append("Other")
self.variable = StringVar(frame)
self.variable.set(options[0])
self.options = OptionMenu(frame, self.variable, *options, command=self.this)
self.options.grid(row=0, column=2, sticky=EW)
self.button = Button(frame,text = "Print Value",width=20,command = self.printit(self.variable.get()))
self.button.grid(row=1)
self.otherEntry = Entry(frame, state=DISABLED)
self.otherEntry.grid(row=0, column=1, sticky=EW)
def this(self, value):
if value == "Other":
self.otherEntry.config(state=NORMAL)
else:
self.otherEntry.config(state=DISABLED)
def printit(self,value):
print value
if __name__ == "__main__":
root = Tk()
app = Interface(root)
root.mainloop()
In order to save space, i didn't add the function that prints the value of the "Other" option. My question again is: Is there anyway to delete the value in the entry box when the state of the widget goes from DISABLED to NORMAL without having to press a button?
To delete the text in an entry widget when the state is disabled, you simply need to set the state to normal first and then call the delete method:
def this(self, value):
if value == "Other":
self.otherEntry.config(state=NORMAL)
self.otherEntry.delete(0, "end")
...