I'm trying to make a notebook app using kivy, where the user will be able to scroll the sheet up and down and write on it. I tried using ScrollView, but it doesn't seem to work - I wanted the sheet image to be stretched to the width of the window and as the height is greater than the width - have the image scrollable up and down. What happened instead was this:
I would really appreciate anyone looking into the code and trying the figure out what I was doing wrong :)
python code:
import kivy
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from PIL import Image as Image1
from kivy.uix.image import Image
GUI = Builder.load_file('style.kv')
Window.size = (1000, 200)
img_size = Image1.open("images/notebook.png").size
class NotebookScreen(GridLayout):
def __init__(self, **kwargs):
self.rows = 1
super(NotebookScreen, self).__init__(**kwargs)
def get_size_for_notebook(self, **kwargs):
global img_size
width, height = Window.size
return width, (img_size[0] * height / width)
class MainApp(App):
def build(self):
return NotebookScreen()
if __name__ == "__main__":
MainApp().run()
kv file:
<NotebookScreen>
FloatLayout:
rows: 2
GridLayout:
size_hint: 1, .05
pos_hint: {"top": 1, "left": 1}
id: tool_bar
cols: 1
canvas:
Color:
rgba: 0, 0, 1, 1
Rectangle:
pos: self.pos
size: self.size
GridLayout:
id: notebook_grid
size_hint: 1, .95
pos_hint: {"top": .95, "left": 0}
cols: 1
ScrollView:
Image:
id: notebook_image
source: 'images/notebook.png'
allow_stretch: True
keep_ratio: True
pos: self.pos
size: root.get_size_for_notebook()
The problem is that you are setting the size of the Image in your kv, but it is having no effect, since size_hint over-rules size. The default size_hint is (1,1), so no scrolling is done (the Image is constrained to fit in the ScrollView). To allow your size to take effect, just add:
size_hint: None, None
to the Image in your kv.
Related
I want to know how to remove a specific Rectangle object in Kivy. I create the Rectangle in .py file by pressing a button and I want to the second button could be able to remove that specific Rectangle.
My .py code:
import kivy
kivy.require("1.10.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.app import App
from kivy.graphics import RoundedRectangle
class Screen1(Screen):
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg")
def remove_on_press(self):
pass #I don't know what to write there
class Test(App):
def build(self):
Builder.load_file("Test.kv")
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
return sm
Test().run()
And the .kv file:
#: kivy 1.10.1
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
pos: (root.width - 600) / 2, 800
size: 600, 200
text: "Create rectangle"
on_press: scr1.create_on_press()
pos_hint: {'width': 0.5, 'top': 0.8}
size_hint: None, None
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press
pos_hint: {'width': 0.5, 'top': 0.2}
size_hint: None, None
Thanks for any help.
I tried to use self.parent.remove_widget, but it removed the whole Screen1. However, I want to remove only this Rectangle.
One of the various ways can be adding a group attr. and remove that group later. Since RoundedRectangle is inherited from class Instruction, you can set its attr. group and later use method remove_group to remove that particular group as follows,
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg", group = u"rect")
def remove_on_press(self):
self.canvas.remove_group(u"rect")
Also an obvious change you need in .kv,
...
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press() # Call the function.
...
Thanks, it finally works. My .py:
import kivy
kivy.require("1.10.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.app import App
from kivy.graphics import RoundedRectangle
class Screen1(Screen):
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg", group = u"rect")
def remove_on_press(self):
self.canvas.remove_group(u"rect")
class Test(App):
def build(self):
Builder.load_file("Test.kv")
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
return sm
Test().run()
And the .kv:
#: kivy 1.10.1
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
pos: (root.width - 600) / 2, 800
size: 600, 200
text: "Create rectangle"
on_press: scr1.create_on_press()
pos_hint: {'width': 0.5, 'top': 0.8}
size_hint: None, None
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press()
size_hint: None, None
Thanks everyone for answer.
I'm making an app in python kivy and in my tacscreen I have a draggable Image and an Label, whenever I drag my draggable Image my label drags with it as well. The position of the label is just below the draggable Image. The problem that I'm facing is whenever I resize my window and drag the image the label goes from this to this the space between the Image and the label is too much. How can I fix this? I want the label to always be like how it is in the first screenshot even after I resize the screen and drag. Below is my code. I have been trying to find a solution to this for a months now. I really appreciate any help. Thanks!
main.py
from kivy.properties import BooleanProperty
from kivy.properties import ObjectProperty
from kivy.metrics import dp
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
from kivy.uix.behaviors import DragBehavior
from kivy.uix.image import Image
class DragImage1(DragBehavior, Image):
dragging = BooleanProperty(False)
drag_label = ObjectProperty()
def on_touch_move(self, touch):
if touch.grab_current is self:
self.drag_label.pos = self.x, self.y - dp(300)
return super().on_touch_move(touch)
def on_touch_up(self, touch):
uid = self._get_uid()
if uid in touch.ud:
# do something on drop
print(self.source, 'dropped at', touch.x, touch.y)
return super(DragImage1, self).on_touch_up(touch)
class TacScreen(Screen):
Pass
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name, *args):
self.root.current = screen_name
MainApp().run()
main.kv
#:include tacscreen.kv
ScreenManager:
id: screen_manager
TacScreen:
name: "tac_screen"
id: tac_screen
tacscreen.kv
<tacscreen>:
#:import utils kivy.utils
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
DragImage1:
drag_label: per
pos: root.center_x - self.width/2, root.center_y - self.height/0.3
size_hint: 1, .1
source: "Image.png"
Label:
id: per
text: "Hello"
font_size: "20dp"
pos: root.center_x - self.width/2, root.center_y - self.height/1.2
You can modify your kv to do the positioning for you:
DragImage1:
id: di # added id for use by the Label
drag_label: per
pos: root.center_x - self.width/2, root.center_y - self.height/0.3
size_hint: 1, .1
source: "Image.png"
Label:
id: per
text: "Hello"
font_size: "20dp"
size_hint: None, None
size: self.texture_size # set size to just contain the text
pos: di.center_x - self.width/2, di.y - self.height # adjust postion based on the DragImage1
And, since kv is now handling the positioning, you should remove the on_touch_move() method from the DragImage1 class.
Note that this will handle positioning when resizing before dragging. But if you drag before resizing, then the resizing will revert the position to that defined in the kv.
My for loop creates a simple list of labels within a scrollview, instead of changing the font size and colour in the python file, I would rather customise the labels within my KV file. Is this possible?
I know I can use ids to reference a label in the KV file, but I cant wrap my head around how to do it here.
If I create a label in my python file, is it good practice to customise in my kv file or continue to customise it in the python file. What is the best way to go about this?
*.py
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.core.window import Window
class FirstWindow(Screen):
def __init__(self, **kwargs):
super(FirstWindow, self).__init__(**kwargs)
Clock.schedule_once(self.create_scrollview)
def create_scrollview(self, dt):
list1 = ['1','2','3','4','5','6','7','8','9','10','11','12']
layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
layout.bind(minimum_height=layout.setter("height"))
for x in list1:
l = Label(text=x, size=(10, 50), size_hint=(1, None)) <----- Change the font colour in my kv file
layout.add_widget(l)
scrollview = ScrollView(size_hint=(1, None), size=(Window.width, Window.height))
self.view.add_widget(scrollview)
scrollview.add_widget(layout)
class WindowManager(ScreenManager):
pass
kv = Builder.load_file('NearMe.kv')
class NearMeApp(App):
def build(self):
return kv
*.kv
WindowManager:
FirstWindow:
<FirstWindow>:
view: view
BoxLayout:
orientation: 'vertical'
BoxLayout:
size: (64, 64)
size_hint: (1, None)
Label:
text: "NearMeApplications"
canvas.before:
Color:
rgba: .5, .5, .5, 1
Line:
width: 2
rectangle: self.x, self.y, self.width, self.height
ScrollView:
id: view
canvas.before:
Color:
rgba: .8, .8, .8, 1
Line:
width: 2
rectangle: self.x, self.y, self.width, self.height
You can define your own customized Label like this:
def MyLabel(Label):
pass
Then in your kv make a rule for MyLabel:
<MyLabel>:
color: 1,0,0,1
size: 10, 50
size_hint: 1, None
And in your loop:
for x in list1:
l = MyLabel(text=x) <----- Change the font colour in my kv file
layout.add_widget(l)
Correct me if I'm wrong, but it is not possible to set the value of any widget that is initiallized in the python file from the .kv file, as it is only added AFTER the entire .kv file is loaded, which would most likely result in an error. Though you can still edit the color in the python file, which would require ids to be added into individual label, something like this:
for x in list1:
l = Label(id=x, text=x, size=(10, 50), size_hint=(1, None)) #ids from the list1 list
layout.add_widget(l)
You just need to add color: r,g,b,a.
Label:
text: "NearMeApplications"
color: 1,0,1,1
class MyTestApp(App):
def build(self):
return Button(text="HELLO", color=(0, 0, 5, 1), background_color=(7, 4, 5, 1), font_size=150)
if __name__ == "__main__":
MyTestApp().run()
I'm trying to get a part of the screen a scrollable image, so it will fit in the screen without ruining the image ratio (referring to notebook.jpg in the code). I saw some comments that suggested using ScrollView, but I couldn't really figure out how to add it to the existing class I already have (I mean as a second class in addition to NotebookScreen, so NotebookScreen will be able to use it).
Would really appreciate some help :)
Python code:
import kivy
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
GUI = Builder.load_file('style.kv')
# Window.size = (2224, 1668)
class NotebookScreen(GridLayout):
def __init__(self, **kwargs):
self.rows = 1
super(NotebookScreen, self).__init__(**kwargs)
class MainApp(App):
def build(self):
return NotebookScreen()
if __name__ == "__main__":
MainApp().run()
kivy code:
<NotebookScreen>
FloatLayout:
rows: 2
GridLayout:
size_hint: 1, .05
pos_hint: {"top": 1, "left": 1}
id: tool_bar
cols: 1
canvas:
Color:
rgba: 0, 0, 1, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
id: notebook_grid
size_hint: 1, .95
pos_hint: {"top": .95, "left": 0}
cols: 1
Image:
id: notebook_image
source: 'images/notebook.jpg'
allow_stretch: True
keep_ratio: True
pos: self.pos
size_hint: 1, 1
Here is a quick and dirty example of how you could do that. I simply used a Label's canvas to draw the image. I added the label to the scrollview and added scrollview along with another label to show you that you don't need to have the entire screen for your scrolling part. I only used PIL to get the size of the image, because I wanted to make sure that your window is smaller than the image you want to scroll. I hope it helps you with your approach.
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.graphics import Rectangle
from PIL import Image
class MyApp(App):
def build(self):
img = Image.open("path/to/your/image/dummy.jpg")
Window.size = (img.size[0]*0.8, img.size[1]*1.2)
layout = BoxLayout(orientation="vertical", size_hint=(None, None), size=Window.size)
lbl = Label(text="This is your picture!", size_hint=(None, None), size=(Window.width, Window.height*0.5))
layout.add_widget(lbl)
sv = ScrollView(size_hint=(None, None), size=(Window.width, Window.height*0.5))
img_box = Label(size_hint=(None, None), size=img.size)
with img_box.canvas:
Rectangle(source="path/to/your/image/dummy.jpg", size=img.size)
sv.add_widget(img_box)
layout.add_widget(sv)
return layout
MyApp().run()
[EDIT]
Here is basically the same thing I made at first, but with some outsourcing to a kv string. I hope this is now, what you are looking for.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.lang.builder import Builder
from PIL import Image
kv = """
#:import Window kivy.core.window.Window
<MyImageView>:
orientation: "vertical"
size_hint: None, None
size: Window.size
Label:
text: "This is your picture!"
size_hint: None, None
size: Window.width, Window.height * .5
ScrollView:
size_hint: None, None
size: Window.width, Window.height * .5
Label:
id: img_box
size_hint: None, None
size: root.img.size
canvas:
Rectangle:
source: root.img_path
size: root.img.size
"""
class MyImageView(BoxLayout):
def __init__(self, img_path, **kwargs):
self.img_path = img_path
self.img = Image.open(self.img_path)
Window.size = (self.img.size[0] * 0.8, self.img.size[1] * 1.2)
super(MyImageView, self).__init__(**kwargs)
class MyApp(App):
def build(self):
Builder.load_string(kv)
layout = MyImageView("Path/to/your/image.png")
return layout
MyApp().run()
I was trying to get an image to fill out my entire grid, but it would leave parts of it blank - even while defining allow_strech and keep_ratio. How do I get my image to fill out the entire grid?
(The problem is the width in this case as the height is fine, but I assume it's to do with the image itself and not the code..)
python code:
import kivy
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
GUI = Builder.load_file('style.kv')
# Window.size = (2224, 1668)
class NotebookScreen(GridLayout):
def __init__(self, **kwargs):
self.rows = 1
super(NotebookScreen, self).__init__(**kwargs)
class MainApp(App):
def build(self):
return NotebookScreen()
if __name__ == "__main__":
MainApp().run()
kv file:
<NotebookScreen>
FloatLayout:
rows: 2
GridLayout:
size_hint: 1, .1
pos_hint: {"top": 1, "left": 1}
id: tool_bar
cols: 1
canvas:
Color:
rgba: 0, 0, 1, 1
Rectangle:
pos: self.pos
size: self.size
GridLayout:
id: notebook_grid
size_hint: 1, .9
pos_hint: {"top": .9, "left": 0}
cols: 1
Image:
id: notebook_image
source: 'images/notebook.jpg'
allow_strech: True
keep_ratio: False
pos: self.pos
size_hint: 1, 1
Spelling error:
allow_strech: True
should be:
allow_stretch: True
Instead of GridLayout, use BoxLayout