Python integration of Objects in others - python-3.x

fix: I moved an invoke of a Radiobutton from the init of my view to the controller like this:
class Controller(object):
def __init__(self):
self.model = Model()
print('start init of View')
self.view = View(self.importieren, self.exportieren, self.beenden, self.J5, self.J6, self.J7, self.J8, self.J9,
self.J10, self.J11, self.J12, self.JA, self.tabelle_sorti, self.hinzufugen, self.zuordnen,
self.ande, self.JA, self.model.ausgabe('projekte'), self.tabelle_update, self.a1, self.a2, self.a3,
self.a4, self.a5, self.a6, self.a7)
print('done init')
self.wahlen = ('sErst', 'sZweit', 'sDritt')
self.delimiter = {'imp_s': None, 'imp_p': None, 'exp': None}
self.dchosen = None
self.slcsv = 'schuelerliste.csv'
self.plcsv = 'projektliste.csv'
self.double = False
self.andernx = ""
self.view.radios['jahrg-Alle'].invoke()
self.tabelle()
self.view.table['main'].bind('<Double-Button-1>', self.treevent)
# Erstimportierung
if self.model.ausgabe('schueler'):
self.importieren(True)
self.view.mainloop()
def tabelle_update(self, fetchshlr=None, fetchprj=None):
print('start update')
if fetchshlr is None:
fetchshlr = self.model.ausgabe('schueler')
if fetchprj is None:
fetchprj = self.model.ausgabe('projekte')
self.view.tableup(fetchshlr, fetchprj)
This invoke called the function, before View was ready and so caused the error. Thanks for the Help
I tried to not ask a question hear, but after hours of searching I have to.
I am currently working on a programm and recently splitted the main file in three in style of the MVC-Scheme. And I have one function to update a Treeview working as a table. But this function (and only that one!), says:
Exception in Tkinter callback\
Traceback (most recent call last):\
File "C:\Users\...\Python38\lib\tkinter\__init__.py", line 1883, in __call__\
return self.func(*args)\
File "C:/Users/.../Controller.py", line 184, in tabelle_update\
self.view.table['main'].tag_configure(i[0], background='white')\
AttributeError: 'Controller' object has no attribute 'view'
I already tried to use lambda (and if you ask why, because someone said it in the internet) and it just prevented the function to work at all. Also I think it has something todo with this:
{'model': <Model.Model object at 0x0000018CA8068160>, 'view': <View.View object .>, ...}
this is an extract of the attributes ditionary and I think it has something to do with the missing at 0x... part in 'view' as seen in 'model'
Please Help me to get this up and running
And here are my important code parts (if you need more, pls write)
note that the diffrent classes are in diffrent files and are properly imported
Major Changes in programmcode under tabelle_update and under the View class tableup
I tried to move the View heavy parts to View, didn't fix anything.
class Controller(object):
def __init__(self):
self.model = Model()
self.view = View(self.importieren, self.exportieren, self.beenden, self.J5, self.J6, self.J7, self.J8, self.J9,
self.J10, self.J11, self.J12, self.tabelle_update, self.tabelle_sorti, self.hinzufugen,
self.zuordnen, self.ande, self.tabelle_update, self.model.ausgabe('projekte'),
self.tabelle_update, self.a1, self.a2, self.a3, self.a4, self.a5, self.a6, self.a7)
self.wahlen = ('sErst', 'sZweit', 'sDritt')
self.delimiter = {'imp_s': None, 'imp_p': None, 'exp': None}
self.dchosen = None
self.slcsv = 'schuelerliste.csv'
self.plcsv = 'projektliste.csv'
self.double = False
self.andernx = ""
self.tabelle()
self.view.table['main'].bind('<Double-Button-1>', self.treevent)
# Erstimportierung
if self.model.ausgabe('schueler'):
self.importieren(True)
print(self.__dict__)
self.view.mainloop()
def tabelle(self):
fetch = self.model.ausgabe('schueler')
self.view.shlr_tabelle(fetch)
def tabelle_update(self, fetchshlr=None, fetchprj=None):
print('start update')
if fetchshlr is None:
fetchshlr = self.model.ausgabe('schueler')
if fetchprj is None:
fetchprj = self.model.ausgabe('projekte')
self.view.tableup(fetchshlr, fetchprj)
class View(ttkthemes.ThemedTk):
def __init__(self, imp, exp, bee, j5, j6, j7, j8, j9, j10, j11, j12, ja, tabsort, hin, zord, ande, scht,
prjt, aktutable, a1, a2, a3, a4, a5, a6, a7):
# print(ttkthemes.THEMES) # zum Ausgeben der verfügbaren Themes
ttkthemes.ThemedTk.__init__(self, theme='breeze')
self.title("Projektwochenverwaltungsprogramm")
self.geometry('800x300')
self.minsize(800, 300)
self.resizable(width=True, height=True)
# bestimmen der Callbacks
self.callback_imp = imp
self.callback_exp = exp
self.callback_bee = bee
self.radiocom = {'jahrg': [j5, j6, j7, j8, j9, j10, j11, j12, ja], 'ande': [a1, a2, a3, a4, a5, a6, a7]}
self.tabelle_sorti = tabsort
self.callback_aktut = aktutable
self.callback_zord = zord
self.callback_hin = hin
self.callback_ande = ande
self.callback_scht = scht
self.callback_prjt = prjt
# Tabelle
self.scrollbars = {'main': Scrollbar(self.rahmen[2], orient="vertical")}
self.table = {'main': Treeview(self.rahmen[2], yscrollcommand=self.scrollbars['main'].set, height=200)}
self.scrollbars['main'].pack(side=RIGHT, fill=BOTH)
self.rahmen[1].pack()
self.rahmen['popup_pro'].pack(fill=X)
self.rahmen[2].pack(fill=BOTH, expand=True)
def shlr_tabelle(self, fetch):
ml = ['ID']
for name in self.names['schueler']:
ml.append(name)
ml.append('Zugeordned zu')
self.table['main']['columns'] = ml
self.table['main']['show'] = 'headings'
for i in range(len(ml)):
self.table['main'].column(ml[i], width=self.width['schueler'][i], minwidth=self.width['schueler'][i])
for i in range(len(ml)):
self.table['main'].heading(ml[i], text=ml[i], command=lambda col=i: self.tabelle_sorti(col, False, 'main'))
for t in fetch:
self.table['main'].insert('', t[0], t[0], values=t) # , tags=t[0]
self.scrollbars['main'].config(command=self.table['main'].yview)
self.table['main'].pack(fill=BOTH)
def prj_tabelle(self, fetch):
self.top['prjt'] = Toplevel()
self.top['prjt'].title('Projekte Liste')
self.top['prjt'].geometry('800x300')
self.top['prjt'].minsize(800, 300)
self.scrollbars['prj'] = Scrollbar(self.top['prjt'], orient="vertical")
self.table['prj'] = Treeview(self.top['prjt'], yscrollcommand=self.scrollbars['prj'].set, height=200)
ml = ['ID']
for name in self.names['projekte']:
ml.append(name)
self.table['prj']['columns'] = ml
self.table['prj']['show'] = 'headings'
for i in range(len(ml)):
self.table['prj'].column(ml[i], width=self.width['projekte'][i], minwidth=self.width['projekte'][i])
for i in range(len(ml)):
self.table['prj'].heading(ml[i], text=ml[i], command=lambda col=i: self.tabelle_sorti(col, False, 'prj'))
for t in fetch:
self.table['prj'].insert('', t[0], t[0], values=t) # , tags=t[0]
self.scrollbars['prj'].config(command=self.table['prj'].yview)
self.scrollbars['prj'].pack(side=RIGHT, fill=BOTH)
self.table['prj'].pack(fill=BOTH)
def tableup(self, fetchshlr, fetchprj):
print('läuft')
for i in fetchshlr:
self.view.table['main'].tag_configure(i[0], background='white')
self.view.table['main'].delete(*self.view.table['main'].get_children())
for t in fetchshlr:
self.view.table['main'].insert('', t[0], t[0], values=t) # , tags=t[0]
for ele in fetchshlr:
if ele[8] is None:
self.view.table['main'].tag_configure(ele[0], background='#fa6150')
try:
for i in fetchprj:
self.view.table['prj'].tag_configure(i[0], background='white')
self.view.table['prj'].delete(*self.view.table['prj'].get_children())
for t in fetchprj:
self.view.table['prj'].insert('', t[0], t[0], values=t) # , tags=t[0]
failed = self.model.prj_aktu()
for prj in failed:
self.view.table['prj'].tag_configure(prj, background='#fa6150')
except KeyError:
pass

You have to ask yourself when self.view is fully initialized, and when you're trying to access self.view. The error is telling you that you're trying to access it before you define it.
You should be able to visualize this by adding print statements like so:
print("before self.view is defined")
self.view = View(...)
print("after self.view is defined")
and then:
print("using self.view...")
self.view.table['main'].tag_configure(i[0], background='white')

Related

Prevent recursion when changing QTreeWidgetItem flags

I'm trying to make a very basic password manager and I've encountered an issue when trying to edit an item. When the button "Edit Password" is pressed, it makes the currently selected item editable, and I would like it to be removed once the user is done making modifications. Trying to remove the flag ItemIsEditable causes it to go into infinite recursion on the line item.setFlags(item.flags() & ~Qt.ItemIsEditable).
# app class -------------------------------------------------------------------------- #
class App(QApplication):
# initialisation ----------------------------------------------------------------- #
def __init__(self, argv):
super().__init__(argv)
self.__ready = False
self.__setup__()
self.__load_data__()
self.__ready = True
# private methods ---------------------------------------------------------------- #
def __load_data__(self):
self.data_tree.headerItem().setText(0, "Client")
self.data_tree.headerItem().setText(1, "Workstation")
self.data_tree.headerItem().setText(2, "Login")
self.data_tree.headerItem().setText(3, "Password")
for level_1, client in enumerate(self.data["clients"]):
row_1 = QTreeWidgetItem(self.data_tree)
self.data_tree.topLevelItem(level_1).setText(0, client["name"])
for level_2, workstation in enumerate(client["workstations"]):
row_2 = QTreeWidgetItem(row_1)
self.data_tree.topLevelItem(level_1).child(level_2).setText(
1, workstation["name"]
)
for level_3, login in enumerate(workstation["logins"]):
row_3 = QTreeWidgetItem(row_2)
row_3.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
self.data_tree.topLevelItem(level_1).child(level_2).child(
level_3
).setText(2, login["username"])
self.data_tree.topLevelItem(level_1).child(level_2).child(
level_3
).setText(3, login["password"])
self.data_tree.setSortingEnabled(True)
def __setup__(self):
self.data = pd.read_json("list.json")
self.main_window = QMainWindow()
self.central_widget = QWidget(self.main_window)
self.data_tree = QTreeWidget(self.central_widget)
self.edit_password_button = QPushButton(self.central_widget)
self.central_widget.setGeometry(QRect(0, 0, 500, 500))
self.central_widget.setObjectName("central_widget")
self.data_tree.itemChanged.connect(self.save_password)
self.data_tree.setGeometry(QRect(0, 0, 500, 450))
self.data_tree.setObjectName("data_tree")
self.data_tree.sortByColumn(0, Qt.SortOrder.AscendingOrder)
self.main_window.setCentralWidget(self.central_widget)
self.main_window.setGeometry(QRect(200, 200, 500, 500))
self.main_window.setWindowTitle("Password Manager")
self.main_window.show()
self.edit_password_button.clicked.connect(self.edit_password)
self.edit_password_button.setGeometry(QRect(345, 455, 150, 40))
self.edit_password_button.setText("Edit Password")
# events ------------------------------------------------------------------------- #
#pyqtSlot()
def edit_password(self):
try:
item = self.data_tree.currentItem()
item.setFlags(item.flags() | Qt.ItemIsEditable)
self.data_tree.scrollToItem(item)
self.data_tree.editItem(item, 3)
except Exception as e:
print(e)
#pyqtSlot(QTreeWidgetItem, int)
def save_password(self, item, column):
if not self.__ready:
return
for client in self.data["clients"]:
if client["name"] == item.parent().parent().text(0):
for workstation in client["workstations"]:
if workstation["name"] == item.parent().text(1):
for login in workstation["logins"]:
if login["username"] == item.text(2):
login["password"] = item.text(3)
with open("list.json", "w") as file:
json.dump(json.loads(self.data.to_json()), file, indent=4)
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
def simulate_password(self):
keyboard.write(self.data_tree.currentItem().text(3))
def test_func(self):
print("test")
app = App(sys.argv)
keyboard.add_hotkey("ctrl+insert", app.simulate_password)
sys.exit(app.exec())
The itemChanged signal is emitted for all changes to an item, not just to its text. To avoid the recursion, you can temporarily block signals whilst setting the flags, so that the save_password slot doesn't get triggered again:
#pyqtSlot()
def edit_password(self):
item = self.data_tree.currentItem()
blocked = item.treeWidget().blockSignals(True)
item.setFlags(item.flags() | Qt.ItemIsEditable)
item.treeWidget().blockSignals(blocked)
self.data_tree.scrollToItem(item)
self.data_tree.editItem(item, 3)
#pyqtSlot(QTreeWidgetItem, int)
def save_password(self, item, column):
...
blocked = item.treeWidget().blockSignals(True)
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
item.treeWidget().blockSignals(blocked)

How can I programmatically expand/collapse and select an item in a tree?

Let's say I added a TreeView widget called treeView in Qt Designer and I'm using this code to add a few items to it:
class StandardItem(Qt.QStandardItem):
def __init__(self, txt='', font_size=11, set_bold=False, color=Qt.QColor(0, 0, 0)):
super().__init__()
fnt = Qt.QFont('Open Sans', font_size)
fnt.setBold(set_bold)
self.setEditable(False)
self.setForeground(color)
self.setFont(fnt)
self.setText(txt)
model = Qt.QStandardItemModel()
rootNode = model.invisibleRootItem()
A = StandardItem("A")
A.appendRows([StandardItem("1"),StandardItem("2"),StandardItem("3")])
B = StandardItem("B")
B.appendRows([StandardItem("1"),StandardItem("2")])
rootNode.appendRows([A,B])
self.treeView.setModel(model)
How can I write a function that collapses/expands item A? And how can I write a function that selects item A, as if it has been clicked? I might be missing something in the docs.
Expansion and selection are tasks that affect the visual part so they must be handled by sight. The first task is done through the setExpanded() method and the second using the select() method of the selectionModel() associated with the view, in both cases the QModelIndex associated with the item is used:
import sys
from PyQt5 import Qt
class StandardItem(Qt.QStandardItem):
def __init__(self, txt="", font_size=11, set_bold=False, color=Qt.QColor(0, 0, 0)):
super().__init__()
fnt = Qt.QFont("Open Sans", font_size)
fnt.setBold(set_bold)
self.setEditable(False)
self.setForeground(color)
self.setFont(fnt)
self.setText(txt)
class MainWindow(Qt.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.treeView = Qt.QTreeView()
self.setCentralWidget(self.treeView)
self.model = Qt.QStandardItemModel()
rootNode = self.model.invisibleRootItem()
A = StandardItem("A")
A.appendRows([StandardItem("1"), StandardItem("2"), StandardItem("3")])
B = StandardItem("B")
B.appendRows([StandardItem("1"), StandardItem("2")])
rootNode.appendRows([A, B])
self.treeView.setModel(self.model)
index_A = A.index()
self.treeView.setExpanded(index_A, True)
self.treeView.selectionModel().select(index_A, Qt.QItemSelectionModel.Select)
def main():
app = Qt.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()
Note:
It seems that the index() function of the QStandardItem has a bug since it sometimes returns invalid QModelIndex for valid QStandardItem.
print(A.child(0).index().isValid())
Output:
False
This is caused because when the child was added, the "A" was not part of the model then the model is null in those children, as can be seen with.
print(A.child(0).model())
Output:
None
If instead "A" are added to the model first and then the children are newly added, then the model is passed.
self.model = Qt.QStandardItemModel()
rootNode = self.model.invisibleRootItem()
A = StandardItem("A")
B = StandardItem("B")
rootNode.appendRows([A, B])
A.appendRows([StandardItem("1"), StandardItem("2"), StandardItem("3")])
B.appendRows([StandardItem("1"), StandardItem("2")])
print(A.child(0).model())
print(A.child(0).index().isValid())
Output:
<PyQt5.QtGui.QStandardItemModel object at 0x7fb40aa868b0>
True
In that case it is better to use the indexFromItem() method of QStandardItemModel:
self.treeView.selectionModel().select(
self.model.indexFromItem(A.child(0)), Qt.QItemSelectionModel.Select
)

PySide2: How to re-implement QFormLayout.takeRow()?

I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field

Object has no attribute but attribute is defined

I have defined an attribute in a custom class, but I keep receiving an AttributeError when I try to access it.
class SMainWindow(QMainWindow):
def __init__(self):
# Constructor
super(SMainWindow, self).__init__()
self.myapp = PyQtApp()
self.layout = QVBoxLayout()
self.label_text = ''
self.settings = scrudb.retrieve_settings('current')
self.competition = self.retrieve_competition()
self.set_competition(self.competition.id)
self.label = QLabel(self.label_text)
self.button_scrutineer = QPushButton('Scrutineer Competition')
self.button_comps = QPushButton('Change Competition')
self.button_comp = QPushButton('Edit Competition Details')
self.button_dancers = QPushButton('Add/Edit Competitors')
self.button_judges = QPushButton('Add/Edit Judges')
self.button_dancerGroups = QPushButton(
'Define Competitor Groups & Dances')
self.button_import = QPushButton('Import CSV')
self.button_delete = QPushButton('Delete Competition')
self.button_exit = QPushButton('Exit')
self.button_comps.clicked.connect(self.select_competition)
self.button_delete.clicked.connect(self.delete_competition)
self.button_exit.clicked.connect(self.exit_app)
if (self.competition == None):
self.disable_buttons()
self.layout.addWidget(self.label)
self.layout.addWidget(self.button_scrutineer)
self.layout.addWidget(self.button_comps)
self.layout.addWidget(self.button_comp)
self.layout.addWidget(self.button_dancers)
self.layout.addWidget(self.button_judges)
self.layout.addWidget(self.button_dancerGroups)
self.layout.addWidget(self.button_import)
self.layout.addWidget(self.button_delete)
self.layout.addWidget(self.button_exit)
self.myapp.setLayout(self.layout)
def set_competition(self, comp_id):
self.competition = scrudb.retrieve_competition(comp_id)
if (self.competition != None):
self.label_text = ('<center>Competition:<br><strong>%s</strong><br>%8s<br>%s</center>' % (self.competition.name, self.get_formatted_date(self.competition.eventDate), self.competition.location))
self.label.setText(self.label_text)
self.settings.lastComp = self.competition.id
scrudb.set_settings(self.settings)
return self.competition
else:
self.label_text = ('<center>No Competition Selected</center>')
return None
File "/Users/majikpig/mu_code/src/main/python/scruinterface1.py", line 182, in set_competition
self.label.setText(self.label_text)
AttributeError: 'SMainWindow' object has no attribute 'label'
You need to change order of fields ... in set competition function you try to access field, which you haven't defined yet.
self.set_competition(self.competition.id)
self.label = QLabel(self.label_text)

How do I make sure all GUI input variables can be accessed by certain classes and function calls?

When I provide an address /and or location to the entry bar and I press the "Get forecast" button the script fails at line 22. I think the error is raised because the str(address.get()) cant find the address variable, probably because it doesn't technically exist during that point of run time (I'm not able to log the error due to the structure of that function).
My question is; How do I make sure that my "get_hourly_forecast" function is able to access the address entry variable?
I have tried instantiating the address variable in various locations, e.g in the MainWeatherHub class, as well as in the MyWeatherApp class and then passing it as an argument to the MainWeatherHub in line 79, neither variation has worked. The current code shows the former variation.
import urllib, json, requests
from tkinter import *
from tkinter import ttk
def get_hourly_forecast(*args):
## params *args:
#A location argument
#Returns:
# A list of temps in Farenheit for the next 156 hours
API_KEY = 'removing my API key for security purposes'
try:
print('here') # The code makes it to here
curr_address = str(address.get()) # Code seems to fail here (not sure how to have the error print)
print('here')
geocode_url = "https://maps.googleapis.com/maps/api/geocode/json?address={}&key={}".format(cur_address, API_KEY)
response = requests.get(geocode_url)
response_dict = response.json()['results']
location = response_dict[0]['geometry']['location']
lat = location['lat']
lng = location['lng']
local_url_request = 'https://api.weather.gov/points/lat={}lng={}'.format(lat, lng)
response_one = requests.get(local_url_request)
json_dict_one = response_one.json()
local_props = json_dict_one['properties']
local_forecast_request = local_props['forecastHourly']
resposne_two = requests.get(local_forecast_request)
json_dict_two = resposne_two.json()
local_forecast_properites = json_dict_two['properties']
hourly_updates = local_forecast_properites['periods']
out = []
for i in hourly_updates:
for key, value in i.items():
if key == "temperature":
out.append(value)
current_weather.set(out[0])
except:
print("Not working.")
#############################################################
class MyWeatherApp:
"""
MyWeatherApp is the primary Frame for this GUI application
"""
def __init__(self, master):
super(MyWeatherApp, self).__init__()
self.master = master
# Create the main window Frame
master_style = ttk.Style()
master_style.configure('Master.TFrame')
self.master.title("My Weather")
self.master.geometry("500x500")
MWA = ttk.Frame(self.master, style='Master.TFrame')
MWA.place(relheight=1.0, relwidth=1.0)
# Run other widgets within this class
MainWeatherHub(MWA)
#############################################################
class MainWeatherHub(MyWeatherApp):
"""
The MainWeatherHub (MWH) is the top panel of the app
"""
def __init__(self, mainwindow):
super(MyWeatherApp, self).__init__()
self.mainwindow = mainwindow
# Create a Frame for the MainWeatherHub
MWH_style = ttk.Style()
MWH_style.configure('MWH.TFrame')
MWH = ttk.Frame(self.mainwindow, style='MWH.TFrame', relief='sunken')
MWH.place(relheight=0.33, relwidth=0.95, relx=0.025, rely=0.025)
# Create an entry widget to take a location
# and store that as a loction variable.
address = StringVar()
loc_entry = ttk.Entry(MWH, textvariable=address)
loc_entry.place(relheight=0.30, relwidth=.95, relx=0.025, rely=0.05)
# Get weather button finds weather for the users location
current_weather = StringVar()
get_weather_button = ttk.Button(loc_entry, text="Get Forecast", command=get_hourly_forecast)
get_weather_button.place(relheight=0.85,relwidth=0.2, relx=0.79, rely=0.075)
#Display weather in the Message widget
weath_display = Message(MWH, textvariable=current_weather)
weath_display.place(relwidth=0.95, relheight=0.55, relx=0.025, rely=0.375)
root = Tk()
my_gui = MyWeatherApp(root)
root.mainloop()
If this script works properly, it should return the current temperature in degrees Fahrenheit of the location that was provided in the entry bar.
You should send it as parameter
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
And then assing to button function which runs get_hourly_forecast with string
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get())
or using lambda
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
ttk.Button(loc_entry, text="Get Forecast", command=lambda:get_hourly_forecast(address.get()))
EDIT:
I see you use current_weather (StringVar from MainWeatherHub) in get_hourly_forecast to set value current_weather.set(out[0]).
You could send current_weather to get_hourly_forecast as parameter
def get_hourly_forecast(cur_address, current_weather):
geocode_url = "...".format(cur_address, API_KEY)
current_weather.set(out[0])
and
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
self.current_weather = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get(), self.current_weather)
but it could be better to return value from get_hourly_forecast
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
return out[0]
and get it in run_it
def run_it(self):
result = get_hourly_forecast(self.address.get())
if result is not None:
self.current_weather.set(result)
This way get_hourly_forecast doesn't work with StringVar and you can use it in other program which doesn't use StringVar.

Resources