I am trying to use the Python Tkinter .config() method to update some message text. I can't get it to work. What might I be doing wrong (see the update_message method):
#!/usr/bin/python
import alsaaudio as aa
import audioop
import Tkinter as tk
import tkFont
import threading
import Queue
# styles
BACKROUND_COLOR = '#000000'
TYPEFACE = 'Unit-Bold'
FONT_SIZE = 50
TEXT_COLOR = '#777777'
TEXTBOX_WIDTH = 400
# text
TITLE = 'listen closely'
SCORE_MESSAGE = 'your score:\n '
END_MESSAGE = 'too loud!\ntry again'
# configuration
DEVICE = 'hw:1' # hardware sound card index
CHANNELS = 1
SAMPLE_RATE = 8000 # Hz // 44100
PERIOD = 256 # Frames // 256
FORMAT = aa.PCM_FORMAT_S8 # Sound format
NOISE_THRESHOLD = 3
class Display(object):
def __init__(self, parent, queue):
self.parent = parent
self.queue = queue
self._geom = '200x200+0+0'
parent.geometry("{0}x{1}+0+0".format(
parent.winfo_screenwidth(), parent.winfo_screenheight()))
parent.overrideredirect(1)
parent.title(TITLE)
parent.configure(background=BACKROUND_COLOR)
parent.displayFont = tkFont.Font(family=TYPEFACE, size=FONT_SIZE)
self.process_queue()
def process_queue(self):
try:
score = self.queue.get(0)
self.print_message(score)
except Queue.Empty:
pass
self.parent.after(100, self.update_queue)
def update_queue(self):
try:
score = self.queue.get(0)
self.update_message(score)
except Queue.Empty:
pass
self.parent.after(100, self.update_queue)
def print_message(self, messageString):
print 'message', messageString
displayString = SCORE_MESSAGE + str(messageString)
self.message = tk.Message(
self.parent, text=displayString, bg=BACKROUND_COLOR,
font=self.parent.displayFont, fg=TEXT_COLOR, width=TEXTBOX_WIDTH, justify="c")
self.message.place(relx=.5, rely=.5, anchor="c")
def update_message(self, messageString):
print 'message', messageString
displayString = SCORE_MESSAGE + str(messageString)
self.message.config(text=displayString)
def setup_audio(queue, stop_event):
data_in = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NONBLOCK, 'hw:1')
data_in.setchannels(2)
data_in.setrate(44100)
data_in.setformat(aa.PCM_FORMAT_S16_LE)
data_in.setperiodsize(256)
while not stop_event.is_set():
# Read data from device
l, data = data_in.read()
if l:
# catch frame error
try:
max_vol = audioop.rms(data, 2)
scaled_vol = max_vol // 4680
print scaled_vol
if scaled_vol <= 3:
# Too quiet, ignore
continue
queue.put(scaled_vol)
except audioop.error, e:
if e.message != "not a whole number of frames":
raise e
def main():
root = tk.Tk()
queue = Queue.Queue()
window = Display(root, queue)
stop_event = threading.Event()
audio_thread = threading.Thread(target=setup_audio,
args=[queue, stop_event])
audio_thread.start()
try:
root.mainloop()
finally:
stop_event.set()
audio_thread.join()
pass
if __name__ == '__main__':
main()
I don't want to be laying down a new message every time I update. If the .config() doesn't work, is there another method to update the text configuration of the message?
I would use string variables, first create your string variable then set it to want you want it to display at the start next make your object and in text put the sting variable then when you want to change the text in the object change the string variable.
self.messaget = StringVar()
self.messaget.set("")
self.message = tk.Message(
self.parent, textvariable=self.messaget, bg=BACKROUND_COLOR,
font=self.parent.displayFont, fg=TEXT_COLOR,
width=TEXTBOX_WIDTH, justify="c").grid()
#note renember to palce the object after you have created it either using
#.grid(row = , column =) or .pack()
#note that it is textvariable instead of text if you put text instead it will run but
#but will show PY_Var instead of the value of the variable
edit
to change the text without recreating the object you do the name of the string variable you have used and .set
self.messaget.set("hi")
Related
I am creating an app where the server and clients run on the same machine, see picture.
I want the user to be able to send data from the server to a specific client (= specific window). For this, the user needs to know which ID belongs to which client (for example the corresponding ID could be displayed in each window's title).
Is it possible to get the corresponding descriptor ID on the client side? If not, how could I achieve the same result anyway?
I expect something like this as a result:
Here is an example code in pyside2 but I don't mind if the solution is using C++ qt.
QTCPServer:
import sys
from typing import List
from PySide2.QtCore import *
from PySide2.QtNetwork import *
from PySide2.QtWidgets import *
class MainWindow(QMainWindow):
new_message = Signal(bytes)
_connection_set: List[QTcpSocket] = []
def __init__(self):
super().__init__()
self.server = QTcpServer()
# layout
self.setWindowTitle("QTCPServer")
self._central_widget = QWidget()
self._main_layout = QVBoxLayout()
self.status_bar = QStatusBar()
self.text_browser_received_messages = QTextBrowser()
self._controller_layout = QHBoxLayout()
self.combobox_receiver = QComboBox()
self.lineEdit_message = QLineEdit()
self._controller_layout.addWidget(self.combobox_receiver)
self._controller_layout.addWidget(self.lineEdit_message)
self._buttons_layout = QHBoxLayout()
self.send_message_button = QPushButton("Send Message")
self.send_message_button.clicked.connect(self.send_message_button_clicked)
self._buttons_layout.addWidget(self.send_message_button)
# end layout
if self.server.listen(QHostAddress.Any, 8080):
self.new_message.connect(self.display_message)
self.server.newConnection.connect(self.new_connection)
self.status_bar.showMessage("Server is listening...")
else:
QMessageBox.critical(self, "QTCPServer", f"Unable to start the server: {self.server.errorString()}.")
self.server.close()
self.server.deleteLater()
sys.exit()
# set layout
self.setStatusBar(self.status_bar)
self.setCentralWidget(self._central_widget)
self._central_widget.setLayout(self._main_layout)
self._main_layout.addWidget(self.text_browser_received_messages)
self._main_layout.addLayout(self._controller_layout)
self._main_layout.addLayout(self._buttons_layout)
def new_connection(self) -> None:
while self.server.hasPendingConnections():
self.append_to_socket_list(self.server.nextPendingConnection())
def append_to_socket_list(self, socket: QTcpSocket):
self._connection_set.insert(len(self._connection_set), socket)
self.connect(socket, SIGNAL("readyRead()"), self.read_socket)
self.connect(socket, SIGNAL("disconnected()"), self.discard_socket)
self.combobox_receiver.addItem(str(socket.socketDescriptor()))
self.display_message(f"INFO :: Client with socket:{socket.socketDescriptor()} has just entered the room")
def read_socket(self):
socket: QTcpSocket = self.sender()
buffer = QByteArray()
socket_stream = QDataStream(socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
socket_stream.startTransaction()
socket_stream >> buffer
if not socket_stream.commitTransaction():
message = f"{socket.socketDescriptor()} :: Waiting for more data to come.."
self.new_message.emit(message)
return
header = buffer.mid(0, 128)
file_type = header.split(",")[0].split(":")[1]
buffer = buffer.mid(128)
if file_type == "message":
message = f"{socket.socketDescriptor()} :: {str(buffer, 'utf-8')}"
self.new_message.emit(message)
def discard_socket(self):
socket: QTcpSocket = self.sender()
it = self._connection_set.index(socket)
if it != len(self._connection_set):
self.display_message(f"INFO :: A client has just left the room")
del self._connection_set[it]
socket.deleteLater()
self.refresh_combobox()
def send_message_button_clicked(self):
receiver = self.combobox_receiver.currentText()
if receiver == "Broadcast":
for socket in self._connection_set:
self.send_message(socket)
else:
for socket in self._connection_set:
if socket.socketDescriptor() == int(receiver):
self.send_message(socket)
return
self.lineEdit_message.clear()
def send_message(self, socket: QTcpSocket):
if not socket:
QMessageBox.critical(self, "QTCPServer", "Not connected")
return
if not socket.isOpen():
QMessageBox.critical(self, "QTCPServer", "Socket doesn't seem to be opened")
return
string = self.lineEdit_message.text()
socket_stream = QDataStream(socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
header = QByteArray()
string_size = len(string.encode('utf-8'))
fstring = f"fileType:message,fileName:null,fileSize:{string_size}"
header.prepend(fstring.encode())
header.resize(128)
byte_array = QByteArray(string.encode())
byte_array.prepend(header)
socket_stream.setVersion(QDataStream.Qt_5_15)
socket_stream << byte_array
def display_message(self, string):
self.text_browser_received_messages.append(string)
def refresh_combobox(self):
self.combobox_receiver.clear()
self.combobox_receiver.addItem("Broadcast")
for socket in self._connection_set:
self.combobox_receiver.addItem(str(socket.socketDescriptor()))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
QTCPClient
import sys
from PySide2.QtCore import *
from PySide2.QtNetwork import *
from PySide2.QtWidgets import *
class MainWindow(QMainWindow):
new_message = Signal(bytes)
def __init__(self):
super().__init__()
self.socket = QTcpSocket(self)
# layout
self.setWindowTitle("QTCPClient")
self._central_widget = QWidget()
self._main_layout = QVBoxLayout()
self.status_bar = QStatusBar()
self.text_browser_received_messages = QTextBrowser()
self._controller_layout = QHBoxLayout()
self.lineEdit_message = QLineEdit()
self._controller_layout.addWidget(self.lineEdit_message)
self._buttons_layout = QHBoxLayout()
self.send_message_button = QPushButton("Send Message")
self.send_message_button.clicked.connect(self.on_send_message_button_clicked)
self._buttons_layout.addWidget(self.send_message_button)
# end layout
self.new_message.connect(self.display_message)
self.connect(self.socket, SIGNAL("readyRead()"), self.read_socket)
self.connect(self.socket, SIGNAL("disconnected()"), self.discard_socket)
# set layout
self.setStatusBar(self.status_bar)
self.setCentralWidget(self._central_widget)
self._central_widget.setLayout(self._main_layout)
self._main_layout.addWidget(self.text_browser_received_messages)
self._main_layout.addLayout(self._controller_layout)
self._main_layout.addLayout(self._buttons_layout)
self.socket.connectToHost(QHostAddress.LocalHost, 8080)
if self.socket.waitForConnected():
self.status_bar.showMessage("Connected to Server")
else:
QMessageBox.critical(self, "QTCPClient", f"The following error occurred: {self.socket.errorString()}.")
if self.socket.isOpen():
self.socket.close()
sys.exit()
def discard_socket(self):
self.socket.deleteLater()
self.socket = None
self.status_bar.showMessage("Disconnected!")
def read_socket(self):
buffer = QByteArray()
socket_stream = QDataStream(self.socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
socket_stream.startTransaction()
socket_stream >> buffer
if not socket_stream.commitTransaction():
message = f"{self.socket.socketDescriptor()} :: Waiting for more data to come.."
self.new_message.emit(message)
return
header = buffer.mid(0, 128)
file_type = header.split(",")[0].split(":")[1]
buffer = buffer.mid(128)
if file_type == "message":
message = f"{self.socket.socketDescriptor()} :: {str(buffer, 'utf-8')}"
self.new_message.emit(message)
def on_send_message_button_clicked(self):
if not self.socket:
QMessageBox.critical(self, "QTCPServer", "Not connected")
return
if not self.socket.isOpen():
QMessageBox.critical(self, "QTCPServer", "Socket doesn't seem to be opened")
return
string = self.lineEdit_message.text()
socket_stream = QDataStream(self.socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
header = QByteArray()
string_size = len(string.encode('utf-8'))
fstring = f"fileType:message,fileName:null,fileSize:{string_size}"
header.prepend(fstring.encode())
header.resize(128)
byte_array = QByteArray(string.encode())
byte_array.prepend(header)
socket_stream << byte_array
self.lineEdit_message.clear()
def display_message(self, string: str):
self.text_browser_received_messages.append(string)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
The socket descriptors are only valid for the constructor and they do not match on both sides.
One possibility is to automatically send a first "handshake" message to the client as soon as it's connected, the client will identify that message as a "descriptor id" type, and eventually set its window title.
In the following changes to your code, I'm using a simple fileType:descriptor header, and the descriptor id is actually sent as an integer value into the datastream. You can obviously use a string there, if you want to send any other value.
# server
def append_to_socket_list(self, socket: QTcpSocket):
# ...
descriptor = int(socket.socketDescriptor())
socket_stream = QDataStream(socket)
fstring = 'fileType:descriptor,fileName:null,fileSize:{},'.format(descriptor.bit_length())
header = QByteArray()
header.prepend(fstring.encode())
header.resize(128)
socket_stream << header
socket_stream.writeInt32(descriptor)
# client
def read_socket(self):
# ...
header = buffer.mid(0, 128)
fields = header.split(",")
file_type = fields[0].split(":")[1]
buffer = buffer.mid(128)
if file_type == "descriptor":
self.id = socket_stream.readInt32()
self.setWindowTitle("QTCPClient - id {}".format(self.id))
Some suggestions:
both signals have a bytes signature, but this is wrong as you're emitting those signals as str types; if you're not sure, you can use the basic object type;
the self.connect syntax is considered obsolete, use the "new" (well, not so new anymore) style one: object.signal.connect(slot); for instance:
self.socket.readyRead.connect(self.read_socket)
use QApplication.quit() instead of sys.exit(), so that the application properly does everything it needs before actually quitting the python interpreter;
instead of using the text value of the combo, you should use the user data:
descriptor = socket.socketDescriptor()
self.combobox_receiver.addItem(str(descriptor), descriptor)
then you can access it by using self.combobox_receiver.currentData() (you can add the "broadcast" item with a -1 value); you could even add the socket itself as user data;
to properly split the header without getting garbled results for the last field, you must add a final comma, otherwise split() will return the whole rest of the string;
Note for PyQt users: socketDescriptor() returns a sip.voidptr, to obtain the actual value use int(socket.socketDescriptor()).
How can I access an Entry content with pressing the corresponding Button in dynamically created widgets?
Below is the best I come up with so far. Thank you for any help.
from Tkinter import *
class App(object):
def __init__(self, master):
self.master = master
self.mf = Frame(self.master)
self.l = ["white", "red", "blue", "brown"]
self.font = ("Arial", 30)
self.c, self.r = 1, 0
self.cc, self.rr = 0, 0
self.bel = []
for c in self.l:
action = self.print_entry
self.e = Entry(self.mf, bg=c, width=10, font=self.font)
self.e.grid(row=self.r, column=self.c)
self.b = Button(self.mf, bg=c, text=c, font=self.font)
self.b.grid(row=self.rr, column=self.cc)
self.b.config(command=action)
self.bel.append((self.b, self.e))
self.rr += 1
self.r += 1
self.mf.pack()
def print_entry(self): # this function prints the content of the entry
pass
def main():
root = Tk()
display = App(root)
root.mainloop()
if __name__=="__main__":
main()
You can pass a reference to the entry widget into the command, using lambda or functools.partial. For example:
self.b.config(command= lambda entry=self.e: action(entry))
...
def print_entry(self, entry):
print("the entry is '%s'" % entry.get())
By the way, using self.b and self.e is pointless, since those variables will only ever hold references to the last button and last entry. You should either use a local variable, and/or append the values to a list.
My code uses pygame in order to play all MIDI files within its location, however A Tkinter Slider Window is supposed to show up but it doesn't. And I don't know why.
import os,fnmatch,pygame
import Tkinter as tk
pygame.mixer.init()
root = tk.Tk()
List = []
Song = 0
def getThrottle(event):
Volume = Throttle.get()
print "|"*((Volume)*50/100)
def Update():
List = []
for file in os.listdir('.'):
if fnmatch.fnmatch(file, '*.mid'):
List.append(file)
return List
class Slider:
def _Init_(self,root):
self.Throttle = self.Scale(master, from_=0, to=100, tickinterval=10, length=200, orient=HORIZONTAL, command=getThrottle)
self.Throttle.set(0)
self.Throttle.pack()
List = Update()
S = Slider()
root.mainloop()
print List
while True:
while Song <= len(List):
pygame.mixer.music.load(List[Song])
pygame.mixer.music.play(1)
while pygame.mixer.music.get_busy() == True:
List=Update()
Song = 3
Song = 0
I just ran into some strange behavior that has me stumped. I'm writing a simple little GUI for some in-house data processing. I want to allow a user to switch between a few different data-processing modes and input some parameters which define how the data is processed for each mode. The problem is that when the user inputs new parameters, the app ignores requests to switch modes.
The code below replicates the issue. I apologize for the size, this was the shortest code that replicates the problem.
import Tkinter as Tk
class foo(Tk.Frame):
def __init__(self):
self.master = master =Tk.Tk()
Tk.Frame.__init__(self,self.master) #Bootstrap
#Here mode and parameters as key, value pairs
self.data = {'a':'Yay',
'b':'Boo'
}
self.tex = Tk.Text(master=master)
self.tex.grid(row=0,column=0,rowspan=3,columnspan=4)
self.e = Tk.Entry(master=master)
self.e.grid(row=3,column=0,columnspan=4)
self.sv =Tk.StringVar()
self.sv.set('a')
self.b1 = Tk.Radiobutton(master=master,
text = 'a',
indicatoron = 0,
variable = self.sv,
value = 'a')
self.b2 = Tk.Radiobutton(master=master,
text = 'b',
indicatoron = 0,
variable = self.sv,
value = 'b')
self.b3 = Tk.Button(master = master,
text='Apply',command=self.Apply_Func)
self.b4 = Tk.Button(master = master,
text='Print',command=self.Print_Func)
self.b1.grid(row=4,column=0)
self.b2.grid(row=4,column=1)
self.b3.grid(row=4,column=2)
self.b4.grid(row=4,column=3)
def Apply_Func(self):
self.innerdata = self.e.get()
def Print_Func(self):
self.tex.insert(Tk.END,str(self.innerdata)+'\n')
#This is how I'm retrieving the user selected parameters
#property
def innerdata(self):
return self.data[self.sv.get()]
#And how I'm setting the user defined parameters
#innerdata.setter
def innerdata(self,value):
self.data[self.sv.get()] = value
if __name__ == "__main__":
app = foo()
app.mainloop()
Expected behavior:
1) Press button 'a' then 'print' prints:
Yay
2) Press button 'b' then 'print' prints:
Boo
3) Type 'Zep Rocks' into the entry field and press apply
4) Pressing 'print' now yields
Zep Rocks
5) Pressing 'a' then 'print' should yield
Yay
But instead yields
Zep Rocks
Which might be true, but not desired right now. What is going on here?
Edit: I have some new information. Tk.Frame in python 2.7 is not a new-style class. It isn't friendly with descriptors, so rather than interpreting the '=' as a request to use the foo.innerdata's __set__ method, it just assigns the result of self.e.get() to innerdata.
ARGLEBARGLE!!!
Still an open question: how do I get this to do what I want in a clean manner?
So the core problem is that Tk.Frame doesn't subclass from object, so it is not a new-style python class. Which means it doesn't get down with descriptors like I was trying to use. One solution that I found is to subclass my app from object instead.
Code that solves my problem is below:
import Tkinter as Tk
class foo(object):
def __init__(self,master):
self.master = master #Bootstrap
self.mainloop = master.mainloop
self.data = {'a':{'value':7,'metavalue':False},
'b':{'value':'Beeswax','metavalue':True}
}
self.tex = Tk.Text(master=master)
self.tex.grid(row=0,column=0,rowspan=3,columnspan=4)
self.e = Tk.Entry(master=master)
self.e.grid(row=3,column=0,columnspan=4)
self.sv =Tk.StringVar()
self.sv.set('a')
self.b1 = Tk.Radiobutton(master=master,
text = 'a',
indicatoron = 0,
variable = self.sv,
value = 'a')
self.b2 = Tk.Radiobutton(master=master,
text = 'b',
indicatoron = 0,
variable = self.sv,
value = 'b')
self.b3 = Tk.Button(master = master,text='Apply',command=self.Apply_Func)
self.b4 = Tk.Button(master = master,text='Print',command=self.Print_Func)
self.b1.grid(row=4,column=0)
self.b2.grid(row=4,column=1)
self.b3.grid(row=4,column=2)
self.b4.grid(row=4,column=3)
def Apply_Func(self):
self.innerdata = self.e.get()
def Print_Func(self):
self.tex.insert(Tk.END,str(self.innerdata)+'\n')
#property
def innerdata(self):
return self.data[self.sv.get()]
#innerdata.setter
def innerdata(self,value):
self.data[self.sv.get()] = value
if __name__ == "__main__":
master = Tk.Tk()
app = foo(master)
app.mainloop()
I am using Tkinter to create a GUI for a program I am writing that will adjust some Zigbee controlled LED lights that I have. I am using a loop to create multiple copies of a Scale that I'm going to use as a brightness slider. I manage to create the sliders properly, but I am having difficulties actually adjust the sliders correctly. Here's my code:
import simplejson as json
import requests # submits http requests
from Tkinter import *
from ttk import Frame, Button, Label, Style, Notebook
# MD5 hash from http://www.miraclesalad.com/webtools/md5.php
myhash = "d9ffaca46d5990ec39501bcdf22ee7a1"
appname = "dddd" # name content isnt relevant
num_lights = int(3)
class hueApp(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self, *args, **kwds):
# title the app window
self.parent.title("Hue controller")
self.style = Style()
# create grid layout
self.columnconfigure(0, pad=3)
self.columnconfigure(1, pad=3)
self.columnconfigure(2, pad=3)
self.rowconfigure(0, pad=3)
self.scale=[]
self.val=[]
for i in range(num_lights):
print 'i=', i, type(i)
self.val.append(i+1)
print 'val=', self.val, type(self.val)
self.scale.append(Scale(self, from_=255, to_=0, command=lambda i=self.val: self.brightness_adj(i,light_id=i)))
print self.scale[i]
print 'i = ', i, type(i), '\n\n'
self.scale[i].set(150)
self.scale[i].grid(row=1, column=i)
if i == 2:
print '\n', self.scale, '\n'
print self.val, '\n'
self.scale[i].set(200)
self.centerWindow
self.pack()
def brightness_adj(self,light_val, light_id):
#global bri_val
print 'light_id:', light_id, type(light_id)
print 'light_val:', light_val, type(light_val)
print self.val[int(light_id)]
#print int(light_id)
bri_val = self.scale[light_id-1].get()
print bri_val
light = light_id
global huehub
huehub = "http://192.168.0.100/api/"+ myhash + "/lights/" + str(light)
#brightness_logic()
reply = requests.get(huehub)
a=json.loads(reply.text)
#print bri_val
payload = json.dumps({"bri":bri_val})
sethuehub = huehub + "/state"
reply = requests.put(sethuehub, data=payload)
def centerWindow(self):
w = 250
h = 150
sw = self.parent.winfo_screenwidth()
sh = self.parent.winfo_screenheight()
x = (sw-w)/2
y = (sh-h)/2
self.parent.geometry('%dx%d+%d+%d' % (w, h, x, y))
def main():
root=Tk() #the root window is created
app=hueApp(root) #create an instance of the application class
root.mainloop()
if __name__ == '__main__':
main()
I realize that this code probably gives an error when you try to run it. Basically my problem is that the command for each scale is only send brightness_adj the value of the scale, but I can't get it to pass through the id of the light. I was trying to do this by sending through the index of the self.scale list that it is appended into when it is created. I need to know which light is being adjusted so that I can send a new brightness to the corresponding light bulb. I hope I was clear enough. Thanks in advance!
I'm a little confused about what you're trying to do with the line that assigns callback functions to the scale widgets:
self.scale.append(Scale(self, from_=255, to_=0, command=lambda i=self.val: self.brightness_adj(i,light_id=i)))
since self.val is a list, and you're sending it as both the light_val and the light_id arguments, which I would think should be integers.
Possible fix:
I'm guessing that you want each callback to send a different ID to the brightness_adj function depending on which scale it's assigned to. Here's how I would fix this up:
Add this function to your hueApp class namespace:
def brightnessCallbackFactory(self, id):
return lambda light_val:self.brightness_adj(light_val, id)
Then change the callback assignment line from the above to this:
self.scale.append(Scale(self, from_=255, to_=0, command=self.brightnessCallbackFactory(i)))
That should create callback functions that retain the ID value in their internal namespace and assign them to the corresponding scale widget.