In Jupyterlab when selecting a new value in the Select-widget using the mouse, the callback does not trigger.
When assigning a new value using code, the callback is triggered.
Expected behaviour:
clicking on/selecting a new value with the mouse should trigger the callback.
Example:
from ipywidgets import Select
# callback
def cb(change):
print(change)
# widget
s_w = Select(value=None, options=['a','b'])
s_w.observe(cb, names='value')
s_w
Clicking on a or b triggers nothing:
but assigning triggers the callback and the print function.
s_w.value = 'a'
{'name': 'value', 'old': 'b', 'new': 'a', 'owner': Select(options=('a', 'b'), value='a'), 'type': 'change'}
How do I trigger the callback on mouse selection?
ipywidgets-version: 7.5.1
jupyter_client-version: 6.1.2
Related
I am using Flet module to create a simple GUI.
I want to access the custom data on the button. However, using the clicked method, it seems that the event handler of the button using the data property of it, returns empty string. Therefore, the method does not print anything.
Using flet 0.1.62, Python 3.10
import flet
from flet import UserControl, TextButton, Row, Page
class Test(UserControl):
def __init__(self):
super().__init__()
self.text_button = TextButton(text='test', data='test', on_click=self.clicked)
self.view = Row(controls=[self.text_button])
def clicked(self, e):
print(e.data, ' was clicked')
def build(self):
return self.view
def main(page: Page):
t = Test()
page.add(t)
app = flet.app(target=main)
It seems in the flet's tutorials for the Calculor example, that you can have multiple buttons using the same method for on_click argument.
this is a part from the example's code where it refers to Handling events:
https://flet.dev/docs/tutorials/python-calculator/
Now let's make the calculator do its job. We will be using the same event handler for all the buttons and use data property to differentiate between the actions depending on the button clicked. For each ElevatedButton control, specify on_click=self.button_clicked event and set data property equal to button's text, for example:
ft.ElevatedButton(
text="AC",
bgcolor=ft.colors.BLUE_GREY_100,
color=ft.colors.BLACK,
expand=1,
on_click=self.button_clicked,
data="AC",
)
Below is on_click event handler that will reset the Text value when "AC" button is clicked:
def button_clicked(self, e):
if e.data == "AC":
self.result.value = "0"
With similar approach, specify on_click event and data property for each button and add expected action to the button_clicked event handler depending on e.data value.
I am looking for a way to get the same result using the TextButton,
I'd appreciate your help!
I found the answer to my own question. Apparently in the version of the flet that I am using, the data property has to be looked up through the control instance attribute.
Therefore
I tried this in the code from my question:
def clicked(self, e):
print(e.control.data, ' was clicked')
instead of:
def clicked(self, e):
print(e.data, ' was clicked')
I tried it out of curiosity and it worked.
I have a dropdown in tkinter, that i have populated with some items.
OPTIONS = [0,1,2,3,4,5,6,7]
clicked = tk.StringVar()
clicked.set(OPTIONS[0]) # default value
drop = tk.OptionMenu(frame2, clicked, *OPTIONS)
drop.place(relx = 0.65, rely=0.25, relwidth=0.08, relheight=0.6)
However, when a user selects a value, i want other things to happen as well.
Like returning the value to a global variable, or making the state of a button normal, so it's visible again.
How can i run a function, when an item is selected, or when a different item is selected?
EDIT:
Following the suggestions of TheLizzard, i changed my code to this:
# this function is triggered, when a value is selected from the dropdown
def dropdown_selection():
global dropdown_value
dropdown_value = clicked.get()
print("You changed the selection. The new selection is %s." % dropdown_value)
button_single['state'] = 'normal'
OPTIONS = list(range(8))
clicked = tk.StringVar(master=frame2)
clicked.set(OPTIONS[0])
clicked.trace("w", dropdown_selection)
drop = tk.OptionMenu(frame2, clicked, *OPTIONS)
drop.place(relx = 0.65, rely=0.25, relwidth=0.08, relheight=0.6)
However, i get this error:
TypeError: dropdown_selection() takes 0 positional arguments but 3 were given
Try this:
import tkinter as tk
def changed(*args):
print("You changed the selection. The new selection is %s." % clicked.get())
root = tk.Tk()
OPTIONS = list(range(8))
clicked = tk.StringVar(master=root) # Always pass the `master` keyword argument
clicked.set(OPTIONS[0]) # default value
clicked.trace("w", changed)
drop = tk.OptionMenu(root, clicked, *OPTIONS)
drop.pack()
root.mainloop()
In tkinter you can add callbacks to variables like StringVar using <tkinter variable>.trace(mode, callback) for more info read this.
Also always pass in the master keyword argument to all tkinter widgets/variables. You did this with the OptionMenu (it's the first argument usually). But you didn't do it for the StringVar. If you always pass the master keyword argument, you can save yourself a headache.
Edit:
When tkinter calls the callback when the variable is changed it passes some arguments (I don't think they are useful) so make sure that the callback accepts them. Instead of having def callback() use def callback(*args).
I am working on a project that uses ipywidgets and I have a question concerning the button on_click method. I am trying to store the output of the function that is called by the button in a variable (without having to make it global). Here is a simplified example:
import ipywidgets as widgets
my_btn = widgets.Button(description='Press me!', disabled=False, button_style='', tooltip='Click me', icon = '')
def affect_value():
return 10
display(my_btn)
x = my_btn.on_click(affect_value)
So I know this cannot work because the variable is being affected a value before the button is clicked, but at least it shows what I am trying to do (I hope).
Thanks in advance!
on_click doesn't return anything, so assigning it to x gives you None. This is pretty common misconception; the on_click or observe function only associates the widget with the function as a side effect.
To solve the problem, you could create an instance of basic class and assign values to that.
import ipywidgets as widgets
my_btn = widgets.Button(description='Press me!', disabled=False, button_style='', tooltip='Click me', icon = '')
class ValueHolder():
x: int = None
vh = ValueHolder()
def affect_value(btn):
vh.x = 10
display(my_btn)
my_btn.on_click(affect_value)
import PySimpleGUI as sg
layout = [[sg.Button("OK1", key='1', bind_return_key=True)],
[sg.Button("OK2", key='2', bind_return_key=True)],
[sg.Button("OK3", key='3', bind_return_key=True)]]
window = sg.Window("Keyboard Test", return_keyboard_events=True).Layout(layout)
while True:
event, values = window.Read()
print(event)
if event == None:
break
When I run the above code and perform mouse click operation, I get the output as following that is as expected,
clicking on OK1 Button print in the console as: 1
clicking on OK2 Button print in the console as: 2
clicking on OK3 Button print in the console as: 3
But, when I perform keyboard event means, I visit on the button through tab key of keyboard and press Enter on that Button via keyboard it return the same key on all these three button,
Visit on these button one by one via tab key and then press Enter on each I get the result as,
Press Enter on OK1 Button print in the console as: 1
Press Enter on OK2 Button print in the console as: 1
Press Enter on OK3 Button print in the console as: 1
That is not expected output. What I want it should also print their own key same as mouse click event.
What is my intention:
When user press Enter button over OK1, It should print('Hello, OK1 pressed.')
When user press Enter button over OK2, It should print('Hello, OK2 pressed.')
When user press Enter button over OK3, It should print('Hello, OK3 pressed.')
When user click on OK1, It should print('Hello, OK1 pressed.')
When user click on OK2, It should print('Hello, OK2 pressed.')
When user click on OK3, It should print('Hello, OK3 pressed.')
How to achieve this?
I finally understood the question and wrote this as a solution. It's been turned into a Demo Program for the project because people do expect the ENTER key to click a button and yet tkinter and Qt both seem to not work this way. They select buttons using the spacebar.
This source code works for both PySimpleGUI and PySimpleGUIQt. Just use the correct import statement at the top.
import PySimpleGUI as sg
# import PySimpleGUIQt as sg
QT_ENTER_KEY1 = 'special 16777220'
QT_ENTER_KEY2 = 'special 16777221'
layout = [ [sg.T('Test of Enter Key use')],
[sg.In(key='_IN_')],
[sg.Button('Button 1', key='_1_')],
[sg.Button('Button 2', key='_2_')],
[sg.Button('Button 3', key='_3_')], ]
window = sg.Window('My new window', layout,
return_keyboard_events=True)
while True: # Event Loop
event, values = window.Read()
if event is None:
break
if event in ('\r', QT_ENTER_KEY1, QT_ENTER_KEY2): # Check for ENTER key
elem = window.FindElementWithFocus() # go find element with Focus
if elem is not None and elem.Type == sg.ELEM_TYPE_BUTTON: # if it's a button element, click it
elem.Click()
# check for buttons that have been clicked
elif event == '_1_':
print('Button 1 clicked')
elif event == '_2_':
print('Button 2 clicked')
elif event == '_3_':
print('Button 3 clicked')
Out of all of the code above, the part that implements this feature is contained in these 4 lines of code. Add these lines of code to your event loop and then your window will behave such that the ENTER key will click a button. The rest of the code does things with the clicks, is the layout, etc. It's really these statements that implement it.
if event in ('\r', QT_ENTER_KEY1, QT_ENTER_KEY2): # Check for ENTER key
elem = window.FindElementWithFocus() # go find element with Focus
if elem is not None and elem.Type == sg.ELEM_TYPE_BUTTON: # if it's a button element, click it
elem.Click()
I don't fully understand what your goal is. What behavior do you want?
When a button has the focus, pressing the return key doesn't seem to trigger buttons by default. The space bar will though.
Here is some code that will click a button based on a user's keyboard input. I THINK that was what you described after I re-read your question a few times.
import PySimpleGUI as sg
layout = [[sg.Button("OK1", key='_1_', )],
[sg.Button("OK2", key='_2_', )],
[sg.Button("OK3", key='_3_', )]]
window = sg.Window("Keyboard Test", layout, return_keyboard_events=True)
while True:
event, values = window.Read()
# window.Maximize()
print('event: ', event)
if event == None:
break
if event == '1':
window.Element('_1_').Click()
elif event == '2':
window.Element('_2_').Click()
elif event == '3':
window.Element('_3_').Click()
window.Close()
If you want to control the buttons using the keyboard, then what you'll have to do is collect the keyboard key presses and then convert those into whatever behavior you want.
Pressing ENTER when a button has focus (is highlighted) does NOT generate a button click, a SPACE BAR does. You can see this demonstrated in this tkinter program:
import tkinter as tk
def write_slogan():
print("Tkinter is easy to use!")
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame,
text="QUIT",
fg="red",
command=quit)
button.pack(side=tk.LEFT)
slogan = tk.Button(frame,
text="Hello",
command=write_slogan)
slogan.pack(side=tk.LEFT)
root.mainloop()
[EDIT - sorry about the edits but I'm experimenting... ]
I'm trying to write a simple plugin that generates a quick panel based on some list, waits for the user to select an item, and then performs an action based on the value the user selected. Basically, I'd like to do the following:
class ExampleCommand(sublime_plugin.TextCommand):
def __init__(self):
self._return_val = None
self._list = ['a', 'b', 'c']
def callback(self, idx)
self._return_val = self._list[idx]
def run(self):
sublime.active_window().show_quick_panel(
options, self.callback)
if self._return_val == 'a'
// do something
However, show_quick_panel returns before anything is selected and therefore self._return_val won't be assigned to the index selected until after the if statement runs.
How can I solve this problem? With an event listener? I'm very new to Python and Sublime plugin development.
The show_quick_panel() is asynchronous, so while it is executing the remainder of the run() method is finished executing. The action after selection should be done in the callback. The callback will only be called once the user has either selected an item from the quick panel, or dismisses it.
First, you are using a TextCommand so the signature for the run() method is run(edit, <args>), it requires the edit argument.
Also note that the callback will receive an index of -1 if the user doesn't select anything (dismisses the quick panel) e.g. if the user presses Escape.
Here is the show_quick_panel API:
show_quick_panel(items, on_done, <flags>, <selected_index>, <on_highlighted>)
Shows a quick panel, to select an item in a list. on_done will be called once, with the index of the selected item. If the quick panel was cancelled, on_done will be called with an argument of -1.
items may be a list of strings, or a list of string lists. In the latter case, each entry in the quick panel will show multiple rows.
flags is a bitwise OR of sublime.MONOSPACE_FONT and sublime.KEEP_OPEN_ON_FOCUS_LOST
on_highlighted, if given, will be called every time the highlighted item in the quick panel is changed.
— Sublime Text API Reference
Now, let's rework the example command.
class ExampleCommand(sublime_plugin.TextCommand):
def on_done(self, index):
if index == -1:
# noop; nothing was selected
# e.g. the user pressed escape
return
selected_value = self.items[index]
# do something with value
def run(self, edit):
self.items = ['a', 'b', 'c']
sublime.active_window().show_quick_panel(
self.items,
self.on_done
)
For some more examples on the usage of show_quick_panel() see my polyfill package.
Showing the quickpanel obviously does not block the program execution. I recommend to create and pass a continuation:
import sublime
import sublime_plugin
class ExampleQuickpanelCommand(sublime_plugin.WindowCommand):
def run(self):
# create your items
items = ["a", "b", "c"]
def on_select(index):
if index == -1: # canceled
return
item = items[index]
# process the selected item...
sublime.error_message("You selected '{0}'".format(item))
self.window.show_quick_panel(items, on_select)