0

Hi senior users of python, and thank you for reading this through. I am currently trying to program an accounting application that can read and write files for users.

I have a bit of trouble when showing the data from the user file in listitemview in kivy. I would like the expenditure and description of the item be aligned with their headings, basically by using the tab function in a keyboard. However, tab is not represented by the list item view at all. How do I fix this?

In addition, am i doing this app efficiently? I know logic is handled by the python file and design related stuff goes in the .kv file

Pardon if I do make some mistakes as this is my first post and I am new using python, and even newer with kivy.

python file:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label 
from kivy.uix.screenmanager import Screen, ScreenManager 
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty, ListProperty 
from kivy.uix.popup import Popup 
from kivy.uix.button import Button 
import os, re 


class HomePage(Screen):
    #Includes the name of the App, Instructions, as well as 2 buttons that lead to their respective screens 
    pass


class NewUserPage(Screen):

    def saveuser(self, username, filename):

        if len(username) != 0:
            newuserfilepath = "users\\"+str(username)   
            if not os.path.exists(newuserfilepath):
                os.makedirs(newuserfilepath)
            if len(filename) != 0:
                filepath = os.path.join("users\\" + str(username), str(filename) + ".txt")
                f = open(filepath, "w")
                f.close()
            else:
                print "Please key in at least one character for a filename."
        else:
            print "Please type in a Username."

    def saveuserpopup(self, username, filename):

        filepath = os.path.join("users\\"+str(username), str(filename))
        if not os.path.exists(filepath):
            box = BoxLayout(orientation="vertical")
            box.add_widget(Label(text="Confirm Save %s, under filename: %s" %(username,filename)))
            Button1 = Button(text = "Confirm")
            Button2 = Button(text = "Cancel")
            box.add_widget(Button1)
            box.add_widget(Button2)

            popup = Popup(title = "Confirm Save User", content = box, size_hint=(0.5,0.5))
            buttoncallback = lambda *args: self.saveuser(username, filename)
            Button1.bind(on_press = buttoncallback)
            Button1.bind(on_release = popup.dismiss)
            #Button1.bind(on_press = self.saveuser(username,filename))
            Button2.bind(on_press = popup.dismiss)
            popup.open()

class ExistingUserPage(Screen):
    text = StringProperty("")
    content = StringProperty("")
    expenditure = StringProperty("")
    itemlist = StringProperty("")
    def loaduser(self, username, filename):
        filepath = os.path.join("users\\"+str(username), str(filename)+".txt")
        if os.path.exists(filepath):
            try: 
                file = open(filepath,"r")
                content = file.read()
                expenditure = content.split()[0::2]
                itemlist = content.split()[1::2]
                self.text = "User: " + str(username).upper() + " filename: " + str(filename).upper()
                self.content = str(content).upper()
                self.expenditure = str(expenditure)
                self.itemlist = str(itemlist)
            except(IOError):
                print "You did not fill in sufficient information"
                self.content = str("Please fill in the details correctly")
        else:
            print "Please key in a valid username or filename."
            self.text = "User: " + str(username).upper() + " filename: " + str(filename).upper()
            self.content = str("Please fill in the details correctly")

class ProcessingUserPage(Screen):
    text = StringProperty("")
    content = StringProperty("")
    expenditure = StringProperty("")
    itemlist = StringProperty("")
    content_list = ListProperty([])
    def function(self,expenditure,itemlist,content): #Function called from the .kv file 
        integer = re.findall("[0-9]+", expenditure)
        word = re.sub("[^\w]"," ", itemlist).split() #https://stackoverflow.com/questions/6181763/converting-a-string-to-a-list-of-words
        for i in range(len(integer)):
            self.content_list.append((integer[i] + word[i]))


class ScreenManagement(ScreenManager):
    pass


class AccountApp(App):

    def build(self):
        pass

if __name__ == "__main__":
    AccountApp().run()

.kv file:

#: import Factory kivy.factory.Factory
#: import ListAdapter kivy.adapters.listadapter.ListAdapter 
#: import ListItemButton kivy.uix.listview.ListItemButton

ScreenManagement:
    HomePage:
    NewUserPage:
    ExistingUserPage:
        id: ExistingUserPage
    ProcessingUserPage:
        text: ExistingUserPage.text
        content: ExistingUserPage.content 
        expenditure: ExistingUserPage.expenditure
        itemlist: ExistingUserPage.itemlist

<CustomListItemButton@ListItemButton>:
    halign: "left"
    text_size: self.size 

<HomePage>:
    name: "HomePage"
    GridLayout:
        cols:1
        rows:3
        padding: 10
        spacing: 10

        BoxLayout:
            Label: 
                text: "My Account"
                color: 0,0,1,1  #Blue
                text_size: root.width, None 
                font_size: root.height * 0.1
                halign: "center"
                valign: "top"

        BoxLayout:

            Label:
                text: "Hello there! I am your friendly accountant Bill! Please select below if you are a 'New' or 'Existing' member"
                color: 0.6, 0, 0.6, 1   #True Purple 
                text_size: root.width, None 
                font_size: root.height * 0.05
                halign: "center"
                valign: "top"

        GridLayout: 
            cols: 2
            rows: 1
            spacing: 30
            padding: 50 

            Button:

                font_size: self.width * 0.1
                text: "New"
                color: 0.43, 0.61, 0.95, 1
                background_color: 1, 0.84, 0, 1
                on_release: app.root.current = "NewUserPage"

            Button:

                font_size: self.width * 0.1         
                text: "Existing"
                color: 0.43, 0.61, 0.95, 1
                background_color: 1, 0.84, 0, 1
                on_release: app.root.current = "ExistingUserPage"


<NewUserPage>:
    name: "NewUserPage"
    id: NewUserPage
    username: user_name
    GridLayout:
        rows: 7
        cols: 1

        BoxLayout:
            height: "100dp"
            size_hint_y: None 
            padding: 10 
            Label: 
                size: self.texture_size
                text: "My Account"
                color: 0,0,1,1  #Blue
                text_size: root.width, None 
                font_size: root.height * 0.1
                halign: "center"
                valign: "top"
        BoxLayout:  
            height: "100dp"
            size_hint_y: None 
            Label:
                size: self.texture_size
                text: "New User Sign Up"
                color: 0.6, 0, 0.6, 1 #True Purple
                text_size: root.width, None
                font_size: root.height * 0.05
                halign: "center"
                valign: "top"
        BoxLayout:

            FileChooserIconView:
                path: "users\\"


        BoxLayout:
            height: "40dp"
            size_hint_y: None 
            orientation: "horizontal"
            Label:
                text: "Insert User Name Here"

            TextInput:
                id: user_name


        BoxLayout:
            height: "40dp"
            size_hint_y: None
            orientation: "horizontal"
            Label:
                text: "Insert File Name Here"
            TextInput:
                id: file_name 
        Button:
            height: "40dp"
            size_hint_y: None 
            text: "Submit"
            #on_release: NewUserPage.saveuserpopup(user_name.text, file_name.text)
            on_release: root.saveuserpopup(user_name.text, file_name.text)
        BoxLayout:
            height: "40dp"
            size_hint_y: None 

            Button:
                text: "Return to Home Page"
                on_release: app.root.current = "HomePage"

<ExistingUserPage>:
    name: "ExistingUserPage"
    id: ExistingUserPage

    GridLayout:
        cols: 1
        rows: 7

        BoxLayout:
            height: "100dp"
            size_hint_y: None 
            Label: 
                size: self.texture_size
                text: "My Account"
                color: 0,0,1,1  #Blue
                text_size: root.width, None 
                font_size: root.height * 0.1
                halign: "center"
                valign: "bottom"

        BoxLayout:  
            height: "100dp"
            size_hint_y: None 
            Label:
                size: self.texture_size
                text: "Existing User"
                color: 0.6, 0, 0.6, 1 #True Purple
                text_size: root.width, None 
                font_size: root.height * 0.05
                halign: "center"
                valign: "top"

        BoxLayout:

            FileChooserIconView:
                path: "C:\\Users\\Jamesong\\mystuff\\Kivy_apps\\account\\users\\"

        BoxLayout:
            height: "40dp"
            size_hint_y: None 
            orientation: "horizontal"
            Label:
                text: "Insert User Name Here"

            TextInput:
                id: user_name
                multiline: False 

        BoxLayout:
            height: "40dp"
            size_hint_y: None
            orientation: "horizontal"
            Label:
                text: "Insert File Name Here"
            TextInput:
                id: file_name 
                multiline: False 

        Button:
            height: "40dp"
            size_hint_y: None 
            text: "Submit"
            on_press: ExistingUserPage.loaduser(user_name.text, file_name.text)
            on_release: app.root.current = "ProcessingUserPage"
            on_release: user_name.text = ""
            on_release: file_name.text = ""
            #on_release: ProcessingPage.load(user_name.text, file_name.text)

        BoxLayout:
            height: "40dp"
            size_hint_y: None 

            Button:
                text: "Return to Home Page"
                on_release: app.root.current = "HomePage"



<ProcessingUserPage>:
    name: "ProcessingUserPage"

    BoxLayout:
        orientation: "vertical"
        Label: 
            size_hint_y: 0.1
            text: "My Account"
            color: 0,0,1,1  #Blue
            text_size: self.size
            font_size: root.height * 0.1
            halign: "center"
            valign: "bottom"

        Label: 
            size_hint_y: 0.1
            text: "Processing " + root.text 
            color: 0.6, 0, 0.6, 1 #True Purple
            text_size: self.size  
            font_size: root.height * 0.05
            halign: "center"
            valign: "bottom"


        BoxLayout:
            orientation: "horizontal"
            size_hint_y: 0.1
            Label:
                size_hint_x: 0.2
                text_size: self.size 
                text: "Expenditure"
                halign: "center"
            Label:
                size_hint_x: 0.8
                text_size: self.size 
                text: "Item"
                halign: "left"
        BoxLayout:
            orientation: "horizontal"   
            size_hint_y: 0.5


            ListView:
                id: content_list_view
                adapter: 
                    ListAdapter(data = root.content_list, cls = Factory.CustomListItemButton)



        BoxLayout:
            orientation: "horizontal"
            size_hint_y: 0.1
            Label: 
                text: "Input"
            TextInput:
                id: input
                multiline: False


        Button:
            size_hint_y: 0.1
            text: "Press"
            on_press: root.function(root.expenditure,root.itemlist,root.content)

        Button:
            size_hint_y: 0.1
            text: "Back to Home"
            on_press: app.root.current = "HomePage"

Eg .txt file

10  LUNCH
13  DINNER
8   DRINK 
18  SHOP
13  DRINK
4   SNACK
6   SNACK 
10  LUNCH
13  DINNER
8   DRINK 
18  SHOP
13  DRINK
4   SNACK
6   SNACK 

1 Answers1

0

The solution is to replace ListView with RecycleView because ListView has been deprecated since Kivy version 1.10.0, and we are using RecycleGridLayout. Please refer to the example for details.

Note

The example was developed using Python 3.5 on Ubuntu Linux.

Example

account.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import StringProperty, ListProperty, BooleanProperty
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior

import os
import re


class HomePage(Screen):
    # Includes the name of the App, Instructions, as well as 2 buttons that lead to their
    # respective screens
    pass


class NewUserPage(Screen):
    def saveuser(self, username, filename):

        if len(username) != 0:
            # Windows - newuserfilepath = "users\\" + str(username)
            newuserfilepath = "/home/" + str(username)
            if not os.path.exists(newuserfilepath):
                os.makedirs(newuserfilepath)
            if len(filename) != 0:
                # Windows - filepath = os.path.join("users\\" + str(username), str(filename) + ".txt")
                filepath = os.path.join("/home/" + str(username), str(filename) + ".txt")
                f = open(filepath, "w")
                f.close()
            else:
                # Python2.x - print "Please key in at least one character for a filename."
                print("Please key in at least one character for a filename.")
        else:
            # Python2.x - print "Please type in a Username."
            print("Please type in a Username.")

    def saveuserpopup(self, username, filename):
        # Windows - filepath = os.path.join("users\\"+str(username), str(filename))
        filepath = os.path.join("/home/"+str(username), str(filename))

        if not os.path.exists(filepath):
            box = BoxLayout(orientation="vertical")
            box.add_widget(Label(text="Confirm Save %s, under filename: %s" % (username, filename)))
            Button1 = Button(text="Confirm")
            Button2 = Button(text="Cancel")
            box.add_widget(Button1)
            box.add_widget(Button2)

            popup = Popup(title="Confirm Save User", content=box, size_hint=(0.5, 0.5))
            buttoncallback = lambda *args: self.saveuser(username, filename)
            Button1.bind(on_press=buttoncallback)
            Button1.bind(on_release=popup.dismiss)
            # Button1.bind(on_press = self.saveuser(username,filename))
            Button2.bind(on_press=popup.dismiss)
            popup.open()


class ExistingUserPage(Screen):
    text = StringProperty("")
    content = StringProperty("")
    content_list = ListProperty([])

    def loaduser(self, username, filename):
        # Windows - filepath = os.path.join("users\\"+str(username), str(filename)+".txt")
        filepath = os.path.join("/home/"+str(username), str(filename)+".txt")
        self.text = "User: " + str(username).upper() + " filename: " + str(filename).upper()
        self.content = str("Please fill in the details correctly")

        if os.path.exists(filepath):
            try:
                with open(filepath, "r") as fobj:
                    for line in fobj:
                        for word in line.rsplit():
                            self.content_list.append(word)
            except IOError:
                # Python2.x - print "You did not fill in sufficient information"
                print("You did not fill in sufficient information")
        else:
            # Python2.x - print "Please key in a valid username or filename."
            print("Please key in a valid username or filename.")


class ProcessingUserPage(Screen):
    text = StringProperty("")
    content = StringProperty("")
    content_list = ListProperty([])

    def function(self, content_list, inputString):  # Function called from the .kv file
        words = re.sub("[^\w]", " ", inputString).rsplit()
        print("words=", words)
        for word in words:
            print("word=", word)
            content_list.append(word)
        # TO-DO List
        # Append words to file


class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
                                  RecycleGridLayout):
    ''' Adds selection and focus behaviour to the view. '''


class SelectableButton(RecycleDataViewBehavior, Button):
    ''' Add selection support to the Label '''
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def refresh_view_attrs(self, rv, index, data):
        ''' Catch and handle the view changes '''
        self.index = index
        return super(SelectableButton, self).refresh_view_attrs(rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableButton, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        ''' Respond to the selection of items in the view. '''
        self.selected = is_selected


class ScreenManagement(ScreenManager):
    pass


class AccountApp(App):
    def build(self):
        return ScreenManagement()


if __name__ == "__main__":
    AccountApp().run()

account.kv

#:kivy 1.10.0

<ScreenManagement>:
    HomePage:
    NewUserPage:
    ExistingUserPage:
        id: ExistingUserPage
    ProcessingUserPage:
        text: ExistingUserPage.text
        content: ExistingUserPage.content
        content_list: ExistingUserPage.content_list

<HomePage>:
    name: "HomePage"

    GridLayout:
        cols:1
        rows:3
        padding: 10
        spacing: 10

        BoxLayout:

            Label:
                text: "My Account"
                color: 0,0,1,1  #Blue
                text_size: root.width, None
                font_size: root.height * 0.1
                halign: "center"
                valign: "top"

        BoxLayout:

            Label:
                text: "Hello there! I am your friendly accountant Bill! Please select below if you are a 'New' or 'Existing' member"
                color: 0.6, 0, 0.6, 1   #True Purple
                text_size: root.width, None
                font_size: root.height * 0.05
                halign: "center"
                valign: "top"

        GridLayout:
            cols: 2
            rows: 1
            spacing: 30
            padding: 50

            Button:
                font_size: self.width * 0.1
                text: "New"
                color: 0.43, 0.61, 0.95, 1
                background_color: 1, 0.84, 0, 1
                on_release: app.root.current = "NewUserPage"

            Button:
                font_size: self.width * 0.1
                text: "Existing"
                color: 0.43, 0.61, 0.95, 1
                background_color: 1, 0.84, 0, 1
                on_release: app.root.current = "ExistingUserPage"


<NewUserPage>:
    name: "NewUserPage"
    id: NewUserPage
    username: user_name

    BoxLayout:
        orientation: "vertical"

        BoxLayout:
            height: dp(100)
            size_hint_y: None
            padding: 10

            Label:
                size: self.texture_size
                text: "My Account"
                color: 0,0,1,1  #Blue
                text_size: root.width, None
                font_size: root.height * 0.1
                halign: "center"
                valign: "top"

        BoxLayout:
            height: dp(100)
            size_hint_y: None

            Label:
                size: self.texture_size
                text: "New User Sign Up"
                color: 0.6, 0, 0.6, 1 #True Purple
                text_size: root.width, None
                font_size: root.height * 0.05
                halign: "center"
                valign: "top"

        BoxLayout:

            FileChooserIconView:
                # Windows - path: "users\\"
                path: "/home/"


        BoxLayout:
            height: dp(40)
            size_hint_y: None
            orientation: "horizontal"

            Label:
                text: "Insert User Name Here"

            TextInput:
                id: user_name
                multiline: False


        BoxLayout:
            height: dp(40)
            size_hint_y: None
            orientation: "horizontal"

            Label:
                text: "Insert File Name Here"

            TextInput:
                id: file_name
                multiline: False

        Button:
            height: dp(40)
            size_hint_y: None
            text: "Submit"
            #on_release: NewUserPage.saveuserpopup(user_name.text, file_name.text)
            on_release: root.saveuserpopup(user_name.text, file_name.text)

        Button:
            height: dp(40)
            size_hint_y: None
            text: "Return to Home Page"
            on_release: app.root.current = "HomePage"

<ExistingUserPage>:
    name: "ExistingUserPage"
    id: ExistingUserPage

    BoxLayout:
        orientation: "vertical"

        BoxLayout:
            height: dp(100)
            size_hint_y: None

            Label:
                size: self.texture_size
                text: "My Account"
                color: 0,0,1,1  #Blue
                text_size: root.width, None
                font_size: root.height * 0.1
                halign: "center"
                valign: "bottom"

        BoxLayout:
            height: dp(100)
            size_hint_y: None

            Label:
                size: self.texture_size
                text: "Existing User"
                color: 0.6, 0, 0.6, 1 #True Purple
                text_size: root.width, None
                font_size: root.height * 0.05
                halign: "center"
                valign: "top"

        BoxLayout:

            FileChooserIconView:
                # Windows - path: "C:\\Users\\Jamesong\\mystuff\\Kivy_apps\\account\\users\\"
                path: "/home/iam/"

        BoxLayout:
            height: dp(40)
            size_hint_y: None
            orientation: "horizontal"

            Label:
                text: "Insert User Name Here"

            TextInput:
                id: user_name
                multiline: False

        BoxLayout:
            height: dp(40)
            size_hint_y: None
            orientation: "horizontal"

            Label:
                text: "Insert File Name Here"

            TextInput:
                id: file_name
                multiline: False

        Button:
            height: dp(40)
            size_hint_y: None
            text: "Submit"
            on_press: ExistingUserPage.loaduser(user_name.text, file_name.text)
            on_release:
                app.root.current = "ProcessingUserPage"
                user_name.text = ""
                file_name.text = ""

        Button:
            height: dp(40)
            size_hint_y: None
            text: "Return to Home Page"
            on_release: app.root.current = "HomePage"


<SelectableButton>:
    # Draw a background to indicate selection
    canvas.before:
        Color:
            rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
        Rectangle:
            pos: self.pos
            size: self.size


<ProcessingUserPage>:
    name: "ProcessingUserPage"

    BoxLayout:
        orientation: "vertical"

        Label:
            size_hint_y: 0.1
            text: "My Account"
            color: 0,0,1,1  #Blue
            text_size: self.size
            font_size: root.height * 0.1
            halign: "center"
            valign: "bottom"

        Label:
            size_hint_y: 0.1
            text: "Processing " + root.text
            color: 0.6, 0, 0.6, 1 #True Purple
            text_size: self.size
            font_size: root.height * 0.05
            halign: "center"
            valign: "bottom"


        BoxLayout:
            orientation: "horizontal"
            size_hint_y: 0.1

            GridLayout:
                size_hint: 1, None
                size_hint_y: None
                height: 25
                cols: 2

                Label:
                    text: "Expenditure"

                Label:
                    text: "Item"

        BoxLayout:
            orientation: "horizontal"
            size_hint_y: 0.5

            RecycleView:
                viewclass: 'SelectableButton'
                data: [{'text': str(x)} for x in root.content_list]
                SelectableRecycleGridLayout:
                    default_size: None, 25
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    multiselect: True
                    touch_multiselect: True
                    cols: 2

        BoxLayout:
            orientation: "horizontal"
            size_hint_y: 0.1

            Label:
                text: "Input"

            TextInput:
                id: input
                multiline: False

        Button:
            size_hint_y: 0.1
            text: "Press"
            on_press: root.function(root.content_list, input.text)

        Button:
            size_hint_y: 0.1
            text: "Back to Home"
            on_press: app.root.current = "HomePage"

Output

enter image description here enter image description here

ikolim
  • 15,721
  • 2
  • 19
  • 29