Accessing dynamically created Variables set in a for loop - python-3.x

TLDR FIX: put your objects(or list of objects) into a dictionary
Simply put, i am creating a bunch of pages in a wxpython notebook dynamically and on each page i am placing further widgets and sizers and such. my issue is that if i want to access a variable set in the for loop when it creates the page, i am unable to because once the for loops runs again the variable will be reset and used to populate the new page.
something along the lines of this
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((400, 300))
self.notebook_1 = wx.Notebook(self, wx.ID_ANY)
self.text_button = wx.Button(self, wx.ID_ANY, "get text")
self.Bind(wx.EVT_BUTTON, self.get_text_handler, self.text_button)
self.__set_properties()
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.text_button, 0, 0, 0)
sizer_1.Add(self.notebook_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
numlist = (1, 2, 3, 4)
#global page_dictionary
#page_dictionary = {}
for i in numlist:
self.notebook_1_pane_2 = wx.Panel(self.notebook_1, wx.ID_ANY)
self.notebook_1_pane_2.SetSizer(sizer_2)
self.notebook_1.AddPage(self.notebook_1_pane_2, str(i))
self.label_1 = wx.TextCtrl(self.notebook_1_pane_2, wx.ID_ANY, str(i))
sizer_2.Add(self.label_1, 0, 0, 0)
self.Layout()
#page_k = self.label_1.GetValue()
#page_k_good = page_k + str(i)
#page_dictionary[page_k_good] = i
#print(page_dictionary)
def __set_properties(self):
self.SetTitle("frame")
def get_text_handler(self, event): # wxGlade: MyFrame.<event_handler>
print(self.label_1.GetValue())
the only difference between this and what im actually running, is that the numlist variable is actually generating a random number of values dynamically
my issue is that if i want to reference any of the labels ive set in previous pages, i can't because the self.label_1 variable gets reset to the entry on the last page. so if i want to access the label on say page number 1 the label variable is set to the values of the last page created in the for loop. its like once the label is placed and populated on the wxNotebook page its just gone and what is in that widget isn't accessible anymore.
so how would i, say for example, print(self.label_1) of page 1 in the notebook when self.label_1 was rewritten to match the label in the last page generated?
was messing around with dictionaries so i commented that out.
how would i be able to click that button, and have it get the text from the boxes in current pages?
Final edit hopefully:
how would i go about assigning multiple objects into the dictionary? would i need to just assign the value i as a list (label_1, Label_2) then when needing to call methods on them just reference them from the list, that is now in the dictionary?
yup, that did it, put them into the dictionary as a list, can pull the list when needed.

I am still at a loss as to exactly what you are trying to do, but based on an educated guess I think you are trying to preserve the identity of objects created in a loop so that they maybe accessed outside of the loop. With this in mind, here is an example of how this can be done. Here is a very simplistic example:
import random as rnd #used to create a random number for each object
# just a list of names to be scanned in for loop
lst = ["a", "b", "c", "d"]
# Just an illustrative object for demo purposes
class SillyClass:
def __init__(self, name, nmb):
self._id = name
self._nmb = nmb
#property
def name(self):
return self._id
#property
def number(self):
return self._nmb
# create a list of objects for later reference
def build_objects(lst):
rslt = []
for itm in lst:
rslt.append(SillyClass(itm, rnd.randrange(5, 200)))
return rslt
# show use of list to print object data
obj_lst = build_objects(lst)
# Show the contents of obj_lst
print(obj_lst, "\n")
# show access of object attributes
for obj in obj_lst:
print(f"Object: {obj.name} contains {obj.number}")
The result of running this code is:
[<__main__.SillyClass object at 0x0000022C5CB9E9D0>, <__main__.SillyClass object at 0x0000022C5CB9EFD0>, <__main__.SillyClass object at 0x0000022C5CB9E670>, <__main__.SillyClass object at 0x0000022C5CC42370>]
Object: a contains 187
Object: b contains 164
Object: c contains 97
Object: d contains 52
This illustrates that obj_lst contains a list of objects, which then can be used to access the attributes of the object. You can utilize this approach or possibly associate the object to a specific identifier using a dict structure. What ever you need to best serve your purposes.

Related

Connecting comboboxes created in a loop in PyQt5

I'm trying to build a GUI that is generated dynamically based on some input dictionary. I'm using the GridLayout, iterate over the dictionary keys and generate per iteration/grid row the key of the dictionary as a QLineEdit (to give some background color as a visual cue) and two comboboxes next to it. Ideally, the second combobox would change its items based on what is selected in the first combobox. Here's the relevant part of my code:
from PyQt5.QtCore import Qt
from PyQt5.Widgets import QApplication, QMainWindow, QVBoxLayout, QGridLayout, QLineEdit, QComboBox
class GUI(QMainWindow):
"""App View"""
def __init__(self, data):
super().__init__()
self.data = data
self.generalLayout = QVBoxLayout()
self._displayview()
def _displayview(self):
self.layout = QGridLayout()
subdict = self.data
combolist = ['Auto', 'Select value', 'Calculate']
maxwidth = sum([len(key) for key in subdict.keys()])
self.count = 0
for item in subdict.keys():
color = subdict[item]['Color']
# Display tags and bg-color
display = QLineEdit()
display.setMaximumWidth(maxwidth-maxwidth//4)
display.setStyleSheet("QLineEdit"
"{"
f"background : {color}"
"}")
display.setText(item)
display.setAlignment(Qt.AlignLeft)
display.setReadOnly(True)
# Combobox action
self.cb_action = QComboBox()
if color == 'Lightgreen':
self.cb_action.addItem('Auto')
self.cb_action.setEnabled(False)
else:
self.cb_action.addItems(combolist)
# Combobox value
self.cb_value = QComboBox()
self.cb_value.addItem('Text')
self.cb_action.currentIndexChanged.connect(self.react)
self.layout.addWidget(display, self.count, 0)
self.layout.addWidget(self.cb_action, self.count, 1)
self.layout.addWidget(self.cb_value, self.count, 2)
self.count += 1
self.generalLayout.addLayout(self.layout)
def react(self):
... # my humble approaches here
Basically, the first combobox has the three options: Auto, Select value and Calculate and based on that selection, the combobox next to it should present different options (right now, it only has 'Text' for testing purposes).
I tried different approaches in the self.react(), e.g. a simple self.cb_value.addItem('something'), which would however add the item in the last combobox (which makes sense). I also tried to simply build a new combobox with self.layout.addWidget(), however without an index, that won't work. Lastly, I tried to simply create that second column of comboboxes anew in another iteration using self.cb_action.currentText() as help, however, that again returns only the text from the last combobox.
I understand that due to the nature of creating everything while iterating I get these problems. It's not unlikely that I haven't fully understood the concept of widgets. Would anybody be so kind to point me in the right direction how I would do this with variable input data? I'd probably face the same issue when I tried to extract all these information from the comboboxes to get some output I can work with.
Thank you and have a good day.

Updating and deleting the MySQLite database through dynamic button on QTableWidget

I have created a fairly simple app that takes three input parameter from the LineEdit's and displays it in the QTablewidget through the Button placed side it. In QTableWidget dynamic update button and removes button are created as the rows values are filled. Whenever QTableWidget's cell is changed and the update button is clicked, It updated the value in the database. Removes button helps to remove the specific row entry from the database.I am able to remove from value from QTableWidget but not from database.
ui,_ = loadUiType('drake.ui')
from db_new import DatabaseNew
db_new = DatabaseNew('database-punk-2')
class LoginNew(QMainWindow, ui):
def __init__(self):
QMainWindow.__init__(self)
self.setupUi(self)
self.show_database()
self.pushButton.clicked.connect(self.addToTableWidget)
def addToTableWidget(self):
self.row_data = []
self.val1 = self.lineEdit.text()
self.row_data.append(self.val1)
self.val2 = self.lineEdit_2.text()
self.row_data.append(self.val2)
self.val3 = self.lineEdit_3.text()
self.row_data.append(self.val3)
row = self.tableWidget.rowCount()
self.tableWidget.setRowCount(row+1)
col = 0
for item in self.row_data:
cell = QTableWidgetItem(str(item))
self.tableWidget.setItem(row, col, cell)
col += 1
db_new.insert(self.val1,self.val2, self.val3)
for index in range(self.tableWidget.rowCount()):
self.btx = QPushButton(self.tableWidget)
self.btn = QPushButton(self.tableWidget)
self.btx.setText("Update")
self.btn.setIcon(QIcon(QPixmap("delete.png")))
self.btn.setIconSize(QSize(35,35))
self.btx.clicked.connect(self.update_pos)
self.btn.clicked.connect(self.delete_pos)
self.tableWidget.setCellWidget(index,3, self.btx)
self.tableWidget.setCellWidget(index,4,self.btn)
def show_database(self):
res = db_new.fetch_data()
self.tableWidget.setRowCount(0)
for row_number, row_data in enumerate(res):
self.tableWidget.insertRow(row_number)
for column_number, data in enumerate(row_data):
self.tableWidget.setItem(row_number, column_number, QTableWidgetItem(str(data)))
def update_pos(self):
self.button =self.focusWidget()
self.index = self.tableWidget.indexAt(self.button.pos())
self.button.clicked.connect(self.btn_trigger)
def btn_trigger(self):
QMessageBox.information(self, "Update Data", f' Value is {self.index.row()} {self.index.column()}')
# db_new.update(self.index.row()-1,self.val1,self.val2,self.val3)
# Unable to find appropiate method for updating the values from the database.
def delete_pos(self):
rows = set()
print("First row Value ")
print(rows)
for indexes in self.tableWidget.selectedIndexes():
rows.add(indexes.row())
for row in sorted(rows, reverse=True):
self.tableWidget.removeRow(row)
# Unable to find the appropiate logic for removing from database
def main():
app = QApplication(sys.argv)
win = LoginNew()
win.show()
app.exec_()
if __name__ =='__main__':
main()
Database File
UI file image
I am unable to update the database or delete a specific row using the row-delete button, I a'm unable to do it.
From what I am seeing I understand the following:
With show_database() -> fetch_data() you are only getting the fields description_one, description_two, and status from your database.
The information which is lacking here is "ID". This field is required by your remove and update functions. You try to emulate this with self.index.row()-1, however this is bound to fail as IDs are typically auto-incrementing and hence not necessarily in a 1-2-3-fashion anymore.
So I would suggest adding an ID-column to the table, and getting this field with fetch_data as well. Once you have that, you should be able to use your remove and update functions in a straight forward way, directly implementing the fetched ID.
If you wand to declutter the interface, you can hide this column, as it does not contain any viable information for a potential user of the interface.

Return old value to combobox with dynamic search and autocompletion

I have a reimplemented comboBox that performs dynamic search and autocompletion (code isn't mine). The problem is when I type something, that doesn't match any value in combobox list and press enter - I receive an empty string. But I wish to receive instead an old value, that was in combobox before I started to type other value. Could anybody help me with that?
Also I want to ask the meaning of 2 strings in ExtendedComboBox class (as long as code isn't mine):
inside function on_completer_activated there is expression if text: ; I can't understand what does it mean, because I always write the whole expression (like if text == True: or something like that)
I don't understand the meaning of [str] in line self.activated[str].emit(self.itemText(index)). I have never seen this kind of construction in pyqt when something in square brackets comes directly after a signal.
code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class ExtendedComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
super(ExtendedComboBox, self).__init__(parent)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setEditable(True)
# add a filter model to filter matching items
self.pFilterModel = QtCore.QSortFilterProxyModel(self)
self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.pFilterModel.setSourceModel(self.model())
# add a completer, which uses the filter model
self.completer = QtWidgets.QCompleter(self.pFilterModel, self)
# always show all (filtered) completions
self.completer.setCompletionMode(QtWidgets.QCompleter.UnfilteredPopupCompletion)
self.setCompleter(self.completer)
# connect signals
self.lineEdit().textEdited.connect(self.pFilterModel.setFilterFixedString)
self.completer.activated.connect(self.on_completer_activated)
# on selection of an item from the completer, select the corresponding item from combobox
def on_completer_activated(self, text):
if text:
index = self.findText(text)
self.setCurrentIndex(index)
self.activated[str].emit(self.itemText(index))
# on model change, update the models of the filter and completer as well
def setModel(self, model):
super(ExtendedComboBox, self).setModel(model)
self.pFilterModel.setSourceModel(model)
self.completer.setModel(self.pFilterModel)
# on model column change, update the model column of the filter and completer as well
def setModelColumn(self, column):
self.completer.setCompletionColumn(column)
self.pFilterModel.setFilterKeyColumn(column)
super(ExtendedComboBox, self).setModelColumn(column)
class ComboBox_Model(QtCore.QAbstractListModel):
def __init__(self, data_list = [], parent = None):
super(ComboBox_Model, self).__init__()
self.data_list = data_list
def rowCount(self, parent):
return len(self.data_list)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
value = self.data_list[row]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
value = self.data_list[row]
return value
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.combobox = ExtendedComboBox()
self.layout_1 = QtWidgets.QHBoxLayout()
self.layout_1.addWidget(self.combobox)
self.setLayout(self.layout_1)
data = ['some text to display', 'other text to display', 'different text']
self.model = ComboBox_Model(data)
self.combobox.setModel(self.model)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
When a combobox is set as editable, by default allows insertion of non existing items at the bottom of the current model when pressing return. Since the model used in that code is not editable, when pressing return with unrecognized text the combobox is unable to add the new item (and select it), which results in setting the index to -1.
You can connect to the embedded QLineEdit returnPressed signal and check whether the current index is valid or not; this is possible because the signal is also previously connected to the combobox insertion, so when you receive the signal the combo has already tried to add the new item and eventually set the (possibly) invalid index.
In order to store the previous index, just connect to the currentIndexChanged() and save it as long as it's greater or equal to 0.
class ExtendedComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
# ...
self.lineEdit().returnPressed.connect(self.returnPressed)
self.currentIndexChanged.connect(self.storePreviousIndex)
self.previousIndex = self.currentIndex()
def storePreviousIndex(self, index):
if index >= 0:
self.previousIndex = index
def returnPressed(self):
if self.currentIndex() < 0 or self.currentText() != self.itemText(self.currentIndex()):
self.setCurrentIndex(self.previousIndex)
Note that the second comparison in returnPressed is to add compatibility to the default internal model, in case setModel() is not called and the insertion policy is NoInsert.
About the two final questions:
the if statement checks if the condition is true or not, or, if you want, the condition is not false, as in "not nothing" (aka, False, 0, None); you can do some experiments with simple statements to better understand: if True:, if 1:, if 'something': will all result as valid conditions, while if False:, if 0: or if '': not.
some signals have multiple signatures for their arguments, meaning that the same signal can be emitted more than once, each time with different types of arguments; for example the activated signal of QComboBox is emitted twice, the first time as int with the new current index, then with the new current text; whenever you want to connect to (or emit) an overload that is not the default one, you need to specify the signature in brackets. In the case above, the signal is explicitly emitted for the str signature only (I don't know why the int was not, though). Note that overloaded signals are being gradually removed in Qt (in fact, the [str] signature of activated() is considered obsolete since Qt 5.14).

wxPython listctrl insertitem and SetItem all at once

I have a listctrl,
self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT | wx.LC_NO_HEADER)
self.list.InsertColumn(col=0, heading='', format=wx.LIST_FORMAT_CENTER, width=150)
self.list.InsertColumn(col=1, heading='', format=wx.LIST_FORMAT_CENTER, width=450)
for person in people:
#this is the issue right here
index = self.list.InsertItem(0, person.age) #one line to insert an item and get the index, and the next line to update the item at that index... two lines to actually put a full entry in.
self.list.SetItem(index=index, column=1, label=person.name)
This works fine setting up the listctrl initially in the constructor, but what if I want to dynamically add/remove items in the listctrl at runtime?
I've come across wx.CallAfter, wx.CallLater, startWorker (from wx.lib.delayedresult), and wx.Timer
If you look at the example above the issue is that I've got one line that inserts the item, and another line that updates the item to have the correct name on the item that was just inserted. So if I've got threads that are taking turns removing and adding items to the listctrl, if I insert an item and another thread inserts an item at the same time, the index that I just got won't be relevant for updating. i.e. I need an atomic operation for inserting an item that includes inserting both the person's age and the person's name. So my first question is, is there a way to insert all information of a list item in one line?
If I cannot do that, then my next question is how could I accomplish the prescribed behavior? For example, suppose there are threads randomly coloring the top row, adding, and deleting:
self.color_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_colorizer, self.color_timer)
self.red_shown = True
self.color_timer.Start(500)
self.delete_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_deleter, self.delete_timer)
self.delete_timer.Start(500)
self.adder_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_queuer, self.adder_timer)
self.adder_timer.Start(400)
Then here are my methods that are used to add people, delete people, and color the top row:
def item_queuer(self, event):
startWorker(consumer=self.item_adder,
workerFn=self.person_generator)
def person_generator(self):
return {'person':random.choice(people)}
def item_adder(self, result):
res = result.get()
person = res['person']
wx.CallAfter(self.list.InsertItem, 0, person.name) # this is the problem right here. If another thread does a color swap in between this line and the next, then this won't work.
wx.CallAfter(self.list.SetItem, index=0, column=1, label=person.name)
def item_deleter(self, event):
wx.CallAfter(self.list.DeleteItem, 0)
def item_colorizer(self, event):
if self.color_timer.IsRunning():
if not self.red_shown:
wx.CallAfter(self.list.SetItemBackgroundColour, 0, wx.RED)
wx.CallAfter(self.list.SetItemTextColour, 0, wx.WHITE)
self.red_shown = True
else:
wx.CallAfter(self.list.SetItemBackgroundColour, 0, wx.BLUE)
wx.CallAfter(self.list.SetItemTextColour, 0, wx.BLACK)
self.red_shown = False
What actually happens when I run this is that I end up having rows where the person is partially inserted (just the age), and the color changes before the name is inserted. I've noticed that the InsertItem method on the listctrl is overloaded and offers one signature where I can insert a ListItem, but I cannot get that to work either.
item1 = wx.ListItem()
item1.SetBackgroundColour(wx.GREEN)
item1.SetColumn(0)
item1.SetText(32)
item1.SetColumn(1)
item1.SetText('John')
self.list.InsertItem(item1)
wx._core.wxAssertionError: C++ assertion "rv != -1" failed at ....\src\msw\listctrl.cpp(1730) in wxListCtrl::InsertItem(): failed to insert an item in wxListCtrl

How to add a set of multiple values to a key?

I created a class which is basically a hobby book. The book can be accessed by two methods, enter(n,h) which takes a name and keep adding hobbies to that name(one name can have multiple hobbies). The other method returns a set of hobbies for a particular name. My hobby book is storing every hobby that I insert to one name. Can someone help me fixing it?
class Hobby:
def __init__(self):
self.dic={}
self.hby=set()
def enter(self,n,h):
if n not in self.dic.items():
self.dic[n]=self.hby
for k in self.dic.items():
self.hby.add(h)
def lookup(self,n):
return self.dic[n]
I tried running following cases
d = Hobby(); d.enter('Roj', 'soccer'); d.lookup('Roj')
{'soccer'}
d.enter('Max', 'reading'); d.lookup('Max')
{'reading', 'soccer'} #should return just reading
d.enter('Roj', 'music'); d.lookup('Roj')
{'reading', 'soccer','music'} #should return soccer and music
Why are you re-inventing a dict here? Why are you using a separate set to which you always add values, and reference it to every key which ensures that it always returns the same set on a lookup?
Don't reinvent the wheel, use collections.defaultdict:
import collections
d = collections.defaultdict(set)
d["Roj"].add("soccer")
d["Roj"]
# {'soccer'}
d["Max"].add("reading")
d["Max"]
# {'reading'}
d["Roj"].add("music")
d["Roj"]
# {'soccer', 'music'}
.
UPDATE - If you really want to do it through your own class (and before you do, watch Stop Writing Classes!), you can do it as:
class Hobby(object):
def __init__(self):
self.container = {}
def enter(self, n, h):
if n not in self.container:
self.container[n] = {h}
else:
self.container[n].add(h)
def lookup(self, n):
return self.container.get(n, None)
d = Hobby()
d.enter("Roj", "soccer")
d.lookup("Roj")
# {'soccer'}
d.enter("Max", "reading")
d.lookup("Max")
# {'reading'}
d.enter("Roj", "music")
d.lookup("Roj")
# {'soccer', 'music'}
Note how no extra set is used here - every dict key gets its own set to populate.

Resources