I try to change the size_hint of a button in Python, Kivy, but every value i put there the size of the button remain the same.. for the pos is changing but for the size_hint no, and if i change from pos to pos_hint the button is stuck in the corner of the window and from there i cant change nothing... also i tried to stick the pos of the button on a position where is a text in the photo but every time when i resize the kivy window the button and image are changing theyr position.. how i can solve this probelm ?? THANKSSS !!
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.button import Button
from random import choice
class MyImage(Image):
images = ["Q1.png", "Q2.png", "Q3.png"] # ........
def __init__(self, **kwargs):
super().__init__(**kwargs)
correct = Button(pos=(200, 200), size_hint=(.1, .1), on_release=self.random_image, background_color=(1, 1, 1, 0.2))
self.add_widget(correct)
self.random_image()
def random_image(self, *_):
self.source = choice(self.images)
class RandomQuestionApp(App):
def build(self):
return MyImage()
randomApp = RandomQuestionApp()
RandomQuestionApp().run()
Try size instead of size_hint.
correct = Button(
pos=(200, 200),
size=(100, 100),
on_release=self.random_image,
background_color=(1, 1, 1, 0.2)
)
size : This is for static sizing of widgets and takes two arguments
i.e. (width, height). Default size of the button = (100, 100).
size_hint : This is for dynamic sizing of the button and provide hint
of size. It contains two arguments i.e. width and height it can be
floating values.By default, all widgets have their size_hint=(1, 1).
If there is nothing that influences the button size, your dynamics won't do anything. Size on the other hand would define a fixed size of the button where 100x100 is the default.
Related
I have three buttons (one column, three rows) in a GridLayout.
The text on the top of the button is centered but when it is too long it extends past the screen. How do I wrap it? Current code is:
self.add_widget(Button(text="", text_size = (None, self.height), halign = "center", valign = "center"))
for each of the buttons.
Setting
text_size = (None, self.height)
as an arg to the Button constructor does not set up a binding. It just sets the text_size to the values at the moment that the Button __init__() method is executed. So the text_size gets set to (None, 100) and stays that way (the default height of a widget is 100). If you want to actually have such a binding, you must either use the Kivy language, or set up the binding yourself. Something like this:
butt = Button(text="", text_size = self.size, halign = "center", valign = "center")
butt.bind(size=self.resize)
self.add_widget(butt)
def resize(self, butt, new_size):
butt.text_size = new_size
I'm still learning Kivy nuances, & I just can't seem to make my Chessboard widget appear in my splitter. I substituted a working button widget in the splitter for my custom widget, but it's not acting the same. What am I doing wrong?
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.splitter import Splitter
from kivy.graphics import Color, Rectangle
from kivy.uix.image import Image
from kivy.uix.widget import Widget
kivy.require('2.0.0')
class ChessBoardWidget(Widget): # FloatLayout
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1)
self.rect = Rectangle(size=(self.width, self.height), pos=self.pos)
self.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", pos=self.pos,
size_hint=(1, 1), keep_ratio=True, allow_stretch=True))
class SplitterGui(BoxLayout):
def __init__(self, **kwargs):
super(SplitterGui, self).__init__(**kwargs)
self.orientation = 'horizontal'
split1_boxlayout = BoxLayout(orientation='vertical')
split1 = Splitter(sizable_from='bottom', min_size=100, max_size=1000)
chessboard_widget = ChessBoardWidget() # was s1_button = Button(text='s1', size_hint=(1, 1)) WORKED!
s3_button = Button(text='s3', size_hint=(1, 1))
split1.add_widget(chessboard_widget) # was split1.add_widget(s1_button) WORKED!
split1_boxlayout.add_widget(split1)
split1_boxlayout.add_widget(s3_button)
self.add_widget(split1_boxlayout)
split2 = Splitter(sizable_from='left', min_size=100, max_size=1000)
s2_button = Button(text='s2', size_hint=(.1, 1))
split2.add_widget(s2_button)
self.add_widget(split2)
class SplitterTestApp(App):
def build(self):
return SplitterGui() # root
if __name__ == '__main__':
SplitterTestApp().run()
Your ChessBoardWidget is being drawn, it's just not where you expect it. Three things to keep in mind:
Using the Widget class as a container for other widgets eliminates the capabilities of widgets that are intended for use as containers such as size_hint and pos_hint. From the documentation:
A Widget is not a Layout: it will not change the position or the size
of its children. If you want control over positioning or sizing, use a
Layout.
When you reference pos or size of a widget in its __init__() method, you will always get the default values of (0,0) for pos and (100,100) for size.
References to properties (such as pos and size) in a widgets __init__() method use the current values of those properties (see #2 above), and there is no binding to handle changes in those values. So, for example, creating a Rectangle in a widgets __init__() using the widgets pos and size will create an unchanging Rectangle at pos of (0,0) and size of (100,100).
So, a fix for getting the ChessBoardWidget drawn where you expect is to just change the base class of ChessBoardWidget to RelativeLayout. The nice thing about RelativeLayout is that adding a child with the default pos and size_hint will result in the child (in your case, the Image) being drawn with the same position and size as its parent (the ChessBoardWidget). Something like this:
class ChessBoardWidget(RelativeLayout):
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1)
self.rect = Rectangle(size=(self.width, self.height), pos=self.pos)
self.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", keep_ratio=True, allow_stretch=True))
Note that the green Rectangle will still be drawn at the lower left corner of the ChessBoardWidget with size of (100,100). To fix that, you either need to define the Rectangle in a kivy language file or string that gets loaded before the ChessBoardWidget gets created. Or you need to set up bindings that will redraw the Rectangle whenever the ChessBoardWidget gets re-sized.
I believe this is the easiest way to draw the green background, using Builder.load_string():
Builder.load_string('''
<ChessBoardWidget>:
canvas.before:
Color:
rgba: 0,1,0,1
Rectangle:
pos: 0,0
size: self.size
''')
class ChessBoardWidget(RelativeLayout):
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
# with self.canvas.before:
# Color(0, 1, 0, 1)
# self.rect = Rectangle(size=(self.width, self.height), pos=self.pos)
self.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", keep_ratio=True, allow_stretch=True))
I want to scale an image in Widget using size_hint, but it doesn't work. What did I do wrong?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
class Game(Widget):
def __init__(self):
super().__init__(size=[1280, 960])
self.add_widget(Image(source='img.jpg', size_hint=(1, 1)))
class GameApp(App):
def build(self):
gm = Game()
return gm
GameApp().run()
As a result I see a black window with a 100x100 image.
The problem is that you are adding your Image to a class that extends Widget. The Widget class is not intended as a container, and does not handle things like size_hint. So, I suggest changing Game to extend some type of container, like FloatLayout. Here is your code with that change:
class Game(FloatLayout):
def __init__(self):
super().__init__(size=[1280, 960])
self.add_widget(Image(source='img.jpg', size_hint=(1,1), allow_stretch=True))
You may need the allow_stretch=True to allow the Image to stretch the source.
Question
Why size_hint doesn't work for Widget?
Answer
The keyword, size_hint that you have provided, is not applying to the
Widget. It is applying to the Image object. The base class of Image widget is a Widget. The default size of a widget is (100, 100) and default size_hint is (1, 1). Therefore, by providing size_hint=(1, 1) when instantiating an Image widget, you are not changing the default size_hint or size.
When you provided size=[1280, 960] to the super constructor, you have already overrode the default size of the widget of (100, 100) or default size_hint of (1, 1)
Solution
If you add pos=self.pos, size=self.size, allow_stretch=True) to Image, you will get an image displayed.
But there is still a black strip at the bottom.
Snippets
self.add_widget(Image(source='Jupiter.png', pos=self.pos, size=self.size, allow_stretch=True))
Recommended Solution.
You can replace Image object by doing the following:
Use the Widget's canvas to display an image in a Rectangle
Bind the Widget's pos and size to a method to redraw the image
Add import statement, from kivy.graphics import Rectangle
Snippets
from kivy.graphics import Rectangle
class Game(Widget):
def __init__(self):
super().__init__(size=[1280, 960])
with self.canvas:
self.rect = Rectangle(source="Jupiter.png", pos=self.pos, size=self.size)
self.bind(pos=self.redraw, size=self.redraw)
def redraw(self, *args):
self.rect.size = self.size
self.rect.pos = self.pos
Output
How do you disable relative position for a Kivy Popup object? For example, how would you define absolute pos for this example?:
from kivy.uix.popup import Popup
from kivy.uix.label import Label
popup = Popup(title='Test popup', content=Label(text='Hello world'),
size_hint=(None, None),
#pos_hint=None, pos_hint=(None, None), pos_hint={},
size=(200,200), pos=(10, 10))
popup.open()
Notice if the pos_hint attempts are uncommented, it fails either because pos_hint mustn't be null, cannot be a tuple, or simply has no effect (the popup is always centered vertically and horizontally). Notice also that the custom size does work.
Since pos_hint works well you can always do: ("10.0" is the absolute coordinate )
popup = Popup(title='Test popup', content=Label(text='Hello world'),
size_hint=(None, None),
pos_hint={'x': 10.0 / Window.width,
'y':10.0 / Window.height},
size=(200,200), #pos=(10, 10),
)
The only problem here is that you'll have to update the pos_hint on a re-size event
pos_hint should be a dict, not a tuple, so probably pos_hint={} or pos_hint=None will work (but I'm not sure which).
Summary of test application: I am writing a Kivy app with a scrollable view (named Scroller) with many fields (named Field) to look at. These separate fields are really difficult to distinguish on occasion, so I decided to use alternating background colors for each field to help distinguish each other. My testing application uses 20 individual fields each of which alternates between dark grey and darker grey.
Testing trials:
Starting the application, the program looks great. The alternating background appear just fine. Even when I scroll down the application looks fine. However, the application seems to get bizarre when I scroll up on the application. The text scrolls with the application, but the background does not. Even better (sarcastically), the text starts to fade away into their neighbors background. The problem just seems to vanish when I scroll down again (passed the point of the furthest scroll up point).
Brief problem description: The Field's "background color" messes up the application during scrolling up events.
Side note: I have also noticed that the application got a little sluggish after scrolling too much. I am not that familiar with the drawing cycle of Kivy, but blitting backgrounds should not yield an excessive slowdown.
Testing application:
import kivy
kivy.require('1.0.7')
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.graphics import Color, Rectangle
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self):
ScrollView.__init__(self)
self.view = GridLayout(cols = 1, size_hint = (1, None))
self.add_widget(self.view)
self.view.bind(minimum_height = self.view.setter('height'))
for i in range(20):
self.view.add_widget(Field('Test field {}'.format(i),i%2 is 0))
class Field(GridLayout):
def __init__(self, name, bg):
assert isinstance(name, str)
assert isinstance(bg, bool)
self.bg = bg
GridLayout.__init__(self,
rows = 1,
padding = 10,
size = (0, 60),
size_hint = (1, None))
self.add_widget(Label(text = name))
self.add_widget(Button(text = 'Test button',
size = (200, 0),
size_hint = (None, 1)))
self.bind(pos = self.change_background)
self.bind(size = self.change_background)
def change_background(self, *args):
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()
def change_background(self, *args):
self.canvas.before.clear()#<- clear previous instructions
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
You are adding/piling instructions to the canvas every time the Field's position/size changes, without clearing the previous instructions.
You should also look into using kv as for anything more than a small snippet it ends up saving you a lot of time. You can convert you code using kv like so ::
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
<Scroller>
# root is Scroller here
# create a new ObjectProperty in kv that holds the ref to Gridlayout
# so you can access the instance in python code
view: glayout
GridLayout:
id: glayout
cols: 1
size_hint: (1, None)
height: self.minimum_height
<Field>
canvas.before:
Color:
rgba: (0.2, 0.2, 0.2, 1) if self.bg else (0.1, 0.1, 0.1, 1)
Rectangle:
# binding properties is done implicitly and instructions aren't
# piled up while doing that.
pos: self.pos
# self here refers to Field as `self` is supposed to refer to the
# Widget not the drawing instruction
size: self.size
rows: 1
padding: 10
size: (0, 60)
size_hint: (1, None)
Label:
text: root.name
Button:
text: 'test button'
size: (200, 0)
size_hint: (None, 1)
''')
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self, **kwargs):
super(Scroller, self).__init__(**kwargs)
for i in range(20):
# access self.view that was set in kv
self.view.add_widget(
Field(
name = 'Test field {}'.format(i),
bg = i%2 is 0))
class Field(GridLayout):
# use kivy's Properties so it becomes easier to observe and apply changes
# as a plus these can also be directly used in kv. As a advantage of using this now
# you can change name and bg dynamically and the changes should be reflected on
# screen
name = ObjectProperty('Test field uninitialized')
bg = BooleanProperty(False)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()