Posts: 27
Threads: 11
Joined: Apr 2020
Apr-24-2020, 07:55 AM
(This post was last modified: Apr-24-2020, 07:55 AM by nanok66.)
Hey all,
I'm really stuck on a number of things here. I'm trying to make a page with many buttons for incrementing/decrementing settings that another script will use to run a machine. I am trying to do 3 things with these buttons:
increment/decrement the label (on this page)
increment/decrement the local value of each setting (in another file)
increment/decrement the sqlite database value
I'm sorry that it won't compile, that's just how stuck I am right now. I think the label change will work. I think the database change would work. I'm not really sure how to pass all these arguments to my increase/decrease functions, but I am trying 'partial' from what I read in other tutorials. But I really have no idea how to change a variable that is not in this file (and specifically how to change THAT variable, not work with the local copy made in the function). I was thinking of making the values in another file into a dictionary to easily be able to reference the other file variables. For now I put ??? in my function cuz I have no clue. Lastly, I have about 40 of these settings to make so I was also trying to make some function that will simplify writing all the code for these 40 buttons.
from tkinter import *
import machineProgram #the file where my settings variables live
from functools import partial
import sqlite3Connection
def increase(setting_in_other_file, value, label, db_setting_name):
value += 1
label['text'] = value
setting_in_other_file = value
sqlite3Connection.update_setting(conn, db_setting_name)
def decrease(setting_in_other_file, value, label, db_setting_name):
value -= 1
label['text'] = value
setting_in_other_file = value
sqlite3Connection.update_setting(db_setting_name)
settingsPage = Tk()
settingsPage.geometry("1024x600")
settingsPage.columnconfigure([0, 1, 2, 3], minsize=5)
sqlite3Connection.create_connection(db_file="somewhere on RPi")
btn_decrease = Button(master=settingsPage, text="-", command=partial(increase, ???, machineProgram.machineClass.rinseTime, rinse_time_label, 'rinseTime'), width=10)
btn_decrease.grid(row=2, column=0, sticky="nsew")
rinse_time_label = Label(master=settingsPage, text=machineProgram.machineClass.rinseTime)
rinse_time_label.grid(row=2, column=1)
btn_increase = Button(master=settingsPage, text="+", command=partial(decrease, ???, machineProgram.machineClass.rinseTime, rinse_time_label, 'rinseTime'), width=10)
btn_increase.grid(row=2, column=2, sticky="nsew")
lbl_desc = Label(master=settingsPage, text="Rinse Time")
lbl_desc.grid(row=2, column=3)
settingsPage.mainloop()
Posts: 6,813
Threads: 20
Joined: Feb 2020
Apr-24-2020, 04:59 PM
(This post was last modified: Apr-25-2020, 03:58 AM by deanhystad.)
I think I understand your question. Let me rephrase to be sure.
You have this variable(machineProgram.machineClass.rinseTime). You want to get and set the variable's value. You want to be able to do this without having to use the variable's name because you will be expanding your program to not only using rinseTime.
Unfortunately you cannot do what you want to do. There is no "pass by reference" in Python, so you cannot pass a variable to a function and have the function change the variable's value.
What you need are getter and setter functions. These should really be part of your machineClass. MachineClass should have a method for getting the value of rinseTime and another for setting the value of rinseTime. If you are allowed to modify MachineClass you could add these methods. Using @property decorators you could even hide these new methods and make it look like rinseTime is some sort of super variable that supports pass by reference. In MachineClass:
@property
def rinse_time(self):
return self.rinseTime
@rinse_time.setter
def rinse_time(self, value):
self.rinseTime = value If you cannot modify MachineControl you can write the getter and setter functions externally. Inside your GUI module you could write getter's and setters for each machine parameter you want to control.
def set_rinseTime(value):
machineProgram.machineClass.rinseTime = value
def get_rinseTime():
return machineProgram.machineClass.rinseTime Unfortunately I don't think this can be done using lambdas as they don't allow assignment.
The only other way I see around this is sneaky. setAttr sets the value of an attribute in an object. If machineProgram.machineClass is an object, setAttr(machineProgram.machineClass, 'rinseTime', 5) is the same as machineProgram.machineClass.rinseTime = 5.
This is an ugly, ugly, ugly dirty bandaid. The kind of thing you should only ever do when there is no other choice. That doesn't mean it can't be prettied up a bit.
class MachineControl:
def __init__(self, rinseTime):
self.rinseTime = rinseTime
class MachineParameter:
def __init__(self, parent, attribute):
self.parent = parent
self.attribute = attribute
@property
def value(self):
return getattr(self.parent, self.attribute)
@value.setter
def value(self, new_value):
setattr(self.parent, self.attribute, new_value)
def incr(param, amount):
param.value += amount
machine = MachineControl(5)
param = MachineParameter(machine, 'rinseTime')
print('Rinse time =', machine.rinseTime)
incr(param, 1)
print('Rinse time =', machine.rinseTime) Output: Rinse time = 5
Rinse time = 6
Of course if you go to the trouble of writing a class, you man as well teach the class how to increment and decrement the parameter value.
class MachineControl:
def __init__(self, rinseTime):
self.rinseTime = rinseTime
class MachineParameter:
def __init__(
self,
parent,
attribute,
increment=1,
label=None,
db=None,
db_field=None):
self.parent = parent
self.attribute = attribute
self.increment = increment
self.label = label
self.db = db
self.db_field = db_field
@property
def value(self):
return getattr(self.parent, self.attribute)
@value.setter
def value(self, new_value):
setattr(self.parent, self.attribute, new_value)
if self.db is not None:
sqlite3Connection.update_setting(
self.db, self.db_field, self.value)
if self.label is not None:
self.label['Text'] = str(self.value)
def incr(self, count=1):
self.value += self.increment * count
def decr(self, count=1):
self.value -= self.increment * count
machine = MachineControl(5)
param = MachineParameter(machine, 'rinseTime')
print('Rinse time =', machine.rinseTime)
param.incr(2)
print('Rinse time =', machine.rinseTime)
Posts: 27
Threads: 11
Joined: Apr 2020
Apr-25-2020, 03:21 AM
(This post was last modified: Apr-25-2020, 03:21 AM by nanok66.)
Yes you understood my question just fine. Cool I definitely didn't think of the getter setting thing, thanks! Also I think I'll try and use your last example in my MachineClass so that it's all handled there instead of in my GUI file.
Otherwise I guess I'll just take the long way to do the increase/decrease and make 2 functions for all 40 settings that I have. That way I don't have to pass any args through the button command.
If I "go the long way" and the amount of code is higher (as in more actual text) it's not like it actually will slow down the program right? Just means the script might be a few kb bigger, which seems harmless.
Posts: 6,813
Threads: 20
Joined: Feb 2020
My first example would be something you could add to your machine class. My last example is something that would be used only if you couldn't modify the machine class. Since it is short you could include it (only the MachineParameter class) in you GUI file. I would write a function that created an instance of the class, created a label, an increment and a decrement button. Something kind of like this:
def new_machine_control(title, param_name, db_field_name, row):
title_lbl = Label(master=settingsPage, 'text'=title)
title_lbl.grid('row' = row, 'column' = 0)
value_lbl = Label(master=settingsPage)
value_lbl.grid('row' = row, 'column' = 3)
param = MachineParameter(machineProgram.machineClass, param_name, 1, value_lbl, \
sqlite3Connection, db_field_name)
param.incr(0)
incr_btn = Button(master=settingsPage, text="+", command=partial(param.incr))
incr_btn.grid('row' = row, 'column' = 1)
decr_btn = Button(master=settingsPage, text="-", command=partial(param.decr))
incr_btn.grid('row' = row, 'column' = 2)
return param
new_machine_control('Rinse TIme', 'rinseTime', db_stuff, 1)
new_machine_control('Tumble TIme', 'tumbleTime', db_stuff, 2) That would make a fairly compact GUI file even if you have many parameters to control.
Be aware I have no idea how the database connection stuff would look. I haven't made it to using databases in python (yet). You might also want to save the buttons and the title in the MachineParameter class so everything would be in one place. I hate throwing things away and not being able to get to them again
Posts: 27
Threads: 11
Joined: Apr 2020
Awesome yeah that last function you made was what I was trying to do but I bet yours will actually work!
Ok then yes of course I can modify MachineClass it's my file. Confused though, so in my MachineClass I would have a get and set for each setting but in another class MachineParameter would contain all the settings?
I am getting the error "Instance attribute rinseTime defined outside __init__" so am I supposed to consolidate these two classes into one?
class MachineClass:
def __init__(
self,
parent,
attribute,
increment=1,
label=None,
db=None,
db_field=None):
self.parent = parent
self.attribute = attribute
self.increment = increment
self.label = label
self.db = db
self.db_field = db_field
@property
def rinse_time(self):
return self.rinseTime
@rinse_time.setter
def rinse_time(self, value):
self.rinseTime = value #error from this line
def incr(self, count=1):
self.value += self.increment * count
def decr(self, count=1):
self.value -= self.increment * count
class MachineParameter:
def __init__(self, rinseTime, anotherSetting):
self.rinseTime = rinseTime
self.anotherSetting = anotherSetting
Posts: 6,813
Threads: 20
Joined: Feb 2020
Property getters and setters for MachineClass would look like this:
class MachineClass:
def __init__(self):
self._rinseTime = 5 #can't have same name as property. _name is a convention
@property
def rinseTime(self)
return self._rinseTime
@rinseTime.setter
def rinseTime(self, value):
self._rinseTime = value Code outside MachineClass would use rinseTime like this:
print(machine.rinseTime) # uses getter
machine.rinseTime += 1 # uses getter and setter
Posts: 27
Threads: 11
Joined: Apr 2020
|