I'm trying to achieve a basic todo list app in Ipython Jupyter Notebook using ipywidgets.
I can easily achieve the functionality of adding items to my list, however, I can't properly handle removing of existing items if the 'Remove' button is clicked. The entire code is run in a single cell.
import ipywidgets as widgets
from ipywidgets import VBox, HBox, Text, Button
from IPython.display import display
todo = []
def completed_sentence(sentence):
""" To display existing notes with a 'Remove' button """
sentenceField = Text(value=sentence)
removeButton = Button(description='Remove',
button_style='danger')
return HBox([sentenceField, removeButton])
def render_sentences(_):
""" To update the view """
global a,b
if a.value != '':
todo.append(a.value)
a.value = ''
todoWidget.children = tuple\
([VBox([VBox([completed_sentence(each)
for each in todo]),
HBox([a, b])])])
# Setting up a basic view- with an empty field and a button
a = widgets.Text(value='')
b = widgets.Button(description='Add')
b.on_click(render_sentences)
todoWidget = widgets.HBox([a, b])
display(todoWidget)
Now, in order to enable the removal of sentences, I update the definition of the function completed_sentence as follows:
def completed_sentence(sentence):
""" To display existing notes """
def remove_sentence(_):
global render_sentences
try:
if todo.index(sentenceField.value) >= 0:
todo.remove(sentenceField.value)
render_sentences()
except:
return
sentenceField = Text(value=sentence)
removeButton = Button(description='Remove', button_style='danger')
removeButton.on_click(remove_sentence)
return HBox([sentenceField, removeButton])
But now, this has the issue that its call to render_sentences is ignored! What is the optimal way to deal with such a kind of 'reactive' programming, if you will, using Ipython Widgets.
Updating the definition of completed_sentence seems to do the job. But it still remains a mystery why the original definition didn't work.
def completed_sentence(sentence):
def remove_sentence(_):
global render_sentences
try:
if todo.index(sentenceField.value) >= 0:
todo.remove(sentenceField.value)
except:
pass
render_sentences(_)
sentenceField = Text(value=sentence)
removeButton = Button(description='Remove', button_style='danger')
removeButton.on_click(remove_sentence)
sentence_view = HBox([sentenceField, removeButton])
return sentence_view
Related
I've never really used classes before, I just simply went the easy way (global variables), and now I would like to make my code right to avoid future complications.
This is my code:
from dearpygui.core import *
class Engine:
def __init__(self,serial,type,profile):
self.serial = serial
self.type = type
self.profile = profile
def apply_selected_file():
res = []
html_name= "example.html"
path= "C:/"
#Function that reads data from a file and saves selected data in a list
res = html_imp(path + '/' + html_name)
#I would like to remove the code below and use a class for each file instead
setvalue(sn1,es[0]) #shows a label with this value
setvalue(type1,res[1]) #shows a label with this value
setvalue(profile1,res[2]) #shows a label with this value
return res
def button():
#This was my initial idea but it doesn't seem to work.
# res = apply_selected_file()
# E = Engine(res[0],res[1],res[2])
I have in mind reading multiple HTML files so using a class would be much easier than declaring variables for each file:
1- Use apply_selected_file to read a file and assign values (s/n,type,profile) to a new class (E1,E2,E3,...,E20,...)
2- Use another function button() to access those stored class values.
I have various textboxes on a form to enter numbers and want to have a single function which can check the entry and highight the box in red if incorrect rather than repeating the same code for each textbox.
Is it possible to set the name of the textbox (_FreqTextBox) as a variable so I can make this a generic function for each textbox. An example function is below. I've tried to find examples of this but no luck so far. Newish to Python so apologies if this is obvious.
def FreqTextBoxTextChanged(self, sender, e):
self._FreqTextBox.BackColor = System.Drawing.SystemColors.Window
IsNumber,text_value = self.CheckNumber(self._FreqTextBox.Text)
if (IsNumber == False):
self._FreqTextBox.BackColor = System.Drawing.Color.Red
return
IsNumber = self.NegativeValueMsg(text_value)
if (IsNumber == True):
self._FreqTextBox.BackColor = System.Drawing.Color.Red
return
I'm pretty sure this has been answered, but I can't seem to locate it.
What I want is a python script for Blender that creates a custom tab that contains a button. When that button is pressed, it prints the value of an integer and increments it, so that when you press the button again, it shows an incremented value. Everything seems to work, except for the incremental part.
Here is the code I am using at the moment:
===
import bpy
from bpy.props import (IntProperty,)
from bpy.types import (Panel, Operator, AddonPreferences, PropertyGroup,)
def main(context):
my_number += 1
print(str(my_number))
class MySettings(PropertyGroup):
my_number = IntProperty(
name="Int property",
description="This is an integer.",
default = 1
)
class AddOne(bpy.types.Operator):
"""This is an operator"""
bl_idname = "op.add_one"
bl_label = "Increment by 1"
def execute(self, context):
main(context)
return {'FINISHED'}
class CreatePanel(bpy.types.Panel):
bl_label = "Render Setup Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'TOOLS'
bl_category = "Increment by 1 Tab"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.operator("op.add_one")
def register():
bpy.utils.register_class(AddOne)
bpy.utils.register_class(MySettings)
bpy.utils.register_class(CreatePanel)
def unregister():
bpy.utils.unregister_class(AddOne)
bpy.utils.unregister_class(MySettings)
bpy.utils.unregister_class(CreatePanel)
if __name__ == "__main__":
register()
===
However, when I press the button 'Increment by 1', I get the following error:
"local variable 'my_number' referenced before assignment"
The point of this exercise is just to create an integer variable, store it, then increment it's value and print it out.
EDIT: I added the actual code, rather than an image of it.
The variable my_number is defined in the class MySettings - it can only be accessed through that class, whether that is inside a method that is also part of the class (self.my_number) or directly as a property that is part of an instance of the class (settings_instance.my_number).
You need to find a place outside of the operator and panel to store persistent variables. Adding a custom property to the object or scene types are common options. As you are showing your panel in the node editor, maybe you will want to add it to the material to keep it specific to a material, instead of global to the scene. You define these properties in the addons register() and remove them in unregister().
def register():
bpy.types.Scene.my_settings = bpy.props.PointerProperty(type=MySettings)
def unregister():
del bpy.types.Scene.my_settings
Then in your operator (or main() function) and your panel you can access the variable through the context paramater.
context.scene.my_settings.my_number += 1
Putting that together into your example, with a label to show the value -
import bpy
from bpy.props import (IntProperty,)
from bpy.types import (Panel, Operator, AddonPreferences, PropertyGroup,)
def main(context):
context.scene.my_settings.my_number += 1
print(str(context.scene.my_settings.my_number))
class MySettings(PropertyGroup):
my_number: IntProperty(
name="Int property",
description="This is an integer.",
default = 1
)
class AddOne(Operator):
"""This is an operator"""
bl_idname = "op.add_one"
bl_label = "Increment by 1"
def execute(self, context):
main(context)
return {'FINISHED'}
class CreatePanel(Panel):
bl_label = "Render Setup Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Increment by 1 Tab"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.operator("op.add_one")
row = layout.row()
row.label(text='Value is: '+str(context.scene.my_settings.my_number))
def register():
bpy.utils.register_class(AddOne)
bpy.utils.register_class(MySettings)
bpy.utils.register_class(CreatePanel)
bpy.types.Scene.my_settings = bpy.props.PointerProperty(type=MySettings)
def unregister():
bpy.utils.unregister_class(AddOne)
bpy.utils.unregister_class(MySettings)
bpy.utils.unregister_class(CreatePanel)
del bpy.types.Scene.my_settings
if __name__ == "__main__":
register()
You will find blender.stackexchange a better place to ask for blender specific python help.
Generally this problem "local variable 'my_number' referenced before assignment" comes when you have 'my_number' variable in code and you had not initialized that variable at top of your code or before using that variable do one thing .
Declare my_number=0 and then do your calculation on my_number variable .
I'm currently creating a GUI in order to turn a lot of individual instruments into one complete system. In def smuSelect(self) I create a list self.smuChoices I can use to call individual choices such as smuChoices[0] and it will return "2410(1)".
Once I call def checkBoxSetup it returns PY_VARxxx. I've tried searching the different forums and everything. I've seen mentions using the .get() which just gives me the state of the individual choice. The reason I want the actual string itself is I would like to use it in def testSetup(self) for the user to assign specific names to the individual machine, for example, 2410 = Gate.
My initial attempt was to create another variable smuChoice2 but I believe this is still changing the original list self.smuChoices.
import tkinter as tk
import numpy as np
from tkinter import ttk
def checkBoxSetup(smuChoice2): #TK.INTVAR() IS CHANGING NAME OF SMUS NEED TO CREATE ANOTHER INSTANCE OF SELF.SMUCHOICES
for val, SMU in enumerate(smuChoice2):
smuChoice2[val] = tk.IntVar()
b = tk.Checkbutton(smuSelection,text=SMU,variable=smuChoice2[val])
b.grid()
root = tk.Tk()
root.title("SMU Selection")
"""
Selects the specific SMUs that are going to be used, only allow amount up to chosen terminals.
--> If only allow 590 if CV is picked, also only allow use of low voltage SMU (maybe dim options that aren't available)
--> Clear Checkboxes once complete
--> change checkbox selection method
"""
smuChoices = [
"2410(1)",
"2410(2)",
"6430",
"590 (CV)",
"2400",
"2420"
]
smuChoice2 = smuChoices
smuSelection = ttk.Frame(root)
selectInstruct = tk.Label(smuSelection,text="Choose SMUs").grid()
print(smuChoices[0]) #Accessing list prior to checkboxsetup resulting in 2410(1)
checkBoxSetup(smuChoice2)
print(smuChoices[0]) #Accessing list after check box setup resulting in PY_VAR376
variableSMUs = tk.StringVar()
w7_Button = tk.Button(smuSelection,text="Enter").grid()
w8_Button = tk.Button(smuSelection,text="Setup Window").grid()
root.mainloop()
I was able to solve the problem by changing my list, smuChoices, to a dictionary then modifying
def checkBoxSetup(smuChoice2):
for val, SMU in enumerate(smuChoice2):
smuChoice2[val] = tk.IntVar()
b = tk.Checkbutton(smuSelection,text=SMU,variable=smuChoice2[val])
b.grid()
to
def checkBoxSetup(self):
for i in self.smuChoices:
self.smuChoices[i] = tk.IntVar()
b = tk.Checkbutton(self.smuSelection,text=i,variable=self.smuChoices[i])
b.grid()
Previously I was replacing the variable with what I'm guessing is some identifier that tkinter uses to store the state which is why I was getting PYxxx.
First of all getting PY_VARXX instead of what's in a variable class indicates the lack of get().
replace:
print(self.smuChoices[0])
with:
print(self.smuChoices[0].get())
Secondly, if you want to display the value of a variable class on a label, button, etc. you could rather just use the textvariable option by simply assigning the variable class to it.
Replace:
tk.Label(self.smuName,text=SMU).grid()
with:
tk.Label(self.smuName, textvariable=self.smuChoices[val]).grid()
Your question is still a bit unclear to me but I will try to provide an answer to the best of my understanding.
As I understand it, you're trying to create a set of Checkbuttons for a given list of items. Below is an example of a method that takes items as an argument and returns a dictionary of checkboxes that have root as their parent:
import tkinter as tk
def dict_of_cbs(iterable, parent):
if iterable:
dict_of_cbs = dict()
for item in iterable:
dict_of_cbs[item] = tk.Checkbutton(parent)
dict_of_cbs[item]['text'] = item
dict_of_cbs[item].pack() # it's probably a better idea to manage
# geometry in the same place wherever
# the parent is customizing its
# children's layout
return dict_of_cbs
if __name__ == '__main__':
root = tk.Tk()
items = ("These", "are", "some", "items.")
my_checkboxes = dict_of_cbs(items, root)
root.mainloop()
Additionally note that I haven't used any variable classes (BooleanVar, DoubleVar, IntVar or StringVar) as they seem to be redundant in this particular case.
I am using an IPython Jupyter notebook. In the following situation, I call a function using interact(), which in turns calls a second function again using interact().
def fun1(dataset_id):
dataset = read_dataset(dataset_id)
interact(fun2, data=dataset, var=(0,dataset.property,0.1))
def fun2(data, var):
# something
interact(fun1, dataset_id=(0,5,1))
When first running this, it display 2 slider widgets: one for dataset_id, and one for the variable var. But if I vary the dataset_id slider once, a second slider for var is added below the first var slider, so now I have 3 sliders in total. How can I avoid this?
This is only one step less hacky, but at least you don't have to have a button:
from ipywidgets import *
from IPython.display import display
datasets=[{"property":1},{"property":2},{"property":3},{"property":4},{"property":5}]
def read_dataset(dataset_id):
return datasets[dataset_id]
def fun1(dataset_id):
global sliders
try:
sliders.close()
except NameError:
pass
dataset = read_dataset(dataset_id)
sliders = interactive(fun2, data=fixed(dataset), var=(0,dataset["property"],0.1)) # note I am now using interactive, instead of interact, because I need the close() function
display(sliders)
def fun2(data, var):
print var
interact(fun1, dataset_id=(0,5,1))
After a frustrating day, I came up with a totally hacky way to solve this (but at least it achieves 100% what I want). I am adding a button which, when clicked, invokes .close() on the second slider, as well as on the button itself. Therefore, before each time I need to move the first slider, I press this button to clear up.
Here is a fully-functioning code based on the snippet in the question, that you can copy-paste in your interpreter.
from ipywidgets import *
from IPython.display import display
datasets=[{"property":1},{"property":2},{"property":3},{"property":4},{"property":5}]
def read_dataset(dataset_id):
return datasets[dataset_id]
def fun1(dataset_id):
dataset = read_dataset(dataset_id)
sliders = interactive(fun2, data=fixed(dataset), var=(0,dataset["property"],0.1)) # note I am now using interactive, instead of interact, because I need the close() function
close_button = widgets.Button(description="Remove sliders")
def remove_sliders(b):
sliders.close()
b.close()
close_button.on_click(remove_sliders)
display(sliders)
display(close_button)
def fun2(data, var):
print
# something
interact(fun1, dataset_id=(0,5,1))
Here is another solution, you could create two sliders, and make the "max" of the second slider dependent on the property selected with the first slider:
import ipywidgets as widgets
from ipywidgets import *
from IPython.display import display
datasets=[{"property":1},{"property":2},{"property":3},{"property":4},{"property":5}]
def read_dataset(dataset_id):
return datasets[dataset_id]
w_slider1 = IntSlider(min=0, max=len(datasets)-1, step=1)
w_slider2 = FloatSlider(min=0, step=0.1)
def fun1(dataset_id):
dataset = read_dataset(dataset_id)
#you could get rid of function "read_dataset"
#dataset = datasets[dataset_id]
w_slider2.max = dataset['property']
def fun2(data, var):
#call fun1 to update the size of 2nd slider
fun1(data)
#do something
print(data, var)
interact(fun2, data=w_slider1, var=w_slider2)