When I use add_widget, and try to access it by using ids, I get a key error.
def create_rss(self, *args):
for rss in "food", "wood", "stone", "iron", "gold":
self.ids["res"].add_widget(ToggleButton(id=rss, text=rss))
self.ids["res"].ids[rss].state = "down"
I expected to be able to change the state of the toggle button since it was being done before creating it with add_widget.
When trying to print the parent of the toggle button, I get nothing. So maybe add_widget its not making the togglebuttons the children of "res"?
Question
how can I access the button out side of the for loop?
Solution
There are two methods for the solution.
Method 1 - Create own ids dictionary
This method involves creating our own ids dictionary type property. The advantages of this method are random and sequential access.
Snippets
from kivy.properties import DictProperty
...
class class-name(...):
my_ids = DictProperty({})
def create_rss(self, *args):
for rss in "food", "wood", "stone", "iron", "gold":
toggle_button = ToggleButton(id=rss, text=rss)
self.my_ids[rss] = toggle_button
self.ids["res"].add_widget(toggle_button)
toggle_button.state = "down"
def access_using_my_ids(self):
print(f"\nsequential access:")
for key, value in self.my_ids.items():
print(f"key={key}, value={value}, text={value.text}")
print(f"\nrandom access:")
print(f"object={self.my_ids['stone']}, text={self.my_ids['stone'].text}")
Method 2 - Kivy Widget Tree
This method uses Kivy Widget Tree and a for loop to access the children. The disadvantages of this method is sequential access only.
Snippets
for child in reversed(self.ids.res.children):
if isinstance(child, ToggleButton):
print(f"ToggleButton.text={child.text}")
Kivy ids - created in Python script
Kivy ids created in Python script are not stored in self.ids dictionary type property. Therefore, one will get an error when trying to access it.
Solution
Assign it to a variable.
Snippets
def create_rss(self, *args):
for rss in "food", "wood", "stone", "iron", "gold":
toggle_button = ToggleButton(id=rss, text=rss)
self.ids["res"].add_widget(toggle_button)
toggle_button.state = "down"
Related
I am using Flet module to create a simple GUI.
I want to access the custom data on the button. However, using the clicked method, it seems that the event handler of the button using the data property of it, returns empty string. Therefore, the method does not print anything.
Using flet 0.1.62, Python 3.10
import flet
from flet import UserControl, TextButton, Row, Page
class Test(UserControl):
def __init__(self):
super().__init__()
self.text_button = TextButton(text='test', data='test', on_click=self.clicked)
self.view = Row(controls=[self.text_button])
def clicked(self, e):
print(e.data, ' was clicked')
def build(self):
return self.view
def main(page: Page):
t = Test()
page.add(t)
app = flet.app(target=main)
It seems in the flet's tutorials for the Calculor example, that you can have multiple buttons using the same method for on_click argument.
this is a part from the example's code where it refers to Handling events:
https://flet.dev/docs/tutorials/python-calculator/
Now let's make the calculator do its job. We will be using the same event handler for all the buttons and use data property to differentiate between the actions depending on the button clicked. For each ElevatedButton control, specify on_click=self.button_clicked event and set data property equal to button's text, for example:
ft.ElevatedButton(
text="AC",
bgcolor=ft.colors.BLUE_GREY_100,
color=ft.colors.BLACK,
expand=1,
on_click=self.button_clicked,
data="AC",
)
Below is on_click event handler that will reset the Text value when "AC" button is clicked:
def button_clicked(self, e):
if e.data == "AC":
self.result.value = "0"
With similar approach, specify on_click event and data property for each button and add expected action to the button_clicked event handler depending on e.data value.
I am looking for a way to get the same result using the TextButton,
I'd appreciate your help!
I found the answer to my own question. Apparently in the version of the flet that I am using, the data property has to be looked up through the control instance attribute.
Therefore
I tried this in the code from my question:
def clicked(self, e):
print(e.control.data, ' was clicked')
instead of:
def clicked(self, e):
print(e.data, ' was clicked')
I tried it out of curiosity and it worked.
How to get attributes (not methods) of a class in Python
Hello everyone!
Basically, I'm looking to retrieve all attributes of a class without having access to self (To create a diagram that includes the attributes).
For now I don't have any code, I just have an 'obj' variable which contains the class.
I would therefore like to know, how, via "obj" I can retrieve all the attributes including those which are in functions.
Thanking you in advance,
VitriSnake
You can call __dict__ on your class and it will return a dictionary containing all attributes with their values set by the constructor.
class Tree:
def __init__(self):
self.trunk_size = 20
self.leaf_colour = "Orange"
if "__main__" == __name__:
tree = Tree()
print(tree.__dict__)
Result: {'trunk_size': 20, 'leaf_colour': 'Orange'}
If you just want the values call tree.__dict__.values() and for your keys or rather attribute variable names do tree.__dict__.keys().
I am trying to display JSON metadata in PyQt6/PySide6 QTreeView. I want to generalize for the case where multiple persistent windows (QtWidgets) pop up if my JSON metadata list has a length greater than 1.
for example:
def openTreeWidget(app, jmd):
view = QTreeView()
model = JsonModel()
view.setModel(model)
model.load(jmd)
app.w = view # app = `self` of a QMainWindow instance
app.w.show()
for md in jsonMetadataList:
openTreeWidget(self, md)
where TreeItem and JsonModel are based on: https://doc.qt.io/qtforpython/tutorials/basictutorial/treewidget.html
I stole the app.w idea from: https://www.pythonguis.com/tutorials/pyqt6-creating-multiple-windows/
In the current case, all pop ups (except one) close after momentarily opening. Only the last item in jsonMetadataList remains displayed in a persistent window. I believe that somehow I am not keeping the reference to previous windows and reopening/rewriting data on a single widget. How can I keep the reference?
Also, I am very new to PyQt/PySide so I'm just doing things no matter how ugly they look at the moment. This will, of course, get better with time :);
I managed to bodge it up by not destroying the reference. Here's how I did it.
def openTreeWidget(app, jmd):
"""
app is the parent QWidget (here, a QMainWindow)
jmd is JSON metadata stored in a string
"""
view = QTreeView()
model = JsonModel()
view.setModel(model)
model.load(jmd)
return view
# `self` of a QMainWindow instance
self.temp = [None]*len(jsonMetadataList) # a list storing individual handles for all JSON metadata entries
for ii, md in enumerate(jsonMetadataList):
self.temp[ii] = openTreeWidget(self, md) # get the reference for QTreeView and store it in temp[ii]
self.temp[ii].show() # show the ii-th metadata in QTreeView
Better ideas are still welcome :)
I am confused how to use setData, setItemData and setItem method of QStandardItemModel, these method seem have the same effect, i wonder to know which method should i choose to use will be best?
class DemoD(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
table = QTableView()
model = QStandardItemModel(4, 2)
table.setModel(model)
# delegate = SpinBoxDelegate()
# table.setItemDelegate(delegate)
for row in range(4):
for col in range(2):
item = QStandardItem('china')
model.setItem(row, col, item)
index = model.index(row, col)
value = QVariant((row + 1)*(col + 1))
model.setData(index, value)
model.setItemData(index, {1: 'a', 2: 'b'})
self.setCentralWidget(table)
self.resize(400, 300)
app = QApplication([])
demo = DemoD()
demo.show()
app.exec()
If you want to understand the concepts of a Qt model you should read the following guides:
Model/View Programming
Model/View Tutorial
Why Qt is misusing model/view terminology?
Documentation of each method: setData(), setItemData() and setItem().
Previous concepts:
QStandarItemModel: It is a class that inherits from QAbstractItemModel that allows to store any type of information unlike QAbstractItemModel that only defines the behavior.
Considering that you have read the previous links carefully, we will try to explain the difference between the different methods that you indicate:
setData(): Every Qt model inherits from QAbstractItemModel so this class defines the general behavior, in this case it is defined that the setData() model is responsible for modifying the information of a role associated with a QModelIndex. In other words, it is the generic method that you have to implement if you want to implement an editable model, for example QStringListModel is not an editable model so it does not implement it but in the case of QStandardItemModel it is editable so you can modify the information of the model through of that method.
setItem(): QStandardItem is a concept of QStandardItemModel that is conceptually similar to QModelIndex. This element allows you to easily interact with the QModelIndex. If a QStandardItem is not associated with a model it will only store the information, at the time a model is assigned all information is passed to the model, and the model informs you of any changes that can be made by other methods such as setData. An equivalent to setData of the model is the setData method of QStandardItem but the latter does not need to provide the QModelIndex since that information is internally available or can be obtained when a model is established.
For example:
it.setText("foo")
it.setTextAlignment(QtCore.Qt.AlignCenter)
is equivalent to
it.model().setData(it.index(), "foo", QtCore.Qt.DisplayRole)
it.model().setData(it.index(), QtCore.Qt.AlignCenter, QtCore.Qt.TextAlignmentRole)
As you can see, QStandardItem allows you to modify the information of the item in a simple way, and in a simple way you can say that it is an item of the model.
setItemData(): It is a method that allows you to modify the information of several roles associated to a QModelIndex by checking if the roles are valid, in general if you use an invalid model the method will not update the information but in the case of QStandardItemModel that handles the generic information it is established that everything Role is valid for what will always work.
In the case of QStandardItemModel the following codes are equivalent:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
model = QtGui.QStandardItemModel(1, 1)
it = QtGui.QStandardItem()
model.setItem(0, 0, it)
# The following lines modify the text shown
# to be related to the Qt::DisplayRole role and
# the QModelIndex associated with the QStandardItem:
it.setText("foo")
it.setData("foo", QtCore.Qt.DisplayRole)
model.setData(it.index(), "foo", QtCore.Qt.DisplayRole)
model.setItemData(it.index(), {QtCore.Qt.DisplayRole: "foo"})
# The same as the previous lines but in the case of
# the background and the text colors of the item.
it.setForeground(QtGui.QColor("red"))
it.setBackground(QtGui.QColor("blue"))
it.setData(QtGui.QColor("red"), QtCore.Qt.ForegroundRole)
it.setData(QtGui.QColor("blue"), QtCore.Qt.BackgroundRole)
model.setData(it.index(), QtGui.QColor("red"), QtCore.Qt.ForegroundRole)
model.setData(it.index(), QtGui.QColor("blue"), QtCore.Qt.BackgroundRole)
model.setItemData(
it.index(),
{
QtCore.Qt.ForegroundRole: QtGui.QColor("red"),
QtCore.Qt.BackgroundRole: QtGui.QColor("blue"),
},
)
Both setData and setItemData are very similar.
What you have to understand is that Qt models use roles to assign certain data to each "index". This means that each index (a reference to a model row and column, possibly including a parent if the model supports trees) can have different data attached to it. The most commonly used data role is the "DisplayRole", which is what an item view usually shows as text; but other data is usually implemented (see ItemDataRole, which helps an item view to correctly show the model data to the user.
The most important difference between setData and setItemData is the mapping. What you're doing does not work as the keywords you're using are not recognized as usable roles.
In your example ({1: 'a', 2: 'b'}), 1 maps to DecorationRole (which is used from item views to show a decoration - an icon) and 2 maps to EditRole, which is used whenever the user wants to edit the contents of that item, something that can differ from what's displayed (think about entering a date in a short form such as "10/11", that can be an actual date that is finally shown as "november 10 2019").
Finally, setItem is a special function of QStandardItemModel that creates a new item (or overwrites an existing one) with the new provided QStandardItem object.
I'm providing a test example that will better show what happens in all three situations.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.table = QtWidgets.QTableView()
layout.addWidget(self.table)
# hide headers, we're not interested
self.table.horizontalHeader().setVisible(False)
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
self.model = QtGui.QStandardItemModel()
self.table.setModel(self.model)
for item in range(1, 6):
item = QtGui.QStandardItem('item {}'.format(item))
self.model.appendRow(item)
toolLayout = QtWidgets.QHBoxLayout()
layout.addLayout(toolLayout)
self.itemTextEdit = QtWidgets.QLineEdit('text')
toolLayout.addWidget(self.itemTextEdit)
self.itemSetTextButton = QtWidgets.QPushButton('Set text')
toolLayout.addWidget(self.itemSetTextButton)
self.itemSetTextButton.clicked.connect(self.setText)
toolLayout.addSpacing(5)
self.itemAlignCombo = QtWidgets.QComboBox()
toolLayout.addWidget(self.itemAlignCombo)
for alignText in ('Left', 'Center', 'Right'):
alignment = QtCore.Qt.AlignVCenter | getattr(QtCore.Qt, 'Align{}'.format(alignText))
self.itemAlignCombo.addItem(alignText, alignment)
self.itemSetAlignButton = QtWidgets.QPushButton('Set alignment')
toolLayout.addWidget(self.itemSetAlignButton)
self.itemSetAlignButton.clicked.connect(self.setAlignment)
self.table.setCurrentIndex(self.model.index(0, 0))
toolLayout.addSpacing(5)
self.setDataButton = QtWidgets.QPushButton('SetItemData()')
toolLayout.addWidget(self.setDataButton)
self.setDataButton.clicked.connect(self.setItemData)
setItemLayout = QtWidgets.QHBoxLayout()
layout.addLayout(setItemLayout)
self.itemRowSpin = QtWidgets.QSpinBox()
setItemLayout.addWidget(self.itemRowSpin)
self.itemRowSpin.setRange(1, self.model.rowCount() + 1)
self.itemRowSpin.setValue(self.itemRowSpin.maximum())
self.setItemButton = QtWidgets.QPushButton('SetItem()')
setItemLayout.addWidget(self.setItemButton)
self.setItemButton.clicked.connect(self.setItem)
def setText(self):
# set the text of the current item
index = self.table.currentIndex()
self.model.setData(index, self.itemTextEdit.text())
def setAlignment(self):
# set the alignment of the current item
index = self.table.currentIndex()
self.model.setData(index, self.itemAlignCombo.currentData(), QtCore.Qt.TextAlignmentRole)
def setItemData(self):
# set *both* text and alignment of the current item
index = self.table.currentIndex()
self.model.setItemData(index, {
QtCore.Qt.DisplayRole: self.itemTextEdit.text(),
QtCore.Qt.TextAlignmentRole: self.itemAlignCombo.currentData()
})
def setItem(self):
# set a new item for the selected row with the selected text and alignment
item = QtGui.QStandardItem()
item.setText(self.itemTextEdit.text())
item.setTextAlignment(QtCore.Qt.Alignment(self.itemAlignCombo.currentData()))
self.model.setItem(self.itemRowSpin.value() - 1, 0, item)
self.itemRowSpin.setMaximum(self.model.rowCount() + 1)
self.itemRowSpin.setValue(self.itemRowSpin.maximum())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I am working on a project that involves creating many instances of Tkinter Labels and Entry widgets that will always be aligned next to one another. To try and save myself time, I created a custom class that I am showing below:
class labelEntry(Label,Entry):
def __init__(self,parent,label,row,column,bg_color):
Label.__init__(self,parent)
self['text']=label
self['justify']='right'
self['bg']=bg_color
self.grid(row=row,column=column, sticky=E)
Entry.__init__(self,parent)
self['width']="10"
self.grid(row=row,column=column+1)
This creates the configuration I want and is easy enough to arrange (I have them stored in a frame). The problem is I don't know how to access the Entry widgets that I have created as they are part of this new class.
I have a desire to read and delete the entries from the entry widgets. My best guess at clearing them was with this button that was being fed into the same frame:
class clearAllEntry(Button):
def clearAll(self,targetFrame):
targetFrame.labelEntry.Entry.delete(0,END)
def __init__(self,parent,targetFrame):
Button.__init__(self,parent,text='Clear All Entries',bg='black',fg='white')
self['command']= "clearAll(targetFrame)"
I have also looked at grid_slave as an approach but am having the same issue.
Any advice/help would be greatly appreciated.
First off, if you're creating a new class that contains two objects of different classes, you should not be using inheritance. Instead, use composition.
Second, to be able to access the entry widget, save it to an instance variable.
For example:
class LabelEntry():
def __init__(self, parent, label, row, column, bg_color):
self.label = Label(parent, text=label, justify='right', bg=bg_color)
self.entry = Entry(parent, width=10)
self.label.grid(row=row, column=column, sticky="e")
self.grid(row=row,column=column+1)
Later, you can reference these attributes like you can any other attribute:
le1 = LabelEntry(root)
...
print(le1.entry.get())