Thumbnailctrl size (trying to get it to fullscreen) - python-2.7

I've been trying to insert two Thubmnailctrl under a Multisplitter, I have managed to put them in there, but I can't manage to make them ocuppy the full space. On thumbnailctrl.py I've seen that on the the maximum size it can be is 350x280:
def SetThumbSize(self, width, height, border=6):
"""
Sets the thumbnail size as width, height and border.
:param `width`: the desired thumbnail width;
:param `height`: the desired thumbnail height;
:param `border`: the spacing between thumbnails.
"""
if width > 350 or height > 280:
return
self._tWidth = width
self._tHeight = height
self._tBorder = border
self.SetScrollRate((self._tWidth + self._tBorder)/4,
(self._tHeight + self._tBorder)/4)
self.SetSizeHints(self._tWidth + self._tBorder*2 + 16,
self._tHeight + self._tBorder*2 + 8)
But on the other hand on the demo under ThumbnailCtrl, it uses an Splitter to create a Thumbnailctrl as big as you want, so I don't know if I'm doing something wrong (maybe with Sizers) or is some feature from Splitter (totally diferent than multisplitter) that allows the Thumbnailctrl to occupy it's full space.
Thumbnailctrl + Splitter Demo:
import wx
import os
import sys
try:
dirName = os.path.dirname(os.path.abspath(__file__))
except:
dirName = os.path.dirname(os.path.abspath(sys.argv[0]))
sys.path.append(os.path.split(dirName)[0])
try:
from agw import thumbnailctrl as TC
except ImportError: # if it's not there locally, try the wxPython lib.
import wx.lib.agw.thumbnailctrl as TC
class MainFrame(wx.Frame):
def __init__(self, redirect=False, filename=None):
wx.Frame.__init__(self, None, title="Elephant")
# self.SetMenuBar(self.CreateMenuBar())
splitter = wx.SplitterWindow(self, -1, style=wx.CLIP_CHILDREN | wx.SP_3D | wx.WANTS_CHARS | wx.SP_LIVE_UPDATE)
self.panel = wx.Panel(splitter, -1)
sizer = wx.BoxSizer(wx.HORIZONTAL)
scroll = TC.ThumbnailCtrl(splitter, -1, imagehandler=TC.NativeImageHandler)
scroll.ShowFileNames()
if os.path.isdir("../bitmaps"):
scroll.ShowDir(os.path.normpath(os.getcwd() + "/../bitmaps"))
else:
scroll.ShowDir(os.getcwd())
self.TC = scroll
splitter.SplitVertically(scroll, self.panel, 180)
splitter.SetMinimumPaneSize(140)
self.SetMinSize((700, 590))
self.CenterOnScreen()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
# import wx.lib.inspection
# wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
My attempt at a Multisplitter with two thumbnails (and when that works a third panel with text and stuff):
import wx
import os
import cv2
import ctypes
from PIL import Image
from wx.lib.splitter import MultiSplitterWindow
try:
from agw import thumbnailctrl as TC
except ImportError: # if it's not there locally, try the wxPython lib.
import wx.lib.agw.thumbnailctrl as TC
class SamplePane(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.thumbnail11 = TC.ThumbnailCtrl(self, imagehandler=TC.NativeImageHandler, thumboutline=4)
self.thumbnail11.EnableDragging(True)
# self.thumbnail11.SetThumbSize(350, screensize[0] / 15, 25) # For images -> Max values 350,280
# ################VID################ #
topmostSizer = wx.BoxSizer(wx.VERTICAL)
topmostSizer.Add(self.thumbnail11, proportion=0, flag=wx.EXPAND)
self.SetSizer(topmostSizer)
self.MaxSize
# topmostSizer.Fit(self)
class MainFrame(wx.Frame):
""""""
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Elephant")
splitter = MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE)
# t1Sizer = wx.BoxSizer(wx.VERTICAL)
# self.thumbnail11 = TC.ThumbnailCtrl(splitter, imagehandler=TC.NativeImageHandler, thumboutline=4)
panel = SamplePane(splitter)
splitter.AppendWindow(panel)
panel2 = SamplePane(splitter)
splitter.AppendWindow(panel2)
# t1Sizer.Add(panel, proportion=0, flag=wx.EXPAND)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
# import wx.lib.inspection
# wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
As you can see there are two thumbnails and they expand left to right, but they are capped at a maximum height.
Thanks a lot for the help!

Not 100% sure what it is that you're trying achieve with this but I suspect that your problem is with the topmostSizer's proportion attribute.
Try:
topmostSizer.Add(self.thumbnail11, proportion=1, flag=wx.EXPAND)
From the manual:
proportion - Although the meaning of this parameter is undefined in
wx.Sizer, it is used in wx.BoxSizer to indicate if a child of a sizer
can change its size in the main orientation of the wx.BoxSizer - where
0 stands for not changeable and a value of more than zero is
interpreted relative (a proportion of the total) to the value of other
children of the same wx.BoxSizer.
In this case, you have defined topmostSizer as VERTICAL

Related

Adding notebook tabs in tkinter - how do I do it with a class-based structure? (Python 2)

I want each tab to come from it's own class (classes are in their own files - I am just testing the first one for now).
Here is what I tried:
tab1.py
from Tkinter import *
import Tkinter as tk
class Tab(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
fr = Frame(self).pack()
Label(fr, text="one", bg='red', bd=2).pack()
Label(fr, text="two", bg='yellow', bd=2).pack()
if __name__ == '__main__':
root = Tk()
frame = Frame(root).pack()
Tab(frame)
Button(frame, text='only if class', command=root.destroy).pack()
mainloop()
noteBook.py
from Tkinter import *
from ttk import *
from tab1 import Tab
root = Tk()
note = Notebook(root)
main_frame = Frame(note)
button1 = Button(main_frame, text='test').pack()
#tab1 = Tab(note)
tab1 = Frame(note)
tab2 = Frame(note)
tab3 = Frame(note)
Tab(tab1)
Button(tab1, text='Exit', command=root.destroy).pack()
note.add(tab1, text = "Tab One", compound=TOP)
note.add(tab2, text = "Tab Two")
note.add(tab3, text = "Tab Three")
note.pack()
root.mainloop()
exit()
run with:
python2.7 noteBook.py
The problem is that the content of tab1.py does not appear within the first tab, it instead appears within the frame that contains the whole noteBook.
Also when running tab1.py directly with python2.7 noteBook.py I need it to behave properly meaning from what it has now it should show just the tab with an extra button from the if __name___... part.
I have come accros multiple examples but only found one that was what I want but it had no working solution and it was for python3 - I would like python2. python3 question with no working answer Thanks.
The problem is this line of code:
fr = Frame(self).pack()
When you do the above, fr is None because .pack() returns None (because x().y() returns the value of y()). Later, you do this:
Label(fr, text="one", bg='red', bd=2).pack()
Since fr is None, the label is created in the root window.
Unrelated to the problem, here's some advice: you are creating too many frames. You don't need fr inside of Tab, and you don't need tab1, tab2, or tab3
Here's all you need for Tab:
class Tab(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, background="pink")
Label(self, text="one", bg='red', bd=2).pack()
Label(self, text="two", bg='yellow', bd=2).pack()
To add it to the notebook, you just need two lines:
tab1 = Tab(note)
note.add(tab1, text = "Tab One", compound=TOP)
This works perfectly and just for fun I've illustrated the populating of tabs 2 and 3 althought I just reused the same class for simplicity here. The goal was to be able to run the tabs directly to view them alone during developpement without having to run the whole thing every time.
noteBook.py
from Tkinter import *
from ttk import *
from tab1 import Tab
root = Tk()
note = Notebook(root)
main_frame = Frame(note)
button1 = Button(main_frame, text='test').pack()
tab1 = Frame(note)
tab2 = Frame(note)
tab3 = Frame(note)
Tab(tab1)
Tab(tab2)
Tab(tab3)
Button(tab1, text='Exit', command=root.destroy).pack()
note.add(tab1, text = "Tab One", compound=TOP)
note.add(tab2, text = "Tab Two")
note.add(tab3, text = "Tab Three")
note.pack()
root.mainloop()
exit()
tab1.py
import Tkinter as tk
class Tab(tk.Frame):
def __init__(self, parent_widget):
tk.Frame.__init__(self, parent_widget)
self.fr = tk.Frame(parent_widget, width=200, height=200, bg='pink', bd=2)
tk.Label(self.fr, text="one", bg='red', bd=2).pack()
tk.Label(self.fr, text="two", bg='yellow', bd=2).pack()
self.fr.pack() # this packing must be done after 2 above packings
if __name__ == '__main__':
root = tk.Tk() # the app window
main_frame = tk.Frame(root, height=200, width=200, bg='blue', bd=2) # main frame
Tab(main_frame) # instatiate Tab(), sending main_frame as the parent_widget
tk.Button(main_frame, text='only if class', command=root.destroy).pack()
main_frame.pack() # display main frame on window
tk.mainloop()

Why is pyplot having issues with PyQt4's QThread?

I am designing a gui that creates multiple QThreads to be activated. Each thread creates an excel workbook with Pandas Excelwriter and creates a heatmap using seaborn and saves that heatmap (for later use by the user for whatever) and then places it into the excel workbook.
I believe the error is that pyplot is not made into its own instance for the thread that is created..rather a resource that all threads are pointing to..if I run just one thread, there is no issue...two or more threads..in this example 4, there are internal pyplot errors pointing to dictionary size change occurring.
The issue I'm having is when pyplot is put into play. Do I have to do something specific to pyplot like I had to for getting the right backend for matplotlib? I thought the changes I made for matplotlib is inherent to pyplot?
---main.py---
import sys
from MAIN_GUI import *
from PyQt4 import QtGui, QtCore
from excel_dummy import *
df1 = pd.DataFrame(np.array([[1,22222,33333],[2,44444,55555],[3,44444,22222],[4,55555,33333]]),columns=['hour','input','out'])
df2 = pd.DataFrame(np.array([[1,22233,33344],[2,44455,55566],[3,44455,22233],[4,55566,33344]]),columns=['hour','input','out'])
df3 = pd.DataFrame(np.array([[1,23456,34567],[2,98765,45674],[3,44444,22222],[4,44455,34443]]),columns=['hour','input','out'])
df4 = pd.DataFrame(np.array([[1,24442,33443],[2,44444,54455],[3,45544,24442],[4,54455,33443]]),columns=['hour','input','out'])
df_list = [df1,df2,df3,df4]
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
class MAIN_GUI(QtGui.QMainWindow):
def __init__(self):
super(MAIN_GUI, self).__init__()
self.uiM = Ui_MainWindow()
self.uiM.setupUi(self)
self.connect(self.uiM.updateALL_Button,QtCore.SIGNAL('clicked()'),self.newThread)
def newThread(self):
count = 0
for df in df_list:
count += 1
Excelify = excelify(df,count)
self.connect(Excelify,QtCore.SIGNAL('donethread(QString)'),(self.done))
Excelify.start()
def done(self):
print('done')
main_gui = MAIN_GUI()
main_gui.show()
main_gui.raise_()
sys.exit(app.exec_())
---excel_dummy.py---
import pandas as pd
import numpy as np
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QThread
import time
import matplotlib as mpl
mpl.use('Agg')
from matplotlib.backends.backend_agg import FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import seaborn.matrix as sm
class excelify(QThread):
def __init__(self,df,count):
QThread.__init__(self)
self.df = df
self.count = count
def run(self):
heatit = self.heatmap()
self.emit(QtCore.SIGNAL('donethread(QString)'),'')
def heatmap(self):
dfu = pd.DataFrame(self.df.groupby([self.df.input,self.df.hour]).size())
dfu.reset_index(inplace=True)
dfu.rename(columns={'0':'Count'})
dfu.columns=['input','hour','Count']
dfu_2 = dfu.copy()
mask=0
fig = Figure()
ax = fig.add_subplot(1,1,1)
fig.set_canvas(FigureCanvas(fig))
df_heatmap = dfu_2.pivot('input','hour','Count').fillna(0)
sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)
plt.ylabel('ID')
plt.xlabel('Hour')
plt.title('heatmap for df' + str(self.count))
plt.savefig(path + '/' + 'heat' + str(self.count) + '.png')
plt.close()
---MAIN_GUI.py---
from PyQt4 import QtCore,QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.unicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(320,201)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.updateALL_Button = QtGui.QPushButton(self.centralwidget)
self.updateALL_Button.setGeometry(QtCore.QRect(40,110,161,27))
self.updateALL_Button.setFocusPolicy(QtCore.Qt.NoFocus)
self.updateALL_Button.setObjectName(_fromUtf8("Options_updateALL_Button"))
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 320, 24))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self,MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.updateALL_Button.setText(_translate("MainWindow", "updateALL", None))
While the code in the question is still not really a minimal example (some undefined variable) it is much clearer, where the problem lies.
First, one problem might be that the MAIN_GUI class looses a reference to the thread, such that it will be garbage collected before it can finish. One can prevent this by just putting all threads in a list. [See MAIN_GUI code below]
Second, you cannot use pyplot directly to operate on different figures at once. Or in other words, how should pyplot know in which figure to place the ylabel set by plt.ylabel('ID') if there exist several at the same time?
The way to solve this, is to create different figures and only work within those figures using the object oriented approach. [See excelify code below]
Here is the relevant part of the code, where I also changed the signal to return the plot number for easier debugging.
MAIN_GUI:
class MAIN_GUI(QtGui.QMainWindow):
def __init__(self):
super(MAIN_GUI, self).__init__()
self.uiM = Ui_MainWindow()
self.uiM.setupUi(self)
self.connect(self.uiM.updateALL_Button,QtCore.SIGNAL('clicked()'),self.newThread)
self.threats=[]
def newThread(self):
count = 0
for df in df_list:
count += 1
Excelify = excelify(df,count)
self.connect(Excelify,QtCore.SIGNAL('donethread(int)'),(self.done))
# appending all threats to a class attribute,
# such that they will persist and not garbage collected
self.threats.append(Excelify)
Excelify.start()
def done(self, val=None):
print('done with {nr}'.format(nr=val))
excelify:
class excelify(QThread):
def __init__(self,df,count):
QThread.__init__(self)
self.df = df
self.count = count
def run(self):
heatit = self.heatmap()
self.emit(QtCore.SIGNAL('donethread(int)'),self.count)
def heatmap(self):
print ("{nr} started".format(nr=self.count) )
dfu = pd.DataFrame(self.df.groupby([self.df.input,self.df.hour]).size())
dfu.reset_index(inplace=True)
dfu.rename(columns={'0':'Count'})
dfu.columns=['input','hour','Count']
dfu_2 = dfu.copy()
mask=0
# create a figure and only work within this figure
# no plt.something inside the threat
fig = Figure()
ax = fig.add_subplot(1,1,1)
fig.set_canvas(FigureCanvas(fig))
df_heatmap = dfu_2.pivot('input','hour','Count').fillna(0)
sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)
ax.set_ylabel('ID')
ax.set_xlabel('Hour')
ax.set_title('heatmap for df' + str(self.count))
fig.savefig( 'heat' + str(self.count) + '.png')
fig.clear()
del fig

How to fade in/out on a Tkinter Frame

How can you fade in and/or out on a Tkinter.Frame or any other widget for that matter. All of the examples that I have seen have been for either root (Tkinter.Tk) or Toplevel setting the alpha e.g.
https://stackoverflow.com/a/22491808/1552953
Is it possible to apply this to an individual widget?
You can't do it on individual widgets. Transparency in Tkinter is only available for instances of Tk and Toplevel.
Based on Bryan's answer I came up with this solution, which he inadvertently provided most of the code for also.
One thing to note is if you move the main window the toplevel doesn't move with it...
import Tkinter
import Queue
class Flash(Tkinter.Toplevel):
def __init__(self, root, **options):
Tkinter.Toplevel.__init__(self, root, width=100, height=20, **options)
self.overrideredirect(True) # remove header from toplevel
self.root = root
self.attributes("-alpha", 0.0) # set transparency to 100%
self.queue = Queue.Queue()
self.update_me()
def write(self, message):
self.queue.put(message) # insert message into the queue
def update_me(self):
#This makes our tkinter widget threadsafe
# http://effbot.org/zone/tkinter-threads.htm
try:
while 1:
message = self.queue.get_nowait() # get message from the queue
# if a message is received code will execute from here otherwise exception
# http://stackoverflow.com/questions/11156766/placing-child-window-relative-to-parent-in-tkinter-pythons
x = root.winfo_rootx() # set x coordinate of root
y = root.winfo_rooty() # set y coordinate of root
width = root.winfo_width() # get the width of root
self.geometry("+%d+%d" % (x+width-self.winfo_width() ,y)) # place in the top right cornder of root
self.fade_in() # fade in when a message is received
label_flash = Tkinter.Label(self, text=message, bg='black', fg='white', padx=5, pady=5)
label_flash.pack(anchor='e')
self.lift(self.root)
def callback():
label_flash.after(2000, label_flash.destroy) # destroy the label after 5 seconds
self.fade_away() # fade away after 3 seconds
label_flash.after(3000, callback)
except Queue.Empty:
pass
self.after(100, self.update_me) # check queue every 100th of a second
# http://stackoverflow.com/questions/3399882/having-trouble-with-tkinter-transparency
def fade_in(self):
alpha = self.attributes("-alpha")
alpha = min(alpha + .01, 1.0)
self.attributes("-alpha", alpha)
if alpha < 1.0:
self.after(10, self.fade_in)
# http://stackoverflow.com/questions/22491488/how-to-create-a-fade-out-effect-in-tkinter-my-code-crashes
def fade_away(self):
alpha = self.attributes("-alpha")
if alpha > 0:
alpha -= .1
self.attributes("-alpha", alpha)
self.after(10, self.fade_away)
if __name__ == '__main__':
root = Tkinter.Tk()
root.minsize(700, 300)
root.geometry("700x500")
flash = Flash(root) # create toplevel instance
def callback():
# put a delay between each message so we can check the behaviour depending on the lenght of the delay between messages
import time
flash.write('Hello World')
time.sleep(1)
flash.write('Ready!')
time.sleep(2)
flash.write('Steady!')
time.sleep(4)
flash.write('Go!')
# create a thread to prevent the delays from blocking our GUI
import threading
t = threading.Thread(target=callback)
t.daemon = True
t.start()
root.mainloop()
exit()

wxPython can't properly position a matplotlib block

I've taken the code to draw a rectangle over image form here, and modified it a little bit to suite my purposes. The only problem I have, it doesn't appear to be full screen, even though I make fr.ShowFullScreen(True). There is a grey area around the image.
Edited code is below (the rectangle part is not relevant):
# Use the wxPython backend of matplotlib
import matplotlib
matplotlib.use('WXAgg')
# Matplotlib elements used to draw the bounding rectangle
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
# wxPython stuff for the demo
import wx
class RectangleSelectImagePanel(wx.Panel):
def __init__(self, parent, pathToImage=None):
# Initialise the parent
wx.Panel.__init__(self, parent)
# Intitialise the matplotlib figure
self.figure = Figure(figsize=(20,15), dpi=80)
# Create an axes, turn off the labels and add them to the figure
self.axes = self.figure.add_subplot(1, 1, 1)
self.axes.set_axis_off()
# Add the figure to the wxFigureCanvas
self.canvas = FigureCanvas(self, -1, self.figure)
# Sizer to contain the canvas
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 0, wx.ALL)
self.SetSizer(self.sizer)
self.Fit()
def setImage(self, pathToImage):
'''Sets the background image of the canvas'''
import matplotlib.image as mpimg
# Load the image into matplotlib
image = matplotlib.image.imread(pathToImage)
# left in as anchor for image size
self.imageSize = image.shape[0:2]
print self.imageSize
#
# Add the image to the figure and redraw the canvas. Also ensure the aspect ratio of the image is retained.
self.axes.imshow(image, interpolation="quadric", aspect='auto')
self.canvas.draw()
if __name__ == "__main__":
# Create an demo application
app = wx.App()
# Create a frame and a RectangleSelectorPanel
fr = wx.Frame(None, title='test')
panel = RectangleSelectImagePanel(fr)
# Set the image in the panel
from matplotlib.cbook import get_sample_data
imgpath = get_sample_data('logo2.png', asfileobj=False)
panel.setImage(imgpath)
# Start the demo app
fr.ShowFullScreen(True)
app.MainLoop()
probably what you need is subplots_adjust.
from pylab import *
from matplotlib.cbook import get_sample_data
imgpath = get_sample_data('logo2.png', asfileobj=False)
image = matplotlib.image.imread(imgpath)
imshow(image, aspect='auto')
subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0)
This will produce a plot that has no margin around the image.
So, if you put
self.figure.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0)
after imshow line, you will get something closer to what you need.
Then, you will probably have to adjust the figsize parameter too. for my 1920 x 1080 display, I will need 1920.0/80 inch by 1080.0/80 inch so (24,13.5)

TreeView in a ScrollView in Kivy - No Scroll

I'm working on a kivy app that pulls data from an sqlite3 database and populates a TreeView with it. The TreeView becomes too large to fit on my screen when I expand a few of the groups so I want to put it inside a ScrollView so I can still scroll down and see the items that have gone off the bottom of my screen. I can get a basic ScrollView to work, but when I put my TreeView inside it there is no scrolling and the top part of my TreeView is off the top of my screen.
I have trimmed down the code into this working example of the problem that runs without a .kv file:
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.treeview import TreeView, TreeViewNode
from kivy.uix.treeview import TreeViewLabel
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.button import Button
class TreeViewButton(Button, TreeViewNode):
pass
modGroups = [u'Fruit', u'Fruit', u'Meat', u'Dairy', u'Dairy', u'Fruit']
modItems = [u'Apple', u'Pear', u'Spam', u'Egg', u'Milk', u'Banana']
modDict = dict()
modDictUnique = dict()
def populate_tree_view(tv):
modDict = zip(modGroups, modItems)
print modGroups
print modItems
for k, v in modDict:
if k not in modDictUnique:
modDictUnique[k] = [v]
else:
modDictUnique[k].append(v)
sortedGroups = modDictUnique.keys()
sortedGroups.sort()
#print modItems
#print modDictUnique
n = tv.add_node(TreeViewLabel(text='Food', is_open=True))
for group in sortedGroups:
g = tv.add_node(TreeViewLabel(text='%s' % group), n)
for item in modDictUnique[group]:
tv.add_node(TreeViewButton(text='%s' % item), g)
class POSFMApp(App):
def build(self):
layout = GridLayout(cols=1, spacing=50, size_hint_y=None,width=800)
layout.bind(minimum_height=layout.setter('height'))
#for i in range(30):
# btn = Button(text=str(i), size=(480, 40),
# size_hint=(None, None))
# layout.add_widget(btn)
tv = TreeView(root_options=dict(text='Tree One'), hide_root=True, indent_level=4, minimum_height=5000)
populate_tree_view(tv)
layout.add_widget(tv)
root = ScrollView(size_hint=(None, None), size=(800, 700))
root.center = Window.center
root.add_widget(layout)
return root
if __name__ == '__main__':
POSFMApp().run()
In my actual app, modGroups and modItems are populated from an sqlite3 database, but this example presents the problem without having to mess around with sqlite3. I put in the (commented out) lines:
#for i in range(30):
# btn = Button(text=str(i), size=(480, 40),
# size_hint=(None, None))
# layout.add_widget(btn)
from this kivy ScrollView example to show that if I uncomment these lines and comment out the three lines about my TreeView
tv = TreeView(root_options=dict(text='Tree One'), hide_root=True, indent_level=4, minimum_height=5000)
populate_tree_view(tv)
layout.add_widget(tv)
Then I can get a working ScrollView with a scroll bar on the right as expected when I use my mouse's scroll wheel.
My best guess is that the TreeView doesn't tell the ScrollView how long it is vertically so the ScrollView doesn't realize it needs to scroll on the y-axis. That's just a guess, though.
How can I get a TreeView to work inside a ScrollView so I can scroll (especially on the y-axis) through my TreeView?
a) Using a GridLayout just for one child, pease don't. I'll asume that it's a left over from when/if you add more children to it.
b) Documentation of TreeView states that it has minimun_height property which indicates the minimum width/height needed to hold all it's children. The Treeview does not change it's height on it's own depending on the no of children. You should update (in this case) TreeViews height to it's minimum_height... tv.bind(minimum_height=tv.setter('height'))
c) taking into account the information provided in the points above, you can just do::
tv = TreeView(root_options=dict(text='Tree One'), hide_root=True, indent_level=4)
tv.size_hint = 1, None
tv.bind(minimum_height = tv.setter('height'))
populate_tree_view(tv)
root = ScrollView(pos = (0, 0))
root.add_widget(tv)
Here is the entire code including these changes so one can just copy and paste the code to a .py file and run it.
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.treeview import TreeView, TreeViewNode
from kivy.uix.treeview import TreeViewLabel
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.button import Button
class TreeViewButton(Button, TreeViewNode):
pass
modGroups = [u'Fruit', u'Fruit', u'Meat', u'Dairy', u'Dairy', u'Fruit']
modItems = [u'Apple', u'Pear', u'Spam', u'Egg', u'Milk', u'Banana']
modDict = dict()
modDictUnique = dict()
def populate_tree_view(tv):
modDict = zip(modGroups, modItems)
print modGroups
print modItems
for k, v in modDict:
if k not in modDictUnique:
modDictUnique[k] = [v]
else:
modDictUnique[k].append(v)
sortedGroups = modDictUnique.keys()
sortedGroups.sort()
#print modItems
#print modDictUnique
n = tv.add_node(TreeViewLabel(text='Food', is_open=True))
for group in sortedGroups:
g = tv.add_node(TreeViewLabel(text='%s' % group), n)
for item in modDictUnique[group]:
tv.add_node(TreeViewButton(text='%s' % item), g)
class POSFMApp(App):
def build(self):
#for i in range(30):
# btn = Button(text=str(i), size=(480, 40),
# size_hint=(None, None))
# layout.add_widget(btn)
tv = TreeView(root_options=dict(text='Tree One'), hide_root=True, indent_level=4)
tv.size_hint = 1, None
tv.bind(minimum_height = tv.setter('height'))
populate_tree_view(tv)
root = ScrollView(pos = (0, 0))
root.add_widget(tv)
return root
if __name__ == '__main__':
POSFMApp().run()
Add hight to TreeView then it will scroll. Like this.
ScrollView:
id: kr_scroll
do_scroll_x: False
TreeView:
id: trvMenu
root_options: { 'text': 'Home', 'font_size': 15}
hide_root: False
indent_level: 4
size_hint_y: None
height: self.parent.height*2