This is the streamlit code that I'm currently working on.
When I click the button "About Python" It is working fine and showing the "Generate Syntax" button. But then when I click on the "Generate Syntax" button it is not showing anything and my program is running from the first again.
left, center, right = st.beta_columns(3)
left_one = left.button("About Python", key="1")
center_one = center.button("Learn Python", key="2")
right_one = right.button("Practice Python", key="3")
if left_one:
generate_syntax = st.button("Generate Syntax", key="4")
if generate_syntax:
st.write("Hello All..")
How can I fix the problem?
This can be seen quite often in Streamlit discussions because it looks like a bug at the first glance, but it's not. A simple and silly answer would be "That's the way streamlit is designed".
So every time you press the button, streamlit will re-run the whole web page and it doesn't remember the previous state(information in the previous button).
Although I never found any official answer on how to exactly solve this, I did manage to do some workarounds.
Using checkboxes rather than buttons
Using Session State
Creating small functions and calling them during your if conditions.
When you click About Python:
the value of left_one becomes True
the value of all other buttons (including generate_syntax) becomes False
When you click Generate Syntax:
the value of generate_syntax becomes True
the value of left_one becomes False
The issue is that you never meet both conditions of generate_syntax being True AND left one being True. So you never print "Hello All" Streamlit only thinks of one statechange at a time when it comes to buttons.
The way to get around this is by using an object to store state. #pathe_rao has already suggesting using SessionState object from this gist:
import streamlit as st
### Copy and Paste Session state code here
session_state = SessionState.get(left =False, right = False, center = False)
left, center, right = st.beta_columns(3)
left_one = left.button("About Python",key = "1")
center_one = center.button("Learn Python", key = "2")
right_one = right.button("Practice Python", key = "3")
if left_one or session_state.left:
session_state.left = True
session_state.right = False
session_state.center = False
generate_syntax = st.button("Generate Syntax", key = "4")
if generate_syntax:
st.write("Hello All..")
if center_one or session_state.center:
session_state.left = False
session_state.right = False
session_state.center = True
if right_one or session_state.right:
session_state.left = False
session_state.right = True
session_state.center = False
For starters, two of your buttons aren't really wired into anything, so I'll just focus on a single nested button.
Unlike most other UI libraries, Streamlit isn't event-driven and instead works like Leonard Shelby from the film Memento. In that movie, the protagonist repeatedly blacks out and forgets recent events. He uses tattoos to store information that should persist between amnesia blackouts.
Similarly, in Streamlit, triggered events cause the script to re-execute from the start, totally forgetting everything except st.session_state, which works like Leonard's tattoos to keep track of important information, like which menus are in an open state.
Here's one approach using state:
import streamlit as st # 1.18.1
def handle_about_python_open():
state.about_python = True
def handle_generate_syntax_open():
state.about_python = True
def close():
state.about_python = False
state.generate_syntax = False
state = st.session_state
state.about_python = state.get("about_python")
state.generate_syntax = state.get("generate_syntax")
if (
st.button("About Python", on_click=handle_about_python_open) or
state.about_python
):
if (
st.button("Generate Syntax", on_click=handle_generate_syntax_open)
or state.generate_syntax
):
st.write("Hello All..")
# close the outer button, optionally
st.button("Close", on_click=close)
Note that the on_click callback changes state but the condition version doesn't seem to:
if st.button("Close"):
state.about_python = False
If you want to generalize this, you can:
def create_nested_button_col(i, col, text):
with col:
if st.button(text) or state.open[i][0]:
state.open[i][0] = True
if st.button(f"{text} inner") or state.open[i][1]:
st.write(f"Hello {text}..")
state.open[i][1] = True
def handle_close():
state.open[i][0] = False
state.open[i][1] = False
st.button(f"Close {text}", on_click=handle_close)
state = st.session_state
button_text = "foo", "bar", "baz"
state.open = state.get("open", [[False] * 2 for _ in range(len(button_text))])
pairs = zip(st.columns(len(button_text)), button_text)
for i, (col, text) in enumerate(pairs):
create_nested_button_col(i, col, text)
From this, it should be clear that we can arbitrarily generalize with as many nested buttons as we want, as long as we keep state on open/closed state for each button. A 2d grid of booleans could do it.
Related
Looking for another tip to get the focus to return back to the original TextInput area answer.
Here's what I tried:
def setPracPopBtns(instance):
if (instance.id == "sbt") and (answer.text != ""):
# Make lots of irrelevant changes, and then...
answer.font_name = myTrgF
answer.text = unicode("")
answer.focus = True # This does not return the focus.
# Set the answer TextInput area as the focus.
def setAnswerFocus(self):
answer.focus = True
# This only works on the initial load of the popup.
# That's to be expected. The popup never reopens.
# Build the essential practice layout/widget.
# Define the elements.
practice = BoxLayout(orientation="vertical")
answer = TextInput(id="sbt", multiline=False, size_hint=(.8, .5), text="")
answer.bind(text=lambda instance, text: setattr(sbtLen, "text", str(len(text)) + "/" + ansLen.text),
on_text_validate=setPracPopBtns)
# Lots of irrelevant formatting things to build practice.
# Load practice into the pracPop.
pracPop = Popup(on_open=setAnswerFocus, title_align="center", content=practice)
Will I have to develop a whole focus path, or is there some neater way of getting the focus back to the original TextInput area?
So to clarify, this is what happens:
A popup called pracPop opens.
The setAnswerFocus function correctly places the focus on a
TextInput area called answer. A user hits Enter to submit text, triggering on_text_validate=setPracPopBtns.
answer loses its focus. Resetting the focus with answer.focus = True does nothing.
(4) is the problem for which I have not yet found the solution.
I have plotted a circle glyph. When I select some points they are marked as selected. But I lose the selection when I change the tool I am using or when I click on some other place of the figure. Which is the best way to keep the selection on the circle glyph?. I want to deselect all the points when the ResetTool is pressed. I am working with the master (0.12.14+25.g675aacf72) branch of bokeh where the Selection class already exists.
def update_selection(self, attr, old, new):
''' I do this to avoid deselection '''
if new.indices == []:
self.source.selected.indices = self.old_selection
else:
self.old_selection = list(new.indices)
source.on_change(
'selected',
update_selection
)
I use this to call a callback when the Reset tool is pressed:
def deselect_points(self, event):
''' I do this to deselect point on Reset event
But when the indices are updated the update_selection method is called
'''
self.source.selected.indices = []
plot.on_event(Reset, deselect_points)
So is there a way to keep the selection and only deselect point on Reset event?
Update 03/14/2018
This is happening only when the Tap Tool is enabled. So I have written an issue on GitHub to check if this is the expected behaviour
Finally I made a workaround like this:
# [...]
self.reset_selection = False
# [...]
def _reset_lines(self, event):
# hide the glyphs which mark the selected points
self.lr.visible = False
self.lr_circle_selected.visible = False
self.lr.data_source.data = self.empty_source.data
self.lr_circle_selected.view = self.empty_view
self.reset_selection = True
def update_selection(self, attr, old, new):
if new.indices == []:
if not self.reset_selection:
new.indices = list(self.selection) # keep the selection
else:
self.reset_selection = False
self.selection = []
else:
self.selection = list(new.indices)
self.plot.on_event(Reset, self._reset_lines)
So I am going to use this in the mean time I don´t find anything better
Update
It seems that the issue is already closed. So I hope this is solved already
I have written an app indicator for Ubuntu (basically an icon next to the wifi and battery icons) to set the action for closing the lid. It worked perfectly but I wanted to add a checkmark ✓ to see which state is active. This can be done using CheckMenuItems instead of MenuItems in Gtk. The problem is, in my case, the active state of the CheckMenuItems are mutually exclusive. I.e. when one is active, the other is not. I have listed the most relevant parts of the code.
def build_menu():
menu = gtk.Menu()
item_suspend = gtk.CheckMenuItem('Set lid to suspend')
item_suspend.connect('activate', set_lid_suspend)
menu.append(item_suspend)
item_nothing = gtk.CheckMenuItem('Set lid to nothing')
item_nothing.connect('activate', set_lid_nothing)
menu.append(item_nothing)
menu.show_all()
return menu
def set_lid_suspend(menuItem):
call([os.path.join(__location__, "setLidSuspend.sh")])
noot.update("<b>Set lid to suspend.</b>", "", None)
noot.show()
# adjusting the checkmarks
menu = menuItem.get_parent()
for item in menu.get_children(): # loop over all children
if item.get_label() == 'Set lid to nothing':
item.set_active(False)
break
menuItem.set_active(True)
def set_lid_nothing(menuItem):
call([os.path.join(__location__, "setLidNothing.sh")])
noot.update("<b>Set lid to nothing.</b>", "", None)
noot.show()
# adjusting the checkmarks
menu = menuItem.get_parent()
for item in menu.get_children(): # loop over all children
if item.get_label() == 'Set lid to suspend':
print(item)
item.set_active(False)
break
print("broke from nothing")
menuItem.set_active(True)
The problem is that when I use the appIndicator and call one of both methods, everything is fine and it behaves well; but when I then pick the other method, they will loop forever alternating between both of them. Does anyone know what I'm doing wrong?
Also, is this the right way to find menuItems of a menu? I find it hard to believe that there is no such method implemented as replacement for the for loop I've used.
I think you are using the wrong approach. I would use a RadioMenuItem. Then I would block the 'changing' code if the active state of the radiomenuitem is False. That would result in something like this:
def build_menu():
menu = gtk.Menu()
item_suspend = gtk.RadioMenuItem('Set lid to suspend')
item_suspend.connect('activate', set_lid_suspend)
menu.append(item_suspend)
item_nothing = gtk.RadioMenuItem('Set lid to nothing')
item_nothing.connect('activate', set_lid_nothing)
menu.append(item_nothing)
item_suspend.join_group(item_nothing)
menu.show_all()
return menu
def set_lid_suspend(menuItem):
if menuItem.get_active() == False:
return
call([os.path.join(__location__, "setLidSuspend.sh")])
noot.update("<b>Set lid to suspend.</b>", "", None)
noot.show()
def set_lid_nothing(menuItem):
if menuItem.get_active() == False:
return
call([os.path.join(__location__, "setLidNothing.sh")])
noot.update("<b>Set lid to nothing.</b>", "", None)
noot.show()
Since you posted incomplete code, I cannot test this for sure. Try it and let me know.
Is it possible to track the change of the highlighted item in an urwid.ListBox object? Or even via a ListWalker object?
I would like to call a callback when the user moves from one item to another using the arrow keys [🠉], [🠋], not when the user hits [Enter] on one item.
After some research and experimentation, it's possible to do this by registering the modified signal with the ListWalker object.
import urwid
def callback():
index = str(listBox.get_focus()[1])
debug.set_text("Index of selected item: " + index)
debug = urwid.Text("Debug")
captions = "A B C D E F".split()
items = [urwid.Button(caption) for caption in captions]
walker = urwid.SimpleListWalker(items)
listBox = urwid.ListBox(walker)
urwid.connect_signal(walker, "modified", callback)
frame = urwid.Frame(body=listBox, header=debug)
urwid.MainLoop(frame).run()
Reference: Urwid > Signal functions > connect
I am trying to figure out how to change a rectangle's color continuously, with a second between each change. Right now I have this simple function which makes a window with a square above a button, that changes the color of the square after every button click:
def junk():
def random_color():
red = int(random.random()*256)
green = int(random.random()*256)
blue = int(random.random()*256)
return '#' + ('{:0>#02X}'*3).format(red,green,blue)
def change_color():
c.itemconfig(r, fill=random_color())
x = Tkinter.Tk()
c = Tkinter.Canvas(master=x)
c['width'] = 400; c['height'] = 400
r = c.create_rectangle(0,0,400,400)
b = Tkinter.Button(master=x, command=change_color)
b['text'] = 'change color'
c.pack(); b.pack(); x.mainloop()
What I want is to be able to click once, and then have the colors change automatically. I know I want to use a CheckButton instead of a Button for this, so that one click will start the loop, and and the next click will stop it.
Also, this is not how I am structuring my "real" code, this is how I am testing from the IDLE shell. Defining the helper functions inside the junk function makes it easy to get at all the relevant code at once, without having the bloat of a full class. So please don't give me comments on style, this is quick and dirty on purpose.
TL;DR I'm not sure how to get a continuous loop running to change the color, while being able to start and stop the loop with a button click.
I figured it out. Before I show my solution, I want to correct a mistaken statement I made above: I don't to use a Checkbutton to make this work. I can make a normal button into a toggle button by changing the 'relief' option of the button. Here is my solution:
def junk():
def color_poll():
global alarm
c.itemconfig(r, fill=random_color())
if keep_going:
alarm = c.after(1000, color_poll)
def change_color():
global keep_going, alarm
if not keep_going:
keep_going = True
b['text']='STOP';b['fg']='red';b['relief']=Tkinter.SUNKEN
color_poll()
else:
keep_going = False; c.after_cancel(alarm); alarm = None
b['text']='GO';b['fg']='green';b['relief']=Tkinter.RAISED
x = Tkinter.Tk()
c = Tkinter.Canvas(master=x)
c['width'] = 400; c['height'] = 400
r = c.create_rectangle(0,0,400,400)
global keep_going, alarm
keep_going = False; alarm = None
b = Tkinter.Button(master=x, command=change_color)
b['text'] = 'GO';b['fg']='green';b['font']='Arial 16';b['relief']=Tkinter.RAISED
c.pack(); b.pack(); x.mainloop()
I'm using the same random_color function, but I moved it out because it out of the junk function because it didn't need to be there.