GridLayout with different number or columns

730 views
Skip to first unread message

Lefteris

unread,
Nov 9, 2015, 5:36:36 AM11/9/15
to Kivy users support
Hi all!

I'm having some difficulty trying to do something so I would really appreciate any help

I'm trying to create a GridLayout which will be displaying the contents of an excel spreadsheet.
The problem is that there's not a fixed number of columns (or rows for that matter) in the excel file. For example, when you load one file it can have, say, 12 columns and the next time you load a different excel file with 15 columns.

In my python code, I have a method which returns the number of columns and rows. However, in my .kv file, I cannot find a way to make it recognise the numbers.
So, when I do for example:
ScrollView:
            id: scroll_view
            size_hint_y: 0.8
            size_hint_x: 1
            GridLayout:
                id: scroll_box
                orientation: 'vertical'
                cols: m.MainMenu.get_cols()
                raws: m.MainMenu.get_rows()
                size_hint: None, None

I get this error (I post the whole Trace back):

 Traceback (most recent call last):
   File "main.py", line 84, in <module>
     IWTApp().run()
   File "/usr/lib/python2.7/dist-packages/kivy/app.py", line 798, in run
     root = self.build()
   File "main.py", line 57, in build
     sm.add_widget(TestCondition(name='test'))
   File "/home/eleftherios/Desktop/App/choice.py", line 27, in __init__
     super(TestCondition, self).__init__(**kwargs)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/relativelayout.py", line 255, in __init__
     super(RelativeLayout, self).__init__(**kw)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/floatlayout.py", line 66, in __init__
     super(FloatLayout, self).__init__(**kwargs)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/layout.py", line 66, in __init__
     super(Layout, self).__init__(**kwargs)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/widget.py", line 271, in __init__
     Builder.apply(self)
   File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1872, in apply
     self._apply_rule(widget, rule, rule)
   File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 2018, in _apply_rule
     e), cause=tb)
 kivy.lang.BuilderException: Parser: File "./iwtapp.kv", line 213:
 ...
     211:                #:set nrows 15
     212:                cols: m.MainMenu.get_cols()
 >>  213:                raws: m.MainMenu.get_rows()
     214:                size_hint: None, None
     215:        # TextInput:
 ...
 ValueError: None is not allowed for GridLayout.raws
   File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 2012, in _apply_rule
     setattr(widget_set, key, value)
   File "weakproxy.pyx", line 22, in kivy.weakproxy.WeakProxy.__setattr__ (kivy/weakproxy.c:1218)
   File "properties.pyx", line 397, in kivy.properties.Property.__set__ (kivy/properties.c:4543)
   File "properties.pyx", line 426, in kivy.properties.Property.set (kivy/properties.c:4982)

In my python code I have defined
cols = None
rows = None
so I suppose that's why I get the above ValueError. However, if I comment out this line (rows = None) I get an AttributeError:
AttributeError: type object 'MainMenu' has no attribute 'rows'

I have to admit that my experience with python and kivy is limited so I would really appreciate it if someone could help me out with this!


Thanks,

Lefteris

Lefteris

unread,
Nov 10, 2015, 5:26:01 AM11/10/15
to Kivy users support
I would really appreciate any help/hint/tip anyone could offer.

Thanks a lot!

Lefteris

Andreas Ecker

unread,
Nov 10, 2015, 7:34:34 AM11/10/15
to kivy-...@googlegroups.com
Hi Lefteris,

I am also no Kivy expert but two tips I can give to you:
1) the cols = None/rows = None should be removed from your python main.py.
2) in your kv file you have a typo: you are using raw instead of row

Hope that helps
Andi


--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Lefteris

unread,
Nov 10, 2015, 9:12:47 AM11/10/15
to Kivy users support
Hi Andreas,

Thanks for the reply!

It was working with "raws" in the .kv file all this time, maybe by luck? I don't know, but I fixed it now. Thanks for pointing this out.

I added the cols = None and rows = None while trying to figure out a solution, I didn't have them before.

I think the problem is that the screen gets created before I read in the excel file and get the number of columns from there. So I tend to believe that what I need is to find how to destroy that screen and recreate it.
Or alternatively, not create that screen until I know how many columns I need but I don't know how to do that in kivy (or in python for that matter) yet.

Any insights will be greatly appreciated!

Once again, thanks for the replies!

Lefteris

Andreas Ecker

unread,
Nov 10, 2015, 9:30:00 AM11/10/15
to kivy-...@googlegroups.com
If the number of number of columns will not change while the kivy app is running then you could read the Excel file in the build() method of your App class.

And if instead the number of columns are changing while the app is running then I'd recommend to create a numeric property (e.g. call it excel_cols) in your App class holding the number of columns. And you can use this property in your kv file for to specify the cols value (so the kv line reads as row: app.excel_cols) - this way the grid will be redrawn as soon as you change the app.excel_cols value after reloading the next/other Excel file.

HTH

Lefteris

unread,
Nov 10, 2015, 10:10:38 AM11/10/15
to Kivy users support
I'm tempted to use the second approach with the numeric property.

Let me explain what I'm doing.
I load the excel file in the module menu.py:

class MainMenu(Screen):
    loadfile = ObjectProperty(None)
    test_matrix = None
    cols = NumericProperty()
    rows = NumericProperty()

    def load(self, path, filename):
        fn = str(filename)
        if fn.endswith(suffix_xls):
            with open(os.path.join(path, filename[0]), 'rb'):
                MainMenu.test_matrix = open_workbook(filename[0])
                MainMenu.sheet_names = MainMenu.test_matrix.sheet_names()
                MainMenu.xl_sheet = MainMenu.test_matrix.sheet_by_name(MainMenu.sheet_names[0])
                cols = MainMenu.xl_sheet.ncols
                rows = MainMenu.xl_sheet.nrows


Then I have a second module which is supposed a number of things. Say this module is the choose.py. So I have:
class Condition(Screen):
    def __init__(self, **kwargs):
        super(TestCondition, self).__init__(**kwargs)
        self.scroll_view = self.ids['scroll_view']
        self.scroll_box = self.ids['scroll_box']

    def scrol(self):
        test_matrix = MainMenu.get_testMatrix()
        sheet_names = test_matrix.sheet_names()
        xl_sheet = test_matrix.sheet_by_name(sheet_names[0])
        for i in range(0, xl_sheet.nrows):
            self.scroll_box.add_widget(Button(text=str(xl_sheet.cell(i,0).value), height = '100sp'))
            for j in range (0, xl_sheet.ncols-1):
                self.scroll_box.add_widget(Label(text=str(xl_sheet.cell(i,j+1).value), height = '100sp'))
        self.h = i
        self.scroll_box.size = (self.h * 5, self.h * 100)

And in my .kv file I have:

<TestCondition>:
    #:import m mainMenu

        ScrollView:
            id: scroll_view
            size_hint_y: 0.8
            size_hint_x: 1
            GridLayout:
                id: scroll_box
                orientation: 'vertical'
                cols: m.MainMenu.cols
                rows: m.MainMenu.rows
                size_hint: None, None
        BoxLayout:
            id: box_btm
            size_hint_y: 0.1
            Button:
                id: dummy
                text: 'gen scroll view'
                font_size: 30
                on_release: root.scrol()
                on_release: dummy.disabled = True

So in the cols I reference the numeric property in the mainMenu class of the meny.py. What I get in this case however, is this:
File "/usr/lib/python2.7/dist-packages/kivy/uix/gridlayout.py", line 365, in do_layout
     self.update_minimum_size()
   File "/usr/lib/python2.7/dist-packages/kivy/uix/gridlayout.py", line 303, in update_minimum_size
     cols = [self.col_default_width] * current_cols
 TypeError: can't multiply sequence by non-int of type 'kivy.properties.NumericProperty'

So it seems that an integer is expected but that's how far I can understand. I tried a few other things but they didn't work either so I'm really at a loss.

Andreas Ecker

unread,
Nov 10, 2015, 12:04:18 PM11/10/15
to kivy-...@googlegroups.com
Not sure why you get this error - sorry but I have limited time for to check all your code (currently working on a very urgent project).

At least I found a small bug in your load() method of your MainMenu class - the following two lines need to have self. in front of the cols/rows variables (else these variables would be initialized only locally in the load() method):

                self.cols = MainMenu.xl_sheet.ncols
                self.rows = MainMenu.xl_sheet.nrows

HTH

Lefteris

unread,
Nov 10, 2015, 12:15:25 PM11/10/15
to Kivy users support
Hi Andreas,

Once again, thanks for your answer.

Of course I don't expect an immediate answer so it's ok ;) I'm trying various things to learn and make it work in the mean time!

I changed what you said but it didn't really do anything.

Anyway, thanks for your time. I do appreciate it!

Lefteris

Lefteris

unread,
Nov 13, 2015, 5:53:13 AM11/13/15
to Kivy users support

Quick update on this one.

I found a way to do this. So, I follow a modular approach in my programming and in my main.py I have the build function:

class App(App):
    def build(self):
        sm = ScreenManager(transition=NoTransition())
        ...
        sm.add_widget(Test(name='test'))
        ...
        return sm

if __name__ == '__main__':
    App().run()

At this point, the screen on which I want to view the excel file has been created. However, the excel file has not been read yet.
Hence, I cannot add the GridLayout because I don't know the number of columns. What I do therefore, is to have only the ScrollView created in that screen (in the .kv file along with a button to generate the grid at the moment):
<Test>:
    BoxLayout:
        id: ext
        orientation: 'vertical'
        Label:
            id: spec_label
            font_size: 30
            size_hint_y: 0.1
            text: "Test"

        ScrollView:
            id: scroll_view
            size_hint_y: 0.8
            size_hint_x: 1
        BoxLayout:
            id: box_btm
            size_hint_y: 0.1
            Button:
                id: dummy
                text: 'gen scroll view'
                font_size: 30
                on_release: root.scroll()
                on_release: dummy.disabled = True

Then my scroll function is like this:
class Test(Screen):
    def __init__(self, **kwargs):
        super(Test, self).__init__(**kwargs)
        self.scroll_view = self.ids['scroll_view']

    def scroll(self):
        test = MainMenu.get_test()                                                                                                                           # takes the excel file read from another module
        sheet_names = test.sheet_names()
        xl_sheet = test.sheet_by_name(sheet_names[0])
        self.grid = GridLayout(orientation = 'vertical', cols = xl_sheet.ncols, size_hint_x = None, size_hint_y = None)      # creates the GridLayout
        self.scroll_view.add_widget(self.grid)                                                                                                              # adds the GridLayout inside the ScrollView
        for i in range(0, xl_sheet.nrows):                                                                                                                    # populates the GridLayout with Buttons and Labels
            self.grid.add_widget(Button(text=str(xl_sheet.cell(i,0).value), height = '100sp'))

            for j in range (0, xl_sheet.ncols-1):
                self.grid.add_widget(Label(text=str(xl_sheet.cell(i,j+1).value), height = '100sp'))
        self.h = i
        self.grid.size = (self.h * 5, self.h * 100)

So this does the trick. It doesn't seem to be the optimum solution but creating the grid after the screen has been build, does the job for now.

If anyone has any suggestion to make it better or if one spots a mistake (programming or logic) I'd be happy to know!


Thanks!

Lefteris

Reply all
Reply to author
Forward
0 new messages