Kivy custom widget behaving differently when used in box layout - python-3.x

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.

Related

how to display a textlabel as well as draw a circle on clicking a pushbutton "ADD"

I have read all related article of multiple slots with one signal but I am unable to display at the time of drawing a circle both trigerred by a push button "ADD". I can display the text label near the circle before clicking the button but i want it to dislay only after clicking the button. Please Help. Also, i want the text label to be near circle and can be modified anytime on clicking
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QWidget
from PyQt5 import QtGui
from PyQt5.QtCore import QRect,Qt
from PyQt5.QtGui import QPainter,QBrush, QPen
from PyQt5 import QtCore
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="layout management"
left=500
top=200
width=500
height=400
iconName="fosseeicon.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_circle = False
self.windowcomponents()
self.initUI()
self.show()
def initUI(self):
if self.should_paint_circle:
self.label=QtWidgets.QLabel(self)
self.label.setText('<h2>circle<h2>')
def windowcomponents(self):
button=QPushButton("Add", self)
button.setGeometry(QRect(0, 0, 50, 28))
button.setIcon(QtGui.QIcon("addbutton.png"))
button.setToolTip("<h3>This is for creating random circles<h3>")
button.clicked.connect(self.paintcircle)
button=QPushButton("Generate Report", self)
button.setGeometry(QRect(49,0,150,28))
button.setIcon(QtGui.QIcon("generatereport.png"))
button.setToolTip("This is for generating pdf report of connection between two circles")
button=QPushButton("Save", self)
button.setGeometry(QRect(199,0,120,28))
button.setIcon(QtGui.QIcon("saveicon.png"))
button.setToolTip("This is for saving an image of canvas area")
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_circle:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawEllipse(100, 100, 100, 100)
self.initUI()
self.label.move(60,100)
def paintcircle(self, painter):
self.should_paint_circle = True
self.update()
app = QApplication(sys.argv)
circle=Window()
circle.show()
sys.exit(app.exec_())
Widgets that are created with a parent, outside their __init__ (or their parent's), but not added to a layout, have to be explicitly shown; you're missing this:
self.label.show()
Besides that, you MUST NOT create new widgets within the paintEvent.
Painting is something that happens often, usually in the following situations (which happen very often:
when the widget is shown the first time
whenever the widget is hidden and shown again (for example, after minimizing and restoring the window)
whenever the mouse enters or exits it and/or its children
when the widget or any of its parents are resized
when a new children is shown
The result is that if you add a widget for each paint event, you'll probably end up with dozens (if not hundreds or thousands) of widgets, and, most importantly if you also show it, it will cause an infinite recursion.
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="layout management"
left=500
top=200
width=500
height=400
iconName="fosseeicon.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_circle = False
self.windowcomponents()
self.label = QtWidgets.QLabel(self)
self.label.hide()
# ...
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_circle:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawEllipse(100, 100, 100, 100)
def paintcircle(self, painter):
self.should_paint_circle = True
self.label.setText('<h2>circle<h2>')
self.label.move(60,100)
self.label.show()
self.update()
That said, based on this question and the previous one, I suggest you to study the documentation more carefully, especially what is related to the QMainWindow, the Layout management, painting in Qt and the related QPainter documentation.

How to make an image "float" over the buttons?

I'm starting to learn Kivy.
The code below generates a 10x10 button grid:
from kivy.uix.gridlayout import GridLayout
from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
def build(self):
layout = GridLayout(cols=10)
for i in range (1, 101):
layout.add_widget(Button(text=str(i)))
return layout
MyApp().run()
Now, I'd like to add a png image to an independent layer, which randomly "wander" over these buttons, independently.
Then the user should click on the button on which the image is going, as in a game.
That is, the image should not be clickable and will be shown only visually over the buttons, meanwhile, the buttons should respond perfectly as if there was no image over them.
How to do this?
You can draw the image in the Canvas of the GridLayout using a Rectangle. And the position can be updated using Clock_schedule_interval(). Like this:
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.uix.gridlayout import GridLayout
from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
def build(self):
layout = GridLayout(cols=10)
with layout.canvas.after:
Color(1,1,1,0.5) # if you want to see through the image
self.bg = Rectangle(source='KQxab.png') # source is the image
for i in range (1, 101):
layout.add_widget(Button(text=str(i)))
Clock.schedule_interval(self.update_bg, 1.0/24.0) # schedule the image movement
return layout
def update_bg(self, dt):
self.bg.pos = (self.bg.pos[0] + 1, self.bg.pos[1] + 1)
MyApp().run()
This code just moves the image in a straight line, but you can improve that.

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))

Pyqt4: How to correct QGraphicsItem position?

I'm trying to draw a path on a QGraphicsView. However, the position seems not right. The first point(red) is (0,0), which is supposed to be at the top-left corner. How do I move the drawing to the right position?
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import QPointF as qpf
import sys
from PyQt4.QtGui import QPainterPath
data= [qpf(0,0),qpf(40,30),qpf(30,60),qpf(70,90),qpf(20,120),qpf(60,150)]
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = View(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
class View(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
item = QtGui.QGraphicsRectItem(data[0].x()-2,data[0].y()-2,4,4)
item.setBrush(QtCore.Qt.red)
self.scene().addItem(item)
self.path = path = QPainterPath(data[0])
for d in data[1:]:
path.lineTo(d)
item = QtGui.QGraphicsPathItem(path)
self.scene().addItem(item)
def mouseReleaseEvent(self, event):
pos = event.pos()
rect = QtCore.QRectF(pos.x()-2, pos.y()-2,4,4)
item = QtGui.QGraphicsRectItem(rect)
self.scene().addItem(item)
if self.path.intersects(rect):
print 'on line'
else:
print 'Not belong to line (%d, %d)' % (pos.x(), pos.y())
QtGui.QGraphicsView.mouseReleaseEvent(self, event)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(400, 400)
window.show()
sys.exit(app.exec_())
This behavior is because you draw using QGraphicsScene and QGraphicsView. This is because view and scene have some automatism, which are very convenient, normally. The scene coordinated can be completely different from the view (which is in pixels).
From the docs: "After you call show(), the view will by default scroll to the center of the scene and display any items that are visible at this point." show() is called implicitly in your case, I believe after you add an item to the scene.
I can think of two possibilities to get what you want:
1) change the view onto your scene, so that the scene-coordinate (0, 0) is in the upper left corner of your view.
2) Do NOT use QGraphicsScene and QGraphicsView, but just draw on a widget from its paint event as shown for example here. This means your dimensions are all in pixels, i.e. the coordinates of your points are pixels. And no automatism that might confuse you is done.
You would want to use self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) in your view class constructor, that will make 0,0 of the scene coordinates the same as 0,0 in view coordinates.AlignmentFlag
I would also suggest pos = self.mapToScene(event.pos()) in the mouseReleaseEvent. that way if you zoom in or scroll the scene, the click will happen in the right place in the view. QGraphicsView.mapFromScene

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.

Resources