Using buttons with #interact - python

EDIT: Re-written for clarity Sept. 8 2019 1330 UTC
In a Jupyter notebook, I want to explore dates chosen by a date picker widget. The way of doing this I already knew was to use #interact and make the datepicker widget an argument to the interact function, like this:
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date')):
But I need buttons "Next Holiday" and "Previous Holiday" to move from the selected date to the next holiday. I already have an array of holidays stored as datetime.datetime objects.
So I tried adding the button objects to the arguments to the #interact function, like this:
#interact
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date'),
prev_button = widgets.Button(description="Prev Holiday"),
next_button = widgets.Button(description="Next Holiday")):
That didn't work. Error says the line with the button widget "cannot be transformed into a widget". Hmm, I thought it already was a widget...
My next thought was to put the button code inside the function, rather than as an argument:
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date')):
prev_button = widgets.Button(description="Prev Holiday")
next_button = widgets.Button(description="Next Holiday")
prev_button.on_click(prev_holiday_callback)
next_button.on_click(next_holiday_callback)
box=widgets.HBox([prev_button,next_button])
display(box)
That got the buttons on the screen and I can push them and my callback routines run. But when the datepicker is set up (per above) as an argument to #interact, it appears that it is no longer possible to reset its date using its .value attribute. (See code above) trying to set d1.value results in an error.
I suppose I could get rid of #interact entirely, and just put the datepicker and both buttons into the mainline code. But I don't know how to reproduce the function of #interact at that point. Short of an infinite loop that waits for a click or observe event to come from one of the widgets, other than #interact, I don't know how to tell Python/Jupyter "hey, just chill out until a widget event causes a callback to wake up the code".
In case it's helpful, here are my callback routines for the buttons.
def button_click_common():
global button_flag
global button_date
global holidays
for i in range(len(holidays)):
if(holidays[i]<button_date): # haven't passed the prior holiday yet
continue
# Now holidays[i] has to either be same as or after button_date
prior_holiday = i-1 # prior holiday is the one we just passed that was prior
next_holiday = i # assume at first we are between dates
if holidays[i]==button_date:
next_holiday +=1 # if we were already on a holiday date, next becomes the following one
return prior_holiday, next_holiday
def prev_holiday_callback(_):
global button_flag
global button_date
global holidays
prior_holiday,_ = button_click_common()
button_date = holidays[prior_holiday]
button_flag = True
def next_holiday_callback(_):
global button_flag
global button_date
global holidays
_,next_holiday = button_click_common()
button_date = holidays[next_holiday]
button_flag = True
The idea here is that the callback updates button_date to the date of the next holiday, and sets the flag button_flag. Then code (not shown) in the explore_dates function was going to test the flag. But I can't figure out how to update the datepicker widget with the new date when it is defined as an argument to the #interact function.
Feels like I'm going about this wrong. Any advice or guidance welcome...
Thanks in advance!

I'm not sure I understand the exact aim you're trying to achieve, but interact works best with functions that take numeric inputs, rather than discrete inputs or python objects like dates.
I put together some code that would illustrate how I would use the forward and back buttons to update a datepicker, it's probably not exactly what you are looking for, but maybe you can describe the changes from there?
import ipywidgets as ipyw
from datetime import date
idx = 0
date_picker = ipyw.DatePicker()
date_picker.value = month_starts[idx]
start_button = ipyw.Button(description='Prev')
end_button = ipyw.Button(description='Next')
display(date_picker, start_button, end_button)
def change_date(button):
global idx
if button.description=='Prev':
if idx-1<0:
pass
else:
date_picker.value = month_starts[idx-1]
idx -= 1
elif button.description=='Next':
if idx+1>=len(month_starts):
pass
else:
date_picker.value = month_starts[idx+1]
idx += 1
start_button.on_click(change_date)
end_button.on_click(change_date)

Related

I can't show properly a list in Tkinter

I'm making a study project, just a basic calendar, but I encountered an error, that I can't figure out. Firstly, I input the proper date, which later I append to a list. The next step is to click the button to list all events you already add, but I can't show it proper. It gets the awful brackets, that I can't get rid of. If I try the .format, unfortunately window shows only the first element of a list. Can you help me figure it out?
def window2():
print(events)
win2 = Toplevel(root)
win2.title("Lista wydarzeń")
win2.geometry("300x200")
events_label = Label(win2, text="Wydarzenia:")
events_label.pack()
if len(events) > 0:
for x in events:
events_show_label = Label(win2, text=events)
events_show_label.pack()
else:
no_events_label = Label(win2, text="Nie dodano żadnego wydarzenia.")
no_events_label.pack(pady=10)
I get the variables by .get() and add them to the list like that
events.append(f"Nazwa: {title} \nData: {date} {time}\n")

How to get state of each checkbox generated through a loop, as soon as it is checked/unchecked in tkinter?

I'm trying add/remove an item (text of checkbox) to/from a list whenever a checkbox is checked/unchecked in tkinter.
My idea was to add a command to the checkbutton, like:
cb = Checkbutton(master,...,command=some_fun)
but I cannot think of a way to define the function. I was thinking the function should contain the widget attribute cget('text'), but the problem is I have many checkboxes made with the help of a loop.
I guess the question is: how can I reference the checkbox whose state got changed and is therefore calling the function some_fun?
The way I generated the checkboxes is:
cb_identities = []
for i in range(cb_max_num):
cb = Checkbutton(frame_data,bg="white")
cb_identities.append(cb)
And then I'm dynamically changing them depending on some radiobuttons:
def fun_chck(): #shows or hides checkbuttons based on radiobutton input
data = read_data(rb_var.get())
for i in range(cb_max_num):
cbname = (cb_identities[i])
if len(data)-1 < i:
cbname.grid_forget()
else:
cbname.config(text=data[i]) #I would place some_fun here, which gets text option of checked box
cbname.grid(row=i,column=1,sticky=W)
Update! I managed with the following code for anyone interested:
cb_var_init = [0] * cb_max_num #create the initial list of inactive checkbuttons, all 0
input_params=[] #list which needs to be populated/depopulated based on checkbutton state
def get_data(data): #populates a list with parameter from checked checkbuttons,
global cb_var_init
cb_var_list = list(map(lambda var: var.get(),list(cb_var.values())))
for i in range(len(data)):
if cb_var_list[i] > cb_var_init[i]:
input_params.append(data[i])
elif cb_var_list[i] < cb_var_init[i]:
input_params.remove(data[i])
cb_var_init = cb_var_list
return(input_params)
cb_var is a dictionary of IntVars, and data is a list of checkbuttons' names.
As for the command on each checkbutton, I used cbname.config(text=data[i],command=lambda: get_data(data)) as suggested in another topic for functions with arguments.
Now each time I check a checkbutton, I immediately get a list of parameters which should show in the next Frame, which is dynamically updated.

Python (Alternate two messages)

I have some code, and my goal is to make it so that when left button is clicked it will post text saying "Programming is fun!" and when it is pressed again, it will change that text to "It is fun to program", my idea was to make x = 0 if I wanted the first statement, and x = 1 if I wanted the second statement, but it keeps saying x is not defined, I've tried returning x but it simply won't... I've tried a variety of different methods but I can't figure it out. Thoughts? I'd like an alternative method to this, because I'm not sure that mine will work.
def text():
pf = Label(window2,text="Programming is fun")
pf.pack()
x = 0
def text2():
fp = Label(window2,text="It is fun to program")
fp.pack()
x = 1
def bt(event):
if x == 0:
text()
elif x == 1:
text2()
window2 = Tk()
window2.geometry("500x500")
The problem is that, in the scope of your functions, the variable x is indeed undefined.
An example:
height = 5
def some_function():
# code here can not access height just like that
When you want to access variables in functions, you generally have three options:
1) Put your code in a class instead. This requires some understanding of object oriented programming, and if you're a beginner this might not be the best choice for you
2) Pass the variable as an argument to the function and add it to the function's parameter list, for example like this:
def my_function(height):
# here height can be used
And when calling the function:
height_of_bob = 2
my_function(height_of_bob)
3) Use a global variable. This is usually not necessary, and is considered bad practice in general, but it will solve your problem just fine.
I found a way to do this below, the down low is that it's creating the text through that function, and then it's rebinding the key to do other function and then that function deletes previous text, creates the new text and then rebinds to the previous function, and the cycle continues. Here's the code.
def text(event):
global pf
fp.pack_forget()
pf = Label(window2,text="Programming is fun")
pf.pack()
window2.bind("<Button-1>", text2)
def text2(event):
global fp
pf.pack_forget()
fp = Label(window2,text="It is fun to program")
fp.pack()
window2.bind("<Button-1>", text)
window2 = Tk()
window2.geometry("500x500")
pf = Label(window2,text="Programming is fun")
fp = Label(window2,text="It is fun to program")
window2.bind("<Button-1>", text)

How to efficiently code for updating many variables in a script using Tkinter?

I am designing a Tkinter interface to be compatible with my datalogging script. Both are still WIP, but close to getting merged. This script is used for a Raspberry Pi to periodically make measurements using different sensors. In this case, of plants.
I have various Entry widgets to access my variables (input channels, measure interval and measurements per average value ), so i can change them without reprogramming the script itself.
3 entry widgets are associated with the textvariables:
gpio_light1_entry, interval_light1_entry, amount_light1_entry
The actual variables used by the datalogging script are:
gpio_light1, interval_light1, amount_light1
I want to define a function (That i will bind to a button.). That retrieves the entry window value (gpio_light1_entry.get()) and that updates the script variable.
Now I could just use:
gpio_light1 = gpio_light1_entry.get()
However I have at least 12 variables per plant. So coding it like this for some 12 times seems very inefficient to me.
I was thinking of using a for loop and lists.
settings_gpio1 = [gpio_light1, gpio_temp1, etc]
settings_gpio1_entry = [gpio_light1_entry, gpio_temp1_entry, etc]
But this had some problems:
1- It seems that changing a value in a list, does not change the variable used to construct the list.
2- I do not know how to make a "double" for loop to use both the _entry and non entry lists.
3- The _entry list needs a .get() function to retrieve the values, this function does not work on lists directly, but can be solved with a for loop.
Does anyone know a more efficient or easier way to reach my goal?
I advise you to create a class for each Gpio access:
The class it self should hold the values for accessing the hardware and displaying it.
If, for some reason, you already have the list of available gpios and entries, you can do some thing like this:
class Gpio:
def __init__(self,setting, entry):
self.setting = setting
self.entry = entry
def Get(self):
#Do whatever you need with self.entry
return 0
def __str__(self):
return self.setting
#staticmethod
def FromArray(names,settings, entries):
assert(len(settings) == len(entries) == len(names))
ret = {}
for i in range(len(settings)):
ret[names[i]] = Gpio(settings[i], entries[i])
return ret
Here I put a random example, I don't know what values you are going to use:
gpio_light1 = 33
gpio_light_entry = "hardware_address"
gpio_temp_entry = "0x80001234"
names = ["gpio_light1", "gpio_temp1"]
settings_gpio = [gpio_light1, "gpio_temp1"]
settings_gpio_entry = [gpio_light_entry, gpio_temp_entry]
access = Gpio.FromArray(names, settings_gpio, settings_gpio_entry)
print access["gpio_light1"].Get()

Creating Random Events Pygame

I'm working on a small game in Python called "Blank Screen Simulator" (but it won't just be a black screen, I want to have events. Like a time where random images pop up, but for now I only have a time where you click the mouse alot and a time where you press buttons alot) anyway, I need to make these random. Here's my system I tried:
import pygame
from pygame.locals import *
import time
import random
pygame.init()
width, height = 640, 480
screen=pygame.display.set_mode((width, height))
Event1 == False
Event2 == False
screen_rect=screen.get_rect()
player=pygame.Rect(180, 180, 20, 20)
mouseClickNumber = 0
keyPressNumber = 0
a = random.choice(Event1, Event2)
a = True
def Event1:
if pygame.mouse.get_pressed(button 1):
mouseClickNumber = mouseClickNumber + 1
time.sleep(20)
clickHappyFunTime = False
print mouseClickNumber
mouseClickNumber = 0
def Event2:
if pygame.key.get_pressed:
keyPressNumber = keyPressNumber + 1
time.sleep(20)
buttonPressTime = False
print keyPressNumber
keyPressNumber = 0
My system involves having two events as False, then having a random.choice pick between the two variables, Event1 and Event2, but then when I run it, def Event2: is an invalid syntax. Am I just doing something wrong?
Function definitions must contain a parameter list, even if it's empty. Like this:
def Event1():
However, you've got a lot of other problems here.
Event1 == False doesn't assign False to Event1, it just compares Event1 to False—which is going to raise NameError because you don't have anything named Event1 yet, but even if you did, this wouldn't do anything useful, especially since you ignore the result.
If you fix that, a = random.choice(Event1, Event2) is going to fail because choice takes a single argument, a sequence to choose from, not a separate argument for each choice; you wanted a = random.choice([Event1, Event2]).
If you fix that, it's going to choose between False and False. Maybe you wanted it to choose between the two functions you're going to define later? But you can't choose between them before you define them. Defining them to some different value earlier doesn't help; it's just choosing the earlier value. (Also, reusing the same name for a normal value and a function is very confusing.)
If you fix that, it doesn't matter anyway, because a = True is just going to replace the value you chose.
You never call the functions directly, or attach them to events so they'll be called indirectly by PyGame, so they aren't doing much good.
The functions will raise an UnboundLocalError if you call them, because assigning to mouseClickNumber inside a function makes it a local variable, hiding the global variable of that name. To avoid that, you need to add global mouseClickNumber.
time.sleep inside a GUI application freezes the entire GUI, preventing it from updating the screen.

Categories