How to add widgets to a rule both from kv file and py file in the same time? - python-3.x

Is there a way so that I can add widgets to a specific rule in kivy both from kivy file and python file at the same time?
.kv
<A>:
GridLayout:
cols:1
id: someID
Button:
text:"some text"
on_press: root.do_some_stuff()
<RootWidget>:
A:
name: "rule"
.py
class A(Screen):
def do_some_stuff(self):
print("I was told to do some stuff")
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
return RootWidget()
How can I add another label to GridLayout widget from the class A?

You can use the id that you have specified in the kv:
class A(Screen):
def do_some_stuff(self):
self.ids.someID.add_widget(Label(text="I was told to do some stuff"))

Related

How do I dynamically add widget to grid?

I have a Button in the top boxlayout that I want to add a widget to my grid layout below.
Here is my .py file code
class rootWidget(BoxLayout):
pass
class topWidget(BoxLayout):
print("top part")
class middelWidget(GridLayout):
pass
class bottomWidget(BoxLayout):
pass
class NewButton(Button):
pass
class mainkv(App):
def newButt(self):
print("button clicked")
butt = NewButton(text="New Button")
middelWidget().add_widget(butt)
def build(self):
return rootWidget()
mainkv().run()
And here is my .kv file
<topWidget>:
Button:
text: 'CLick add to grid'
on_press:
app.newButt()
<middelWidget>:
cols: 6
id: grid
Button:
text: 'middel Grid'
<bottomWidget>:
Button:
text: 'bottom'
<rootWidget>:
orientation: 'vertical'
Button:
text: 'Root'
topWidget:
middelWidget:
bottomWidget:
I am not getting any errors but it's not working.
Can't figure out what am missing any help much appreciated.
The problem is that the code:
middelWidget().add_widget(butt)
is creating a new instance of the middelWidget, and adding the Button to that new instance. However, that new instance of middelWidget is not part of your GUI, so nothing happens. The fix is to use the instance of middelWidget that is already in your GUI. That can be done using ids. Here is a modified version of your rootWidget rule in the kv:
<rootWidget>:
orientation: 'vertical'
Button:
text: 'Root'
topWidget:
middelWidget:
id: middle # id for accessing middelWidget
bottomWidget:
Then the newButt() method can be:
def newButt(self):
print("button clicked")
butt = NewButton(text="New Button")
middle = self.root.ids.middle # get a reference to the middelWidget instance
middle.add_widget(butt)
# middelWidget().add_widget(butt)
Also, your classes should begin with and upper case letters. This is not just for appearance, it can cause errors when those classes are used in kv.

Widgets keep repeating/duplicating in KV file

I have a UI designed in KV language, as follows:
<MainLayout>:
BoxLayout:
orientation:"horizontal"
BoxLayout:
orientation:"vertical"
Button:
size_hint:(1,.9)
text:"1"
Button:
size_hint:(1,.1)
text:"2"
Here is the accompanying py file:
from kivy import Config
from kivy.app import App, Builder
from kivy.uix.boxlayout import BoxLayout
with open('gui.kv', 'r') as kv:
Builder.load_string(kv.read())
class MainLayout(BoxLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
class GuiApp(App):
Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'width', '1600')
Config.set('graphics', 'height', '1000')
def build(self):
self.title = "TestGUI"
return MainLayout()
if __name__ == '__main__':
GuiApp().run()
I want an asymmetrical layout, where the main window has two columns, and the left column has two widgets (top and bottom halves) and the right column is similar.
Each of the widgets in each column needs to be asymmetric in height.
For whatever reason, the given design above keeps "repeating" my left and right halves, there is no "blank" space.
I wanted two BoxLayout, one for each column. My <MainLayout> is also a BoxLayout. I have tried making that a GridLayout with cols:2 as well, but I get similar results.
This is my output:
So, tl;dr, the left column is exactly correct, but it repeats to the right half as well. How can I change that?
Problem - Two Root Widgets
You are getting the column repeated because there are two root widgets. One from Builder.load_string(kv.read()) and the other one from class GuiApp() i.e. by Builder and by name convention respectively.
Solution
Remove the following:
with open('gui.kv', 'r') as kv:
Builder.load_string(kv.read())
Since <MainLayout> is a BoxLayout, therefore you do not need another BoxLayout. Please refer to the example for details.
Note
How to load KV
There are two ways to load Kv code into your application:
By name convention:
Kivy looks for a Kv file with the same name as your App class in
lowercase, minus “App” if it ends with ‘App’ e.g:
MyApp -> my.kv
If this file defines a Root Widget it will be attached to the App’s
root attribute and used as the base of the application widget tree.
By Builder You can tell Kivy to directly load a string or a file. If this string or file defines a root widget, it will be returned by
the method:
Builder.load_file('path/to/file.kv')
or:
Builder.load_string(kv_string)
Example
test.kv
#:kivy 1.10.0
<MainLayout>:
orientation:"horizontal"
BoxLayout:
orientation:"vertical"
Button:
size_hint:(1,.9)
text:"1"
Button:
size_hint:(1,.1)
text:"2"
BoxLayout:
orientation:"vertical"
Button:
size_hint:(1,.9)
text:"3"
Button:
size_hint:(1,.1)
text:"4"
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class MainLayout(BoxLayout):
pass
class TestApp(App):
def build(self):
return MainLayout()
if __name__ == "__main__":
TestApp().run()
Output

How to use on_enter event to change screens on kivy?

So, here is what I'm trying to do: when entering on the first screen of my app, I want it to check if some files exist in given directory. If they exist, I want it to immediately change to another screen.
I've tried the following:
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from os import listdir
class Manager(ScreenManager):
pass
class CreateFileScreen(Screen):
def on_enter(self):
try:
files = listdir("data/files")
if "file.dat" in files:
self.parent.current = "login"
else:
pass
except FileNotFoundError:
pass
class LoginScreen(Screen):
pass
class ExampleApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
ExampleApp().run()
example.kv
#:kivy 1.10.0
<CreateFileScreen>:
BoxLayout:
Label:
text: "This is Create File Screen"
font_size: "30sp"
<LoginScreen>:
BoxLayout:
Label:
text: "This is Login Screen"
font_size: "30sp"
<Manager>:
CreateFileScreen:
name: "createfile"
LoginScreen:
name: "login"
When file.dat does exist in data/files I get the following error:
kivy.uix.screenmanager.ScreenManagerException: No Screen with name "login".
Any idea on how to fix this?
The problem is that on_enter is executed before the screen gets its name.
You can make a change_screen method, then call it with Clock.schedule_once. That way it will be called the next frame.
from kivy.clock import Clock
class CreateFileScreen(Screen):
def on_enter(self):
Clock.schedule_once(self.change_screen)
def change_screen(self, dt):
try:
files = listdir("data/files")
if "file.dat" in files:
self.manager.current = "login"
else:
pass
except Exception as e:
print(e)

ObjectProperty has no attribute (Kivy, Python 3.x)

This questions has been asked and answered quite often, but I still can't get it to work. I want to access a widget that is a children of another widget. For this I used an ObjectProperty. When I try to access the ObjectProperty (by opening any file via the FileChooser popup) to change the label's text I get this error:
self.pdfpages.text = "change to this" AttributeError:
'kivy.properties.ObjectProperty' object has no attribute 'text'
Do I need to initialize the ObjectProperty? Or is there a problem with my .kv structure?
main.py
import kivy
kivy.require('1.10.0') # replace with your current kivy version !
# Kivy Imports
from kivy.app import App
#UI Eleemnts
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.scrollview import ScrollView
from kivy.uix.scatter import Scatter
from kivy.uix.image import Image
#Core Elements
from kivy.core.image import Image as CoreImage
from kivy.properties import ObjectProperty
# Python Imports
import os
class Window(BoxLayout):
#add child widgets (in kv file)
pass
class PDFView(ScrollView):
pdfpages = ObjectProperty()
def newPage(self, filepath):
#here the error occurs
self.pdfpages.text = "change to this"
class SideBar(BoxLayout):
def openfile(self):
print("Button pressed")
OpenDialog().open()
class OpenDialog(Popup):
def cancelfile(self):
#close popup
self.dismiss()
print("No file opened")
def openfile(self, path, selection):
#selection can contain multiple files, [0] is first or only
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
#open PDFView class
PDFView.newPage(PDFView, os.path.join(path, selection[0]))
class PDFEditor(App):
title = "PDFEditor"
#gets called on startup
#load program
def build(self):
#return root node
return Window()
if __name__ == '__main__':
PDFEditor().run()
PDFEditor.kv
<Window>:
#this is the main (root) "window"
orientation: "horizontal"
SideBar:
size_hint: (.1, 1)
PDFView:
size_hint: (.9, 1)
<PDFView>:
#handler for ObjectProperty
pdfpages: pdfpages
Scatter:
#only zoom
BoxLayout:
#add Images in here somehow
orientation: "vertical"
Label:
id: pdfpages
text: "hi"
<SideBar>:
orientation: "vertical"
Button:
id: btn_openfile
on_release: root.openfile()
text: "Open File"
Label:
text: "Hello!"
<OpenDialog>:
title: "Choose File"
BoxLayout:
#Fullscreen
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: chsr_open
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancelfile()
Button:
text: "Open"
on_release: root.openfile(chsr_open.path, chsr_open.selection)
The problem here is that you set the text of a new instance, wich is not even on the window, rather than the one you allready have.
To fix this, you need to access the one you have.
First, make your Window as an App attribute, so you can reference it later.
class PDFEditor(App):
title = "PDFEditor"
def build(self):
self.root = Window()
return self.root
Then give the PDFView an id in kv
<Window>:
orientation: "horizontal"
SideBar:
size_hint: (.1, 1)
PDFView:
id: pdfview
size_hint: (.9, 1)
Then in your openfile method, you can get the running apps root attribute you created earlier, like this.
def openfile(self, path, selection):
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
app = App.get_running_app()
pdf = app.root.ids.pdfview
pdf.newPage(os.path.join(path, selection[0]))
That way you can access the ids of your Window class
change your openfile method to this:
def openfile(self, path, selection):
#selection can contain multiple files, [0] is first or only
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
#open PDFView class
PDFView().newPage(PDFView, os.path.join(path, selection[0]))
PDFView is the class, PDFView() is an instance of this class, what you need
edit
the label does not change because you are creating a new one without replacing it. So if I understand well all you want is to replace the text of your PDFView. There is many ways to do that but I don't want to edit much your code: try this:
...
class OpenDialog(Popup):
...
def openfile(self, path, selection):
#selection can contain multiple files, [0] is first or only
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
#open PDFView class
app.window.ids.pdfview.newPage(PDFView, os.path.join(path, selection[0]))
...
...
class PDFEditor(App):
...
def build(self):
#return root node
self.window = Window()
return self.window
app = PDFEditor()
if __name__ == '__main__':
app.run()
in your kv:
...
<Window>:
#this is the main (root) "window"
orientation: "horizontal"
SideBar:
size_hint: (.1, 1)
PDFView:
id: pdfview
size_hint: (.9, 1)
...

Updating a Label In Kivy

I am trying to update a label in python. It is a simple Guess the num game. Currently it prints the Computer responses in PC but I want it to print those to a label.
here is the main .py file
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
import random
secret=random.randint(1,100)
class Application(BoxLayout):
label1=""
def button_press(self):
global label1
if (int(self.user_guess.text)<secret):
print("Higher")
self.label1="Higher"
elif(int(self.user_guess.text)>secret):
print("Lower")
self.label1="Lower"
elif(int(self.user_guess.text)==secret):
print("You WOn")
self.label1="You WOn"
class WeatherApp(App):
pass
if __name__ == '__main__':
WeatherApp().run()
the .kv file
Application:
<Application>:
size:(480,30)
user_guess:guess
size_hint:(None,None)
text:""
orientation: "vertical"
BoxLayout:
Button:
text:'Play'
on_press:root.button_press()
TextInput:
id:guess
text:''
Label:
text:root.label1
I think you should use
self.label1.text="Higher"

Resources