Goal: Within a set of buttons, when one is pressed, the all of the buttons colors are reset to its default color, while the button that was pressed changes color. (so there will only be one button with a different color)
setup .py file
class Profiles(Screen):
def __init__(self, **kwargs):
super(Profiles, self).__init__(**kwargs)
Clock.schedule_once(self.create_profs, -1)
def create_profs(self, *args):
#create buttons and screens for each item in acc_abr
for i in acc_abr:
#AccountOne inherits from kivy's Screen class. Store new screen into a screen manager named "am"
self.ids["am"].add_widget(AccountOne(name=i))
#AccButt inherits from kivy's Button class
self.ids["acc_butt_box"].add_widget(AccButt(text=i, id=i, on_press=partial(self.switching_function, i)))
#change current screen
def switching_function(self, screename, *args):
self.ids["am"].current = screename
The buttons are created within a for loop, and given an id based on the items in acc_abr
What I need to work on is the contents of AccButt which looks like:
class AccButt(Button):
def __init__(self, **kwargs):
super(Button,self).__init__(**kwargs)
self.bind(on_press = self.change_butt_color)
def change_butt_color(self, *args):
self.background_color = 0,0,0,0
First of all, I have access to all the buttons id name since its ids are based on the acc_abr list.
So my first idea was to create a for loop that changed all the buttons color to its default before changing it's selfs color. which looks like this:
def change_butt_color(self, *args):
for butts in acc_abr:
self.parent.ids[butts].background_color = 1,1,1,1
self.background_color = 0,0,0,0
The problem is, I'm getting a key error for whatever is in butts. So I'm having a communication issue with the buttons.
I'm probably getting the wrong path as to where the other buttons are, or I did not properly assign an id to the Buttons (AccButt) or timing is messing things up.
SOLUTION
The path that contains the buttons siblings is self.parent.children. With this information, you can change the background_color of all the buttons.
EXAMPLE
def change_butt_color(self, *args):
for child in self.parent.children:
child.background_color = 1,1,1,1
self.background_color = 0,0,0,0
notice how the buttons aren't being searched for by ids. Instead, self.parent.children is used to find the button's parents children, which is, its self of course and the siblings of its self (self).
Solution
Use on_touch_down, reset background_color, and check for collide_point
Widget Touch Event » on_touch_down
on_touch_down(touch)
Receive a touch down event.
Parameters:
touch: MotionEvent class
Touch received. The touch is in parent coordinates. See
relativelayout for a discussion on coordinate systems.
Returns: bool If True, the dispatching of the touch event will stop. If False, the event will continue to be dispatched to the rest
of the widget tree.
Input Management » Touch event basics
By default, touch events are dispatched to all currently displayed
widgets. This means widgets receive the touch event whether it occurs
within their physical area or not.
...
In order to provide the maximum flexibility, Kivy dispatches the
events to all the widgets and lets them decide how to react to them.
If you only want to respond to touch events inside the widget, you
simply check:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Widget touch event bubbling
When you catch touch events between multiple widgets, you often need
to be aware of the order in which these events are propagated. In
Kivy, events bubble up from the first child upwards through the other
children. If a widget has children, the event is passed through its
children before being passed on to the widget after it.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.lang import Builder
class CreateButton(Button):
def on_touch_down(self, touch):
if set(self.background_color) & set([0, 0, 0, 0]) == {0}:
self.background_color = [1, 1, 1, 1] # default texture is grey
if self.collide_point(*touch.pos):
self.background_color = [0, 0, 0, 0] # black color
class OnTouchDownDemo(GridLayout):
def __init__(self, **kwargs):
super(OnTouchDownDemo, self).__init__(**kwargs)
self.build_board()
def build_board(self):
# make 9 buttons in a grid
for i in range(0, 9):
button = CreateButton(id=str(i))
self.add_widget(button)
Builder.load_file('main1.kv')
class OnTouchDownApp(App):
def build(self):
return OnTouchDownDemo()
if __name__ == "__main__":
OnTouchDownApp().run()
main.kv
#:kivy 1.11.0
<CreateButton>:
font_size: 50
<OnTouchDownDemo>:
rows: 3
cols: 3
row_force_default: True
row_default_height: 150
col_force_default: True
col_default_width: 150
padding: [10]
spacing: [10]
Output
Related
How do I alter my code to show Loading in the center of MainWindow relative to wherever MainWindow is at on the screen?
What it is doing now is placing the Loading animation on the top left corner, ignoring the position of the MainWindow.
Even if I add geomtery to the MainWindow, the result will be the same.
Here is the code, both of the classes has been imported from a py.file:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.loading = Loading(parent=self)
self.ui.check.clicked.connect(self.show_animation)
self.show()
def show_animation(self):
self.loading.show()
class Loading(QWidget):
def __init__(self, parent=None):
self.parent = parent
QWidget.__init__(self)
self.ui = Ui_loading()
self.ui.setupUi(self)
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
self.label_animation = QLabel(self)
self.label_animation.setGeometry(QtCore.QRect(25, 25, 256, 256))
self.movie = QMovie('giphy1.gif')
self.label_animation.setMovie(self.movie)
self.movie.start()
geo = self.geometry()
geo.moveCenter(self.parent.geometry().center())
self.setGeometry(geo)
Your code doesn't work because you're already setting the geometry when you create the Loading instance, and that's too soon: not only when you click the button it's possible that the parent window has been moved or resized, but you're creating the instance when it's not been even shown yet, so it still has a default geometry (usually 640x480 unless it has minimum or maximum size constraints based on its contents).
In order to center the widget on the parent, you have to consider the geometries when it is going to be shown.
class Loading(QWidget):
# ...
def showEvent(self, event):
if not event.spontaneous():
geo = self.geometry()
geo.moveCenter(self.parent.geometry().center())
QtCore.QTimer.singleShot(0, lambda: self.setGeometry(geo))
The check on event.spontaneous() is done in order to only move the window when it's explicitly shown (using show() or setVisible(True)), otherwise it would happen in any case, for example when restoring the window after it's been minimized.
The delayed setGeometry() is required because in certain cases (most commonly, on Linux) some amount of time is required between the request Qt makes to the underlying window system and the moment the window is actually mapped on the screen.
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.
This question already has an answer here:
how to accept/ignore QKeyEvent
(1 answer)
Closed 3 years ago.
I have two PyQt5 widgets and both need keyboard input. Initially, one widget has the setFocusPolicy set to QtCore.Qt.StrongFocus, but when both widgets has this property activated, both of them get the input. I would like to initially set the input in one of them and if the user clicks in the other widget, the focus would be changed to the clicked widget or vice versa.
MRE:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QOpenGLWidget, QWidget
import sys
class Renderizador(QOpenGLWidget):
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("OpenGL")
super().keyPressEvent(event)
class Diedrico(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
def paintEvent(self, event):
qp = QPainter(self)
qp.setPen(QPen(Qt.black))
qp.drawRect(0, 0, 1000, 1000) # Marco
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("Widget")
super().keyPressEvent(event)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(1500, 1015)
self.setFixedSize(1500, 1015)
self.statusBar().setSizeGripEnabled(False)
self.widget_central = QtWidgets.QWidget(self)
self.Renderizador = Renderizador(self.widget_central)
self.Renderizador.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
self.Renderizador.setFocusPolicy(QtCore.Qt.StrongFocus)
visor = QtWidgets.QWidget(self.widget_central)
visor.setGeometry(1010, 510, 470, 460)
self.scene = QtWidgets.QGraphicsScene(visor)
self.view = QtWidgets.QGraphicsView(self.scene)
self.diedrico = Diedrico(visor)
self.diedrico.setFixedSize(470, 460)
self.view.setFocusPolicy(QtCore.Qt.StrongFocus)
self.scene.addWidget(self.diedrico)
self.setCentralWidget(self.widget_central)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("Ui")
super().keyPressEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
ui = UiVentana()
ui.show()
sys.exit(app.exec_())
Based on your code, I'm not getting key events from the Diedrico widget, but only from the Renderizador and UiVentana instancies; with your implementation it seems very unlikely that you get key events from both the Renderizador and Diedrico widgets, since their parent hierarchy is completely different and they actually are members of different "physical" windows.
If you really get the Widget and OpenGL outputs from a single key event, it might be a bug, but, frankly, I doubt that, and that's mostly because there are some issues in your implementation, mostly due to confusing parent/child relationship.
If that's not the case (meaning that you're getting the key events from Renderizador and UiVentana), the explanation is simple: a QOpenGLWidget, as any basic QWidget class, doesn't process nor "consume" a key event; as soon as you call the base implementation with super() the event is propagated to its parents (UiVentana, in your case). If you want to stop the event propagation, just return without calling the base implementation of keyPressEvent.
Finally, some notes about your example.
When you want to add a widget to a scene, it must have a parent that is already embedded in the scene or it shouldn't have a parent at all (as in a top level widget). In your code you created the Diedrico widget setting its parent to a child of the "widget_central", which at that point is a widget without no parent (meaning that it would be a top level widget, as in a "window"): no matter what you do afterwards (setting it as the central widget), the topmost parent has not been embedded, and you can't add any of its child to a scene.
Qt itself warns about this when executing your code:
StdErr: QGraphicsProxyWidget::setWidget: cannot embed widget 0x911ae78 which is not a toplevel widget, and is not a child of an embedded widget
Then, you created the view, but you never actually add it to the main window or any of its children. You can see the Diedrico instance only because of the aforementioned problem: the widget is added to the main widget because of the parent set in the class initialization, but it's never added to the scene.
Be very careful when initializing widgets and setting parents, expecially when you're going to embed them into a QGraphicsScene: QWidgets added to a scene are actually QGraphicsProxyWidgets, not "actual" widgets, and some special care is required when dealing with them.
I am new to PyQt perhaps thats why facing this issue. I am trying to make a component inserter for excel sheets. For this purpose I am using QT for interface and using Qlabels within parent class of QMainWindow. On the basis of item selected from Qlist Widget, few Qlabels have to change on mainwindow dynamically Component inserter
As can be seen in above picture, the labels like WCAP-; Part Number and all below needs to change dynamically when the selected items change(when select button is clicked). But what happening is if I choose a different item from list, the previous Label stays and the new label is overlapping it as can be seen from picture below showing overlapping of labels
The code below shows that whenever button "Select" is pressed", label2 (Qlabel2) is formed, how can i delete the previous label whenever select button is pressed so that new Label dynamically replaces the old label.
Thanks a lot in advance.
def Display(self):
self.close()
label1 = QtGui.QLabel("Select the sheet",self)
label1.move(0,15)
self.listwidget = QtGui.QListWidget(self)
self.listwidget.move(0,40)
self.listwidget.resize(150,150)
for i in range(len(self.sheetnames)):
self.listwidget.addItem("%s"%self.sheetnames[i])
btn = QtGui.QPushButton('Select',self)
btn.resize(50,50)
btn.move(170,40)
btn.clicked.connect(self.Selected)
self.show()
def Selected(self):
self.close()
selecteditem = self.listwidget.currentItem().text()
self.sheetindex = self.sheetnames.index(selecteditem)
print self.sheetindex
aa = self.loadsheet.sheet_by_name(selecteditem)
global label2
label2 = QtGui.QLabel("",self)
label2.setText(selecteditem)
label2.move(0,190)
self.show()
self.InputParameters(aa)
You see a new QLabel because you create a new one every time you call Selected. I would initiate the UI at the creation of the widget (in the __init__ method):
def __init__(self):
self.label2 = QtGui.QLabel("",self)
And only update the text of the Qlabel when Selected is executed:
def Selected(self):
self.label2.setText(selecteditem)
About reinitializing all labels with an unknown number of labels and removing the old ones, you might want to look at QLabel.setParent(None). I wrote you a little example:
from PyQt4 import QtGui, QtCore
import sys
class test(QtGui.QWidget):
def __init__(self,parent=None):
self.widget=QtGui.QWidget.__init__(self, parent)
# Button to add labels
self.btnAdd = QtGui.QPushButton('Add')
self.btnAdd.connect(self.btnAdd, QtCore.SIGNAL('clicked()'),self.btnAddPressed)
# Button to remove labels
self.btnRemove = QtGui.QPushButton('Remove')
self.btnRemove.connect(self.btnRemove, QtCore.SIGNAL('clicked()'), self.btnRemovePressed)
# List to keep track of labels
self.labels=[]
# Layout
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.btnAdd)
self.hbox.addWidget(self.btnRemove)
self.setLayout(self.hbox)
self.show()
def btnAddPressed(self):
"""Adds a new label."""
self.labels.append(QtGui.QLabel("lbl"+str(len(self.labels)+1), self))
self.hbox.addWidget(self.labels[-1])
def btnRemovePressed(self):
"""Removes last label."""
self.labels[-1].setParent(None)
self.labels.pop(-1)
def main():
#Creating application
app = QtGui.QApplication(sys.argv)
main_win = test()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I want to create a PyQt combobox with a fixed title. More specific this means that I want to have a dropdown menu from which the user can select but the dropdown button is always labeled the same. So for example I want to create an option for the user to specify where the legend of a plot is drawn. The button for this should always be labled "Legend" but when you click on it, it opens a dropdown menu with the placing options such as "upper right", "upper left", "top", etc. Once the user selected an option the legend is updated but the button still sais "Legend".
I have this so far:
self.fnLegendButton = QtGui.QComboBox()
self.fnLegendButton.addItems('Upper right,Lower right,Upper left,Lower left,Top,Disable'.split(','))
self.fnLegendButton.setCurrentIndex(0)
self.fnLegendButton.setToolTip('Select the legend position.')
self.fnLegendButton.currentIndexChanged.connect( <positioning function> )
self.fnLegendButton.setMaximumWidth(60)
Here's a working example:
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.fnLegendButton = QtGui.QComboBox(self)
self.fnLegendButton.addItems(
'Legend,Upper right,Lower right,Upper left,Lower left,Top,Disable'.split(','))
self.fnLegendButton.setCurrentIndex(0)
self.fnLegendButton.setToolTip('Select the legend position.')
self.fnLegendButton.currentIndexChanged[
str].connect(self.avoid_db_change)
self.fnLegendButton.setMaximumWidth(100)
self.fnLegendButton.move(50, 50)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('QtGui.QCheckBox')
self.show()
def avoid_db_change(self, text):
print("Processing {0} item".format(text))
self.fnLegendButton.blockSignals(True)
self.fnLegendButton.setCurrentIndex(0)
self.fnLegendButton.blockSignals(False)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The important bits of this code are inside the avoid_db_change, that function is the one used to keep the "Legend" text no matter which item you've pressed. Now, you don't want to fire that function again when self.fnLegendButton.setCurrentIndex(0) is executed, so to avoid that, you surround it by a couple of blockSignals methods. Just try to comment the blockSignals methods and you'll understand what this means.