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.
Related
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.
I want to add another widget, in this case a scale widget, to a menu widget in tkinter.
Right now the only solutions I see are creating a new command and open a new window with the scale widget or creating the scale widget elsewhere. Both don't seem too appealing to me.
Any ideas how to archive this are welcome :)
You cant add a scrollbar to it, but I have coded something similar to this. Its a hacky way and maybe its hard to understand but I can try to explain.
Note as Bryan mentioned in the linked Thread, this seems to be a a Windows only solution.
import tkinter as tk
def my_first_function():
print('first')
def my_second_function():
print('second')
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
def scroll_down():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_last_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
next_command_label = list(dict_of_commands)[i+1]
next_command = list(dict_of_commands.values())[i+1]
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
except:
pass
space = ' '
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.bind('<<MenuSelect>>', check_for_scroll)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
So this code creates your window and a menubar on it as usal:
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
Important for you, is this line here:
file_menu.bind('<<MenuSelect>>', check_for_scroll)
This line binds the event MenuSelect and it happens/triggers if your cursor hovers over a command of your menu. To this event I have bound a function called check_for_scroll and it looks like this:
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
The line below checks for the index of the command that has triggered the event. With this we check if its button of our interest like the first or last, with the arrows.
check = root.call(event.widget, "index","active")
if its the first for example this code here is executed:
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
it calls/triggers the function scroll_up and uses then the after method of tkinter to retrigger itself, like a loop. The scroll_up function is build like the scroll_down just in the opposite direction. Lets have a closer look:
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
In this function we need to know the first and the last position of commands, because we want to delete one and insert another on that position/index. To achieve this I had created a dictionary that contains the label and the the function of the command item of tkinter like below. (This could be created dynamically, but lets keep it for another question)
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
So we iterate over this enumerated/indexed dictionary in our function and check if the k/key/label is our item of interest. If true, we get the previous_command by listing the dictionary keys and get the extract the key before by this line:
next_command_label = list(dict_of_commands)[i+1]
similar to the value of the dictionary with this line:
next_command = list(dict_of_commands.values())[i+1]
After all we can delete one and insert one where we like to with this:
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
I know that this code can improved by a lot but it seems hard enough to understand as it is. So dont judge me please.
I hope this solves your question, even if it isnt in the way you wanted to. But this code avoids you from hardly code a own menubar.
If there are questions left on my answer, let me know.
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'm using urwid, which is a Python "framework" for designing terminal user interfaces in ncurses. There's one thing though that I'm not able to do in urwid that was easy in curses - make the cursor invisible. As it is now, the cursor is visible when selecting buttons, and it just looks plain ugly. Is there a way to disable it?
I agree that the flashing cursor on an urwid.Button looks a bit lame, so I've come up with a solution to hide it. In urwid, the Button class is just a subclass of WidgetWrap containing a SelectableIcon and two Text widgets (the enclosing "<" and ">"). It's the SelectableIcon class that sets the cursor position to the first character of the label, by default. By subclassing SelectableIcon, modifying the cursor position and then wrapping it into an urwid.WidgetWrap subclass you can create your own custom button that can do all the tricks a built-in Button, or even more.
Here' what it looks like in my project.
import urwid
class ButtonLabel(urwid.SelectableIcon):
def __init__(self, text):
"""
Here's the trick:
we move the cursor out to the right of the label/text, so it doesn't show
"""
curs_pos = len(text) + 1
urwid.SelectableIcon.__init__(self, text, cursor_position=curs_pos)
Next, you can wrap a ButtonLabel object along with any other objects into a WidgetWrap subclass that will be your custom button class.
class FixedButton(urwid.WidgetWrap):
_selectable = True
signals = ["click"]
def __init__(self, label):
self.label = ButtonLabel(label)
# you could combine the ButtonLabel object with other widgets here
display_widget = self.label
urwid.WidgetWrap.__init__(self, urwid.AttrMap(display_widget, None, focus_map="button_reversed"))
def keypress(self, size, key):
"""
catch all the keys you want to handle here
and emit the click signal along with any data
"""
pass
def set_label(self, new_label):
# we can set the label at run time, if necessary
self.label.set_text(str(new_label))
def mouse_event(self, size, event, button, col, row, focus):
"""
handle any mouse events here
and emit the click signal along with any data
"""
pass
In this code, there is actually not much combination of widgets in the FixedButton WidgetWrap subclass, but you could add a "[" and "]" to the edges of the button, wrap it into a LineBox, etc. If all this is superfluous, you can just move the event handling functions into the ButtonLabel class, and make it emit a signal when it gets clicked.
To make the button reversed when the user moves on it, wrap it into AttrMap and set the focus_map to some palette entry ("button_reversed", in my case).
Building upon Drunken Master's answer, I've cleaned up the solution as much as possible.
The urwid.SelectableIcon is basically an urwid.Text field with this ugly blinking cursor.
So instead of overriding the urwid.SelectableIcon and packing it into an urwid.WidgetWrap, let's take an urwid.Text directly and make it selectable and react to button/mouse activation (inspired from urwid's simple menu tutorial):
import urwid
choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split()
class ListEntry(urwid.Text):
_selectable = True
signals = ["click"]
def keypress(self, size, key):
"""
Send 'click' signal on 'activate' command.
"""
if self._command_map[key] != urwid.ACTIVATE:
return key
self._emit('click')
def mouse_event(self, size, event, button, x, y, focus):
"""
Send 'click' signal on button 1 press.
"""
if button != 1 or not urwid.util.is_mouse_press(event):
return False
self._emit('click')
return True
def menu(title, choices):
body = [urwid.Text(title), urwid.Divider()]
for c in choices:
button = ListEntry(c)
urwid.connect_signal(button, 'click', item_chosen, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def item_chosen(button, choice):
response = urwid.Text([u'You chose ', choice, u'\n'])
done = ListEntry(u'Ok')
urwid.connect_signal(done, 'click', exit_program)
main.original_widget = urwid.Filler(urwid.Pile([response,
urwid.AttrMap(done, None, focus_map='reversed')]))
def exit_program(button):
raise urwid.ExitMainLoop()
main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2)
top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
align='center', width=('relative', 60),
valign='middle', height=('relative', 60),
min_width=20, min_height=9)
urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()
Works like a charm:
urwid uses the curs_set function, but does not expose it as a class method anywhere. Someone could modify urwid to allow using this method; otherwise there's no reliable method of doing this.
You might report it as an issue.
Along the lines of Drunken Master's answer, but with "minimally invasive surgery":
class ButtonLabel(urwid.SelectableIcon):
'''
use Drunken Master's trick to move the cursor out of view
'''
def set_text(self, label):
'''
set_text is invoked by Button.set_label
'''
self.__super.set_text(label)
self._cursor_position = len(label) + 1
class MyButton(urwid.Button):
'''
- override __init__ to use our ButtonLabel instead of urwid.SelectableIcon
- make button_left and button_right plain strings and variable width -
any string, including an empty string, can be set and displayed
- otherwise, we leave Button behaviour unchanged
'''
button_left = "["
button_right = "]"
def __init__(self, label, on_press=None, user_data=None):
self._label = ButtonLabel("")
cols = urwid.Columns([
('fixed', len(self.button_left), urwid.Text(self.button_left)),
self._label,
('fixed', len(self.button_right), urwid.Text(self.button_right))],
dividechars=1)
super(urwid.Button, self).__init__(cols)
if on_press:
urwid.connect_signal(self, 'click', on_press, user_data)
self.set_label(label)
Here, we only modify the button's appearance but otherwise leave its behaviour unchanged.
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.