Python Forum
[Tkinter] button, how to do many things
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] button, how to do many things
#1
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()
Reply
#2
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)
Reply
#3
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.
Reply
#4
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
Reply
#5
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
Reply
#6
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
Reply
#7
Quick response, thanks!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PySimpleGui] How to alter mouse click button of a standard submit button? skyerosebud 3 4,952 Jul-21-2019, 06:02 PM
Last Post: FullOfHelp

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020