Kivy: get the root widget size - layout

I want to get the size of the root widget so I can use it to calculate the size and position of child widget. Here's my simple code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
class PictoGame(FloatLayout):
def __init__(self):
FloatLayout.__init__(self)
self.add_widget(Button(text = "Button 1", size = (10,10)) )
class PictoApp(App):
def build(self):
my_game = PictoGame()
print str(my_game.size)
return my_game
PictoApp().run()
The size printed out is [1,1] ? What I see is big window, so can not be [1,1]
The button take full size of window, not (10,10) as I want.
There must be some concept of kivy I missed. Please help me, thank you very much.

With kivy you shouldn't normally try to manually manage sizes like this - you can do it, but it's the harder way to go. Instead, use layouts to do the work for you.
I'd give an example, but I'm not sure what you're trying to do.
If you're certain you want to do everything manually, you can use Clock.schedule(some_function, 0) to schedule a function that sets the size and position to your liking. The Clock should only call it once the size of everything is determined.

have you tried using size_hint_y, size_hint_x, and pos_hint?
Button:
size_hint_x: .25
size_hint_y: .1
pos_hint: {'x': .375, 'y': .25}
this will help to automatically size and position your button according to the size of your window.

The window size (as comment by #inclement on another answer) is in kivy.core.window.Window.size
A simple use is:
from kivy.core.window import Window
...
window_width, window_height = Window.size
The variables window_width and window_height will get the size of the root window.

Related

my attempt to find the dimensions of a widget results in bbox returning (0,0,0,0)

I am trying to construct a screen consisting of a variable number of image only radio buttons so the user can choose an image. I want to use a vertical scrollbar and understand a key part of coding the scrollbar is setting the scrollregion found by using bbox. I can't get bbox to produce anything other than (0,0,0,0)
I have reduced the code as much as I am able
from tkinter import *
from PIL import ImageTk,Image
root = Tk()
def display():
canvas.grid()
image=Image.open("c:/Python/Art Images/Thumbnails/0001#Ground Swell.jpg")
thumbnail = ImageTk.PhotoImage(image)
thumbnail_button = Radiobutton(canvas,indicatoron=0,image=thumbnail)
thumbnail_button.image=thumbnail #keep a reference to avoid garbage collection
thumbnail_button.grid()
print(root.bbox("all"))
canvas=Canvas(root)
display()
root.mainloop()

How can I add widgets to an existing screen?

my application is built from a .kv file.
I want to add buttons to a Screen from my python file.
class Profiles(Screen):
def suh(self):
for i in range(5):
self.add_widget(Button(text=i))
The suh function seem to have no effect. Is this because the function is called after the app is built? Is there a better way to do this?
Solution 1
Since Screen is a RelativeLayout, use on_pre_enter or on_enter to inovke method, suh(), plus add size and pos to Button widget.
Solution 2
Use a container e.g. BoxLayout or Gridayout on top of Screen.
Note
Whenever widget is added to a screen via on_pre_enter or on_enter, use on_pre_leave or on_leave to remove the widget. This is to prevent doubling your widget each time the screen is entered.
If the widgets are cleared too fast, use Clock.schedule_once with a time interval.
Note 1: Screen Events
Kivy Screen has the following events.
Kivy Screen » Events
on_pre_enter: ()
Event fired when the screen is about to be used: the entering
animation is started.
on_enter: ()
Event fired when the screen is displayed: the entering animation is
complete.
on_pre_leave: ()
Event fired when the screen is about to be removed: the leaving
animation is started.
on_leave: ()
Event fired when the screen is removed: the leaving animation is
finished.
Note 2: Screen is a RelativeLayout
Kivy Screen » RelativeLayout
Please note that by default, a Screen displays nothing: it’s just
a RelativeLayout. You need to use that class as a root widget for
your own screen, the best way being to subclass.
Warning
As Screen is a RelativeLayout, it is important to understand
the Common Pitfalls.
Example - Solution 1
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.lang import Builder
Builder.load_string("""
<Profiles>:
# on_pre_enter: self.suh()
on_enter: self.suh()
""")
class Profiles(Screen):
def suh(self):
for i in range(5):
self.add_widget(Button(text=str(i), size_hint=(0.1, 0.1), pos=(i*100, i*100)))
sm = ScreenManager()
sm.add_widget(Profiles(name='profiles'))
class TestApp(App):
def build(self):
return sm
if __name__ == "__main__":
TestApp().run()
Output - Solution 1
Example - Solution 2
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.lang import Builder
Builder.load_string("""
<Profiles>:
# on_pre_enter: self.suh()
on_enter: self.suh()
BoxLayout:
id: container
""")
class Profiles(Screen):
def suh(self):
for i in range(5):
self.ids.container.add_widget(Button(text=str(i), size_hint=(0.1, 0.1)))
sm = ScreenManager()
sm.add_widget(Profiles(name='profiles'))
class TestApp(App):
def build(self):
return sm
if __name__ == "__main__":
TestApp().run()
Output - Solution 2
Add the widgets through the App class in stead of the Screen. So you would have to create a function that creates the widgets, and in your build function, you would have to use Clock.schedule_once from the Clock module to run the other function. Example:
class TestApp(App):
def build(self):
Clock.schedule_once(self.yoyo, 0)
return MainWin()
def yoyo(self, *args):
for i in memu_acc:
self.root.ids["prof"].ids["pro"].add_widget(Button(text=i))

Kivy custom widget behaving differently when used in box layout

I was trying to make an window with two block with vertical box layout, with upper widget larger than lower one. But rather than that, the widgets are stacking on top of other at the bottom left corner, both being of same size.
Here is my code
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.codeinput import CodeInput
from kivy.core.window import Window
from kivy.uix.button import Button
Window.maximize()
class Editor(Widget):
def __init__(self, *arg, **kwarg):
super(Editor, self).__init__(*arg, **kwarg)
self.size_hint= (1, 0.8)
self.add_widget(Button(text= "1"))
class Output(Widget):
def __init__(self, *arg, **kwarg):
super(Output, self).__init__(*arg, **kwarg)
self.size_hint= (1, 0.2)
self.add_widget(Button(text= "2"))
class IDE(BoxLayout):
def __init__(self, *arg, **kwarg):
super(IDE, self).__init__(*arg, **kwarg)
self.orientation= "vertical"
box1= Editor()
self.add_widget(box1)
box2= Output()
self.add_widget(box2)
class MainApp(App):
def build(self):
return IDE()
if __name__=="__main__":
MainApp().run()
(The buttons are used in Output and Editor class just to check their respective parent widget position in resultant window)
Someone help me to figure out what am I doing wrong.
Your Editor and Output behave as expected, but are completely invisible. What you are seeing are their own child widgets, the two Buttons, each of which has no position or size applied to it and so takes the default of pos (0, 0) and size (100, 100).
Make the Editor and Output classes some kind of layout, e.g. FloatLayout, or simply remove them and add the Buttons directly to the BoxLayout.

How to prevent widget's size changing in Kivy?

I've been playing with this code and I've found some (probably) strange thing: when I add parent to my class, size changes to [100, 100] (see comments):
from random import random, randint
import kivy
kivy.require('1.8.0')
from kivy.config import Config
Config.set('graphics', 'fullscreen', '0')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Line, Ellipse, Triangle, Rectangle
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
with self.canvas:
Color(random(), 1, 1, mode='hsv')
touch.ud['line'] = Line(points=(self.width - touch.x, self.height - touch.y))
print(self.width, self.height) # It works OK if I return painter below, but it's not if I return parent.
def on_touch_move(self, touch):
touch.ud['line'].points += [self.width - touch.x, self.height - touch.y]
class Example(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
print(painter.size) # Shows [100, 100] anyway.
parent.add_widget(painter)
return parent # If I return painter, everything works as I want (except painter.size still shows [100, 100]).
if __name__ == '__main__':
Example().run()
Why does that happen? And how should I make it right?
(100, 100) is the default size for widgets. In this case your painter has that size because you never set it to anything else.
Even if its parent were a layout class that would automatically move and resize the painter, its size would still read (100, 100) at this point because the layout hasn't had time to run yet. You shouldn't generally worry about the pixel values at this point - if you need something else to depend on them, use a binding to update the other thing automatically when the first one changes. This is made particularly easy by kv language.

PYQT QSplitter issue

I use QSplitter and I found out that the minumum width of a widget in
the splitter is 32 pixels (and 23 pixels in height). Does anybody body knows how
to change this default. In other words, you can't drag the splitter so that one of the
widgets (assume that there are 2 widgets in the spllitter) in the spllitter will be less
than 32 pixels in width.
The code:
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.resize(400,400)
m = QtGui.QSplitter(self)
m.resize(200, 100)
x = QtGui.QPushButton(m)
x.setGeometry(0, 0, 100, 100)
y = QtGui.QPushButton(m)
y.setGeometry(0, 100, 100, 100)
m.setSizes([20, 180])
# this will show you that the width of x is 32 (it should be 20!)
print x.width()
Note: I'm using Python 3.6.2 and PyQt5, though the logic in the example stays the same and can be understood even if you're using other versions of Python and PyQt.
Look at what is said here:
If you specify a size of 0, the widget will be invisible. The size policies of the widgets are preserved. That is, a value smaller than the minimal size hint of the respective widget will be replaced by the value of the hint.
One of the options to solve your problem is to call x.setMinimumWidth() with a small value, like:
x.setMinimumWidth(1)
However, if you'll try it yourself, you'll see that
it is a dirty hack as it actually leaves the widget here, just makes it very narrow and
though now you can drag the splitter, the initial width of the widget is still "32" instead of "20".
x.setMinimumWidth(0)
also doesn't work as expected: its minimal width is actually zero by default (as this widget has no contents, I guess), but it doesn't help you to make splitter item less than 32 pixels wide unless you collapse it.
By the way, set
m.setCollapsible(0, False)
m.setCollapsible(1, False)
if you want splitter to stop collapsing its two children widgets. More details here.
The solution I've found is to overload sizeHint() method of the widget you want to include into the splitter, as in example below (look at the ButtonWrapper class and what is output like now).
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#Python 3.6.2 and PyQt5 are used in this example
from PyQt5.QtWidgets import (
QPushButton,
QSplitter,
QWidget,
QApplication,
)
import sys
class ButtonWrapper(QPushButton):
def sizeHint(self):
return self.minimumSize()
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(400, 400)
m = QSplitter(self)
m.resize(200, 100)
x = ButtonWrapper(self)
x.setGeometry(0, 0, 100, 100)
y = QPushButton(self)
y.setGeometry(0, 100, 100, 100)
m.addWidget(x)
m.addWidget(y)
m.setSizes([20, 180])
#Now it really shows "20" as expected
print(x.width())
#minimumWidth() is zero by default for empty QPushButton
print(x.minimumWidth())
#Result of our overloaded sizeHint() method
print(x.sizeHint().width())
print(x.minimumSizeHint().width())
self.setWindowTitle('Example')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I'm not sure if this is the right way to do stuff, but I've spent lots of time trying to solve my own problem connected to this, and haven't seen anything satisfying yet so far. I'll really appreciate it if someone knows a better actually working & clear workaround.

Resources