Related
So I want a window that updates a shown picture after clicking.
It works fine as long as there is no further tk.Tk() instance (remove/add line 8 of the code below).
If one is created before, this error is raised:
line 29, in CreatePeakSelectionWindow
[...]
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.img1)
[...]
_tkinter.TclError: image "pyimage1" doesn't exist
I think I need to pass some argument to Tk()?
I don't know where to even look to address this issue and understand how it is caused.
Sadly this widget is to be used to allow manual selection of some peaks and should be done in an external window.
FYI all arrays are dummies (random arrays) for simplicities sake.
Thank you very much for any help!
The code which causes the issue is the following:
import tkinter as tk
import numpy as np
from PIL import Image,ImageTk
import matplotlib.pyplot as plt
class Dummy:
def __init__(self):
self.MainWin = tk.Tk() #>this line causes the issue
imgs = np.random.randint(0,255,(512,624,2))
self.img = imgs[:,:,0] #self.img is a numpy array in black and white
self.imgSize = self.img.shape
self.peakList = np.array([[200,200],[300,400]])
self.selectedIndexOfPeaksList = []
self.peakListGenerated = True
def CreatePeakSelectionWindow(self):
if self.peakListGenerated:
self.selectedIndexOfPeaksList = []
self.PeakSelectionWindow = tk.Tk()
self.PeakSelectionWindow.protocol("WM_DELETE_WINDOW",self.PeakSelectionWindowClose)
self.PeakSelectionWindow.geometry("%sx%s"%(self.imgSize[1],self.imgSize[0]))
self.PeakSelectionWindow.title("Peak Slection")
self.img1 = ImageTk.PhotoImage(image=Image.fromarray(self.img))
self.imgCanvas = tk.Canvas(self.PeakSelectionWindow,width=self.imgSize[1],height=self.imgSize[0])
self.imgCanvas.place(x=0,y=0)
self.PeakSelectionWindow.bind("<Button 1>",self.LeftClick)
self.PeakSelectionWindow.bind("<Button 3>",self.RightClick)
self.PeakSelectionWindow.update()
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.img1)
else:
print("List of peaks has not yet been generated!",file=sys.stderr)
def PeakSelectionWindowClose (self):
if len(self.selectedIndexOfPeaksList) > 0:
print("put extraction here")
#self.selectedPeaksEntry.insert(tk.END,", ".join(map(str,self.selectedIndexOfPeaksList)))
self.PeakSelectionWindow.destroy()
def LeftClick(self,event):
distance = np.sqrt((self.peakList[:,1]-event.x)**2+(self.peakList[:,0]-event.y)**2)
index = np.argmin(distance)
if index not in self.selectedIndexOfPeaksList:
self.peakList[index]
self.selectedIndexOfPeaksList += [index]
newImg = np.random.randint(0,255,(self.img.shape[0],self.img.shape[1],3))
self.PeakSelectionWindow.newImg = img = ImageTk.PhotoImage(image=Image.fromarray(newImg.astype("uint8"),mode="RGB"))
self.imgCanvas.delete("all")
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.PeakSelectionWindow.newImg)
self.imgCanvas.update()
def RightClick (self,event):
distance = np.sqrt((self.peakList[:,1]-event.x)**2+(self.peakList[:,0]-event.y)**2)
index = np.argmin(distance)
print(self.selectedIndexOfPeaksList)
if index in self.selectedIndexOfPeaksList:
if len(self.selectedIndexOfPeaksList) > 1:
self.selectedIndexOfPeaksList.remove(index)
newImg = np.random.randint(0,255,(self.img.shape[0],self.img.shape[1],3))
self.PeakSelectionWindow.newImg = img = ImageTk.PhotoImage(image=Image.fromarray(newImg.astype("uint8"),mode="RGB"))
self.imgCanvas.delete("all")
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.PeakSelectionWindow.newImg)
self.imgCanvas.update()
else:
self.selectedIndexOfPeaksList = []
self.PeakSelectionWindow.newImg = newImg = ImageTk.PhotoImage(image=Image.fromarray(self.img.astype("uint8")))
self.imgCanvas.delete("all")
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.PeakSelectionWindow.newImg)
self.imgCanvas.update()
if __name__ == "__main__":
window = Dummy()
window.CreatePeakSelectionWindow()
tk.mainloop()
Okay so I found a solution.
The additional window needs to be a class with tk.Toplevel().
All changes for the code above are:
class Dummy: to class Dummy (tk.Toplevel()):
def __init__(self): to def __init__ (self,master):
self.peakSelectionWindow to self (as a reference to master
passed to the class)
any tk object (like buttons) also needs this master set as the window too to be rendered
Of course the creation of the first window should be handled outside of the class, passing the windowName = tk.Tk() onto the call of Dummy like a normal variable/reference.
In case you need to share variables of the master to this dummy class, I think windowName.variableName = 5 makes them known/accessable in dummy too (via self.variableName). However this might be messy, so instead pass them on normally if possible.
I'm trying to create a widget filter (made up of TextInput and MultiSelect) that is replicated on two different Bokeh Tabs. The desired functionality is that filtering results should be preserved between tabs, regardless of which filter receives the text to filter off of.
The code below(it is working code) builds the Filter widget which is instantiated as filter1 and filter2. The callback is the update function which does the actual filtering and updates the MultiSelect part of the filter.
from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
multiselect = None
input_box = None
def update(widget, attr, old, new):
print("df['fruits']: {}".format(list(df['fruits'])))
print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(widget, attr, old, new))
if widget == 'input':
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
print("col_date: {}".format(col_data))
multiselect.update(options = sorted(list(col_data)))
def init():
global multiselect
multiselect = MultiSelect(title = 'multiselect',
name = 'multiselect',
value = [],
options = list(df["fruits"]))
multiselect.on_change('value', partial(update, multiselect.name))
global input_box
input_box = TextInput(title = 'input',
name ='input',
value='Enter you choice')
input_box.on_change('value', partial(update, input_box.name))
class Filter:
def __init__(self):
self.multiselect = multiselect
self.input_box = input_box
self.widget = widgetbox(self.input_box, self.multiselect)
init()
filter1 = Filter().widget
filter2 = Filter().widget
curdoc().add_root(row(filter1, filter2))
The code above produces/assembles the widget as shown here:
Also, the functionality of the two mirrored filters is as desired; when text is entered in one of the boxes, the results are displayed on both filters.
Now, and here is where I need help, I want the same filters with the same functionality but I need them in two different tabs; one filter in one tab and the other filter in the other tab.
The code used to build the two tabs structure is:
p1 = Panel(child = filter1, title = "Panel1")
p2 = Panel(child = filter2, title = "Panel2")
tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
On the results side, the code preserves the desired functionality but filters are displayed on the same page. More than that, panels/tabs are not even being built.
Any idea what's missing? (If you want to play with the code it should work right off the bat if you have bokeh installed.)
I do not think your example should even build a document, both your textinputs and multiselect models have the same id, which may be why the display of tabs gets messed up.
My solution is similar to HYRY's, but with a more general function to share attributes using two different things:
model.properties_with_values()
Can be used with any bokeh model and returns a dictionary of all the attribute:value pairs of the model. It's mostly useful in ipython to explore bokeh objects and debug
Document.select({'type':model_type})
Generator of all the widgets of the desired type in the document
Then I just filter out the widgets that do not share the same tags as the input widget, which would avoid "syncing" other inputs/multiselect not generated with box_maker(). I use tags because different models cannot have the same name.
When you change a TextInput value, it will change the associated Multiselect in the update function, but it will also change all the other TextInputs and trigger their update in the same way too. So each Input triggers update once and changes the options of their respective multiselect (and not multiplte times each because it's a "on_change" callback, if you give the same value for the new input it does not trigger).
For the Multiselect the first trigger of update will do the job, but since it changed the values of the other Multiselect it still triggers as many times as there are Multiselect widgets.
from bokeh.io import curdoc
from bokeh.layouts import widgetbox
from bokeh.models import TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
def sync_attr(widget):
prop = widget.properties_with_values() # dictionary of attr:val pairs of the input widget
for elem in curdoc().select({'type':type(widget)}): # loop over widgets of the same type
if (elem!=widget) and (elem.tags==widget.tags): # filter out input widget and those with different tags
for key in prop: # loop over attributes
setattr(elem,key,prop[key]) # copy input properties
def update(attr,old,new,widget,other_widget):
print("\ndf['fruits']: {}".format(list(df['fruits'])))
print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(str(widget),attr, old, new))
if type(widget)==TextInput:
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
print("col_date: {}".format(col_data))
other_widget.update(options = sorted(list(col_data)))
sync_attr(widget)
def box_maker():
multiselect = multiselect = MultiSelect(title = 'multiselect',tags=['in_box'],value = [],options = list(df["fruits"]))
input_box = TextInput(title = 'input',tags=['in_box'],value='Enter you choice')
multiselect.on_change('value',partial(update,widget=multiselect,other_widget=input_box))
input_box.on_change('value',partial(update,widget=input_box,other_widget=multiselect))
return widgetbox(input_box, multiselect)
box_list = [box_maker() for i in range(2)]
tabs = [Panel(child=box,title="Panel{}".format(i)) for i,box in enumerate(box_list)]
tabs = Tabs(tabs=tabs)
curdoc().add_root(tabs)
Note that the highlighting of the options in multiselect may not look consistent, but that just seems to be visual as the values/options of each of them are changing correctly.
But unless you are particularly attached to the layout look when you put the widgets inside the panels, you could just put one input and multiselect outside, and write their callbacks to deal with what will be in the different panels.
You can't use the same widget model to create multiple views. You can create new widgets in every tabs and link the value:
from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect, CustomJS
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
class Filter:
def __init__(self):
self.multiselect = MultiSelect(title = 'multiselect',
name = 'multiselect',
value = [],
options = list(df["fruits"]))
self.multiselect.on_change('value', self.selection_changed)
self.input_box = TextInput(title = 'input',
name ='input',
value='Enter you choice')
self.input_box.on_change('value', self.input_box_updated)
self.widget = widgetbox(self.input_box, self.multiselect)
def input_box_updated(self, attr, old, new):
print(attr, old, new)
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
self.multiselect.update(options = sorted(list(col_data)))
def selection_changed(self, attr, old, new):
print(new)
filter1 = Filter()
filter2 = Filter()
def link_property(property_name, *widgets):
wb = widgetbox(*widgets)
wb.tags = [property_name, 0]
def callback(widgets=wb):
if widgets.tags[1] != 0:
return
widgets.tags[1] = 1
for widget in widgets.children:
widget[widgets.tags[0]] = cb_obj.value
widgets.tags[1] = 0
jscallback = CustomJS.from_py_func(callback)
for widget in widgets:
widget.js_on_change(property_name, jscallback)
link_property("value", filter1.input_box, filter2.input_box)
link_property("value", filter1.multiselect, filter2.multiselect)
p1 = Panel(child = filter1.widget, title = "Panel1")
p2 = Panel(child = filter2.widget, title = "Panel2")
tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
It seems that there is a bug in MultiSelect that doesn't deselect previous items.
I load the list of files with checkbox to be filtered using ipywidgets:
from ipywidgets import Checkbox, interact, Layout, Button
import ipywidgets as widgets
import glob
from traitlets import traitlets
from IPython.display import display, clear_output
class LoadedButton(widgets.Button):
def __init__(self, value=None, *args, **kwargs):
super(LoadedButton, self).__init__(*args, **kwargs)
# Create the value attribute.
self.add_traits(value=traitlets.Any(value))
def file_list(**all_kwargs):
cols = [k for k, v in all_kwargs.items() if v==True]
return cols
def reload_files(rr):
for c in all_files:
c.close()
clear_output()
print('Unselect the csv files above *to EXCLUDE*')
rr.value =interact(file_list, **all_kwargs)
return rr
extension = 'csv' # extention of file you want to read, csv, dat, etc.
all_file_list = [i for i in glob.glob('*.{}'.format(extension))]
all_files = [Checkbox(description=a, value=True) for a in all_file_list ]
all_kwargs = {c.description: c.value for c in all_files}
lb = LoadedButton(description="Load/reload file list", value=None)
lb.on_click(reload_files)
display(lb)
I wish to overwrite the previous outputs (both widget and widget outputs) for every time I click on button created using the Button widget, but instead of overwriting it creates another instance of output. I tried both clear_output and widget.close() options, but nothing helps. I know clear_output is not expected to clear the widget, but I expect this to be possible using close(). Does anyone know how to clear/overwrite the ipywidgets as well ipywidget outputs when reloading by button?
If I understand the question correctly I think that this might work for you;
from ipywidgets import Checkbox, interact, Layout, Button
import ipywidgets as widgets
import glob
from traitlets import traitlets
from IPython.display import display, clear_output
class LoadedButton(widgets.Button):
def __init__(self, value=None, *args, **kwargs):
super(LoadedButton, self).__init__(*args, **kwargs)
# Create the value attribute.
self.add_traits(value=traitlets.Any(value))
def file_list(**all_kwargs):
cols = [k for k, v in all_kwargs.items() if v==True]
return cols
def reload_files(rr):
if rr.value is not None:
# print("Reloading...")
# Reset the panel to just the button.
panel.children = [panel.children[0]]
rr.value = None
if rr.value is None:
extension = 'csv' # extention of file you want to read, csv, dat, etc.
all_file_list = [i for i in glob.glob('*.{}'.format(extension))]
all_files = [Checkbox(description=a, value=True) for a in all_file_list ]
all_kwargs = {c.description: c.value for c in all_files}
rr.value=widgets.interactive(file_list, **all_kwargs)
panel.children += (rr.value,)
# display(rr.value)
lb = LoadedButton(description="Load/reload file list", value=None)
lb.on_click(reload_files)
# A VBox for the Button and checkboxes.
panel = widgets.VBox([lb])
panel
I think that the problem was coming from relying on the display function to represent the widget. When you have one widget modified the visibilty of another the best thing that you could probably do is add and remove them from a layout widget like VBox or HBox. Either way let me know how it works out.
Update:
glob and all other file name operations also need to update when the button is clicked. so it does that now.
Also here is how to access the checkboxes on the interactive widget';
panel.children[1].children
OR
lb.value.children
Both statements refer to the same object.
I came across this snippet for uploading files in Jupyter however I don't know how to save this file on the machine that executes the code or how to show the first 5 lines of the uploaded file. Basically I am looking for proper commands for accessing the file after it has been uploaded:
import io
from IPython.display import display
import fileupload
def _upload():
_upload_widget = fileupload.FileUploadWidget()
def _cb(change):
decoded = io.StringIO(change['owner'].data.decode('utf-8'))
filename = change['owner'].filename
print('Uploaded `{}` ({:.2f} kB)'.format(
filename, len(decoded.read()) / 2 **10))
_upload_widget.observe(_cb, names='data')
display(_upload_widget)
_upload()
_cb is called when the upload finishes. As described in the comment above, you can write to a file there, or store it in a variable. For example:
from IPython.display import display
import fileupload
uploader = fileupload.FileUploadWidget()
def _handle_upload(change):
w = change['owner']
with open(w.filename, 'wb') as f:
f.write(w.data)
print('Uploaded `{}` ({:.2f} kB)'.format(
w.filename, len(w.data) / 2**10))
uploader.observe(_handle_upload, names='data')
display(uploader)
After the upload has finished, you can access the filename as:
uploader.filename
I am working on ML with Jupyter notebook, and I was looking for a solution to select the local files containing the datasets by browsing amongst the local file system. Although, the question here refers more to uploading than selecting a file. I am putting here a snippet that I found here because when I was looking for a solution for my particular case, the result of the search took me several times to here.
import os
import ipywidgets as widgets
class FileBrowser(object):
def __init__(self):
self.path = os.getcwd()
self._update_files()
def _update_files(self):
self.files = list()
self.dirs = list()
if(os.path.isdir(self.path)):
for f in os.listdir(self.path):
ff = os.path.join(self.path, f)
if os.path.isdir(ff):
self.dirs.append(f)
else:
self.files.append(f)
def widget(self):
box = widgets.VBox()
self._update(box)
return box
def _update(self, box):
def on_click(b):
if b.description == '..':
self.path = os.path.split(self.path)[0]
else:
self.path = os.path.join(self.path, b.description)
self._update_files()
self._update(box)
buttons = []
if self.files:
button = widgets.Button(description='..', background_color='#d0d0ff')
button.on_click(on_click)
buttons.append(button)
for f in self.dirs:
button = widgets.Button(description=f, background_color='#d0d0ff')
button.on_click(on_click)
buttons.append(button)
for f in self.files:
button = widgets.Button(description=f)
button.on_click(on_click)
buttons.append(button)
box.children = tuple([widgets.HTML("<h2>%s</h2>" % (self.path,))] + buttons)
And to use it:
f = FileBrowser()
f.widget()
# <interact with widget, select a path>
# in a separate cell:
f.path # returns the selected path
4 years later this remains an interesting question, though Fileupload has slightly changed and belongs to ipywidgets....
Here is some demo that shows how to get the file/files after the button click and reset the button to get more files....
from ipywidgets import FileUpload
def on_upload_change(change):
if not change.new:
return
up = change.owner
for filename,data in up.value.items():
print(f'writing [{filename}] to ./')
with open(filename, 'wb') as f:
f.write(data['content'])
up.value.clear()
up._counter = 0
upload_btn = FileUpload()
upload_btn.observe(on_upload_change, names='_counter')
upload_btn
And here is a "debug" version that shows what is going on / why things work...
from ipywidgets import FileUpload
def on_upload_change(change):
if change.new==0:
print ('cleared')
return
up = change.owner
print (type(up.value))
for filename,data in up.value.items():
print('==========================================================================================')
print(filename)
for k,v in data['metadata'].items():
print(f' -{k:13}:[{v}]')
print(f' -content len :[{len(data["content"])}]')
print('==========================================================================================')
up.value.clear()
up._counter = 0
upload_btn = FileUpload()
upload_btn.observe(on_upload_change, names='_counter')
upload_btn
I stumbled into this thread ~2 years late. For those still confused about how to work with the fileupload widget I have built off of the excellent answer posted by minrk with some other usage examples below.
from IPython.display import display
import fileupload
uploader = fileupload.FileUploadWidget()
def _handle_upload(change):
w = change['owner']
with open(w.filename, 'wb') as f:
f.write(w.data)
print('Uploaded `{}` ({:.2f} kB)'.format(
w.filename, len(w.data) / 2**10))
uploader.observe(_handle_upload, names='data')
display(uploader)
From the widget documentation:
class FileUploadWidget(ipywidgets.DOMWidget):
'''File Upload Widget.
This widget provides file upload using `FileReader`.
'''
_view_name = traitlets.Unicode('FileUploadView').tag(sync=True)
_view_module = traitlets.Unicode('fileupload').tag(sync=True)
label = traitlets.Unicode(help='Label on button.').tag(sync=True)
filename = traitlets.Unicode(help='Filename of `data`.').tag(sync=True)
data_base64 = traitlets.Unicode(help='File content, base64 encoded.'
).tag(sync=True)
data = traitlets.Bytes(help='File content.')
def __init__(self, label="Browse", *args, **kwargs):
super(FileUploadWidget, self).__init__(*args, **kwargs)
self._dom_classes += ('widget_item', 'btn-group')
self.label = label
def _data_base64_changed(self, *args):
self.data = base64.b64decode(self.data_base64.split(',', 1)[1])
Get the data in bytestring format:
uploader.data
Get the data in a regular utf-8 string:
datastr= str(uploader.data,'utf-8')
Make a new pandas dataframe from the utf-8 string (e.g. from a .csv input):
import pandas as pd
from io import StringIO
datatbl = StringIO(datastr)
newdf = pd.read_table(datatbl,sep=',',index_col=None)
You have to enable the file upload option in your code, to enable the browse button to appear in your notebook.
Run the following
!jupyter nbextension enable fileupload --user --py
I tried to access TabbedPanel.content.children[0] and remove the child and recrete a new child but it does not work. A note is that TabbedPanel.content is a TabbedPanelContent and not the ListView (in my case).
What I want to do is edit the contents of my listView. The app creates a button to generate data and two tabs, where the first tab create a listView of the data. If button pressed again it will remove previous ListView and insert new one.
How do I update contents of a tab? The code:
#test tabs
import kivy
kivy.require('1.0.6') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.properties import ListProperty
from kivy.properties import DictProperty
from kivy.uix.listview import ListView, ListItemButton
from kivy.adapters.dictadapter import DictAdapter
from kivy.adapters.models import SelectableDataItem
from kivy.uix.selectableview import SelectableView
from kivy.uix.listview import ListView, ListItemButton
from kivy.factory import Factory
from kivy.lang import Builder
import random
#templates kv for SelectableView+BoxLayout called CustomListItem
Builder.load_string('''
[CustomListItem#SelectableView+BoxLayout]:
size_hint_y: ctx.size_hint_y
height: ctx.height
ListItemButton:
text: ctx.text
is_selected: ctx.is_selected
''')
class testTabs(BoxLayout):
data = ListProperty([1,2,3,4,5])
def __init__(self, *args, **kwargs):
super(testTabs, self).__init__(**kwargs)
self.listViewDict = {}
#layout = GridLayout(rows = 2)
self.layout = BoxLayout(orientation = "vertical")
#buttonLayout = GridLayout(cols = 4)
dataButton = Button(text = "press to load random data to tab 1")
dataButton.bind(on_release = self.randData)
self.layout.add_widget(dataButton)
#create list
list_item_args_converter = \
lambda row_index, rec: {'text': rec['text'],
'is_selected': rec['is_selected'],
'size_hint_y': None,
'height': 35}
entry_dict = \
{str(i): {'text': str(self.data[i]), 'is_selected': False} \
for i in xrange(len(self.data) ) }
self.listViewDict = entry_dict
sortedDateEntriesList = sorted(self.listViewDict)
dict_adapter = DictAdapter(sorted_keys = sortedDateEntriesList,
data = self.listViewDict,
args_converter=list_item_args_converter,
template = 'CustomListItem')
self.list_view = ListView(adapter=dict_adapter)
### Create tabs ###
self.tabbedPanel = TabbedPanel()
self.tabbedPanel.default_tab_text = "data tab"
self.tabbedPanel.tab_pos = "top_left"
self.tabbedPanel.default_tab_content = self.list_view
tabbedPanelHeader = TabbedPanelHeader(text = "tab 2")
tabbedPanelHeader.content = Label(text = "Hello world")
self.tabbedPanel.add_widget(tabbedPanelHeader)
self.layout.add_widget(self.tabbedPanel)
self.add_widget(self.layout)
#self.tabbedPanel.content.bind(children = self.foo)
def foo(self, *args):
print "############################in foo args:"
print args
tabbedPanelHeader = args[0]
print tabbedPanelHeader.children
def printContent(self, object):
print "object:" +str(object) +"'s content: " +str(object.content)
def printChildren(self, object):
for child in object.children:
print "object:" +str(object) +"'s child: " +str(child)
#create list view
def randData(self, *args):
print args
self.tabbedPanel.content.children[0].remove_widget(self.list_view)
print "content tabbedPanel children:"
print self.tabbedPanel.content.children
tempData = []
numValues = random.randint(10,20)-1
for i in xrange(numValues):
tempData.append(random.randint(1,30))
self.data = tempData
list_item_args_converter = \
lambda row_index, rec: {'text': rec['text'],
'is_selected': rec['is_selected'],
'size_hint_y': None,
'height': 35}
entry_dict = \
{str(i): {'text': str(self.data[i]), 'is_selected': False} \
for i in xrange(len(self.data) ) }
self.listViewDict = entry_dict
sortedDateEntriesList = sorted(self.listViewDict)
dict_adapter = DictAdapter(sorted_keys = sortedDateEntriesList,
data = self.listViewDict,
args_converter=list_item_args_converter,
template = 'CustomListItem')
self.list_view = ListView(adapter=dict_adapter)
self.tabbedPanel.content.children[0].add_widget(self.list_view)
def on_data(self, *args):
print "on_data func. Data created:"
print args
class MyApp(App):
def build(self):
return testTabs()
if __name__ == '__main__':
MyApp().run()
You seem to want to change the data given to the listview, you shouldn't have to change te entire ListView for that. Look here https://github.com/kivy/kivy/issues/1321 this mentions a workaround till the data_changed branch for listview is merged in master. Eessentially just add this to your kv::
<ListView>:
on_adapter: self.adapter.bind_triggers_to_view(self._trigger_reset_populate)
simply updating the data after that should show your changes in the listview.
For your issue with TabbedPanel all you need to do is ::
self.tabbedPanel.clear_widgets()
self.tabbedPanel.add_widget(self.list_view)
When you add widgets to the tabbed panel, it actually adds it to the content area. Similarly clear_widgets() clears the content area.
You could also do::
self.tabbedPanel.default_tab.content = self.list_view
self.tabbedPanel.switch_to(self.tabbedPanel.default_tab)
This is better as the linked content is updated and thus every time you change the tab appropriate changed content will be displayed.
The relevant Documentation in the docs::
Tabs and content can be removed in several ways:
tp.remove_widget(Widget/TabbedPanelHeader)
or
tp.clear_widgets() # to clear all the widgets in the content area
or
tp.clear_tabs() # to remove the TabbedPanelHeaders
Warning
To access the children of the tabbed panel, use content.children:
tp.content.children
To access the list of tabs:
tp.tab_list
Rather than mucking around with tabbedpanel.content, you can just keep a reference to your listview (or anything else you put in there) and modify it later.
For instance, in this example you store the listview as self.list_view. That means you can have a method that creates a new adapter then simply replace the old one to update the list. You can also do stuff like modify the data of the original adapter via the same reference.
Some (untested) example code would be something like:
def my_update_method(self, *args):
new_dict_adapter = DictAdapter(data = whatever
# Obviously you have to make thi
args_converter = list_item_args_converter
# You have to make this too, not included in
# the example
)
self.list_view.adapter = new_dict_adapter