[Tutor] Fwd: Re: Dynamic variables changed via Tkinter button

Alan Gauld alan.gauld at yahoo.co.uk
Sun Apr 19 10:11:34 EDT 2020


Forwarding to list


-------- Forwarded Message --------
Subject: 	Re: [Tutor] Dynamic variables changed via Tkinter button
Date: 	Sun, 19 Apr 2020 11:57:47 +0000
From: 	Jon Davies <jon_davies17 at hotmail.co.uk>
To: 	Alan Gauld <alan.gauld at yahoo.co.uk>, Peter Otten


Hi Peter, Alan,


Thanks for your both of your responses. Having reviewed your responses,
I have now made the respective changes and I have a working solution!

  * I've now removed the duplicate fuelprice counter, and based the
    label on a calculation using fuelcount * currency * fuel_price
  * As you both mentioned, it seemed that I'd forgot to include
    controller as as part of self.controller.currency, that seemed to do
    the trick 
  * Also simplified the SelectCurrencyPage by introducing a single
    "makeCurrencyButtons" based on the list of CURRENCIES outlined in
    the constants (which I've now included at the beginning of my code).
    This definitely makes sense, as it makes expansion to provide more
    currencies so much simpler
  * The set currency function now seems to work, and through testing the
    fuel_lever, the fuel litres versus the price (based on the
    calculations) are correct, which is great!

CURRENCIES = [("GBP", "£", 1.00), ("EUR", "€", 1.14), ("USD", "$", 1.24)]

class SelectCurrencyPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        self.currency_lbl = tk.Label(self, text = _("PLEASE SELECT CURRENCY"), font = LARGE_FONT)
        self.currency_lbl.pack(pady = 10, padx = 10)

        self.currency_buttons = [
            self.makeCurrencyButton(*currency)
            for currency in CURRENCIES
        ]
       
        self.restart_btn = tk.Button(self, text = _("RESTART"), 
                            command = lambda: self.controller.setState('restart'))
        self.restart_btn.pack()

    def makeCurrencyButton(self, code, symbol, conversion):
        currency_button = tk.Button(
            self,
            text=f"{symbol} {code}",
            command=lambda: self.setCurrency(conversion)
    )
        currency_button.pack()
        return currency_button

    def setCurrency(self, conversion):
        self.controller.setCurrency(conversion)
        self.controller.setState("fuel")     

I suppose my only question now, to seal the deal, is how can I display
the selected currency symbol as part of the price label in the Fuelling
page? While I can verify that the currency selection/conversion is
working through looking at the actual numbers that are presented,
showing the actual currency symbol would make it a lot more evident.

I'd assumed it would be something like the below (having taken
inspiration from the def MakeCurrencyButton above)

        self.litre_lbl = tk.Label(self, text = _("Fuel = 0"))
        self.litre_lbl.pack(pady = 10, padx = 10)

        self.price_lbl = tk.Label(self, text = _(f"{symbol}" + "Price = 0"))
        self.price_lbl.pack(pady = 10, padx = 10)

    def update(self, symbol):                                           
        self.fuelcount += 0.1
        total_price = self.fuelcount * self.controller.currency * PETROL_PRICE
        self.litre_lbl['text'] = _("Litres = %f") % self.fuelcount
        self.price_lbl['text'] = _(f"{symbol}" + "Price = %f") % total_price
        self.update_idletasks()
        if self.state == 'working':
            self.lever_btn.after(100, self.update)

But the main thing is, the functionality that I required from the start
has now finally been implemented (translation and currency conversion),
so this is a huge milestone for me and I can now look at tweaking the
design parts. I could not have done it without your help so much
appreciated as always.

Kind regards,


------------------------------------------------------------------------
*From:* Tutor <tutor-bounces+jon_davies17=hotmail.co.uk at python.org> on
behalf of Alan Gauld via Tutor <tutor at python.org>
*Sent:* 19 April 2020 12:08
*To:* tutor at python.org <tutor at python.org>
*Subject:* Re: [Tutor] Dynamic variables changed via Tkinter button
 
On 18/04/2020 15:22, Jon Davies wrote:

> Given I need two labels (one for Fuel Price, one for Litres), I've implemented two counts
> (fuelcount and pricecount) and separated these within the "update" function. For the pricecount,
> I am looking to enable an increase based on "fuelcount * (currency * petrol_price)". 

The currency*price value only needs to be calculated one when the
currency is selected. So in your selectCurrency event handler you should
set the currency symbol and a fuel_rate attribute in your controller
class. Then in the fuelling update method use fetch the rate from the
controller and do the multiplication using that rate.


See below for what I've tried to implement.
> 
>     def update(self):
>         self.fuelcount += 0.1
>         self.pricecount += 0.1 * (self.currency * PETROL_PRICE)

The price should not increment it should be calculated.
          self.price = self.controller.fuel_rate * self.fuelcount


>         self.litre_lbl['text'] = "Litres = %f" % self.fuelcount
>         self.price_lbl['text'] = "Price = %f" % self.price
>         self.update_idletasks()
>         if self.state == 'working':
>             self.lever_btn.after(100, self.update)

> The code doesn't seem to recognise self.currency (starting that 
> AttributeError: 'BeginFuellingPage' object has no attribute 'currency'). 

Where did you set the currency? Was it in the fuelling object or in
another class instance? Any data that you want to share between your
objects will need to go into the controller object since it is the
only thing that all the other objects can see. (This is one reason I
don't like this style of GUI design, the controller becomes the owner
of lots of data that should really be in the other objects.)


> I am trying to reference the currency variable that was set earlier on in the GUI via the SelectCurrencyPage.
> class SelectCurrencyPage(tk.Frame):
>     def __init__(self, parent, controller):
>         tk.Frame.__init__(self, parent)
>         self.controller = controller
> 
>         self.currency_lbl = tk.Label(self, text = _("PLEASE SELECT CURRENCY"), font = LARGE_FONT)
>         self.currency_lbl.pack(pady = 10, padx = 10)
> 
>         self.GBP_btn = tk.Button(self, text = "£ GBP",
>                             command = lambda c = "£": self.setGBP(c))
>         self.GBP_btn.pack()
> 
>         self.USD_btn = tk.Button(self, text = "$ USD",
>                             command = lambda c = "$": self.setUSD(c))
>         self.USD_btn.pack()
> 
>         self.EUR_btn = tk.Button(self, text = "€ EUR",
>                             command = lambda c = "€": self.setEUR(c))
>         self.EUR_btn.pack()

As Peter has pointed out you don't need or want lots of separate methods
to set currency. You just want one that gets called with the appropriate
currency. That method is the one that should notify the controller of
the selected currency and the exchange_rate. The controller can then
calculate the fuel_price based on its
base rate * exchange_rate.

In an ideal world the knowledge about currency and rates would stay in
the Currency object and the controller methods would query it from
there. So you would have a get_exchange_details() method in the
controller, called by the Fuelling object. And that method would fetch
the currency symbol and exchange rate from the currency object.
But that frankly complicates matters, so for now just put the data
in the controller...

-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


_______________________________________________
Tutor maillist  -  Tutor at python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

------------------------------------------------------------------------
*From:* Tutor <tutor-bounces+jon_davies17=hotmail.co.uk at python.org> on
behalf of Peter Otten <__peter__ at web.de>
*Sent:* 18 April 2020 16:52
*To:* tutor at python.org <tutor at python.org>
*Subject:* Re: [Tutor] Dynamic variables changed via Tkinter button
 
Jon Davies wrote:

> Hi Alan,
> 
> As always, thanks for your insight - that gives me exactly the sort of
> base I was hoping to build on. I had researched on the web on whether it
> was possible, but given your note that typically this wouldn't considered
> as standard GUI behavior, explains why I maybe didn't find much (or
> perhaps I didn't look hard enough!).
> 
> I've now adapted the below and implemented something similar as part of my
> wider Petrol Pump program code and I've managed to get it functioning.
> 
> However, now I am looking to enhance this further (using the selected
> currency as input to a calculation, which you are already aware of).
> 
> Given I need two labels (one for Fuel Price, one for Litres), I've
> implemented two counts (fuelcount and pricecount) and separated these
> within the "update" function. For the pricecount, I am looking to enable
> an increase based on "fuelcount * (currency * petrol_price)".  See below
> for what I've tried to implement.
> 
>     def update(self):
>         self.fuelcount += 0.1
>         self.pricecount += 0.1 * (self.currency * PETROL_PRICE)
>         self.litre_lbl['text'] = "Litres = %f" % self.fuelcount
>         self.price_lbl['text'] = "Price = %f" % self.pricecount

Don't use two independent counters; count the litres of fuel only, and 
calculate the price from that:

        price = self.fuelcount * self.currency * PETROL_PRICE
          self.price_lbl["text"] = "Price = %f" % price

(I'm not sure, but self.currency might actually have to be 
self.controller.currency)

>         self.update_idletasks()
>         if self.state == 'working':
>             self.lever_btn.after(100, self.update)
> 
> I tried to configure the calculation to use the fuelcount value directly
> i.e. (0.1), but I found that this started to increase the pricecount
> exponentially, so for now I have just aligned the values at 0.1. The code
> doesn't seem to recognise self.currency (starting that AttributeError:
> 'BeginFuellingPage' object has no attribute 'currency'). If I replaced
> self.currency with a number, then it works.
> 
> I am trying to reference the currency variable that was set earlier on in
> the GUI via the SelectCurrencyPage. class SelectCurrencyPage(tk.Frame):
>     def __init__(self, parent, controller):
>         tk.Frame.__init__(self, parent)
>         self.controller = controller
> 
>         self.currency_lbl = tk.Label(self, text = _("PLEASE SELECT
>         CURRENCY"), font = LARGE_FONT) self.currency_lbl.pack(pady = 10,
>         padx = 10)
> 
>         self.GBP_btn = tk.Button(self, text = "£ GBP",
>                             command = lambda c = "£": self.setGBP(c))
>         self.GBP_btn.pack()
> 
>         self.USD_btn = tk.Button(self, text = "$ USD",
>                             command = lambda c = "$": self.setUSD(c))
>         self.USD_btn.pack()
> 
>         self.EUR_btn = tk.Button(self, text = "€ EUR",
>                             command = lambda c = "€": self.setEUR(c))
>         self.EUR_btn.pack()
> 
>         self.restart_btn = tk.Button(self, text = _("RESTART"),
>                             command = lambda:
>                             self.controller.setState('restart'))
>         self.restart_btn.pack()

There's a lot of redundancy here. You can simplify that with a helper 
method:

def makeCurrencyButton(self, code, symbol, conversion):
    button = tk.Button(
        self, 
        text=f"{symbol} {code}", 
        command=lambda: self.setCurrency(conversion)
    )
    button.pack()
    return button

> 
>     def setGBP(self, currency):
>         self.controller.setCurrency(GBP)
>         self.controller.setState('fuel')
> 
>     def setUSD(self, currency):
>         self.controller.setCurrency(USD)
>         self.controller.setState('fuel')
> 
>     def setEUR(self, currency):
>         self.controller.setCurrency(EUR)
>         self.controller.setState('fuel')

Replace these with

def setCurrency(self, conversion):
    self.controller.setCurrency(conversion)
    self.controller.setState("fuel")        

Now

>         self.GBP_btn = tk.Button(self, text = "£ GBP",
>                             command = lambda c = "£": self.setGBP(c))
>         self.GBP_btn.pack()
> 
>         self.USD_btn = tk.Button(self, text = "$ USD",
>                             command = lambda c = "$": self.setUSD(c))
>         self.USD_btn.pack()
> 
>         self.EUR_btn = tk.Button(self, text = "€ EUR",
>                             command = lambda c = "€": self.setEUR(c))
>         self.EUR_btn.pack()

becomes

    self.GBP_btn = self.makeCurrencyButton("GBP", "£", GBP)
    self.USD_btn = self.makeCurrencyButton("USD", "$", USD)
    self.EUR_btn = self.makeCurrencyButton("EUR", "€", EUR)

If you go one step further and use a list to store the (code, symbol, 
conversion-rate) triples

CURRENCIES = [("EUR", "€", 1.14), ...]

and the buttons

    self.currency_buttons = [
        self.makeCurrencyButton(*currency)
        for currency in CURRENCIES
    ]

it will become very easy to add or remove currencies.

Regarding your actual question, I guess that you are making a mess by using 
the currency attribute for two different purposes -- but my idea of your 
program is not concrete enough to tell you how to fix that, or if that even 
is the correct diagnosis. You have to wait for Alan or someone else to
chime 
in -- or provide the complete runnable source code somewhere.


_______________________________________________
Tutor maillist  -  Tutor at python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor



More information about the Tutor mailing list